[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\n      \"@babel/preset-env\", { \"targets\": {\"node\": \"current\"} }\n    ]\n  ],\n  \"env\": {\n    \"test\": {\n      \"plugins\": [ \"istanbul\" ]\n    }\n  }\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig helps maintain consistent coding styles for multiple developers working on the same\n# project across various editors and IDEs: https://editorconfig.org\n\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\nindent_style = space\nindent_size = 2\nmax_line_length = 100\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[COMMIT_EDITMSG]\nmax_line_length = 0\n"
  },
  {
    "path": ".env.sample",
    "content": "# This is an example file. To actually use it, you need to rename it \".env\". The .env file is used\n# by the dotenv module to set environment variables needed during development. The .env file MUST\n# NEVER BE committed.\n\n# Token used by the `release-it` module to create automatic GitHub releases.\nGITHUB_TOKEN=XXXXX\n"
  },
  {
    "path": ".eslintrc.cjs",
    "content": "module.exports = {\n\n  \"env\": {\n    \"amd\": true,\n    \"browser\": true,\n    \"mocha\": true,\n    \"node\": true,\n    \"es6\": true\n  },\n\n  \"parserOptions\": {\n    \"ecmaVersion\": \"latest\",\n    \"sourceType\": \"module\"\n  },\n\n  \"globals\": {\n    \"Promise\": \"readonly\",\n    \"WebMidi\": \"readonly\",\n    \"chai\": \"readonly\",\n    \"sinon\": \"readonly\",\n    \"expect\": \"readonly\",\n    \"Note\": \"readonly\",\n    \"isNative\": \"readonly\",\n    \"config\": \"readonly\"\n  },\n\n  \"extends\": [\n    \"eslint:recommended\",\n    \"prettier\",\n    \"plugin:react/recommended\"\n  ],\n\n  // The idea here is to stick to the rules defined by Prettier (https://prettier.io/) and only make\n  // exceptions in ESLint when absolutely necessary.\n  \"rules\": {\n\n    // Rules to align ESLint with Prettier (even though we are already using eslint-config-prettier)\n    \"indent\": [\"error\", 2],\n    \"semi\": [\"error\", \"always\"],\n    \"quote-props\": [\"error\", \"as-needed\"],\n    \"quotes\": [\"error\", \"double\", {\"avoidEscape\":  true, \"allowTemplateLiterals\": true}],\n\n    // Rules that knowingly change the default Prettier behaviour\n    \"no-multi-spaces\": [\"error\", { \"ignoreEOLComments\": true }],\n    \"linebreak-style\": [\"error\", \"unix\"], // Force \\n instead of Prettier's auto-detect behaviour\n    \"no-trailing-spaces\": [\"error\", { \"skipBlankLines\": true, \"ignoreComments\": true }],\n    \"max-len\": [\"error\", { \"code\": 100, \"comments\": 150 }], // Prettier's 80 is too small. Period.\n    \"no-console\": [\"error\", { \"allow\": [\"info\", \"warn\", \"error\"] }], // Only some (unlike Prettier)\n\n    // Other rules\n    \"no-prototype-builtins\": \"off\",\n\n    \"react/prop-types\": \"off\"\n\n  },\n\n  \"settings\": {\n    \"react\": {\n      \"version\": \"detect\"\n    }\n  }\n\n};\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# Up to 4 GitHub sponsors-enabled usernames e.g. [user1, user2]\ngithub: [djipco]\n\n# Single Patreon username\n#patreon:\n\n# Replace with a single Open Collective username\n#open_collective:\n\n# Replace with a single Ko-fi username\n#ko_fi:\n\n# Replace with a single Tidelift platform-name/package-name e.g., npm/babel\n#tidelift:\n\n# Replace with a single Community Bridge project-name e.g., cloud-foundry\n#community_bridge:\n\n# Replace with a single Liberapay username\n#liberapay:\n\n# Replace with a single IssueHunt username\n#issuehunt:\n\n# Replace with a single Otechie username\n#otechie:\n\n# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n#custom:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/ask-a-question.md",
    "content": "---\nname: Ask a question\nabout: This is to ask a usage, support or general question\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Questions should be submitted to the Forum**\n\nAll questions should be submitted to the **Questions & Support** section of the WebMidi.js Forum: \n\nhttps://webmidijs.org/forum/\n\nThis opens up the question to the community while reserving GitHub strictly for bugs and issues.\n\nThank you.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/report-a-bug.md",
    "content": "---\nname: Report a bug\nabout: This is only to report a bug, issue or problem.\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Description**\nDescribe the problem and how to reproduce it. If appropriate, include code samples, screenshots, error messages, etc. \n\n**Environment:**\nSpecify the environment where you are witnessing the problem:\n  - Library version and flavour (CJS, ESM or IIFE)\n  - Runtime (browser or Node.js) and version\n  - Language (JavaScript or TypeScript)\n  - Operating system\n\n**Details**\nAdd any other information, context or details that could help track down the problem.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/suggest-a-new-feature.md",
    "content": "---\nname: Suggest a new feature\nabout: This is to suggest a new feature or improvement\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Enhancement proposals should be submitted in the Forum**\n\nTo submit a new feature or improvement request, please post it to the **Enhancement Proposals** section of the WebMidi.js Forum: \n\nhttps://webmidijs.org/forum/\n\nThis allows the feature request to be discussed with the community while reserving GitHub strictly for bugs and issues.\n\nThank you.\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ master ]\n  schedule:\n    - cron: '32 5 * * 4'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'javascript' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://git.io/codeql-language-support\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v1\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v1\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v1\n"
  },
  {
    "path": ".gitignore",
    "content": "### System #########################################################################################\n.DS_Store*\nIcon?\n._*\nThumbs.db\nehthumbs.db\nDesktop.ini\n.directory\n*~\n*.tgz\n\n### Editors ########################################################################################\n.idea\n\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n*.sublime-project\n*.sublime-workspace\n\n### Dependency Directories #########################################################################\nnode_modules\n\n### Logs ###########################################################################################\n*.log\n\n### Circle CI ######################################################################################\n.circleci\n\n### Test coverage ##################################################################################\n.nyc_output\ncoverage\n\n### Passwords, tokens and such #####################################################################\n.credentials\n.env*\n!.env.sample\n\n### Production build ###############################################################################\ndist\n"
  },
  {
    "path": ".nycrc",
    "content": "{\n  \"report-dir\": \"./node_modules/nyc/.nyc_output\",\n  \"temp-dir\": \"./node_modules/nyc/.coverage\"\n}\n"
  },
  {
    "path": "BANNER.txt",
    "content": "<%= pkg.webmidi.name %> v<%= pkg.version %>\n<%= pkg.webmidi.tagline %>\n<%= pkg.homepage %>\nBuild generated on <%= moment().format('MMMM Do, YYYY') %>.\n\n© Copyright 2015-<%= moment().format('YYYY') %>, Jean-Philippe Côté.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\nin compliance with the License. You may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License\nis distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\nor implied. See the License for the specific language governing permissions and limitations under\nthe License.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nStarting with version 3.x, all notable changes to WebMidi.js will be documented in this file. The \nformat used is the one suggested by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).\n\n## [3.0.0]\n\n### Added\n\n- WebMidi.js now has builtin **Node.js** support thanks to the [jzz](https://www.npmjs.com/package/jzz)\nnode module by Jazz-Soft.\n\n- WebMidi.js is now available in IIFE (Immediately Invoked Function Expression), CJS (CommonJS) and \nESM (ECMAScript module) flavours (all with normal and minifed versions with sourcemaps).\n\n- WebMidi.js now publishes a **TypeScript** definition file for CJS and ESM with all releases.\n\n- WebMidi.js now explicitly checks for Jazz-Plugin support in environments with no native support \nfor the Web MIDI API.\n\n- The `WebMidi.enable()` method now returns a promise. The promise is fulfilled with the `WebMidi`\nobject. It still supports using a callback.\n\n- `Input` and `Output` objects now emit `opened`, `closed` and `disconnected` events.\n\n- Hundreds of unit tests have been added and test coverage is now available.\n\n- There are new `InputChannel` and `OutputChannel` objects. They are used to communication with a \n  single channel of an input or output.\n\n- All emitted events now have a `target` property referencing the object that triggered the event.\n\n- The `sendNoteOn()` method has been added. It behaves the same way as `playNote()` does except it \ndoes not accept a `duration` option. It was added mostly for completeness' sake.\n\n- The `sendNoteOff()` method has been added. It behaves the same way as `stopNote()` does. \nActually, `stopNote()` is an alias to `sendNoteOff()`.\n\n- All methods of the `Output` object that communication with a device are prefixed with \"send\". This \n  makes it easier to find the right method. Old method names are still usable but are deprecated.\n\n- The `sendChannelAftertouch()`, `sendKeyAftertouch()` and `sendPitchbend()` method now have a \n`useRawValue` options allowing the assignment of value using an integer between 0 and 127 instead of\na float between 0 and 1.\n\n- There is a new `Note` object that can be used in various places such as when calling `playNote()`\nor `stopNote()`. It carries with it the note number, the duration (if any), the attack and release\ninformation, etc.\n\n- A `WebMidi.validation` property (defaults to `true`) can be used to disable all argument checking\nand legacy support throughout the library (for performance). This property can also be in the \noptions of `WebMidi.enable()`.\n\n- The `send()` and `sendSysex()` methods of `Output` and `OutputChannel` can now officially use \n`Uint8Array` input (not supported on Node.js however).\n\n- Licence has been changed to Apache 2.0\n\n- An `octaveOffset` property has been added to `Input`, `InputChannel`, `Output` and \n`OutputChannel`. This means you can offset the octave globally, at the input/output level or at the\n channel level\n\n- A `Message` object has been added. This allows easier routing of messages.\n\n- Added support for RPN messages and improved NRPN parsing.\n\n- A two-position array can now be passed to `sendControlChange()` to specify both MSB and LSB at \nonce.\n\n- The `InputChannel` object offers a `getNoteState()` method that reports if a note is currently \nplaying or not. It also has a new `notesState` property which is an array holding the playing status\nof all notes (0-127).\n\n- It is now possible to add a forwarder to an `Input` that will forward MIDI messages to a specified\noutput. Also, the inbound messages can be filtered for forwarding by message type and channel. A new\n`Forwarder` class has been added for that purpose.\n\n- Added `WebMidi.version`\n\n- The `WedMidi` object now has a `defaults` property where you can set system-wide defaults such as\nthe default `attack` and `release` velocity. More defaults to come!\n\n### Changed\n\n- [BREAKING CHANGE] Passing `undefined` as the `channel` value to `addListener()` no longer means\nthat all channels should be listening. This was a terrible design decision and it ends with version \n3.\n\n- Documentation is now generated with [jsdoc](https://www.npmjs.com/package/jsdoc) instead of the \noutdated [yuidoc](https://www.npmjs.com/package/grunt-contrib-yuidoc).\n\n- [BREAKING CHANGE] The `\"controlchange\"` event's `value` property is now a float between 0 and 1. \nIts `rawValue` property now contains the 7bit integer value (between 0 and 127).\n\n- [BREAKING CHANGE] The `\"nrpn\"` event's `value` property is now a float between 0 and 1. \nIts `rawValue` property now contains the 16bit integer value (between 0 and 65535).\n\n- Grunt has been replaced with NPM scripts for all build purposes.\n\n- [BREAKING CHANGE] The `nrpnEventsEnabled` property has been moved from the `Input` class to the \n`InputChannel` class. Trying to access it will trigger a warning in the console.\n\n- [BREAKING CHANGE] The `getCcNameByNumber()` method has been moved from the `Input` class to the \n`InputChannel` class and now returns `undefined` instead of `false` when no matching name is found.\n\n- [BREAKING CHANGE] The `\"tuningrequest\"` event has been renamed `\"tunerequest\"`. \n\n- The event received by listeners registered on `Input` and `InputChannel` objects has been slightly \nchanged. Its `data` property now contains a regular array (instead of a `Uint8Array`). Its `rawData` \nproperty now contains the `Uint8Array`.\n\n- Several methods have been moved from the `WebMidi` object to the `Utilities` object. Using the old\nmethods will continue to work but will trigger a deprecation warning in the console.\n\n- The `send()` method now accepts a `Message` object.\n\n- If a device is disconnected and connected back, it will retain its state (such as listeners, \netc.). This is particularly useful when the computer goes to sleep and is brought back online.\n\n- Several conversion methods have been added to the new `Utilities` class such as \n`from7bitToFloat()`, `fromFloatTo7Bit()`, `fromMsbLsbToFloat()`, `fromFloatToMsbLsb()`, etc.\n\n- All enumerations have been move to the `Enumerations` object (e.g. `MIDI_CHANNEL_MESSAGES`, \n`MIDI_CHANNEL_NUMBERS`, etc.)\n\n### Deprecated\n\n- The `velocity` option parameter has been renamed `attack`. There are new `rawAttack` and \n`rawRelease` parameters that should be used instead of setting `rawVelocity` to `true`.\n\n- The `WebMidi.noteNameToNumber()` method was renamed and moved to `Utilities.toNoteNumber()`. The \nold method has been deprecated but will continue to work in v3.x.\n\n- The `WebMidi.toMIDIChannels()` method was renamed and moved to `Utilities.sanitizeChannels()`. The\nold method has been deprecated but will continue to work in v3.x.\n\n- The name of the `Output.sendTuningRequest()` method was changed to `Output.sendTuneRequest()`. The\nold name has been deprecated but will continue to work in v3.x.\n\n- The `on()` method of the `Input` class has been deprecated. Use `addListener()` instead.\n\n- The `InputChannel.nrpnEventsEnabled` property has been renamed to\n`InputChannel.parameterNumberEventsEnabled`. The old property is deprecated but will be kept for \nbackwards compatibility.\n\n### Removed\n\n- Support for Bower.\n\n\n## [2.5.1] - 2019-08-25\n\nVersions 2.5.x and earlier have not been tracked in this changelog.\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\ninfo@webmidijs.org.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nFirst off, **thank you** for considering to contribute to this project. You should know that there \nare many ways to contribute. You can write tutorials or blog posts, improve the documentation, \nsubmit bug reports or feature requests and write actual source code. All of these are very \nworthwhile contributions.\n\nFollowing these guidelines helps to communicate that you respect the time of the developers managing \nthis open source project. In return, they will reciprocate that respect in addressing your issue, \nassessing changes, and helping you finalize your pull requests.\n\n## Submitting a Feature Requests\n\nIf you find yourself wishing for a feature, you are probably not alone. There are bound to be others \nout there with similar needs. Before submitting a feature request, first check if the \n[wiki](https://github.com/djipco/webmidi/wiki)'s enhancements section already lists that feature. \n\nIf not, open an [issue](https://github.com/djipco/webmidi/issues) which describes the feature you \nwould like to see, why you need it, and how it should work.\n\n## Understanding How to Contribute\n\nContribution to this project is done via pull requests. This allows the owner and contributors to\nproperly review what gets merged into the project. \n\n**If this is your first pull request**, you can learn how to get started from a free series of \ntutorials called \n[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).\n\n## Submitting a Pull Request\n\n**WebMidi.js** is a relatively small library supported by an even smaller team. Therefore, the \nprocess for contributing is intended to be simple and friendly. \n\nHowever, to insure good quality, there are steps that you should go through when submitting a PR. \nHere are the usual steps:\n\n1. Discuss the change(s) you wish to make by means of an \n[issue](https://github.com/djipco/webmidi/issues).\n2. Unless the PR is for a minor improvement (typo, documentation, etc.), you should write and/or \nupdate unit tests and check your code against the tests (see below).\n3. If appropriate, update the [jsdoc](http://usejsdoc.org/) comments. Keeping the documentation and\nthe API consistant is very important.\n4. If appropriate, update the `README.md` file.\n\nPlease note that your code should adhere to the styles defined in `.eslintrc.js`. You can use \n`npm run lint` to make sure it does.\n\nFinally, **do not** update the library's version number. Version numbering and releases will be \nhandled by the owner. If the PR breaks backwards-compatibility, it must be communicated explicitely\nto the owner. The versioning scheme follows the [SemVer](http://semver.org/) standard.\n\n## Testing\n\nWebMidi.js now has a proper test suite. The tests can be run on the command line without a need for\na browser (thanks to Tim Susa).\n\nYou can execute all tests, including code coverage, by running the following command in the \nterminal or on the command line:\n\n``` \nnpm run test-all\n``` \n\nYou can develop in *watch mode* with hot file reloading like so: \n``` \nnpm run test -- -w\n``` \n\nYou can start a single test in this way:\n``` \nnpx mocha ./test/virtual-midi-test.js\n``` \n\nYou can develop a single test in *watch mode* like this:\n``` \nnpx mocha ./test/virtual-midi-test.js -- -w\n``` \n\nIf you simply want to view code coverage, you can do:\n``` \nnpm run test-coverage\n``` \n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n"
  },
  {
    "path": "README.md",
    "content": "![WebMidi.js Logo](https://webmidijs.org/img/webmidijs-logo-color-on-white.svg \"WebMidi.js\")\n\n[![](https://data.jsdelivr.com/v1/package/npm/webmidi/badge)](https://www.jsdelivr.com/package/npm/webmidi)\n[![npm](https://img.shields.io/npm/dm/webmidi)](https://www.npmjs.com/package/webmidi)\n[![npm](https://img.shields.io/npm/dt/webmidi)](https://www.npmjs.com/package/webmidi)\n[![](https://img.shields.io/github/stars/djipco/webmidi?style=social)](https://github.com/djipco/webmidi)\n[![npm](https://img.shields.io/npm/l/webmidi)](https://www.npmjs.com/package/webmidi)\n\n## Introduction\n\n**WEBMIDI.js** makes it easy to interact with MIDI instruments directly from a web browser or from \nNode.js. It simplifies the control of physical or virtual MIDI instruments with user-friendly \nfunctions such as `playNote()`, `sendPitchBend()` or `sendControlChange()`. It also allows reacting \nto inbound MIDI messages by adding listeners for events such as `\"noteon\"`, `\"pitchbend\"` or \n`\"programchange\"`.\n\nIn short, the goal behind WEBMIDI.js is to get you started with your web-based MIDI project as quickly\nand efficiently as possible.\n\n## Getting Started\n\nThe [**official website**](https://webmidijs.org) site is the best place to get started. Over there,\nyou will find, amongst others, two key resources:\n\n* [Documentation](https://webmidijs.org/docs/)\n* [API Reference](https://webmidijs.org/api/)\n\nTo exchange with fellow users and myself, you can visit our [**Forum**](https://github.com/djipco/webmidi/discussions)\nwhich is hosted on the GitHub Discussions platform: \n\n* [Forum](https://github.com/djipco/webmidi/discussions)\n\nIf you want to stay up-to-date, here are your best sources:\n\n* [Newsletter](https://mailchi.mp/eeffe50651bd/webmidijs-newsletter)\n* [Twitter](https://twitter.com/webmidijs)\n\n## Sponsors\n\nWEBMIDI.js is a passion project but it still takes quite a bit of time, effort and money to develop and \nmaintain. That's why I would like to sincerely thank 👏 these sponsors for their support: \n\n[<img src=\"https://avatars3.githubusercontent.com/u/1488433?s=60&v=4\">](https://github.com/awatterott \"@awatterott\") &nbsp; [<img src=\"https://avatars3.githubusercontent.com/u/3331057?s=60&v=4\">](https://github.com/rubendax \"@rubendax\") &nbsp; <img src=\"https://webmidijs.org/img/person.png\" alt=\"Anonymous Sponsor\" title=\"Anonymous Sponsor\"> &nbsp; [<img src=\"https://avatars.githubusercontent.com/u/3722211?s=60&v=4\">](https://github.com/philmillman \"@philmillman\") &nbsp; <img src=\"https://webmidijs.org/img/person.png\" alt=\"Anonymous Sponsor\" title=\"Anonymous Sponsor\"> &nbsp; <img src=\"https://webmidijs.org/img/person.png\" alt=\"Anonymous Sponsor\" title=\"Anonymous Sponsor\">\n\nIf you use the library and find it useful, please 💜 [**sponsor the project**](https://github.com/sponsors/djipco).\n\n## Feature Request\n\nIf you would like to request a new feature, enhancement or API change, please first check that it is \nnot [already planned](https://webmidijs.org/docs/future-versions/next). Then, discuss it in the \n[Enhancement Proposals](https://github.com/djipco/webmidi/discussions/categories/feature-requests) \nsection of the forum.\n\n## Citing this Software in Research\n\nIf you use this software for research or academic purposes, please cite the project in your \nreferences (or wherever appropriate). Here's an example of how to cite it \n([APA Style](https://apastyle.apa.org/)):\n\n>Côté, J. P. (2021). WebMidi.js v3.0.0 [Computer Software]. Retrieved from \nhttps://github.com/djipco/webmidi\n\nCheers!\n\n-- Jean-Philippe\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n\n| Version       | Support            | Notes                                                         |\n| ------------- | ------------------ | ------------------------------------------------------------- |\n| 3.0.x (alpha) | :white_check_mark: | This is the current, officially-supported version.            |\n| 2.5.x         | :white_check_mark: | Bug and security fixes only.                                  |\n| 1.0.x         | :x:                | This version has reached end of life.                         |\n\n## Reporting a Vulnerability\n\nTo report a security-related problem, **you should not file an issue on GitHub** as this might put \nusers at risk. **Instead, send an email to jp@cote.cc**.\n"
  },
  {
    "path": "examples/README.md",
    "content": "# WEBMIDI.js Usage Examples\n\nIn this directory, you will find starter examples to help you use WEBMIDI.js in various contexts or \nfor various purposes. \n\n## Browser Quick Start Example\n\n  * [Quick Start](quick-start/)\n\n## Frameworks\n\n  * [Next.js](next.js/)\n  * [p5.js](p5.js/)\n  * [React](react/)\n\n## Environments\n\n  * [Electron](electron/)\n\n## Languages\n\n  * [TypeScript](typescript/)\n\n## More Examples wanted!\n\nTo submit a new example, simply send a pull request and it will be quickly reviewed. Please create\na README.md file to provide information regarding how the example should be used.\n"
  },
  {
    "path": "examples/electron/README.md",
    "content": "# Using WEBMIDI.js with Electron\n\nA collection of examples to use WEBMIDI.js inside an Electron application.\n\n## Examples\n\n* [**Basic Electron Example**](basic-example)\n\n## Platform specific notes\n\nThe permission requests must be properly handled in the main process before initializing WEBMIDI:\n\n```javascript\n  mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => {\n    if (permission === 'midi' || permission === 'midiSysex') {\n      callback(true);\n    } else {\n      callback(false);\n    }\n  })\n\n  mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {\n    if (permission === 'midi' || permission === 'midiSysex') {\n      return true;\n    }\n\n    return false;\n  });\n```\n"
  },
  {
    "path": "examples/electron/basic-example/README.md",
    "content": "# Starter Template for Electron\n\nThis is a minimal Electron application based on the [Quick Start Guide](https://electronjs.org/docs/latest/tutorial/quick-start) within the Electron documentation.\n\n## To Use\n\n```bash\n# Install dependencies\nnpm install\n\n# Run the app\nnpm start\n```\n"
  },
  {
    "path": "examples/electron/basic-example/index.html",
    "content": "<!DOCTYPE html>\n\n<html>\n\n  <head>\n\n    <meta charset=\"UTF-8\">\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'\"\n    >\n\n    <title>WEBMIDI.js + Electron Demo</title>\n\n  </head>\n\n  <body>\n\n    <h1>WEBMIDI.js + Electron Demo</h1>\n\n    <p>\n      Node.js <span id=\"node-version\"></span>,\n      Chromium <span id=\"chrome-version\"></span>,\n      Electron <span id=\"electron-version\"></span>\n    </p>\n\n    <p>WEBMIDI.js <span id=\"webmidi-version\"></span></p>\n    <p>MIDI Inputs: <span id=\"webmidi-inputs\"></span></p>\n    <p>MIDI Outputs: <span id=\"webmidi-outputs\"></span></p>\n\n    <!-- You can also require other files to run in this process -->\n    <script src=\"./renderer.js\" type=\"module\"></script>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "examples/electron/basic-example/main.js",
    "content": "// Modules to control application life and create native browser window\nconst {app, BrowserWindow} = require(\"electron\");\nconst path = require(\"path\");\n\nfunction createWindow () {\n\n  // Create the browser window.\n  const mainWindow = new BrowserWindow({\n    width: 800,\n    height: 600,\n    webPreferences: {\n      preload: path.join(__dirname, \"preload.js\")\n    }\n  });\n\n  // Respond affirmatively to requests for MIDI and MIDI SysEx permissions\n  mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => {\n    if (permission === 'midi' || permission === 'midiSysex') {\n      callback(true);\n    } else {\n      callback(false);\n    }\n  })\n\n  // Respond affirmatively to checks for MIDI and MIDI SysEx permissions\n  mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {\n    if (permission === 'midi' || permission === 'midiSysex') {\n      return true;\n    }\n\n    return false;\n  });\n\n  // and load the index.html of the app.\n  mainWindow.loadFile(\"index.html\");\n\n  // Open dev tools\n  // mainWindow.webContents.openDevTools();\n\n}\n\n// This method will be called when Electron has finished initialization and is ready to create\n// browser windows. Some APIs can only be used after this event occurs.\napp.whenReady().then(() => {\n\n  createWindow();\n\n  app.on(\"activate\", function () {\n    // On macOS it's common to re-create a window in the app when the dock icon is clicked and there\n    // are no other windows open.\n    if (BrowserWindow.getAllWindows().length === 0) createWindow();\n  });\n\n});\n\n// Quit when all windows are closed, except on macOS. There, it's common for applications and their\n// menu bar to stay active until the user quits explicitly with Cmd + Q.\napp.on(\"window-all-closed\", function () {\n  if (process.platform !== \"darwin\") app.quit();\n});\n\n// In this file you can include the rest of your app's specific main process code. You can also put\n// them in separate files and require them here.\n"
  },
  {
    "path": "examples/electron/basic-example/package.json",
    "content": "{\n  \"name\": \"webmidijs-electron-demo\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A minimal Electron application\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"start\": \"electron .\"\n  },\n  \"license\": \"CC0-1.0\",\n  \"devDependencies\": {\n    \"electron\": \"^39.8.5\"\n  },\n  \"dependencies\": {\n    \"webmidi\": \"latest\"\n  }\n}\n"
  },
  {
    "path": "examples/electron/basic-example/preload.js",
    "content": "// All of the Node.js APIs are available in the preload process. It has the same sandbox as a Chrome\n// extension.\nwindow.addEventListener(\"DOMContentLoaded\", () => {\n\n  const replaceText = (selector, text) => {\n    const element = document.getElementById(selector);\n    if (element) element.innerText = text;\n  };\n\n  for (const type of [\"chrome\", \"node\", \"electron\"]) {\n    replaceText(`${type}-version`, process.versions[type]);\n  }\n\n});\n"
  },
  {
    "path": "examples/electron/basic-example/renderer.js",
    "content": "// This file is required by the index.html file and will be executed in the renderer process for\n// that window. No Node.js APIs are available in this process because `nodeIntegration` is turned\n// off. Use `preload.js` to selectively enable features needed in the rendering process.\n\nimport {WebMidi} from \"./node_modules/webmidi/dist/esm/webmidi.esm.js\";\n\nWebMidi.enable()\n  .then(onEnabled)\n  .catch(err => console.warning(err));\n\nfunction onEnabled() {\n\n  document.getElementById(\"webmidi-version\").innerHTML += `v${WebMidi.version}`;\n\n  WebMidi.inputs.forEach(input => {\n    document.getElementById(\"webmidi-inputs\").innerHTML += `${input.name}, `;\n  });\n\n  WebMidi.outputs.forEach(output => {\n    document.getElementById(\"webmidi-outputs\").innerHTML += `${output.name}, `;\n  });\n\n}\n"
  },
  {
    "path": "examples/next.js/README.md",
    "content": "# Using WEBMIDI.js with Next.js\n\nA collection of examples to use WEBMIDI.js with the Next.js framework.\n\n## Examples\n\n* [**Basic Next.js Example**](basic-example)\n"
  },
  {
    "path": "examples/next.js/basic-example/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n"
  },
  {
    "path": "examples/next.js/basic-example/README.md",
    "content": "\n\n# Starter Template for Next.js\n\nThis is a starter template for [Learn Next.js](https://nextjs.org/learn).\n\n## To Use\n\n```bash\n# Install dependencies\nnpm install\n\n# Run the app\nnpm run dev\n```\n"
  },
  {
    "path": "examples/next.js/basic-example/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"next\": \"^15.5.15\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"webmidi\": \"latest\"\n  }\n}\n"
  },
  {
    "path": "examples/next.js/basic-example/pages/index.js",
    "content": "import Head from \"next/head\";\n\nimport {WebMidi} from \"webmidi\";\n// const {WebMidi} = require(\"webmidi\");\n\nWebMidi.enable()\n  .then(() => {\n\n    WebMidi.inputs.forEach(input => {\n      console.info(\"Detected input: \", input.name);\n      input.addListener(\"noteon\", e => console.info(e.type, e.note.identifier));\n      input.addListener(\"noteoff\", e => console.info(e.type, e.note.identifier));\n    });\n\n  })\n  .catch(err => console.error(err));\n\nexport default function Home() {\n  return (\n    <div className=\"container\">\n      <Head>\n        <title>Create Next App</title>\n        <link rel=\"icon\" href=\"/favicon.ico\" />\n      </Head>\n\n      <main>\n        <h1 className=\"title\">\n          Welcome to <a href=\"https://nextjs.org\">Next.js!</a>\n        </h1>\n\n        <p className=\"description\">\n          Get started by editing <code>pages/index.js</code>\n        </p>\n\n        <div className=\"grid\">\n          <a href=\"https://nextjs.org/docs\" className=\"card\">\n            <h3>Documentation &rarr;</h3>\n            <p>Find in-depth information about Next.js features and API.</p>\n          </a>\n\n          <a href=\"https://nextjs.org/learn\" className=\"card\">\n            <h3>Learn &rarr;</h3>\n            <p>Learn about Next.js in an interactive course with quizzes!</p>\n          </a>\n\n          <a\n            href=\"https://github.com/vercel/next.js/tree/master/examples\"\n            className=\"card\"\n          >\n            <h3>Examples &rarr;</h3>\n            <p>Discover and deploy boilerplate example Next.js projects.</p>\n          </a>\n\n          <a\n            href=\"https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app\"\n            className=\"card\"\n          >\n            <h3>Deploy &rarr;</h3>\n            <p>\n              Instantly deploy your Next.js site to a public URL with Vercel.\n            </p>\n          </a>\n        </div>\n      </main>\n\n      <footer>\n        <a\n          href=\"https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          Powered by{' '}\n          <img src=\"/vercel.svg\" alt=\"Vercel\" className=\"logo\" />\n        </a>\n      </footer>\n\n      <style jsx>{`\n        .container {\n          min-height: 100vh;\n          padding: 0 0.5rem;\n          display: flex;\n          flex-direction: column;\n          justify-content: center;\n          align-items: center;\n        }\n\n        main {\n          padding: 5rem 0;\n          flex: 1;\n          display: flex;\n          flex-direction: column;\n          justify-content: center;\n          align-items: center;\n        }\n\n        footer {\n          width: 100%;\n          height: 100px;\n          border-top: 1px solid #eaeaea;\n          display: flex;\n          justify-content: center;\n          align-items: center;\n        }\n\n        footer img {\n          margin-left: 0.5rem;\n        }\n\n        footer a {\n          display: flex;\n          justify-content: center;\n          align-items: center;\n        }\n\n        a {\n          color: inherit;\n          text-decoration: none;\n        }\n\n        .title a {\n          color: #0070f3;\n          text-decoration: none;\n        }\n\n        .title a:hover,\n        .title a:focus,\n        .title a:active {\n          text-decoration: underline;\n        }\n\n        .title {\n          margin: 0;\n          line-height: 1.15;\n          font-size: 4rem;\n        }\n\n        .title,\n        .description {\n          text-align: center;\n        }\n\n        .description {\n          line-height: 1.5;\n          font-size: 1.5rem;\n        }\n\n        code {\n          background: #fafafa;\n          border-radius: 5px;\n          padding: 0.75rem;\n          font-size: 1.1rem;\n          font-family: Menlo, Monaco, Lucida Console, Liberation Mono,\n            DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;\n        }\n\n        .grid {\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          flex-wrap: wrap;\n\n          max-width: 800px;\n          margin-top: 3rem;\n        }\n\n        .card {\n          margin: 1rem;\n          flex-basis: 45%;\n          padding: 1.5rem;\n          text-align: left;\n          color: inherit;\n          text-decoration: none;\n          border: 1px solid #eaeaea;\n          border-radius: 10px;\n          transition: color 0.15s ease, border-color 0.15s ease;\n        }\n\n        .card:hover,\n        .card:focus,\n        .card:active {\n          color: #0070f3;\n          border-color: #0070f3;\n        }\n\n        .card h3 {\n          margin: 0 0 1rem 0;\n          font-size: 1.5rem;\n        }\n\n        .card p {\n          margin: 0;\n          font-size: 1.25rem;\n          line-height: 1.5;\n        }\n\n        .logo {\n          height: 1em;\n        }\n\n        @media (max-width: 600px) {\n          .grid {\n            width: 100%;\n            flex-direction: column;\n          }\n        }\n      `}</style>\n\n      <style jsx global>{`\n        html,\n        body {\n          padding: 0;\n          margin: 0;\n          font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,\n            Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,\n            sans-serif;\n        }\n\n        * {\n          box-sizing: border-box;\n        }\n      `}</style>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/p5.js/README.md",
    "content": "# Using WEBMIDI.js with p5.js\n\nA collection of examples to use WEBMIDI.js inside the p5.js framework.\n\n## Examples\n\n* [**Basic Example**](basic-example): drawing circles on canvas when **note on** MIDI event is \nreceived.\n\n* [**Querying note state**](querying-note-state): draw keyboard keys in color when MIDI keys\npresssed\n"
  },
  {
    "path": "examples/p5.js/basic-example/index.html",
    "content": "<!DOCTYPE html>\n\n<html lang=\"en\">\n\n  <head>\n    <meta charset=\"utf-8\" />\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js\"></script>\n    <link rel=\"stylesheet\" href=\"styles.css\">\n    <title>WebMidi.js + p5.js - Basic Example</title>\n  </head>\n\n  <body>\n    <h1>WebMidi.js + p5.js - Basic Example</h1>\n    <p>Randomly draw circles when a <strong>note on</strong> event is received on MIDI channel 1</p>\n    <script src=\"sketch.js\"></script>\n  </body>\n\n</html>\n"
  },
  {
    "path": "examples/p5.js/basic-example/sketch.js",
    "content": "function setup() {\n\n  createCanvas(window.innerWidth, window.innerHeight);\n  noStroke();\n\n  // Enable WebMidi.js and trigger the onWebMidiEnabled() function when ready.\n  WebMidi.enable()\n    .then(onWebMidiEnabled)\n    .catch(err => alert(err));\n\n}\n\nfunction draw() {\n\n}\n\nfunction onWebMidiEnabled() {\n\n  // Check if at least one MIDI input is detected. If not, display warning and quit.\n  if (WebMidi.inputs.length < 1) {\n    alert(\"No MIDI inputs detected.\");\n    return;\n  }\n\n  // Add a listener on all the MIDI inputs that are detected\n  WebMidi.inputs.forEach(input => {\n\n    // When a \"note on\" is received on MIDI channel 1, generate a random color start\n    input.channels[1].addListener(\"noteon\", function() {\n      fill(random(255), random(255), random(255));\n      circle(random(width), random(height), 100);\n    });\n\n  });\n\n}\n"
  },
  {
    "path": "examples/p5.js/basic-example/styles.css",
    "content": "html, body {\n  width: 100vw;\n  height: 100vh;\n  margin: 0;\n}\n\nmain {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100vw;\n  height: 100vh;\n}\n\nh1, p {\n  margin: 0;\n  padding: 16px 16px 0 16px;\n}\n\n"
  },
  {
    "path": "examples/p5.js/querying-note-state/index.html",
    "content": "<!DOCTYPE html>\n\n<html lang=\"en\">\n\n  <head>\n    <meta charset=\"utf-8\" />\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js\"></script>\n    <link rel=\"stylesheet\" href=\"styles.css\">\n    <title>WebMidi.js + p5.js - Querying Note State</title>\n  </head>\n\n  <body>\n    <h1>WebMidi.js + p5.js - Querying Note State</h1>\n    <p>Draw colored rectangles when specific notes are pressed.</p>\n    <script src=\"sketch.js\"></script>\n  </body>\n\n</html>\n"
  },
  {
    "path": "examples/p5.js/querying-note-state/sketch.js",
    "content": "let channel;\n\nasync function setup() {\n\n  // Enable WebMidi.js\n  await WebMidi.enable();\n\n  // Display available inputs in console (use the name to retrieve it)\n  console.log(WebMidi.inputs);\n  const input = WebMidi.getInputByName(\"MPK mini 3\");\n  channel = input.channels[1];\n\n  // Create canvas\n  createCanvas(500, 200);\n\n}\n\nfunction draw() {\n\n  // Check if WebMidi is enabled and channel has been assigned before moving on\n  if (!channel) return;\n\n  // Draw blank keys\n  for (let i = 0; i < 8; i++) {\n\n    // Default fill is white\n    fill(\"white\");\n\n    // Each key has its own color. When it's pressed, we draw it in color (instead of white)\n    if (i === 0 && channel.getNoteState(\"C4\")) {\n      fill(\"yellow\");\n    } else if (i === 1 && channel.getNoteState(\"D4\")) {\n      fill(\"red\");\n    } else if (i === 2 && channel.getNoteState(\"E4\")) {\n      fill(\"pink\");\n    } else if (i === 3 && channel.getNoteState(\"F4\")) {\n      fill(\"orange\");\n    } else if (i === 4 && channel.getNoteState(\"G4\")) {\n      fill(\"purple\");\n    } else if (i === 5 && channel.getNoteState(\"A4\")) {\n      fill(\"green\");\n    } else if (i === 6 && channel.getNoteState(\"B4\")) {\n      fill(\"turquoise\");\n    } else if (i === 7 && channel.getNoteState(\"C5\")) {\n      fill(\"blue\");\n    }\n\n    // Draw the keys\n    rect(85 + i * 40, 50, 30, 100);\n\n  }\n\n}\n"
  },
  {
    "path": "examples/p5.js/querying-note-state/styles.css",
    "content": "html, body {\n  width: 100vw;\n  height: 100vh;\n  margin: 0;\n}\n\nh1, p {\n  margin: 0;\n  padding: 16px 16px 0 16px;\n}\n\n"
  },
  {
    "path": "examples/quick-start/index.html",
    "content": "<!DOCTYPE html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <meta charset=\"UTF-8\">\n    <title>WebMidi.js Quick Start</title>\n\n    <script src=\"https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js\"></script>\n\n    <script type=\"module\">\n\n      // Enable WebMidi.js and trigger the onEnabled() function when ready.\n      WebMidi\n        .enable()\n        .then(onEnabled)\n        .catch(err => alert(err));\n\n      function onEnabled() {\n\n        if (WebMidi.inputs.length < 1) {\n          document.body.innerHTML+= \"No device detected.\";\n        } else {\n          WebMidi.inputs.forEach((device, index) => {\n            document.body.innerHTML+= `${index}: ${device.name} <br>`;\n          });\n        }\n\n        const mySynth = WebMidi.inputs[0];\n        // const mySynth = WebMidi.getInputByName(\"TYPE NAME HERE!\")\n\n        mySynth.channels[1].addListener(\"noteon\", e => {\n          document.body.innerHTML+= `${e.note.name} <br>`;\n        });\n\n      }\n\n    </script>\n\n  </head>\n\n  <body>\n    <h1>WebMidi.js Quick Start</h1>\n  </body>\n\n</html>\n"
  },
  {
    "path": "examples/react/README.md",
    "content": "# Using WEBMIDI.js with p5.js\n\nA collection of examples to use WEBMIDI.js inside the React framework.\n\n## Examples\n\n* [**Basic Example**](basic-example)\n"
  },
  {
    "path": "examples/react/basic-example/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "examples/react/basic-example/README.md",
    "content": "# Getting Started with Create React App\n\nThis project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\n## Available Scripts\n\nIn the project directory, you can run:\n\n### `yarn start`\n\nRuns the app in the development mode.\\\nOpen [http://localhost:3000](http://localhost:3000) to view it in the browser.\n\nThe page will reload if you make edits.\\\nYou will also see any lint errors in the console.\n\n### `yarn test`\n\nLaunches the test runner in the interactive watch mode.\\\nSee the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.\n\n### `yarn build`\n\nBuilds the app for production to the `build` folder.\\\nIt correctly bundles React in production mode and optimizes the build for the best performance.\n\nThe build is minified and the filenames include the hashes.\\\nYour app is ready to be deployed!\n\nSee the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.\n\n### `yarn eject`\n\n**Note: this is a one-way operation. Once you `eject`, you can’t go back!**\n\nIf you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.\n\nInstead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.\n\nYou don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.\n\n## Learn More\n\nYou can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).\n\nTo learn React, check out the [React documentation](https://reactjs.org/).\n\n### Code Splitting\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)\n\n### Analyzing the Bundle Size\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)\n\n### Making a Progressive Web App\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)\n\n### Advanced Configuration\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)\n\n### Deployment\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)\n\n### `yarn build` fails to minify\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)\n"
  },
  {
    "path": "examples/react/basic-example/package.json",
    "content": "{\n  \"name\": \"basic-example\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@testing-library/jest-dom\": \"^5.16.1\",\n    \"@testing-library/react\": \"^12.1.2\",\n    \"@testing-library/user-event\": \"^13.5.0\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-scripts\": \"^5.0.0\",\n    \"web-vitals\": \"^2.1.2\",\n    \"webmidi\": \"latest\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\",\n      \"react-app/jest\"\n    ]\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/react/basic-example/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta\n      name=\"description\"\n      content=\"Web site created using create-react-app\"\n    />\n    <link rel=\"apple-touch-icon\" href=\"%PUBLIC_URL%/logo192.png\" />\n    <!--\n      manifest.json provides metadata used when your web app is installed on a\n      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/\n    -->\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\" />\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>React App</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "examples/react/basic-example/public/manifest.json",
    "content": "{\n  \"short_name\": \"React App\",\n  \"name\": \"Create React App Sample\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"logo192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"logo512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "examples/react/basic-example/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "examples/react/basic-example/src/App.css",
    "content": ".App {\n  text-align: center;\n}\n\n.App-logo {\n  height: 40vmin;\n  pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  .App-logo {\n    animation: App-logo-spin infinite 20s linear;\n  }\n}\n\n.App-header {\n  background-color: #282c34;\n  min-height: 100vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  font-size: calc(10px + 2vmin);\n  color: white;\n}\n\n.App-link {\n  color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "examples/react/basic-example/src/App.js",
    "content": "import logo from './logo.svg';\nimport './App.css';\nimport {WebMidi} from \"webmidi\";\n\nfunction App() {\n\n  WebMidi.enable()\n    .then(() => console.log(WebMidi.inputs))\n    .catch(err => console.log(err));\n\n  return (\n    <div className=\"App\">\n      <header className=\"App-header\">\n        <img src={logo} className=\"App-logo\" alt=\"logo\" />\n        <p>\n          Edit <code>src/App.js</code> and save to reload.\n        </p>\n        <a\n          className=\"App-link\"\n          href=\"https://reactjs.org\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          Learn React\n        </a>\n      </header>\n    </div>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "examples/react/basic-example/src/App.test.js",
    "content": "import { render, screen } from '@testing-library/react';\nimport App from './App';\n\ntest('renders learn react link', () => {\n  render(<App />);\n  const linkElement = screen.getByText(/learn react/i);\n  expect(linkElement).toBeInTheDocument();\n});\n"
  },
  {
    "path": "examples/react/basic-example/src/index.css",
    "content": "body {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n    sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n    monospace;\n}\n"
  },
  {
    "path": "examples/react/basic-example/src/index.js",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n  document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"
  },
  {
    "path": "examples/react/basic-example/src/reportWebVitals.js",
    "content": "const reportWebVitals = onPerfEntry => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n      getCLS(onPerfEntry);\n      getFID(onPerfEntry);\n      getFCP(onPerfEntry);\n      getLCP(onPerfEntry);\n      getTTFB(onPerfEntry);\n    });\n  }\n};\n\nexport default reportWebVitals;\n"
  },
  {
    "path": "examples/react/basic-example/src/setupTests.js",
    "content": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport '@testing-library/jest-dom';\n"
  },
  {
    "path": "examples/typescript/README.md",
    "content": "# Using WEBMIDI.js in a TypeScript project\n\nVersion 3 of WEBMIDI.js officially supports TypeScript. Type declarations are available for the ESM\n(ECMAScript modules) and CJS (CommonJS, a.k.a. Node.js modules) flavours in the `/dist` directory.\n\nTo use the example, `cd` into the example directory, install the necessary modules  with \n`npm install` and write some TypeScript code. You can generate the final JavaScript code with\n`tsc myFile.ts`. This should yield a `myFile.js` which you can run with `node myFile.js`.\n\n## Examples\n\n* [**Basic Example**](basic-nodejs-example): listing input and output ports and reacting to \n  pressed keyboard keys.\n"
  },
  {
    "path": "examples/typescript/basic-nodejs-example/index.ts",
    "content": "import {WebMidi} from \"webmidi\";\n\nasync function start() {\n\n  await WebMidi.enable();\n\n  // List available inputs\n  console.log(\"Available inputs: \");\n\n  WebMidi.inputs.forEach(input => {\n    console.log(\"\\t\" + input.name);\n    input.addListener(\"noteon\", e => console.log(e.note.identifier));\n  });\n\n  // List available outputs\n  console.log(\"Available outputs: \");\n\n  WebMidi.outputs.forEach(output => {\n    console.log(\"\\t\" + output.name);\n  });\n\n}\n\nstart();\n"
  },
  {
    "path": "examples/typescript/basic-nodejs-example/package.json",
    "content": "{\n  \"name\": \"typescript-basic-nidejs-example\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"devDependencies\": {\n    \"typescript\": \"^4.5.4\"\n  },\n  \"dependencies\": {\n    \"webmidi\": \"latest\"\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"webmidi\",\n  \"version\": \"3.1.16\",\n  \"description\": \"WEBMIDI.js makes it easy to talk to MIDI instruments from a browser or from Node.js. It simplifies the control of external or virtual MIDI instruments with functions such as playNote(), sendPitchBend(), sendControlChange(), etc. It also allows reacting to incoming MIDI messages by adding listeners for user-friendly events such as 'noteon', 'pitchbend', 'songposition', etc.\",\n  \"main\": \"./dist/cjs/webmidi.cjs.min.js\",\n  \"types\": \"./dist/cjs/webmidi.cjs.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"require\": {\n        \"types\": \"./dist/cjs/webmidi.cjs.d.ts\",\n        \"default\": \"./dist/cjs/webmidi.cjs.min.js\"\n      },\n      \"import\": {\n        \"types\": \"./dist/esm/webmidi.esm.d.ts\",\n        \"default\": \"./dist/esm/webmidi.esm.js\"\n      }\n    }\n  },\n  \"author\": {\n    \"name\": \"Jean-Philippe Côté\",\n    \"email\": \"webmidi@djip.co\",\n    \"url\": \"https://github.com/djipco/\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/djipco/webmidi.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/djipco/webmidi/issues\"\n  },\n  \"files\": [\n    \"/dist\"\n  ],\n  \"keywords\": [\n    \"midi\",\n    \"message\",\n    \"web\",\n    \"browser\",\n    \"front-end\",\n    \"web midi api\",\n    \"music\",\n    \"djipco\",\n    \"music\",\n    \"protocol\",\n    \"communication\",\n    \"channel\",\n    \"node\",\n    \"instrument\",\n    \"device\",\n    \"note\"\n  ],\n  \"homepage\": \"https://webmidijs.org\",\n  \"license\": \"Apache-2.0\",\n  \"webmidi\": {\n    \"name\": \"WEBMIDI.js\",\n    \"tagline\": \"A JavaScript library to kickstart your MIDI projects\"\n  },\n  \"engines\": {\n    \"node\": \">=8.5\"\n  },\n  \"scripts\": {\n    \"library:build-all\": \"npm run lint && npm run library:build-cjs && npm run library:build-esm && npm run library:build-iife\",\n    \"library:build-cjs\": \"node scripts/library/build.js -t cjs && npm run typescript-declaration:generate -- -t cjs\",\n    \"library:build-esm\": \"node scripts/library/build.js -t esm && npm run typescript-declaration:generate -- -t esm\",\n    \"library:build-iife\": \"node scripts/library/build.js -t iife\",\n    \"test-coverage:report-as-text\": \"nyc --reporter=text mocha\",\n    \"test-coverage:report-as-html\": \"nyc --reporter=html mocha\",\n    \"website:build\": \"npm run --prefix website build\",\n    \"website:serve\": \"npm run --prefix website serve\",\n    \"website:dev-server\": \"npm run --prefix website start\",\n    \"website:generate+push-to-gh-pages\": \"npm run jsdoc:generate-markdown+push && npm run --prefix website build && node scripts/website/deploy.js\",\n    \"jsdoc:generate-html+push\": \"node scripts/api-documentation/generate-html.js\",\n    \"jsdoc:generate-markdown+push\": \"node scripts/api-documentation/generate-markdown.js\",\n    \"typescript-declaration:generate\": \"node scripts/typescript-declarations/generate.js\",\n    \"lint\": \"eslint ./src/*.js\",\n    \"release\": \"dotenv release-it --\",\n    \"release:alpha\": \"npm run release -- prepatch --preRelease=alpha  --npm.tag=next --github.preRelease\",\n    \"sponsors:update\": \"node scripts/sponsors/retrieve-sponsors.js\",\n    \"test:all\": \"mocha\",\n    \"test:Input\": \"mocha test/Input.test.js\",\n    \"test:InputChannel\": \"mocha test/InputChannel.test.js\",\n    \"test:Note\": \"mocha test/Note.test.js\",\n    \"test:Message\": \"mocha test/Message.test.js\",\n    \"test:Enumerations\": \"mocha test/Enumerations.test.js\",\n    \"test:Forwarder\": \"mocha test/Forwarder.test.js\",\n    \"test:Output\": \"mocha test/Output.test.js\",\n    \"test:OutputChannel\": \"mocha test/OutputChannel.test.js\",\n    \"test:Utilities\": \"mocha test/Utilities.test.js\",\n    \"test:WebMidi\": \"mocha test/WebMidi.test.js\"\n  },\n  \"dependencies\": {\n    \"djipevents\": \"^2.0.7\"\n  },\n  \"devDependencies\": {\n    \"@alexbinary/rimraf\": \"^1.0.2\",\n    \"@babel/cli\": \"^7.13.0\",\n    \"@babel/core\": \"^7.9.0\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.8.3\",\n    \"@babel/polyfill\": \"^7.8.7\",\n    \"@babel/preset-env\": \"^7.9.0\",\n    \"@julusian/midi\": \"^3.1.0\",\n    \"@octokit/graphql\": \"^4.8.0\",\n    \"@rollup/plugin-babel\": \"^5.3.0\",\n    \"@rollup/plugin-replace\": \"^5.0.2\",\n    \"array-back\": \"^6.2.0\",\n    \"babel-plugin-istanbul\": \"^6.0.0\",\n    \"chai\": \"^4.3.3\",\n    \"docusaurus\": \"^1.14.7\",\n    \"dotenv-cli\": \"^5.1.0\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-prettier\": \"^7.2.0\",\n    \"eslint-plugin-react\": \"^7.26.1\",\n    \"foodoc\": \"0.0.9\",\n    \"fs-extra\": \"^10.0.0\",\n    \"ftp-deploy\": \"^2.3.7\",\n    \"gh-pages\": \"^5.0.0\",\n    \"handlebars\": \"^4.7.9\",\n    \"jsdoc\": \"^3.6.7\",\n    \"jsdoc-to-markdown\": \"^7.1.0\",\n    \"markdown-it\": \"^12.3.2\",\n    \"marked\": \"^4.0.10\",\n    \"minimist\": \"^1.2.5\",\n    \"mocha\": \"^10.0.0\",\n    \"moment\": \"^2.29.4\",\n    \"nyc\": \"^15.0.1\",\n    \"object-get\": \"^2.1.1\",\n    \"prepend-file\": \"^2.0.0\",\n    \"reduce-flatten\": \"^3.0.1\",\n    \"release-it\": \"^19.0.4\",\n    \"replace-in-file\": \"^6.3.2\",\n    \"rollup\": \"^3.30.0\",\n    \"rollup-plugin-license\": \"^2.3.0\",\n    \"rollup-plugin-strip-code\": \"^0.2.7\",\n    \"rollup-plugin-terser\": \"^5.3.0\",\n    \"rollup-plugin-version-injector\": \"^1.3.3\",\n    \"semver\": \"^7.3.5\",\n    \"simple-git\": \"^3.32.3\",\n    \"sinon\": \"^9.0.1\",\n    \"sinon-browser-only\": \"^1.12.1\",\n    \"system-commands\": \"^1.1.7\",\n    \"test-value\": \"^3.0.0\",\n    \"util\": \"^0.12.4\"\n  },\n  \"release-it\": {\n    \"git\": {\n      \"commitMessage\": \"Release v${version}\"\n    },\n    \"github\": {\n      \"release\": true,\n      \"draft\": false,\n      \"releaseName\": \"Release v${version}\",\n      \"assets\": [\n        \"webmidi-${version}.tgz\"\n      ]\n    },\n    \"hooks\": {\n      \"after:bump\": [\n        \"npm run library:build-all\"\n      ],\n      \"before:git:release\": [\n        \"npm pack\"\n      ],\n      \"after:release\": \"rimraf webmidi-${version}.tgz\"\n    }\n  },\n  \"optionalDependencies\": {\n    \"jzz\": \"^1.8.5\"\n  }\n}\n"
  },
  {
    "path": "scripts/api-documentation/generate-html.js",
    "content": "// This script generates web-based API documentation files from jsdoc comments in the source code\n// and commits them to the '/archives/api' directory of the 'gh-pages' branch. This makes them\n// available at djipco.githib.io/webmidi/archives/api/v3\n\n// Modules\nconst fs = require(\"fs-extra\");\nconst fsPromises = require(\"fs\").promises;\nconst git = require(\"simple-git\")();\nconst pkg = require(\"../../package.json\");\nconst moment = require(\"moment\");\nconst path = require(\"path\");\nconst os = require(\"os\");\nconst rimraf = require(\"@alexbinary/rimraf\");\nconst system = require(\"system-commands\");\n\n// Some paths\nconst CUSTOM_CSS = \"../css/custom.css\";\nconst TARGET_BRANCH = \"gh-pages\";\nconst TARGET_PATH = \"./archives/api/v\" + pkg.version.split(\".\")[0];\n\n// Google Analytics configuration\nconst GA_CONFIG = {\n  ua: \"UA-162785934-1\",\n  domain: \"https://djipco.github.io/webmidi\"\n};\n\n// JSDoc configuration object to write as configuration file\nconst config = {\n\n  tags: {\n    allowUnknownTags: true\n  },\n\n  // The opts property is for command-line arguments passed directly in the config file\n  opts: {\n    template: \"./node_modules/foodoc/template\"\n  },\n\n  // Source files\n  source: {\n    include: [\n      \"./src/Enumerations.js\",\n      \"./src/Forwarder.js\",\n      \"./src/Input.js\",\n      \"./src/InputChannel.js\",\n      \"./src/Message.js\",\n      \"./src/Note.js\",\n      \"./src/Output.js\",\n      \"./src/OutputChannel.js\",\n      \"./src/Utilities.js\",\n      \"./src/WebMidi.js\",\n\n      \"./node_modules/djipevents/src/djipevents.js\"\n    ]\n  },\n\n  sourceType: \"module\",\n\n  plugins: [\n    \"plugins/markdown\"\n  ],\n\n  // Configurations for the Foodoc template\n  templates: {\n\n    systemName: `${pkg.webmidi.name} API`,\n    systemSummary: pkg.webmidi.tagline,\n    // systemLogo: LOGO_PATH,\n    systemColor: \"#ffcf09\",\n\n    copyright: `© <a href=\"${pkg.author.url}\">${pkg.author.name}</a>, ` +\n      `2015-${new Date().getFullYear()}. ` +\n      `${pkg.webmidi.name} v${pkg.version} is released under the ${pkg.license} license.`,\n\n    navMembers: [\n      {kind: \"class\", title: \"Classes\", summary: \"All documented classes.\"},\n      {kind: \"external\", title: \"Externals\", summary: \"All documented external members.\"},\n      // {kind: \"global\", title: \"Globals\", summary: \"All documented globals.\"},\n      {kind: \"mixin\", title: \"Mixins\", summary: \"All documented mixins.\"},\n      {kind: \"interface\", title: \"Interfaces\", summary: \"All documented interfaces.\"},\n      {kind: \"module\", title: \"Modules\", summary: \"All documented modules.\"},\n      {kind: \"namespace\", title: \"Namespaces\", summary: \"All documented namespaces.\"},\n      {kind: \"tutorial\", title: \"Tutorials\", summary: \"All available tutorials.\"}\n    ],\n\n    analytics: GA_CONFIG,\n\n    stylesheets: [\n      CUSTOM_CSS\n    ],\n\n    dateFormat: \"MMMM Do YYYY @ H:mm:ss\",\n    sort: \"longname, linenum, version, since\",\n\n    collapseSymbols: true\n\n  }\n\n};\n\nfunction log(message) {\n  console.info(\"\\x1b[32m\", message, \"\\x1b[0m\");\n}\n\nasync function execute() {\n\n  // Temporary target folder\n  const TMP_SAVE_PATH = await fsPromises.mkdtemp(path.join(os.tmpdir(), \"webmidi-api-doc-\"));\n  const CONF_PATH = TMP_SAVE_PATH + \"/.jsdoc.json\";\n\n  // Write temporary configuration file\n  fs.writeFileSync(CONF_PATH, JSON.stringify(config));\n\n  // Prepare jsdoc command and generate documentation\n  const cmd = \"./node_modules/.bin/jsdoc \" +\n    `--configure ${CONF_PATH} ` +\n    `--destination ${TMP_SAVE_PATH}`;\n  await system(cmd);\n  log(`Documentation temporarily generated in \"${TMP_SAVE_PATH}\"`);\n\n  // Remove temporary configuration file\n  await rimraf(CONF_PATH);\n\n  // Here, we remove the index.html page and replace it with the list_class.html file\n  await fs.copy(\n    TMP_SAVE_PATH + \"/list_class.html\",\n    TMP_SAVE_PATH + \"/index.html\",\n    {overwrite: true}\n  );\n\n  // Get current branch (so we can come back to it later)\n  let results = await git.branch();\n  const ORIGINAL_BRANCH = results.current;\n\n  // Switch to target branch\n  log(`Switching from '${ORIGINAL_BRANCH}' branch to '${TARGET_BRANCH}' branch`);\n  await git.checkout(TARGET_BRANCH);\n\n  // Move dir to final destination and commit\n  await fs.move(TMP_SAVE_PATH, TARGET_PATH, {overwrite: true});\n  await git.add([TARGET_PATH]);\n  await git.commit(\"Updated on: \" + moment().format(), [TARGET_PATH]);\n  await git.push();\n  log(`Changes committed to ${TARGET_PATH} folder of '${TARGET_BRANCH}' branch`);\n\n  // Come back to original branch\n  log(`Switching back to '${ORIGINAL_BRANCH}' branch`);\n  await git.checkout(ORIGINAL_BRANCH);\n\n}\n\n// Execute and catch errors if any (in red)\nexecute().catch(error => console.error(\"\\x1b[31m\", \"Error: \" + error, \"\\x1b[0m\"));\n"
  },
  {
    "path": "scripts/api-documentation/generate-markdown.js",
    "content": "// Imports\nconst djipHelpers = require(\"./templates/helpers/djip-helpers.js\");\nconst fs = require(\"fs-extra\");\nconst git = require(\"simple-git\")();\nconst Handlebars = require(\"handlebars\");\nconst jsdoc2md = require(\"jsdoc-to-markdown\");\nconst moment = require(\"moment\");\nconst path = require(\"path\");\nconst process = require(\"process\");\n\n// Paths\nconst ROOT_PATH = process.cwd();\nconst SOURCE_DIR = path.resolve(ROOT_PATH, \"src\");\nconst TARGET_PATH = path.join(process.cwd(), \"website\", \"api\", \"classes\");\nconst TEMPLATE_DIR = path.resolve(ROOT_PATH, \"scripts/api-documentation/templates/\");\nconst DJIPEVENTS = path.resolve(ROOT_PATH, \"node_modules/djipevents/src/djipevents.js\");\n\n// Register some Handlebars helpers\nHandlebars.registerHelper({\n  eventName: djipHelpers.eventName,\n  stripNewlines: djipHelpers.stripNewlines,\n  inlineLinks: djipHelpers.inlineLinks,\n  methodSignature: djipHelpers.methodSignature,\n  eq: djipHelpers.eq,\n  ne: djipHelpers.ne,\n  lt: djipHelpers.lt,\n  gt: djipHelpers.gt,\n  lte: djipHelpers.lte,\n  gte: djipHelpers.gte,\n  and: djipHelpers.and,\n  or: djipHelpers.or,\n  curly: djipHelpers.curly,\n  createEventAnchor: djipHelpers.createEventAnchor,\n});\n\nasync function generate() {\n\n  // Get source files list\n  let files = await fs.readdir(SOURCE_DIR);\n  files = files.map(file => path.resolve(SOURCE_DIR, file));\n  files.push(DJIPEVENTS);\n\n  // Compute JSON from JSDoc\n  const data = jsdoc2md.getTemplateDataSync({files: files});\n\n  // Build list of classes (explicitly adding Listener and EventEmitter)\n  let classes = files.map(file => path.basename(file, \".js\")).filter(file => file !== \"djipevents\");\n  classes.push(\"Listener\", \"EventEmitter\");\n\n  // Parse each class file and save parsed output\n  classes.forEach(filepath => {\n\n    const basename = path.basename(filepath, \".js\");\n    const filtered = data.filter(x => x.memberof === basename || x.id === basename);\n\n    // Save markdown files\n    fs.writeFileSync(\n      path.resolve(TARGET_PATH, `${basename}.md`),\n      parseFile(filtered)\n    );\n    console.info(`Saved markdown file to ${basename}.md`);\n\n  });\n\n  // Commit generated files\n  await git.add([TARGET_PATH]);\n  await git.commit(\"Automatically generated on: \" + moment().format(), [TARGET_PATH]);\n  console.info(`Files in ${TARGET_PATH} committed to git`);\n  await git.push();\n  console.info(`Files pushed to remote`);\n\n}\n\nfunction parseFile(data) {\n\n  let output = \"\";\n  let hbs;\n  let filtered;\n\n  // Sort elements according to kind and then according to name\n  const order = {class: 0, constructor: 1, function: 2, member: 3, event: 4, enum: 5, typedef: 6};\n  data.sort((a, b) => {\n    if (order[a.kind] === order[b.kind]) {\n      return a.id.localeCompare(b.id);\n    } else {\n      return order[a.kind] < order[b.kind] ? -1 : 1;\n    }\n  });\n\n  // Sort 'fires' if present\n  data.forEach(element => {\n    if (Array.isArray(element.fires)) element.fires.sort();\n  });\n\n  // Class\n  filtered = data.filter(el => el.kind === \"class\")[0];\n  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/class.hbs`), {encoding: \"utf-8\"});\n  output += Handlebars.compile(hbs)(filtered);\n\n  // Constructor\n  filtered = data.filter(el => el.kind === \"constructor\")[0];\n  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/constructor.hbs`), {encoding: \"utf-8\"});\n  output += Handlebars.compile(hbs)(filtered);\n\n  // Members\n  filtered = data.filter(el => el.kind === \"member\" && el.access !== \"private\");\n  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/properties.hbs`), {encoding: \"utf-8\"});\n  output += Handlebars.compile(hbs)(filtered);\n\n  // Methods\n  filtered = data.filter(el => el.kind === \"function\" && el.access !== \"private\");\n  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/methods.hbs`), {encoding: \"utf-8\"});\n  output += Handlebars.compile(hbs)(filtered);\n\n  // Events\n  filtered = data.filter(el => el.kind === \"event\" && el.access !== \"private\");\n  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/events.hbs`), {encoding: \"utf-8\"});\n  output += Handlebars.compile(hbs)(filtered);\n\n  // Enums\n  filtered = data.filter(el => el.kind === \"enum\" && el.access !== \"private\");\n  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/enumerations.hbs`), {encoding: \"utf-8\"});\n  output += Handlebars.compile(hbs)(filtered);\n\n  // Strip out links to Listener class\n  // output = output.replaceAll(\"[**Listener**](Listener)\", \"`Listener`\");\n  // output = output.replaceAll(\"[Listener](Listener)\", \"`Listener`\");\n  // output = output.replaceAll(\"[**arguments**](Listener#arguments)\", \"`arguments`\");\n\n  return output;\n\n}\n\ngenerate().catch(error => console.error(\"\\x1b[31m\", \"Error: \" + error, \"\\x1b[0m\"));\n"
  },
  {
    "path": "scripts/api-documentation/templates/core/class.hbs",
    "content": "{{!\n\n  This template creates the 'class' portion of the output (at the top). It relies on the following\n  helpers:\n\n    - inlineLinks\n    - eq\n    - eventName\n\n}}\n\n{{! Class name }}\n# {{name}}\n\n{{! Class description }}\n{{{inlineLinks description}}}\n\n{{! Since }}\n{{#if since}}\n**Since**: {{since}}\n{{/if}}\n\n{{! Extends }}\n{{#if augments}}\n**Extends**: {{#each augments}}[`{{this}}`]({{this}}){{#unless @last}}, {{/unless}}{{/each}}\n<!--**Extends**: {{#each augments}}{{#if (eq this \"EventEmitter\")}}{{this}}{{else if (eq this \"Listener\")}}{{this}}{{else}}[`{{this}}`]({{this}}){{#unless @last}}, {{/unless}}{{/if}}{{/each}}-->\n{{/if}}\n\n{{! Fires }}\n{{#if fires}}\n**Fires**: {{#each fires}}[`{{eventName this}}`](#event:{{eventName this}}){{#unless @last}}, {{/unless}}{{/each}}\n{{/if}}\n"
  },
  {
    "path": "scripts/api-documentation/templates/core/constructor.hbs",
    "content": "{{!\n\n  This template creates the 'constructor' portion of the output (below the class). It relies on the\n  following helpers:\n\n    - stripNewlines\n\n}}\n{{#if this}}\n\n{{! Constructor name }}\n### `Constructor`\n\n{{! Description }}\n{{{inlineLinks description}}}\n\n{{! Parameters }}\n{{#if params}}\n\n  **Parameters**\n\n  > `new {{this.longname}}({{methodSignature this}})`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n  {{#each params}}\n    |{{#if this.optional}}[{{/if}}**`{{this.name}}`**{{#if this.optional}}]{{/if}} | {{#each this.type.names}}{{this}}<br />{{/each}} |{{this.defaultvalue}}|{{{stripNewlines (inlineLinks this.description)}}}|\n  {{/each}}\n\n  </div>\n\n{{/if}}\n\n{{! Exceptions }}\n{{#if this.exceptions}}\n**Throws**:\n{{#each this.exceptions}}\n* {{#if this.type}}`{{this.type.names.[0]}}` : {{/if}}{{inlineLinks this.description}}\n{{/each}}\n{{/if}}\n\n{{/if}}\n"
  },
  {
    "path": "scripts/api-documentation/templates/core/enumerations.hbs",
    "content": "{{!\n\n  This template creates the 'enums' portion of the output . It relies on the following helpers:\n\n    - inlineLinks\n    - stripNewlines\n\n}}\n{{#if this}}\n***\n\n## Enums\n\n{{#each this}}\n{{! Name }}\n### `.{{this.name}}` {{curly true}}#{{this.name}}{{curly}}\n**Type**: {{this.type.names.[0]}}<br />\n{{! Attributes }}\n{{#if (eq this.scope \"static\")}}\n**Attributes**: static\n{{/if}}\n\n{{! Description }}\n{{{inlineLinks description}}}\n{{/each}}\n\n{{/if}}\n"
  },
  {
    "path": "scripts/api-documentation/templates/core/events.hbs",
    "content": "{{!\n\n  This template creates the 'Events' portion of the output . It relies on the following helpers:\n\n    - inlineLinks\n    - stripNewlines\n\n}}\n{{#if this}}\n***\n\n## Events\n\n{{#each this}}\n{{! Name and anchor }}\n### `{{this.name}}` {{curly true}}{{createEventAnchor this.name}}{{curly}}\n\n<a id=\"event:{{this.name}}\"></a>\n\n\n{{! Description }}\n{{{inlineLinks description}}}\n\n{{! Since }}\n{{#if since}}\n**Since**: {{since}}\n{{/if}}\n\n{{! Properties }}\n{{#if properties}}\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n{{#each properties}}\n  |**`{{this.name}}`** |{{this.type.names.[0]}}|{{{stripNewlines (inlineLinks this.description)}}}|\n{{/each}}\n\n{{/if}}\n\n{{/each}}\n\n{{/if}}\n"
  },
  {
    "path": "scripts/api-documentation/templates/core/methods.hbs",
    "content": "{{!\n\n  This template creates the 'Events' portion of the output . It relies on the following helpers:\n\n    - inlineLinks\n    - stripNewlines\n\n}}\n{{#if this}}\n***\n\n## Methods\n\n{{#each this}}\n\n{{! Name }}\n### `.{{this.name}}({{#if params}}...{{/if}})` {{curly true}}#{{this.name}}{{curly}}\n\n{{! Since }}\n{{#if this.since}}\n**Since**: {{this.since}}<br />\n{{/if}}\n{{! Attributes }}\n{{#if (eq this.async true)}}\n**Attributes**: async\n{{/if}}\n\n{{! Description }}\n{{{inlineLinks description}}}\n\n{{! Parameters }}\n{{#if params}}\n\n  **Parameters**\n\n  > Signature: `{{this.name}}({{methodSignature this}})`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n  {{#each params}}\n    |{{#if this.optional}}[{{/if}}**`{{this.name}}`**{{#if this.optional}}]{{/if}} | {{#each this.type.names}}{{this}}<br />{{/each}} |{{this.defaultvalue}}|{{{stripNewlines (inlineLinks this.description)}}}|\n  {{/each}}\n\n  </div>\n\n{{/if}}\n\n{{! Returns }}\n{{#if returns}}\n**Return Value**\n\n{{! Return value description }}\n> Returns: {{#each returns~}}\n{{#if type~}}\n{{#each type.names~}}\n`{{{this}}}`{{#unless @last}} or {{/unless}}\n{{~/each}}<br />\n{{/if~}}\n\n{{~#if description}}\n\n{{{inlineLinks description}}}\n{{/if~}}\n{{~/each}}\n{{/if}}\n\n{{! Attributes }}\n{{#if (eq this.scope \"static\")}}\n\n**Attributes**: static\n{{/if}}\n\n{{#if this.exceptions}}\n**Throws**:\n{{#each this.exceptions}}\n  * {{#if this.type}}`{{this.type.names.[0]}}` : {{/if}}{{{inlineLinks this.description}}}\n{{/each}}\n{{/if}}\n\n{{/each}}\n\n{{/if}}\n"
  },
  {
    "path": "scripts/api-documentation/templates/core/properties.hbs",
    "content": "{{!\n\n  This template creates the 'properties' portion of the output . It relies on the following helpers:\n\n    - inlineLinks\n    - stripNewlines\n\n}}\n{{#if this}}\n***\n\n## Properties\n\n{{#each this}}\n{{! Name }}\n### `.{{this.name}}` {{curly true}}#{{this.name}}{{curly}}\n{{! Since }}\n{{#if this.since}}\n**Since**: {{this.since}}<br />\n{{/if}}\n{{! Type }}\n**Type**: {{this.type.names.[0]}}<br />\n{{#if (or this.readonly this.nullable)}}\n{{! Attributes }}\n**Attributes**: {{#if this.readonly}}read-only{{/if}}{{#if this.nullable}}, nullable{{/if}}{{#if (eq this.scope \"static\")}}, static{{/if}}<br />\n{{/if}}\n\n\n{{! Description }}\n{{{inlineLinks description}}}\n\n{{! Properties }}\n{{#if properties}}\n\n  **Properties**\n\n  | Property     | Type         | Description  |\n  | ------------ | ------------ | ------------ |\n  {{#each properties}}\n    |**`{{this.name}}`** |{{this.type.names.[0]}}|{{{stripNewlines (inlineLinks this.description)}}}|\n  {{/each}}\n\n{{/if}}\n\n{{/each}}\n\n{{/if}}\n"
  },
  {
    "path": "scripts/api-documentation/templates/helpers/ddata.js",
    "content": "var arrayify = require('array-back');\nvar util = require('util');\nvar handlebars = require('handlebars');\nvar marked = require('marked');\nvar objectGet = require('object-get');\nvar where = require('test-value').where;\nvar flatten = require('reduce-flatten');\nvar state = require('./state.js');\n\n/**\n * ddata is a collection of handlebars helpers for working with the documentation data output by\n * [jsdoc-parse](https://github.com/75lb/jsdoc-parse).\n *\n * @module\n * @example\n * ```js\n * var handlebars = require(\"handlebars\")\n * var ddata = require(\"ddata\")\n * var docs = require(\"./docs.json\") // jsdoc-parse output\n *\n * handlebars.registerHelper(ddata)\n * var template =\n * \"{{#module name='yeah-module'}}\\\n * The author of the module is: {{author}}.\\\n * {{/module}}\"\n * console.log(handlebars.compile(template)(docs))\n * ```\n */\n\n/* utility block helpers */\nexports.link = link;\nexports.returnSig2 = returnSig2;\nexports.sig = sig;\nexports.children = children;\nexports.indexChildren = indexChildren;\n\n/* helpers which return objects */\nexports._link = _link;\n\n/* helpers which return booleans */\nexports.isClass = isClass;\nexports.isClassMember = isClassMember;\nexports.isConstructor = isConstructor;\nexports.isFunction = isFunction;\nexports.isConstant = isConstant;\nexports.isEvent = isEvent;\nexports.isEnum = isEnum;\nexports.isTypedef = isTypedef;\nexports.isCallback = isCallback;\nexports.isModule = isModule;\nexports.isMixin = isMixin;\nexports.isExternal = isExternal;\nexports.isPrivate = isPrivate;\nexports.isProtected = isProtected;\nexports.showMainIndex = showMainIndex;\n\n/* helpers which return lists */\nexports._orphans = _orphans;\nexports._identifiers = _identifiers;\nexports._children = _children;\nexports._globals = _globals;\nexports.descendants = descendants;\n\n/* helpers which return single identifiers */\nexports.exported = exported;\nexports.parentObject = parentObject;\nexports._identifier = _identifier;\n\n/* helpers which return strings */\nexports.anchorName = anchorName;\nexports.md = md;\nexports.md2 = md2;\nexports.methodSig = methodSig;\nexports.parseLink = parseLink;\nexports.parentName = parentName;\nexports.option = option;\nexports.optionEquals = optionEquals;\nexports.optionSet = optionSet;\nexports.optionIsSet = optionIsSet;\nexports.stripNewlines = stripNewlines;\n\n/* helpers which keep state */\nexports.headingDepth = headingDepth;\nexports.depth = depth;\nexports.depthIncrement = depthIncrement;\nexports.depthDecrement = depthDecrement;\nexports.indexDepth = indexDepth;\nexports.indexDepthIncrement = indexDepthIncrement;\nexports.indexDepthDecrement = indexDepthDecrement;\n\n/**\n * omits externals without a description\n * @static\n */\nfunction _globals (options) {\n  options.hash.scope = 'global'\n  return _identifiers(options).filter(function (identifier) {\n    if (identifier.kind === 'external') {\n      return identifier.description && identifier.description.length > 0\n    } else {\n      return true\n    }\n  })\n}\n\n/**\n * This helper is a duplicate of the handlebars `each` helper with the one exception that\n * `depthIncrement` is called on each iteration.\n * @category Block helper: selector\n */\nfunction children (options) {\n  var context = _children.call(this, options);\n  var fn = options.fn;\n  var inverse = options.inverse;\n  var i = 0;\n  var ret = '';\n  var data;\n\n  var contextPath\n\n  if (options.data) {\n    data = handlebars.createFrame(options.data)\n  }\n\n  for (var j = context.length; i < j; i++) {\n    depthIncrement(options)\n    if (data) {\n      data.index = i\n      data.first = (i === 0)\n      data.last = (i === (context.length - 1))\n\n      if (contextPath) {\n        data.contextPath = contextPath + i\n      }\n    }\n    ret = ret + fn(context[i], { data: data })\n    depthDecrement(options)\n  }\n\n  if (i === 0) {\n    ret = inverse(this)\n  }\n\n  return ret\n}\n\n/**\n * This helper is a duplicate of the handlebars `each` helper with the one exception that\n * `indexDepthIncrement` is called on each iteration.\n * @static\n * @category Block helper: selector\n */\nfunction indexChildren (options) {\n  var context = _children.call(this, options)\n  var fn = options.fn\n  var inverse = options.inverse\n  var i = 0\n  var ret = ''\n  var data\n\n  var contextPath\n\n  if (options.data) {\n    data = handlebars.createFrame(options.data)\n  }\n\n  for (var j = context.length; i < j; i++) {\n    indexDepthIncrement(options)\n    if (data) {\n      data.index = i\n      data.first = (i === 0)\n      data.last = (i === (context.length - 1))\n\n      if (contextPath) {\n        data.contextPath = contextPath + i\n      }\n    }\n    ret = ret + fn(context[i], { data: data })\n    indexDepthDecrement(options)\n  }\n\n  if (i === 0) {\n    ret = inverse(this)\n  }\n\n  return ret\n}\n\n/**\n * @param id {string} - the ID to link to, e.g. `external:XMLHttpRequest`, `GlobalClass#propOne`\n * etc.\n * @static\n * @category Block helper: util\n * @example\n * {{#link \"module:someModule.property\"~}}\n *   {{name}} {{!-- prints 'property' --}}\n *   {{url}}  {{!-- prints 'module-someModule-property' --}}\n * {{/link}}\n */\nfunction link (longname, options) {\n  return options.fn(_link(longname, options));\n}\n\n/**\n * e.g. namepaths `module:Something` or type expression `Array.<module:Something>`\n * @static\n * @param {string} - namepath or type expression\n * @param {object} - the handlebars helper options object\n */\nfunction _link (input, options) {\n  if (typeof input !== 'string') return null\n\n  var linked, matches, namepath\n  var output = {}\n\n  /*\n   test input for\n   1. A type expression containing a namepath, e.g. Array.<module:Something>\n   2. a namepath referencing an `id`\n   3. a namepath referencing a `longname`\n   */\n  if ((matches = input.match(/.*?<(.*?)>/))) {\n    namepath = matches[1]\n  } else {\n    namepath = input\n  }\n\n  options.hash = { id: namepath }\n  linked = _identifier(options)\n  if (!linked) {\n    options.hash = { longname: namepath }\n    linked = _identifier(options)\n  }\n  if (!linked) {\n    output = { name: input, url: null }\n  } else {\n    output.name = input.replace(namepath, linked.name)\n    if (isExternal.call(linked)) {\n      if (linked.description) {\n        output.url = '#' + anchorName.call(linked, options)\n      } else {\n        if (linked.see && linked.see.length) {\n          var firstLink = parseLink(linked.see[0])[0]\n          output.url = firstLink ? firstLink.url : linked.see[0]\n        } else {\n          output.url = null\n        }\n      }\n    } else {\n      output.url = '#' + anchorName.call(linked, options)\n    }\n  }\n  return output\n}\n\n/**\n @static\n @returns {{symbol: string, types: array}}\n @category Block helper: util\n */\nfunction returnSig2 (options) {\n  if (!isConstructor.call(this)) {\n    if (this.returns) {\n      var typeNames = arrayify(this.returns).map(function (ret) {\n        return ret.type && ret.type.names\n      })\n      typeNames = typeNames.filter(function (name) {\n        return name\n      })\n      if (typeNames.length) {\n        return options.fn({\n          symbol: '⇒',\n          types: typeNames.reduce(flatten, [])\n        })\n      } else {\n        return options.fn({\n          symbol: null,\n          types: null\n        })\n      }\n    } else if ((this.type || this.kind === 'namespace') && this.kind !== 'event') {\n      if (this.kind === 'namespace') {\n        return options.fn({\n          symbol: ':',\n          types: ['object']\n        })\n      } else {\n        return options.fn({\n          symbol: ':',\n          types: this.type.names\n        })\n      }\n    }\n  }\n}\n\n/**\n @category Block helper: util\n */\nfunction sig (options) {\n  var data\n\n  if (options.data) {\n    data = handlebars.createFrame(options.data || {})\n  }\n\n  data.prefix = this.kind === 'constructor' ? 'new' : ''\n  data.parent = null\n  data.accessSymbol = null\n  data.name = isEvent.call(this) ? '\"' + this.name + '\"' : this.name\n  data.methodSign = null\n  data.returnSymbol = null\n  data.returnTypes = null\n  data.suffix = this.isExported ? '⏏' : isPrivate.call(this) ? '℗' : ''\n  data.depOpen = null\n  data.depClose = null\n  data.codeOpen = null\n  data.codeClose = null\n\n  var mSig = methodSig.call(this)\n  if (isConstructor.call(this) || isFunction.call(this)) {\n    data.methodSign = '(' + mSig + ')'\n  } else if (isEvent.call(this)) {\n    if (mSig) data.methodSign = '(' + mSig + ')'\n  }\n\n  if (!isEvent.call(this)) {\n    data.parent = parentName.call(this, options)\n    data.accessSymbol = (this.scope === 'static' || this.scope === 'instance') ? '.' : this.scope === 'inner' ? '~' : ''\n  }\n\n  if (!isConstructor.call(this)) {\n    if (this.returns) {\n      data.returnSymbol = '⇒'\n      var typeNames = arrayify(this.returns)\n        .map(function (ret) {\n          return ret.type && ret.type.names\n        })\n        .filter(function (name) {\n          return name\n        })\n      if (typeNames.length) {\n        data.returnTypes = typeNames.reduce(flatten, [])\n      }\n    } else if ((this.type || this.kind === 'namespace') && this.kind !== 'event') {\n      data.returnSymbol = ':'\n\n      if (isEnum.call(this)) {\n        data.returnTypes = ['enum']\n      } else if (this.kind === 'namespace') {\n        data.returnTypes = ['object']\n      } else {\n        data.returnTypes = this.type.names\n      }\n    } else if (this.chainable) {\n      data.returnSymbol = '↩︎'\n    } else if (this.augments) {\n      data.returnSymbol = '⇐'\n      data.returnTypes = [this.augments[0]]\n    }\n  }\n\n  if (this.deprecated) {\n    if (optionEquals('no-gfm', true, options) || options.hash['no-gfm']) {\n      data.depOpen = '<del>'\n      data.depClose = '</del>'\n    } else {\n      data.depOpen = '~~'\n      data.depClose = '~~'\n    }\n  }\n\n  if (option('name-format', options) && !isClass.call(this) && !isModule.call(this)) {\n    data.codeOpen = '`'\n    data.codeClose = '`'\n  }\n\n  return options.fn(this, { data: data })\n}\n\n/**\n @this {identifier}\n @returns {boolean}\n @alias module:ddata.isClass\n */\nfunction isClass () { return this.kind === 'class' }\n\n/**\n returns true if the parent of the current identifier is a class\n @returns {boolean}\n @static\n */\nfunction isClassMember (options) {\n  var parent = arrayify(options.data.root).find(where({ id: this.memberof }))\n  if (parent) {\n    return parent.kind === 'class'\n  }\n}\nfunction isConstructor () { return this.kind === 'constructor' }\nfunction isFunction () { return this.kind === 'function' }\nfunction isConstant () { return this.kind === 'constant' }\n/**\n returns true if this is an event\n @returns {boolean}\n @static\n */\nfunction isEvent () { return this.kind === 'event' }\nfunction isEnum () { return this.isEnum || this.kind === 'enum' }\nfunction isExternal () { return this.kind === 'external' }\nfunction isTypedef () {\n  return this.kind === 'typedef' && this.type.names[0] !== 'function'\n}\nfunction isCallback () {\n  return this.kind === 'typedef' && this.type.names[0] === 'function'\n}\nfunction isModule () { return this.kind === 'module' }\nfunction isMixin () { return this.kind === 'mixin' }\nfunction isPrivate () { return this.access === 'private' }\nfunction isProtected () { return this.access === 'protected' }\n\n/**\n True if there at least two top-level identifiers (i.e. globals or modules)\n @category returns boolean\n @returns {boolean}\n @static\n */\nfunction showMainIndex (options) {\n  return _orphans(options).length > 1\n}\n\n/**\n * Returns an array of the top-level elements which have no parents. Output only includes externals which have a description.\n * @returns {array}\n * @static\n * @category Returns list\n */\nfunction _orphans (options) {\n  options.hash.memberof = undefined\n  return _identifiers(options).filter(function (identifier) {\n    if (identifier.kind === 'external') {\n      return identifier.description && identifier.description.length > 0\n    } else {\n      return true\n    }\n  })\n}\n\n/**\n * Returns an array of identifiers matching the query\n * @returns {array}\n * @static\n */\nfunction _identifiers (options) {\n  var query = {}\n\n  for (var prop in options.hash) {\n    if (/^-/.test(prop)) {\n      query[prop.replace(/^-/, '!')] = options.hash[prop]\n    } else if (/^_/.test(prop)) {\n      query[prop.replace(/^_/, '')] = new RegExp(options.hash[prop])\n    } else {\n      query[prop] = options.hash[prop]\n    }\n  }\n  return arrayify(options.data.root).filter(where(query)).filter(function (doclet) {\n    return !doclet.ignore && (state.options.private ? true : doclet.access !== 'private')\n  })\n}\n\n/**\n return the identifiers which are a `memberof` this one. Exclude externals without descriptions.\n @param [sortBy] {string} - \"kind\"\n @param [min] {number} - only returns if there are `min` children\n @this {identifier}\n @returns {identifier[]}\n @static\n */\nfunction _children (options) {\n  if (!this.id) return []\n  var min = options.hash.min\n  delete options.hash.min\n  options.hash.memberof = this.id\n  var output = _identifiers(options)\n  output = output.filter(function (identifier) {\n    if (identifier.kind === 'external') {\n      return identifier.description && identifier.description.length > 0\n    } else {\n      return true\n    }\n  })\n  if (output.length >= (min || 0)) return output\n}\n\n/**\n return a flat list containing all decendants\n @param [sortBy] {string} - \"kind\"\n @param [min] {number} - only returns if there are `min` children\n @this {identifier}\n @returns {identifier[]}\n @static\n */\nfunction descendants (options) {\n  var min = typeof options.hash.min !== 'undefined' ? options.hash.min : 2\n  delete options.hash.min\n  options.hash.memberof = this.id\n  var output = []\n  function iterate (childrenList) {\n    if (childrenList.length) {\n      childrenList.forEach(function (child) {\n        output.push(child)\n        iterate(_children.call(child, options))\n      })\n    }\n  }\n  iterate(_children.call(this, options))\n  if (output.length >= (min || 0)) return output\n}\n\n/**\n returns the exported identifier of this module\n @this {identifier} - only works on a module\n @returns {identifier}\n @static\n */\nfunction exported (options) {\n  var exp = arrayify(options.data.root).find(where({ '!kind': 'module', id: this.id }))\n  return exp || this\n}\n\n/**\n Returns an identifier matching the query\n @static\n */\nfunction _identifier (options) {\n  return _identifiers(options)[0]\n}\n\n/**\n Returns the parent\n @static\n */\nfunction parentObject (options) {\n  return arrayify(options.data.root).find(where({ id: this.memberof }))\n}\n\n/**\n returns a unique ID string suitable for use as an `href`.\n @this {identifier}\n @returns {string}\n @static\n @category Returns string\n @example\n ```js\n > ddata.anchorName.call({ id: \"module:yeah--Yeah()\" })\n 'module_yeah--Yeah_new'\n ```\n */\nfunction anchorName (options) {\n  if (!this.id) throw new Error('[anchorName helper] cannot create a link without a id: ' + JSON.stringify(this))\n  if (this.inherited) {\n    options.hash.id = this.inherits\n    var inherits = _identifier(options)\n    if (inherits) {\n      return anchorName.call(inherits, options)\n    } else {\n      return ''\n    }\n  }\n  return util.format(\n    '%s%s%s',\n    this.isExported ? 'exp_' : '',\n    this.kind === 'constructor' ? 'new_' : '',\n    this.id\n      .replace(/:/g, '_')\n      .replace(/~/g, '..')\n      .replace(/\\(\\)/g, '_new')\n      .replace(/#/g, '+')\n  )\n}\n\n/**\n converts the supplied text to markdown\n @static\n @category Returns string\n */\nfunction md (string, options) {\n  if (string) {\n    var result = marked(string).replace('lang-js', 'language-javascript')\n    return result\n  }\n}\nfunction md2 (options) {\n  return marked.inlineLexer(options.fn(this).toString(), [])\n}\n\n/**\n Returns the method signature, e.g. `(options, [onComplete])`\n @this {identifier}\n @returns {string}\n @category Returns string\n @static\n */\nfunction methodSig () {\n  return arrayify(this.params).filter(function (param) {\n    return param.name && !/\\./.test(param.name);\n  }).map(function (param) {\n    if (param.variable) {\n      return param.optional ? \"[...\" + param.name + \"]\" : \"...\" + param.name;\n    } else {\n      return param.optional ? \"[\" + param.name + \"]\" : param.name;\n    }\n  }).join(\", \");\n}\n\n/**\n * extracts url and caption data from @link tags\n * @param {string} - a string containing one or more {@link} tags\n * @returns {Array.<{original: string, caption: string, url: string}>}\n * @static\n */\nfunction parseLink (text) {\n  if (!text) return ''\n  var results = []\n  var matches = null\n  var link1 = /{@link\\s+([^\\s}|]+?)\\s*}/g // {@link someSymbol}\n  var link2 = /\\[([^\\]]+?)\\]{@link\\s+([^\\s}|]+?)\\s*}/g // [caption here]{@link someSymbol}\n  var link3 = /{@link\\s+([^\\s}|]+?)\\s*\\|([^}]+?)}/g // {@link someSymbol|caption here}\n  var link4 = /{@link\\s+([^\\s}|]+?)\\s+([^}|]+?)}/g // {@link someSymbol Caption Here}\n\n  while ((matches = link4.exec(text)) !== null) {\n    results.push({\n      original: matches[0],\n      caption: matches[2],\n      url: matches[1]\n    })\n    text = text.replace(matches[0], ' '.repeat(matches[0].length))\n  }\n\n  while ((matches = link3.exec(text)) !== null) {\n    results.push({\n      original: matches[0],\n      caption: matches[2],\n      url: matches[1]\n    })\n    text = text.replace(matches[0], ' '.repeat(matches[0].length))\n  }\n\n  while ((matches = link2.exec(text)) !== null) {\n    results.push({\n      original: matches[0],\n      caption: matches[1],\n      url: matches[2]\n    })\n    text = text.replace(matches[0], ' '.repeat(matches[0].length))\n  }\n\n  while ((matches = link1.exec(text)) !== null) {\n    results.push({\n      original: matches[0],\n      caption: matches[1],\n      url: matches[1]\n    })\n    text = text.replace(matches[0], ' '.repeat(matches[0].length))\n  }\n  return results\n}\n\n/**\n returns the parent name, instantiated if necessary\n @this {identifier}\n @returns {string}\n @static\n */\nfunction parentName (options) {\n  function instantiate (input) {\n    if (/^[A-Z]{3}/.test(input)) {\n      return input.replace(/^([A-Z]+)([A-Z])/, function (str, p1, p2) {\n        return p1.toLowerCase() + p2\n      })\n    } else {\n      return input.charAt(0).toLowerCase() + input.slice(1)\n    }\n  }\n\n  /* don't bother with a parentName for exported identifiers */\n  if (this.isExported) return ''\n\n  if (this.memberof && this.kind !== 'constructor') {\n    var parent = arrayify(options.data.root).find(where({ id: this.memberof }))\n    if (parent) {\n      if (this.scope === 'instance') {\n        var name = parent.typicalname || parent.name\n        return instantiate(name)\n      } else if (this.scope === 'static' && !(parent.kind === 'class' || parent.kind === 'constructor')) {\n        return parent.typicalname || parent.name\n      } else {\n        return parent.name\n      }\n    } else {\n      return this.memberof\n    }\n  }\n}\n\n/**\n returns a dmd option, e.g. \"sort-by\", \"heading-depth\" etc.\n @static\n */\nfunction option (name, options) {\n  return objectGet(options.data.root.options, name)\n}\n\n/**\n @static\n */\nfunction optionEquals (name, value, options) {\n  return options.data.root.options[name] === value\n}\n\n/**\n @static\n */\nfunction optionSet (name, value, options) {\n  options.data.root.options[name] = value\n}\n\n/**\n @static\n */\nfunction optionIsSet (name, options) {\n  return options.data.root.options[name] !== undefined\n}\n\n/**\n @static\n */\nfunction stripNewlines (input) {\n  if (input) return input.replace(/[\\r\\n]+/g, ' ')\n}\n\n/**\n @static\n */\nfunction headingDepth (options) {\n  return options.data.root.options._depth + (options.data.root.options['heading-depth'])\n}\n\n/**\n @static\n */\nfunction depth (options) {\n  return options.data.root.options._depth\n}\n\n/**\n @static\n */\nfunction depthIncrement (options) {\n  options.data.root.options._depth++\n}\n\n/**\n @static\n */\nfunction depthDecrement (options) {\n  options.data.root.options._depth--\n}\n\n/**\n @static\n */\nfunction indexDepth (options) {\n  return options.data.root.options._indexDepth\n}\n\n/**\n @static\n */\nfunction indexDepthIncrement (options) {\n  options.data.root.options._indexDepth++\n}\n\n/**\n @static\n */\nfunction indexDepthDecrement (options) {\n  options.data.root.options._indexDepth--\n}\n"
  },
  {
    "path": "scripts/api-documentation/templates/helpers/djip-helpers.js",
    "content": "const ddata = require(\"./ddata.js\");\n\n/**\n * Strips newline characters (\\n) from the input\n * @param input {string}\n * @returns {string}\n */\nfunction stripNewlines (input) {\n  if (input) return input.replace(/[\\r\\n]+/g, \" \");\n}\nexports.stripNewlines = stripNewlines;\n\n/**\n * Extract the event name from the jsdoc-reported name\n * @param input {string}\n * @returns {string}\n */\nfunction eventName (input) {\n  return input.split(\":\")[1];\n}\nexports.eventName = eventName;\n\n/**\n * Replaces JSDoc {@link} tags with markdown links in the supplied text\n */\nfunction inlineLinks (text, options) {\n\n  if (text) {\n    const links = ddata.parseLink(text);\n    links.forEach(function (link) {\n      const linked = ddata._link(link.url, options);\n      if (link.caption === link.url) link.caption = linked.name;\n      if (linked.url) link.url = linked.url;\n      text = text.replace(link.original, `[${link.caption}](${link.url})`);\n    });\n  }\n\n  return text;\n\n}\nexports.inlineLinks = inlineLinks;\n\nfunction curly(object, open) {\n  return open ? \"{\" : \"}\";\n};\nexports.curly = curly;\n\nfunction eq(v1, v2) { return v1 === v2; }\nexports.eq = eq;\nfunction ne(v1, v2) { return v1 !== v2; }\nexports.ne = ne;\nfunction lt(v1, v2) {return v1 < v2; }\nexports.lt = lt;\nfunction gt(v1, v2) { return v1 > v2; }\nexports.gt = gt;\nfunction lte(v1, v2) { return v1 <= v2; }\nexports.lte = lte;\nfunction gte(v1, v2) { return v1 >= v2; }\nexports.gte = gte;\nfunction and() { return Array.prototype.every.call(arguments, Boolean); }\nexports.and = and;\nfunction or() { return Array.prototype.slice.call(arguments, 0, -1).some(Boolean); }\nexports.or = or;\n\nfunction methodSignature(context) {\n  return ddata.methodSig.call(context);\n}\nexports.methodSignature = methodSignature;\n\nfunction createEventAnchor(name) {\n  return \"#event-\" + name.replace(\":\", \"-\");\n}\nexports.createEventAnchor = createEventAnchor;\n"
  },
  {
    "path": "scripts/api-documentation/templates/helpers/state.js",
    "content": "exports.templateData = []\nexports.options = {}\n"
  },
  {
    "path": "scripts/library/build.js",
    "content": "// This script builds the bundled library files (both normal and minified with sourcemaps) and\n// commits them to the 'dist' folder for later publishing. By default, CommonJS, ES Modules and IIFE\n// versions are built.\n//\n// Calling this script with the -t argument allows building only of them. Options are: cjs, esm and\n// iife.\n\n// Modules\nconst system = require(\"system-commands\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\n// Parse arguments (default type is esm). Use -t as type (if valid)\nlet type = \"esm\";\nconst argv = require(\"minimist\")(process.argv.slice(2));\nif ([\"cjs\", \"esm\", \"iife\"].includes(argv.t)) type = argv.t;\n\n// Prepare general command\nlet cmd = `./node_modules/.bin/rollup ` +\n  `--input src/WebMidi.js ` +\n  `--format ${type} `;\n\n// Minified version (with sourcemap)\nlet minified = cmd + ` --file dist/${type}/webmidi.${type}.min.js ` +\n  `--sourcemap ` +\n  `--config ${__dirname}/rollup.config.${type}.min.js`;\n\n// Non-minified version\nlet normal = cmd + ` --file dist/${type}/webmidi.${type}.js ` +\n  `--config ${__dirname}/rollup.config.${type}.js`;\n\nasync function execute(type) {\n\n  // Write package.json file so proper versions are imported by Node\n  if (type === \"esm\") {\n\n    fs.writeFileSync(\n      path.join(process.cwd(), \"dist\", \"esm\", \"package.json\"), '{\"type\": \"module\"}'\n    );\n\n    console.info(\n      \"\\x1b[32m\", // green font\n      `Custom package.json file (\"type\": \"module\") saved to \"dist/${type}/package.json\"`,\n      \"\\x1b[0m\"   // reset font\n    );\n\n  } else if (type === \"cjs\") {\n\n    fs.writeFileSync(\n      path.join(process.cwd(), \"dist\", \"cjs\", \"package.json\"), '{\"type\": \"commonjs\"}'\n    );\n\n    console.info(\n      \"\\x1b[32m\", // green font\n      `Custom package.json file (\"type\": \"commonjs\") saved to \"dist/${type}/package.json\"`,\n      \"\\x1b[0m\"   // reset font\n    );\n\n  }\n\n  // Production build\n  await system(minified);\n\n  console.info(\n    \"\\x1b[32m\", // green font\n    `The \"${type}\" minified build was saved to \"dist/${type}/webmidi.${type}.min.js\"`,\n    \"\\x1b[0m\"   // reset font\n  );\n\n  // Development build\n  await system(normal);\n\n  console.info(\n    \"\\x1b[32m\", // green font\n    `The \"${type}\" non-minified build was saved to \"dist/${type}/webmidi.${type}.js\"`,\n    \"\\x1b[0m\"   // reset font\n  );\n}\n\n// Execute and catch errors if any (in red)\nexecute(type).catch(error => console.error(\"\\x1b[31m\", \"Error: \" + error, \"\\x1b[0m\"));\n\n"
  },
  {
    "path": "scripts/library/rollup.config.cjs.js",
    "content": "import babel from \"@rollup/plugin-babel\";\nimport stripCode from \"rollup-plugin-strip-code\";\nimport replace from \"@rollup/plugin-replace\";\nconst fs = require(\"fs\");\nconst license = require(\"rollup-plugin-license\");\nconst versionInjector = require(\"rollup-plugin-version-injector\");\n\nconst BANNER = fs.readFileSync(__dirname + \"/../../BANNER.txt\", \"utf8\") + \"\\n\\n\\n\";\n\nexport default {\n  plugins: [\n    versionInjector(),\n    replace({__flavour__: \"cjs\"}),\n    stripCode({\n      start_comment: \"START-ESM\",\n      end_comment: \"END-ESM\"\n    }),\n    babel(),\n    license({banner: BANNER})\n  ]\n};\n"
  },
  {
    "path": "scripts/library/rollup.config.esm.js",
    "content": "import stripCode from \"rollup-plugin-strip-code\";\nimport replace from \"@rollup/plugin-replace\";\n\nconst fs = require(\"fs\");\nconst license = require(\"rollup-plugin-license\");\nconst versionInjector = require(\"rollup-plugin-version-injector\");\n\nconst BANNER = fs.readFileSync(__dirname + \"/../../BANNER.txt\", \"utf8\") + \"\\n\\n\\n\";\n\nexport default {\n\n  plugins: [\n    versionInjector(),\n    replace({__flavour__: \"esm\"}),\n    stripCode({\n      start_comment: \"START-CJS\",\n      end_comment: \"END-CJS\"\n    }),\n    license({\n      banner: BANNER\n    })\n  ]\n\n};\n"
  },
  {
    "path": "scripts/library/rollup.config.iife.js",
    "content": "import babel from \"@rollup/plugin-babel\";\nimport stripCode from \"rollup-plugin-strip-code\";\nimport replace from \"@rollup/plugin-replace\";\n\nconst fs = require(\"fs\");\nconst license = require(\"rollup-plugin-license\");\nconst versionInjector = require(\"rollup-plugin-version-injector\");\n\nconst BANNER = fs.readFileSync(__dirname + \"/../../BANNER.txt\", \"utf8\") + \"\\n\\n\\n\";\n\nexport default {\n\n  output: {\n    name: \"window\",       // WebMidi and Note will be added to window\n    extend: true,         // important!\n    exports: \"named\"\n  },\n\n  plugins: [\n    versionInjector(),\n    replace({__flavour__: \"iife\"}),\n    stripCode({\n      start_comment: \"START-CJS\",\n      end_comment: \"END-CJS\"\n    }),\n    stripCode({\n      start_comment: \"START-ESM\",\n      end_comment: \"END-ESM\"\n    }),\n    babel(),\n    license({\n      banner: BANNER\n    })\n  ]\n\n};\n"
  },
  {
    "path": "scripts/sponsors/retrieve-sponsors.js",
    "content": "const { graphql } = require(\"@octokit/graphql\");\nconst { token } = require(\"../../.credentials/sponsors.js\");\nconst replace = require(\"replace-in-file\");\nconst path = require(\"path\");\n\nconst TARGET = path.join(process.cwd(), \"website\", \"src\", \"pages\", \"sponsors\", \"index.md\");\n\nasync function getSponsors() {\n\n  const query = `{\n\n    user (login: \"djipco\") {\n      sponsorshipsAsMaintainer(first: 100, includePrivate: true) {\n        totalCount\n        nodes {\n          sponsorEntity {\n          ... on User {\n            login\n            name\n            avatarUrl\n            url\n            sponsorshipForViewerAsSponsorable {\n                isOneTimePayment\n                privacyLevel\n                tier {\n                  id\n                  name\n                  monthlyPriceInDollars\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n\n  }`;\n\n  const {user} = await graphql(\n    query,\n    { headers: { authorization: `token ${token}` } }\n  );\n\n  return user.sponsorshipsAsMaintainer.nodes;\n\n}\n\ngetSponsors().then(async data => {\n\n  // data.forEach(d => {\n  //   console.log(d.sponsorEntity.login, d.sponsorEntity.sponsorshipForViewerAsSponsorable);\n  //   // console.log(d);\n  // });\n\n  let output = \"\";\n\n  data.sort((a, b) => {\n    a = a.sponsorEntity.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars;\n    b = b.sponsorEntity.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars;\n\n    if (a > b) return -1;\n    if (a < b) return -1;\n    return 0;\n\n  });\n\n  data.forEach(entity => {\n\n    const sponsor = entity.sponsorEntity;\n\n    if (sponsor.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars < 6) {\n      return;\n    }\n\n    if (sponsor.sponsorshipForViewerAsSponsorable.privacyLevel === \"PUBLIC\") {\n      const name = sponsor.name || sponsor.login;\n      output += `<a href=\"${sponsor.url}\" title=\"${name}\" class=\"user-icon\">\\n`;\n      output += `\\t<img src=\"${sponsor.avatarUrl}\" `;\n      output += `alt=\"${name}\" width=\"100\" height=\"100\" />\\n`;\n      output += `</a>\\n\\n`;\n    } else {\n      output += `<a class=\"user-icon\"><img src=\"/img/sponsors/user.png\" `;\n      output += `alt=\"Anonymous\" width=\"100\" height=\"100\" /></a>\\n\\n`;\n    }\n\n  });\n\n  const options = {\n    files: TARGET,\n    from: /<!-- SPONSOR START -->.*<!-- SPONSOR END -->/sgm,\n    to: \"<!-- SPONSOR START -->\\n\\n\" + output + \"<!-- SPONSOR END -->\",\n  };\n\n  const results = await replace(options);\n\n});\n"
  },
  {
    "path": "scripts/typescript-declarations/generate.js",
    "content": "// This script injects a few dynamic properties into the source TypeScript declaration file and\n// copies it to the ./dist/esm and ./dist/cjs directories.\n\n// Modules\nconst path = require(\"path\");\nconst replace = require(\"replace-in-file\");\nconst pkg = require(\"../../package.json\");\nconst fs = require(\"fs-extra\");\n\n// Path to source declaration file\nconst SOURCE_FILE = path.join(__dirname, \"../../typescript\", \"webmidi.d.ts\");\n\n// Output directory\nconst OUTPUT_DIR = \"dist\";\n\n// Console arguments\nconst argv = require(\"minimist\")(process.argv.slice(2));\n\n// Get targets to save the file for\nlet targets = [];\nlet type = \"all\";\nif ([\"cjs\", \"esm\"].includes(argv.t)) type = argv.t;\n\nif (type === \"all\" || type === \"cjs\")\n  targets.push({path: \"webmidi.cjs.d.ts\", name: \"cjs\", type: \"CommonJS\"});\n\nif (type === \"all\" || type === \"esm\")\n  targets.push({path: \"webmidi.esm.d.ts\", name: \"esm\", type: \"ES2020\"});\n\nasync function execute() {\n\n  targets.forEach(async target => {\n\n    const FILE = path.join(OUTPUT_DIR, target.name, target.path);\n\n    // Copy the file to the target directory\n    await fs.copy(SOURCE_FILE, FILE, {overwrite: true});\n\n    // Insert dynamic data\n    replace.sync({\n      files: FILE,\n      from: [\"{{LIBRARY}}\", \"{{VERSION}}\", \"{{HOMEPAGE}}\", \"{{AUTHOR_NAME}}\", \"{{AUTHOR_URL}}\"],\n      to: [pkg.webmidi.name, pkg.version, pkg.homepage, pkg.author.name, pkg.author.url]\n    });\n\n    log(`Saved ${target.type} TypeScript declaration file to '${FILE}'`);\n\n  });\n\n}\n\n// Execute and catch errors if any (in red)\nexecute().catch(error => console.error(\"\\x1b[31m\", \"Error: \" + error, \"\\x1b[0m\"));\n\nfunction log(message) {\n  console.info(\"\\x1b[32m\", message, \"\\x1b[0m\");\n}\n"
  },
  {
    "path": "scripts/typescript-declarations/generateOLD.js",
    "content": "// This script generates TypeScript declaration files (.d.ts) of the library and saves them to the\n// 'dist' directory for later publishing. By default, CommonJS and ES2020 versions are generated.\n//\n// Calling this script with the -t argument allows generating only one declaration file. Options\n// are: cjs and esm.\n//\n// Calling this script with the -c argument allows you to commit and push the generated files.\n// Options are true or false.\n\n// Modules\nconst git = require(\"simple-git\")();\nconst moment = require(\"moment\");\nconst path = require(\"path\");\nconst prependFile = require(\"prepend-file\");\nconst replace = require(\"replace-in-file\");\nconst system = require(\"system-commands\");\nconst pkg = require(\"../../package.json\");\nconst os = require(\"os\");\nconst fsPromises = require(\"fs\").promises;\nconst fs = require(\"fs-extra\");\n\nconst OUT_DIR = \"dist\";\n\nconst WEB_MIDI_API_CLASSES = [\n  \"MIDIAccess\",\n  \"MIDIConnectionEvent\",\n  \"MIDIConnectionEventInit\",\n  \"MIDIInput\",\n  \"MIDIInputMap\",\n  \"MIDIMessageEvent\",\n  \"MIDIMessageEventInit\",\n  \"MIDIOptions\",\n  \"MIDIOutput\",\n  \"MIDIOutputMap\",\n  \"MIDIPort\",\n  \"MIDIPortConnectionStatus\",\n  \"MIDIPortDeviceState\",\n  \"MIDIPortType\"\n];\n\nconst HEADER = `// Type definitions for ${pkg.webmidi.name} ${pkg.version}\\n` +\n  `// Project: ${pkg.homepage}\\n` +\n  `// Definitions by: ${pkg.author.name} <https://github.com/djipco/>\\n` +\n  `// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\\n\\n`;\n\nlet targets = [];\n\nlet type = \"all\";\nconst argv = require(\"minimist\")(process.argv.slice(2));\nif ([\"cjs\", \"esm\"].includes(argv.t)) type = argv.t;\n\nconst commit = argv.c === \"true\";\n\nif (type === \"all\" || type === \"cjs\")\n  targets.push({source: \"webmidi.cjs.js\", name: \"cjs\", type: \"CommonJS\"});\n\nif (type === \"all\" || type === \"esm\")\n  targets.push({source: \"webmidi.esm.js\", name: \"esm\", type: \"ES2020\"});\n\nasync function execute() {\n\n  // Temp dir\n  const TMP_DIR_PATH = await fsPromises.mkdtemp(path.join(os.tmpdir(), \"webmidi-ts-\"));\n\n  targets.forEach(async target => {\n\n    // Make a copy of the source file so we can substitute some elements before parsing\n    const TMP_FILE_PATH = path.join(TMP_DIR_PATH, target.source);\n    await fs.copy(\n      path.join(OUT_DIR, target.name, target.source),\n      TMP_FILE_PATH,\n      {overwrite: true}\n    );\n\n    // Add TypeScript WebMidi namespace before native Web MIDI API objects\n    WEB_MIDI_API_CLASSES.forEach(element => {\n\n      const options = {\n        files: TMP_FILE_PATH,\n        from: new RegExp(\"{\" + element + \"}\", \"g\"),\n        to: () => `{WebMidi.${element}}`\n      };\n\n      replace.sync(options);\n\n    });\n\n    // Replace callback type by simply \"EventEmitterCallback\" (this is needed because tsc does not\n    // support tilde character in types. See below...\n    replace.sync({\n      files: TMP_FILE_PATH,\n      from: new RegExp(\"{EventEmitter~callback}\", \"g\"),\n      to: () => \"{EventEmitterCallback}\"\n    });\n\n    // Generate declaration file\n    const cmd = \"npx -p typescript tsc \" + TMP_FILE_PATH +\n      \" --declaration --allowJs --emitDeclarationOnly\" +\n      \" --module \" + target.type +\n      \" --lib ES2020,DOM --outDir \" + path.join(OUT_DIR, target.name) ;\n    await system(cmd);\n\n    // Path to current .d.ts file\n    const file = path.join(OUT_DIR, target.name, target.source.replace(\".js\", \".d.ts\"));\n\n    // Remove readonly flag\n    const options = {\n      files: [file],\n      from: /readonly /g,\n      to: \"\"\n    };\n    await replace(options);\n\n\n\n\n\n    // // Inject correct definitions for Input class\n    // await replace({\n    //   files: [file],\n    //   from: /export class Input extends EventEmitter \\{/,\n    //   to: fs.readFileSync(\n    //     path.join(__dirname, \"injections\", \"Input.txt\"), {encoding: \"utf8\", flag: \"r\"}\n    //   )\n    // });\n    //\n\n\n\n\n\n\n    // Prepend header for DefinitelyTyped\n    await prependFile(file, HEADER);\n    log(\"Saved \" + target.type + \" TypeScript declaration file to '\" + file + \"'\");\n\n    // // Inject EventEmitterCallback type declaration\n    // fs.appendFileSync(\n    //   file,\n    //   `\\n\\n` +\n    //   `// This is automatically injected to fix a bug with the TypeScript compiler\\n` +\n    //   `export type EventEmitterCallback = (...args: any[]) => void;\\n`\n    //   // dans Tone.js, ils utilisent:\n    //   //`export type EventEmitterCallback<A extends any[] = any[]> = (...args: A) => void;`\n    // );\n\n    // Commit\n    if (commit) {\n      await git.add([file]);\n      await git.commit(\"Generated by TypeScript compiler on \" + moment().format());\n      await git.push();\n    }\n\n  });\n\n}\n\n// Execute and catch errors if any (in red)\nexecute().catch(error => console.error(\"\\x1b[31m\", \"Error: \" + error, \"\\x1b[0m\"));\n\nfunction log(message) {\n  console.info(\"\\x1b[32m\", message, \"\\x1b[0m\");\n}\n"
  },
  {
    "path": "scripts/website/deploy.js",
    "content": "// This script copies the files generated by Docusaurus in /website/build to the root of the\n// 'gh-pages' branch. This makes them available at djipco.github.io/webmidi\n\n// Importation\nconst fs = require(\"fs-extra\");\nconst fsPromises = require(\"fs\").promises;\nconst git = require(\"simple-git\")();\nconst moment = require(\"moment\");\nconst os = require(\"os\");\nconst path = require(\"path\");\nconst rimraf = require(\"@alexbinary/rimraf\");\n\n// Configuration\nconst TARGET_BRANCH = \"gh-pages\";\nconst SOURCE_DIR = path.join(process.cwd(), \"website\", \"build\");\n\nasync function execute() {\n\n  const TMP_DIR = await fsPromises.mkdtemp(path.join(os.tmpdir(), \"webmidi-website-\"));\n\n  // Get list of files and directories inside the Docusaurus build directory\n  let files = await fsPromises.readdir(SOURCE_DIR);\n\n  // Get rid of .DS_Store and other unnecessary files\n  files = files.filter(file => !file.startsWith(\".\"));\n\n  // Copy files to tmp directory\n  for (const file of files) {\n    const source = path.join(SOURCE_DIR, file);\n    const target = path.join(TMP_DIR, file);\n    await fs.copy(source, target, {overwrite: true});\n  }\n  log(\"Copied website files to temp dir: \" + TMP_DIR);\n\n  // Get current branch (so we can come back to it later)\n  let results = await git.branch();\n  const ORIGINAL_BRANCH = results.current;\n\n  // Switch to target branch\n  log(`Switching from '${ORIGINAL_BRANCH}' branch to '${TARGET_BRANCH}' branch`);\n  await git.checkout(TARGET_BRANCH);\n\n  // Copy content of tmp dir to final destination and commit\n  for (const file of files) {\n    const source = path.join(TMP_DIR, file);\n    const target = path.join(process.cwd(), file);\n    await fs.copy(source, target, {overwrite: true});\n    await git.add([target]);\n    await git.commit(\"Updated on: \" + moment().format(), [target]);\n  }\n\n  await git.push();\n  log(`Website files committed to '${TARGET_BRANCH}' branch and pushed to remote`);\n\n  // Remove temp files\n  rimraf(TMP_DIR);\n  log(`Temp directory removed`);\n\n  // Come back to original branch\n  log(`Switching back to '${ORIGINAL_BRANCH}' branch`);\n  await git.checkout(ORIGINAL_BRANCH);\n\n}\n\nfunction log(message) {\n  console.info(\"\\x1b[32m\", message, \"\\x1b[0m\");\n}\n\n// Execution\nexecute().catch(error => console.error(\"\\x1b[31m\", \"Error: \" + error, \"\\x1b[0m\"));\n"
  },
  {
    "path": "src/Enumerations.js",
    "content": "/**\n * The `Enumerations` class contains enumerations and arrays of elements used throughout the\n * library. All its properties are static and should be referenced using the class name. For\n * example: `Enumerations.CHANNEL_MESSAGES`.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Enumerations {\n\n  /**\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @deprecated since 3.1 (use Enumerations.CHANNEL_MESSAGES instead)\n   * @private\n   * @static\n   */\n  static get MIDI_CHANNEL_MESSAGES() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_CHANNEL_MESSAGES enum has been deprecated. Use the \" +\n        \"Enumerations.CHANNEL_MESSAGES enum instead.\"\n      );\n    }\n\n    return Enumerations.CHANNEL_MESSAGES;\n\n  }\n\n  /**\n   * Enumeration of all MIDI channel message names and their associated 4-bit numerical value:\n   *\n   * | Message Name        | Hexadecimal | Decimal |\n   * |---------------------|-------------|---------|\n   * | `noteoff`           | 0x8         | 8       |\n   * | `noteon`            | 0x9         | 9       |\n   * | `keyaftertouch`     | 0xA         | 10      |\n   * | `controlchange`     | 0xB         | 11      |\n   * | `programchange`     | 0xC         | 12      |\n   * | `channelaftertouch` | 0xD         | 13      |\n   * | `pitchbend`         | 0xE         | 14      |\n   *\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @since 3.1\n   * @static\n   */\n  static get CHANNEL_MESSAGES() {\n\n    return {\n      noteoff: 0x8,           // 8\n      noteon: 0x9,            // 9\n      keyaftertouch: 0xA,     // 10\n      controlchange: 0xB,     // 11\n      programchange: 0xC,     // 12\n      channelaftertouch: 0xD, // 13\n      pitchbend: 0xE          // 14\n    };\n\n  }\n\n  /**\n   * A simple array of the 16 valid MIDI channel numbers (`1` to `16`):\n   *\n   * @type {number[]}\n   * @readonly\n   * @since 3.1\n   * @static\n   */\n  static get CHANNEL_NUMBERS() {\n    return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n  }\n\n  /**\n   * @type {number[]}\n   * @readonly\n   * @deprecated since 3.1 (use Enumerations.CHANNEL_NUMBERS instead)\n   * @private\n   * @static\n   */\n  static get MIDI_CHANNEL_NUMBERS() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_CHANNEL_NUMBERS array has been deprecated. Use the \" +\n        \"Enumerations.CHANNEL_NUMBERS array instead.\"\n      );\n    }\n\n    return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n\n  }\n\n  /**\n   * Enumeration of all MIDI channel mode message names and their associated numerical value:\n   *\n   *\n   * | Message Name          | Hexadecimal | Decimal |\n   * |-----------------------|-------------|---------|\n   * | `allsoundoff`         | 0x78        | 120     |\n   * | `resetallcontrollers` | 0x79        | 121     |\n   * | `localcontrol`        | 0x7A        | 122     |\n   * | `allnotesoff`         | 0x7B        | 123     |\n   * | `omnimodeoff`         | 0x7C        | 124     |\n   * | `omnimodeon`          | 0x7D        | 125     |\n   * | `monomodeon`          | 0x7E        | 126     |\n   * | `polymodeon`          | 0x7F        | 127     |\n   *\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @since 3.1\n   * @static\n   */\n  static get CHANNEL_MODE_MESSAGES() {\n\n    return {\n      allsoundoff: 120,\n      resetallcontrollers: 121,\n      localcontrol: 122,\n      allnotesoff: 123,\n      omnimodeoff: 124,\n      omnimodeon: 125,\n      monomodeon: 126,\n      polymodeon: 127\n    };\n\n  }\n\n  /**\n   * @enum {Object.<string, number>}\n   * @deprecated since 3.1 (use Enumerations.CHANNEL_MODE_MESSAGES instead)\n   * @private\n   * @readonly\n   * @static\n   */\n  static get MIDI_CHANNEL_MODE_MESSAGES() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_CHANNEL_MODE_MESSAGES enum has been deprecated. Use the \" +\n        \"Enumerations.CHANNEL_MODE_MESSAGES enum instead.\"\n      );\n    }\n\n    return Enumerations.CHANNEL_MODE_MESSAGES;\n\n  }\n\n  /**\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @static\n   * @private\n   * @deprecated since version 3.0.26 (use `CONTROL_CHANGE_MESSAGES` instead)\n   */\n  static get MIDI_CONTROL_CHANGE_MESSAGES() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_CONTROL_CHANGE_MESSAGES enum has been deprecated. Use the \" +\n        \"Enumerations.CONTROL_CHANGE_MESSAGES array instead.\"\n      );\n    }\n\n    return {\n\n      bankselectcoarse: 0,\n      modulationwheelcoarse: 1,\n      breathcontrollercoarse: 2,\n      controller3: 3,\n      footcontrollercoarse: 4,\n      portamentotimecoarse: 5,\n      dataentrycoarse: 6,\n      volumecoarse: 7,\n      balancecoarse: 8,\n      controller9: 9,\n      pancoarse: 10,\n      expressioncoarse: 11,\n      effectcontrol1coarse: 12,\n      effectcontrol2coarse: 13,\n      controller14: 14,\n      controller15: 15,\n      generalpurposeslider1: 16,\n      generalpurposeslider2: 17,\n      generalpurposeslider3: 18,\n      generalpurposeslider4: 19,\n      controller20: 20,\n      controller21: 21,\n      controller22: 22,\n      controller23: 23,\n      controller24: 24,\n      controller25: 25,\n      controller26: 26,\n      controller27: 27,\n      controller28: 28,\n      controller29: 29,\n      controller30: 30,\n      controller31: 31,\n      bankselectfine: 32,\n      modulationwheelfine: 33,\n      breathcontrollerfine: 34,\n      controller35: 35,\n      footcontrollerfine: 36,\n      portamentotimefine: 37,\n      dataentryfine: 38,\n      volumefine: 39,\n      balancefine: 40,\n      controller41: 41,\n      panfine: 42,\n      expressionfine: 43,\n      effectcontrol1fine: 44,\n      effectcontrol2fine: 45,\n      controller46: 46,\n      controller47: 47,\n      controller48: 48,\n      controller49: 49,\n      controller50: 50,\n      controller51: 51,\n      controller52: 52,\n      controller53: 53,\n      controller54: 54,\n      controller55: 55,\n      controller56: 56,\n      controller57: 57,\n      controller58: 58,\n      controller59: 59,\n      controller60: 60,\n      controller61: 61,\n      controller62: 62,\n      controller63: 63,\n      holdpedal: 64,\n      portamento: 65,\n      sustenutopedal: 66,\n      softpedal: 67,\n      legatopedal: 68,\n      hold2pedal: 69,\n      soundvariation: 70,\n      resonance: 71,\n      soundreleasetime: 72,\n      soundattacktime: 73,\n      brightness: 74,\n      soundcontrol6: 75,\n      soundcontrol7: 76,\n      soundcontrol8: 77,\n      soundcontrol9: 78,\n      soundcontrol10: 79,\n      generalpurposebutton1: 80,\n      generalpurposebutton2: 81,\n      generalpurposebutton3: 82,\n      generalpurposebutton4: 83,\n      controller84: 84,\n      controller85: 85,\n      controller86: 86,\n      controller87: 87,\n      controller88: 88,\n      controller89: 89,\n      controller90: 90,\n      reverblevel: 91,\n      tremololevel: 92,\n      choruslevel: 93,\n      celestelevel: 94,\n      phaserlevel: 95,\n      databuttonincrement: 96,\n      databuttondecrement: 97,\n      nonregisteredparametercoarse: 98,\n      nonregisteredparameterfine: 99,\n      registeredparametercoarse: 100,\n      registeredparameterfine: 101,\n      controller102: 102,\n      controller103: 103,\n      controller104: 104,\n      controller105: 105,\n      controller106: 106,\n      controller107: 107,\n      controller108: 108,\n      controller109: 109,\n      controller110: 110,\n      controller111: 111,\n      controller112: 112,\n      controller113: 113,\n      controller114: 114,\n      controller115: 115,\n      controller116: 116,\n      controller117: 117,\n      controller118: 118,\n      controller119: 119,\n      allsoundoff: 120,\n      resetallcontrollers: 121,\n      localcontrol: 122,\n      allnotesoff: 123,\n      omnimodeoff: 124,\n      omnimodeon: 125,\n      monomodeon: 126,\n      polymodeon: 127\n\n    };\n\n  }\n\n  /**\n   * An array of objects, ordered by control number, describing control change messages. Each object\n   * in the array has 3 properties with some objects having a fourth one (`position`) :\n   *\n   *  * `number`: MIDI control number (0-127);\n   *  * `name`: name of emitted event (eg: `bankselectcoarse`, `choruslevel`, etc) that can be\n   *  listened to;\n   *  * `description`: user-friendly description of the controller's purpose;\n   *  * `position` (optional): whether this controller's value should be considered an `msb` or\n   *  `lsb`\n   *\n   * Not all controllers have a predefined function. For those that don't, `name` is the word\n   * \"controller\" followed by the number (e.g. `controller112`).\n   *\n   * | Event name                     | Control Number |\n   * |--------------------------------|----------------|\n   * | `bankselectcoarse`             | 0              |\n   * | `modulationwheelcoarse`        | 1              |\n   * | `breathcontrollercoarse`       | 2              |\n   * | `controller3`                  | 3              |\n   * | `footcontrollercoarse`         | 4              |\n   * | `portamentotimecoarse`         | 5              |\n   * | `dataentrycoarse`              | 6              |\n   * | `volumecoarse`                 | 7              |\n   * | `balancecoarse`                | 8              |\n   * | `controller9`                  | 9              |\n   * | `pancoarse`                    | 10             |\n   * | `expressioncoarse`             | 11             |\n   * | `effectcontrol1coarse`         | 12             |\n   * | `effectcontrol2coarse`         | 13             |\n   * | `controller14`                 | 14             |\n   * | `controller15`                 | 15             |\n   * | `generalpurposecontroller1`    | 16             |\n   * | `generalpurposecontroller2`    | 17             |\n   * | `generalpurposecontroller3`    | 18             |\n   * | `generalpurposecontroller4`    | 19             |\n   * | `controller20`                 | 20             |\n   * | `controller21`                 | 21             |\n   * | `controller22`                 | 22             |\n   * | `controller23`                 | 23             |\n   * | `controller24`                 | 24             |\n   * | `controller25`                 | 25             |\n   * | `controller26`                 | 26             |\n   * | `controller27`                 | 27             |\n   * | `controller28`                 | 28             |\n   * | `controller29`                 | 29             |\n   * | `controller30`                 | 30             |\n   * | `controller31`                 | 31             |\n   * | `bankselectfine`               | 32             |\n   * | `modulationwheelfine`          | 33             |\n   * | `breathcontrollerfine`         | 34             |\n   * | `controller35`                 | 35             |\n   * | `footcontrollerfine`           | 36             |\n   * | `portamentotimefine`           | 37             |\n   * | `dataentryfine`                | 38             |\n   * | `channelvolumefine`            | 39             |\n   * | `balancefine`                  | 40             |\n   * | `controller41`                 | 41             |\n   * | `panfine`                      | 42             |\n   * | `expressionfine`               | 43             |\n   * | `effectcontrol1fine`           | 44             |\n   * | `effectcontrol2fine`           | 45             |\n   * | `controller46`                 | 46             |\n   * | `controller47`                 | 47             |\n   * | `controller48`                 | 48             |\n   * | `controller49`                 | 49             |\n   * | `controller50`                 | 50             |\n   * | `controller51`                 | 51             |\n   * | `controller52`                 | 52             |\n   * | `controller53`                 | 53             |\n   * | `controller54`                 | 54             |\n   * | `controller55`                 | 55             |\n   * | `controller56`                 | 56             |\n   * | `controller57`                 | 57             |\n   * | `controller58`                 | 58             |\n   * | `controller59`                 | 59             |\n   * | `controller60`                 | 60             |\n   * | `controller61`                 | 61             |\n   * | `controller62`                 | 62             |\n   * | `controller63`                 | 63             |\n   * | `damperpedal`                  | 64             |\n   * | `portamento`                   | 65             |\n   * | `sostenuto`                    | 66             |\n   * | `softpedal`                    | 67             |\n   * | `legatopedal`                  | 68             |\n   * | `hold2`                        | 69             |\n   * | `soundvariation`               | 70             |\n   * | `resonance`                    | 71             |\n   * | `releasetime`                  | 72             |\n   * | `attacktime`                   | 73             |\n   * | `brightness`                   | 74             |\n   * | `decaytime`                    | 75             |\n   * | `vibratorate`                  | 76             |\n   * | `vibratodepth`                 | 77             |\n   * | `vibratodelay`                 | 78             |\n   * | `controller79`                 | 79             |\n   * | `generalpurposecontroller5`    | 80             |\n   * | `generalpurposecontroller6`    | 81             |\n   * | `generalpurposecontroller7`    | 82             |\n   * | `generalpurposecontroller8`    | 83             |\n   * | `portamentocontrol`            | 84             |\n   * | `controller85`                 | 85             |\n   * | `controller86`                 | 86             |\n   * | `controller87`                 | 87             |\n   * | `highresolutionvelocityprefix` | 88             |\n   * | `controller89`                 | 89             |\n   * | `controller90`                 | 90             |\n   * | `effect1depth`                 | 91             |\n   * | `effect2depth`                 | 92             |\n   * | `effect3depth`                 | 93             |\n   * | `effect4depth`                 | 94             |\n   * | `effect5depth`                 | 95             |\n   * | `dataincrement`                | 96             |\n   * | `datadecrement`                | 97             |\n   * | `nonregisteredparameterfine`   | 98             |\n   * | `nonregisteredparametercoarse` | 99             |\n   * | `nonregisteredparameterfine`   | 100            |\n   * | `registeredparametercoarse`    | 101            |\n   * | `controller102`                | 102            |\n   * | `controller103`                | 103            |\n   * | `controller104`                | 104            |\n   * | `controller105`                | 105            |\n   * | `controller106`                | 106            |\n   * | `controller107`                | 107            |\n   * | `controller108`                | 108            |\n   * | `controller109`                | 109            |\n   * | `controller110`                | 110            |\n   * | `controller111`                | 111            |\n   * | `controller112`                | 112            |\n   * | `controller113`                | 113            |\n   * | `controller114`                | 114            |\n   * | `controller115`                | 115            |\n   * | `controller116`                | 116            |\n   * | `controller117`                | 117            |\n   * | `controller118`                | 118            |\n   * | `controller119`                | 119            |\n   * | `allsoundoff`                  | 120            |\n   * | `resetallcontrollers`          | 121            |\n   * | `localcontrol`                 | 122            |\n   * | `allnotesoff`                  | 123            |\n   * | `omnimodeoff`                  | 124            |\n   * | `omnimodeon`                   | 125            |\n   * | `monomodeon`                   | 126            |\n   * | `polymodeon`                   | 127            |\n   *\n   * @type {object[]}\n   * @readonly\n   * @static\n   * @since 3.1\n   */\n  static get CONTROL_CHANGE_MESSAGES() {\n\n    return [\n      {\n        number: 0,\n        name: \"bankselectcoarse\",\n        description: \"Bank Select (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 1,\n        name: \"modulationwheelcoarse\",\n        description: \"Modulation Wheel (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 2,\n        name: \"breathcontrollercoarse\",\n        description: \"Breath Controller (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 3,\n        name: \"controller3\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 4,\n        name: \"footcontrollercoarse\",\n        description: \"Foot Controller (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 5,\n        name: \"portamentotimecoarse\",\n        description: \"Portamento Time (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 6,\n        name: \"dataentrycoarse\",\n        description: \"Data Entry (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 7,\n        name: \"volumecoarse\",\n        description: \"Channel Volume (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 8,\n        name: \"balancecoarse\",\n        description: \"Balance (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 9,\n        name: \"controller9\",\n        description: \"Controller 9 (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 10,\n        name: \"pancoarse\",\n        description: \"Pan (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 11,\n        name: \"expressioncoarse\",\n        description: \"Expression Controller (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 12,\n        name: \"effectcontrol1coarse\",\n        description: \"Effect Control 1 (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 13,\n        name: \"effectcontrol2coarse\",\n        description: \"Effect Control 2 (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 14,\n        name: \"controller14\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 15,\n        name: \"controller15\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 16,\n        name: \"generalpurposecontroller1\",\n        description: \"General Purpose Controller 1 (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 17,\n        name: \"generalpurposecontroller2\",\n        description: \"General Purpose Controller 2 (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 18,\n        name: \"generalpurposecontroller3\",\n        description: \"General Purpose Controller 3 (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 19,\n        name: \"generalpurposecontroller4\",\n        description: \"General Purpose Controller 4 (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 20,\n        name: \"controller20\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 21,\n        name: \"controller21\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 22,\n        name: \"controller22\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 23,\n        name: \"controller23\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 24,\n        name: \"controller24\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 25,\n        name: \"controller25\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 26,\n        name: \"controller26\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 27,\n        name: \"controller27\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 28,\n        name: \"controller28\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 29,\n        name: \"controller29\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 30,\n        name: \"controller30\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 31,\n        name: \"controller31\",\n        description: \"Undefined\",\n        position: \"msb\"\n      },\n      {\n        number: 32,\n        name: \"bankselectfine\",\n        description: \"Bank Select (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 33,\n        name: \"modulationwheelfine\",\n        description: \"Modulation Wheel (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 34,\n        name: \"breathcontrollerfine\",\n        description: \"Breath Controller (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 35,\n        name: \"controller35\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 36,\n        name: \"footcontrollerfine\",\n        description: \"Foot Controller (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 37,\n        name: \"portamentotimefine\",\n        description: \"Portamento Time (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 38,\n        name: \"dataentryfine\",\n        description: \"Data Entry (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 39,\n        name: \"channelvolumefine\",\n        description: \"Channel Volume (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 40,\n        name: \"balancefine\",\n        description: \"Balance (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 41,\n        name: \"controller41\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 42,\n        name: \"panfine\",\n        description: \"Pan (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 43,\n        name: \"expressionfine\",\n        description: \"Expression Controller (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 44,\n        name: \"effectcontrol1fine\",\n        description: \"Effect control 1 (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 45,\n        name: \"effectcontrol2fine\",\n        description: \"Effect control 2 (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 46,\n        name: \"controller46\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 47,\n        name: \"controller47\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 48,\n        name: \"controller48\",\n        description: \"General Purpose Controller 1 (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 49,\n        name: \"controller49\",\n        description: \"General Purpose Controller 2 (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 50,\n        name: \"controller50\",\n        description: \"General Purpose Controller 3 (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 51,\n        name: \"controller51\",\n        description: \"General Purpose Controller 4 (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 52,\n        name: \"controller52\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 53,\n        name: \"controller53\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 54,\n        name: \"controller54\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 55,\n        name: \"controller55\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 56,\n        name: \"controller56\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 57,\n        name: \"controller57\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 58,\n        name: \"controller58\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 59,\n        name: \"controller59\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 60,\n        name: \"controller60\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 61,\n        name: \"controller61\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 62,\n        name: \"controller62\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 63,\n        name: \"controller63\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 64,\n        name: \"damperpedal\",\n        description: \"Damper Pedal On/Off\"\n      },\n      {\n        number: 65,\n        name: \"portamento\",\n        description: \"Portamento On/Off\"\n      },\n      {\n        number: 66,\n        name: \"sostenuto\",\n        description: \"Sostenuto On/Off\"\n      },\n      {\n        number: 67,\n        name: \"softpedal\",\n        description: \"Soft Pedal On/Off\"\n      },\n      {\n        number: 68,\n        name: \"legatopedal\",\n        description: \"Legato Pedal On/Off\"\n      },\n      {\n        number: 69,\n        name: \"hold2\",\n        description: \"Hold 2 On/Off\"\n      },\n      {\n        number: 70,\n        name: \"soundvariation\",\n        description: \"Sound Variation\",\n        position: \"lsb\"\n      },\n      {\n        number: 71,\n        name: \"resonance\",\n        description: \"Resonance\",\n        position: \"lsb\"\n      },\n      {\n        number: 72,\n        name: \"releasetime\",\n        description: \"Release Time\",\n        position: \"lsb\"\n      },\n      {\n        number: 73,\n        name: \"attacktime\",\n        description: \"Attack Time\",\n        position: \"lsb\"\n      },\n      {\n        number: 74,\n        name: \"brightness\",\n        description: \"Brightness\",\n        position: \"lsb\"\n      },\n      {\n        number: 75,\n        name: \"decaytime\",\n        description: \"Decay Time\",\n        position: \"lsb\"\n      },\n      {\n        number: 76,\n        name: \"vibratorate\",\n        description: \"Vibrato Rate\",\n        position: \"lsb\"\n      },\n      {\n        number: 77,\n        name: \"vibratodepth\",\n        description: \"Vibrato Depth\",\n        position: \"lsb\"\n      },\n      {\n        number: 78,\n        name: \"vibratodelay\",\n        description: \"Vibrato Delay\",\n        position: \"lsb\"\n      },\n      {\n        number: 79,\n        name: \"controller79\",\n        description: \"Undefined\",\n        position: \"lsb\"\n      },\n      {\n        number: 80,\n        name: \"generalpurposecontroller5\",\n        description: \"General Purpose Controller 5\",\n        position: \"lsb\"\n      },\n      {\n        number: 81,\n        name: \"generalpurposecontroller6\",\n        description: \"General Purpose Controller 6\",\n        position: \"lsb\"\n      },\n      {\n        number: 82,\n        name: \"generalpurposecontroller7\",\n        description: \"General Purpose Controller 7\",\n        position: \"lsb\"\n      },\n      {\n        number: 83,\n        name: \"generalpurposecontroller8\",\n        description: \"General Purpose Controller 8\",\n        position: \"lsb\"\n      },\n      {\n        number: 84,\n        name: \"portamentocontrol\",\n        description: \"Portamento Control\",\n        position: \"lsb\"\n      },\n      {\n        number: 85,\n        name: \"controller85\",\n        description: \"Undefined\"\n      },\n      {\n        number: 86,\n        name: \"controller86\",\n        description: \"Undefined\"\n      },\n      {\n        number: 87,\n        name: \"controller87\",\n        description: \"Undefined\"\n      },\n      {\n        number: 88,\n        name: \"highresolutionvelocityprefix\",\n        description: \"High Resolution Velocity Prefix\",\n        position: \"lsb\"\n      },\n      {\n        number: 89,\n        name: \"controller89\",\n        description: \"Undefined\"\n      },\n      {\n        number: 90,\n        name: \"controller90\",\n        description: \"Undefined\"\n      },\n      {\n        number: 91,\n        name: \"effect1depth\",\n        description: \"Effects 1 Depth (Reverb Send Level)\"\n      },\n      {\n        number: 92,\n        name: \"effect2depth\",\n        description: \"Effects 2 Depth\"\n      },\n      {\n        number: 93,\n        name: \"effect3depth\",\n        description: \"Effects 3 Depth (Chorus Send Level)\"\n      },\n      {\n        number: 94,\n        name: \"effect4depth\",\n        description: \"Effects 4 Depth\"\n      },\n      {\n        number: 95,\n        name: \"effect5depth\",\n        description: \"Effects 5 Depth\"\n      },\n      {\n        number: 96,\n        name: \"dataincrement\",\n        description: \"Data Increment\"\n      },\n      {\n        number: 97,\n        name: \"datadecrement\",\n        description: \"Data Decrement\"\n      },\n      {\n        number: 98,\n        name: \"nonregisteredparameterfine\",\n        description: \"Non-Registered Parameter Number (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 99,\n        name: \"nonregisteredparametercoarse\",\n        description: \"Non-Registered Parameter Number (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 100,\n        name: \"registeredparameterfine\",\n        description: \"Registered Parameter Number (Fine)\",\n        position: \"lsb\"\n      },\n      {\n        number: 101,\n        name: \"registeredparametercoarse\",\n        description: \"Registered Parameter Number (Coarse)\",\n        position: \"msb\"\n      },\n      {\n        number: 102,\n        name: \"controller102\",\n        description: \"Undefined\"\n      },\n      {\n        number: 103,\n        name: \"controller103\",\n        description: \"Undefined\"\n      },\n      {\n        number: 104,\n        name: \"controller104\",\n        description: \"Undefined\"\n      },\n      {\n        number: 105,\n        name: \"controller105\",\n        description: \"Undefined\"\n      },\n      {\n        number: 106,\n        name: \"controller106\",\n        description: \"Undefined\"\n      },\n      {\n        number: 107,\n        name: \"controller107\",\n        description: \"Undefined\"\n      },\n      {\n        number: 108,\n        name: \"controller108\",\n        description: \"Undefined\"\n      },\n      {\n        number: 109,\n        name: \"controller109\",\n        description: \"Undefined\"\n      },\n      {\n        number: 110,\n        name: \"controller110\",\n        description: \"Undefined\"\n      },\n      {\n        number: 111,\n        name: \"controller111\",\n        description: \"Undefined\"\n      },\n      {\n        number: 112,\n        name: \"controller112\",\n        description: \"Undefined\"\n      },\n      {\n        number: 113,\n        name: \"controller113\",\n        description: \"Undefined\"\n      },\n      {\n        number: 114,\n        name: \"controller114\",\n        description: \"Undefined\"\n      },\n      {\n        number: 115,\n        name: \"controller115\",\n        description: \"Undefined\"\n      },\n      {\n        number: 116,\n        name: \"controller116\",\n        description: \"Undefined\"\n      },\n      {\n        number: 117,\n        name: \"controller117\",\n        description: \"Undefined\"\n      },\n      {\n        number: 118,\n        name: \"controller118\",\n        description: \"Undefined\"\n      },\n      {\n        number: 119,\n        name: \"controller119\",\n        description: \"Undefined\"\n      },\n      {\n        number: 120,\n        name: \"allsoundoff\",\n        description: \"All Sound Off\"\n      },\n      {\n        number: 121,\n        name: \"resetallcontrollers\",\n        description: \"Reset All Controllers\"\n      },\n      {\n        number: 122,\n        name: \"localcontrol\",\n        description: \"Local Control On/Off\"\n      },\n      {\n        number: 123,\n        name: \"allnotesoff\",\n        description: \"All Notes Off\"\n      },\n      {\n        number: 124,\n        name: \"omnimodeoff\",\n        description: \"Omni Mode Off\"\n      },\n      {\n        number: 125,\n        name: \"omnimodeon\",\n        description: \"Omni Mode On\"\n      },\n      {\n        number: 126,\n        name: \"monomodeon\",\n        description: \"Mono Mode On\"\n      },\n      {\n        number: 127,\n        name: \"polymodeon\",\n        description: \"Poly Mode On\"\n      },\n    ];\n\n  }\n\n  /**\n   * Enumeration of all MIDI registered parameters and their associated pair of numerical values.\n   * MIDI registered parameters extend the original list of control change messages. Currently,\n   * there are only a limited number of them:\n   *\n   *\n   * | Control Function             | [LSB, MSB]   |\n   * |------------------------------|--------------|\n   * | `pitchbendrange`             | [0x00, 0x00] |\n   * | `channelfinetuning`          | [0x00, 0x01] |\n   * | `channelcoarsetuning`        | [0x00, 0x02] |\n   * | `tuningprogram`              | [0x00, 0x03] |\n   * | `tuningbank`                 | [0x00, 0x04] |\n   * | `modulationrange`            | [0x00, 0x05] |\n   * | `azimuthangle`               | [0x3D, 0x00] |\n   * | `elevationangle`             | [0x3D, 0x01] |\n   * | `gain`                       | [0x3D, 0x02] |\n   * | `distanceratio`              | [0x3D, 0x03] |\n   * | `maximumdistance`            | [0x3D, 0x04] |\n   * | `maximumdistancegain`        | [0x3D, 0x05] |\n   * | `referencedistanceratio`     | [0x3D, 0x06] |\n   * | `panspreadangle`             | [0x3D, 0x07] |\n   * | `rollangle`                  | [0x3D, 0x08] |\n   *\n   * @enum {Object.<string, number[]>}\n   * @readonly\n   * @since 3.1\n   * @static\n   */\n  static get REGISTERED_PARAMETERS() {\n\n    return {\n      pitchbendrange: [0x00, 0x00],\n      channelfinetuning: [0x00, 0x01],\n      channelcoarsetuning: [0x00, 0x02],\n      tuningprogram: [0x00, 0x03],\n      tuningbank: [0x00, 0x04],\n\n      modulationrange: [0x00, 0x05],\n      azimuthangle: [0x3D, 0x00],\n      elevationangle: [0x3D, 0x01],\n      gain: [0x3D, 0x02],\n      distanceratio: [0x3D, 0x03],\n      maximumdistance: [0x3D, 0x04],\n      maximumdistancegain: [0x3D, 0x05],\n      referencedistanceratio: [0x3D, 0x06],\n      panspreadangle: [0x3D, 0x07],\n      rollangle: [0x3D, 0x08]\n    };\n\n  }\n\n  /**\n   * @enum {Object.<string, number[]>}\n   * @readonly\n   * @deprecated since 3.1 (use Enumerations.REGISTERED_PARAMETERS instead)\n   * @private\n   * @static\n   */\n  static get MIDI_REGISTERED_PARAMETERS() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_REGISTERED_PARAMETERS enum has been deprecated. Use the \" +\n        \"Enumerations.REGISTERED_PARAMETERS enum instead.\"\n      );\n    }\n\n    return Enumerations.MIDI_REGISTERED_PARAMETERS;\n\n  }\n\n  /**\n   * Enumeration of all valid MIDI system messages and matching numerical values. This library also\n   * uses two additional custom messages.\n   *\n   * **System Common Messages**\n   *\n   * | Function               | Hexadecimal | Decimal |\n   * |------------------------|-------------|---------|\n   * | `sysex`                | 0xF0        |  240    |\n   * | `timecode`             | 0xF1        |  241    |\n   * | `songposition`         | 0xF2        |  242    |\n   * | `songselect`           | 0xF3        |  243    |\n   * | `tunerequest`          | 0xF6        |  246    |\n   * | `sysexend`             | 0xF7        |  247    |\n   *\n   * The `sysexend` message is never actually received. It simply ends a sysex stream.\n   *\n   * **System Real-Time Messages**\n   *\n   * | Function               | Hexadecimal | Decimal |\n   * |------------------------|-------------|---------|\n   * | `clock`                | 0xF8        |  248    |\n   * | `start`                | 0xFA        |  250    |\n   * | `continue`             | 0xFB        |  251    |\n   * | `stop`                 | 0xFC        |  252    |\n   * | `activesensing`        | 0xFE        |  254    |\n   * | `reset`                | 0xFF        |  255    |\n   *\n   * Values 249 and 253 are relayed by the\n   * [Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API) but they do not\n   * serve any specific purpose. The\n   * [MIDI 1.0 spec](https://www.midi.org/specifications/item/table-1-summary-of-midi-message)\n   * simply states that they are undefined/reserved.\n   *\n   * **Custom Messages**\n   *\n   * These two messages are mostly for internal use. They are not MIDI messages and cannot be sent\n   * or forwarded.\n   *\n   * | Function               | Hexadecimal | Decimal |\n   * |------------------------|-------------|---------|\n   * | `midimessage`          |             |  0      |\n   * | `unknownsystemmessage` |             |  -1     |\n   *\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @since 3.1\n   * @static\n   */\n  static get SYSTEM_MESSAGES() {\n\n    return {\n\n      // System common messages\n      sysex: 0xF0,            // 240\n      timecode: 0xF1,         // 241\n      songposition: 0xF2,     // 242\n      songselect: 0xF3,       // 243\n      tunerequest: 0xF6,      // 246\n      tuningrequest: 0xF6,    // for backwards-compatibility (deprecated in version 3.0)\n      sysexend: 0xF7,         // 247 (never actually received - simply ends a sysex)\n\n      // System real-time messages\n      clock: 0xF8,            // 248\n      start: 0xFA,            // 250\n      continue: 0xFB,         // 251\n      stop: 0xFC,             // 252\n      activesensing: 0xFE,    // 254\n      reset: 0xFF,            // 255\n\n      // Custom WebMidi.js messages\n      midimessage: 0,\n      unknownsystemmessage: -1\n\n    };\n\n  }\n\n  /**\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @deprecated since 3.1 (use Enumerations.SYSTEM_MESSAGES instead)\n   * @private\n   * @static\n   */\n  static get MIDI_SYSTEM_MESSAGES() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_SYSTEM_MESSAGES enum has been deprecated. Use the \" +\n        \"Enumerations.SYSTEM_MESSAGES enum instead.\"\n      );\n    }\n\n    return Enumerations.SYSTEM_MESSAGES;\n\n  }\n\n  /**\n   * Array of channel-specific event names that can be listened for. This includes channel mode\n   * events and RPN/NRPN events.\n   *\n   * @type {string[]}\n   * @readonly\n   */\n  static get CHANNEL_EVENTS() {\n\n    return [\n\n      // MIDI channel message events\n      \"noteoff\",\n      \"controlchange\",\n      \"noteon\",\n      \"keyaftertouch\",\n      \"programchange\",\n      \"channelaftertouch\",\n      \"pitchbend\",\n\n      // MIDI channel mode events\n      \"allnotesoff\",\n      \"allsoundoff\",\n      \"localcontrol\",\n      \"monomode\",\n      \"omnimode\",\n      \"resetallcontrollers\",\n\n      // RPN/NRPN events\n      \"nrpn\",\n      \"nrpn-dataentrycoarse\",\n      \"nrpn-dataentryfine\",\n      \"nrpn-dataincrement\",\n      \"nrpn-datadecrement\",\n      \"rpn\",\n      \"rpn-dataentrycoarse\",\n      \"rpn-dataentryfine\",\n      \"rpn-dataincrement\",\n      \"rpn-datadecrement\",\n\n      // Legacy (remove in v4)\n      \"nrpn-databuttonincrement\",\n      \"nrpn-databuttondecrement\",\n      \"rpn-databuttonincrement\",\n      \"rpn-databuttondecrement\",\n\n    ];\n  }\n\n}\n"
  },
  {
    "path": "src/Forwarder.js",
    "content": "import {Enumerations} from \"./Enumerations.js\";\nimport {Output} from \"./Output.js\";\nimport {WebMidi} from \"./WebMidi.js\";\n\n/**\n * The `Forwarder` class allows the forwarding of MIDI messages to predetermined outputs. When you\n * call its [`forward()`](#forward) method, it will send the specified [`Message`](Message) object\n * to all the outputs listed in its [`destinations`](#destinations) property.\n *\n * If specific channels or message types have been defined in the [`channels`](#channels) or\n * [`types`](#types) properties, only messages matching the channels/types will be forwarded.\n *\n * While it can be manually instantiated, you are more likely to come across a `Forwarder` object as\n * the return value of the [`Input.addForwarder()`](Input#addForwarder) method.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Forwarder {\n\n  /**\n   * Creates a `Forwarder` object.\n   *\n   * @param {Output|Output[]} [destinations=\\[\\]] An [`Output`](Output) object, or an array of such\n   * objects, to forward the message to.\n   *\n   * @param {object} [options={}]\n   * @param {string|string[]} [options.types=(all messages)] A MIDI message type or an array of such\n   * types (`\"noteon\"`, `\"controlchange\"`, etc.), that the specified message must match in order to\n   * be forwarded. If this option is not specified, all types of messages will be forwarded. Valid\n   * messages are the ones found in either\n   * [`SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES)\n   * or [`CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * A MIDI channel number or an array of channel numbers that the message must match in order to be\n   * forwarded. By default all MIDI channels are included (`1` to `16`).\n   */\n  constructor(destinations = [], options = {}) {\n\n    /**\n     * An array of [`Output`](Output) objects to forward the message to.\n     * @type {Output[]}\n     */\n    this.destinations = [];\n\n    /**\n     * An array of message types (`\"noteon\"`, `\"controlchange\"`, etc.) that must be matched in order\n     * for messages to be forwarded. By default, this array includes all\n     * [`Enumerations.SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) and\n     * [`Enumerations.CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).\n     * @type {string[]}\n     */\n    this.types = [\n      ...Object.keys(Enumerations.SYSTEM_MESSAGES),\n      ...Object.keys(Enumerations.CHANNEL_MESSAGES)\n    ];\n\n    /**\n     * An array of MIDI channel numbers that the message must match in order to be forwarded. By\n     * default, this array includes all MIDI channels (`1` to `16`).\n     * @type {number[]}\n     */\n    this.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    /**\n     * Indicates whether message forwarding is currently suspended or not in this forwarder.\n     * @type {boolean}\n     */\n    this.suspended = false;\n\n    // Make sure parameters are arrays\n    if (!Array.isArray(destinations)) destinations = [destinations];\n    if (options.types && !Array.isArray(options.types)) options.types = [options.types];\n    if (options.channels && !Array.isArray(options.channels)) options.channels = [options.channels];\n\n    if (WebMidi.validation) {\n\n      // Validate destinations\n      destinations.forEach(destination => {\n        if ( !(destination instanceof Output) ) {\n          throw new TypeError(\"Destinations must be of type 'Output'.\");\n        }\n      });\n\n      // Validate types\n      if (options.types !== undefined) {\n\n        options.types.forEach(type => {\n          if (\n            ! Enumerations.SYSTEM_MESSAGES.hasOwnProperty(type) &&\n            ! Enumerations.CHANNEL_MESSAGES.hasOwnProperty(type)\n          ) {\n            throw new TypeError(\"Type must be a valid message type.\");\n          }\n        });\n\n      }\n\n      // Validate channels\n      if (options.channels !== undefined) {\n\n        options.channels.forEach(channel => {\n          if (! Enumerations.MIDI_CHANNEL_NUMBERS.includes(channel) ) {\n            throw new TypeError(\"MIDI channel must be between 1 and 16.\");\n          }\n        });\n\n      }\n\n    }\n\n    this.destinations = destinations;\n    if (options.types) this.types = options.types;\n    if (options.channels) this.channels = options.channels;\n\n  }\n\n  /**\n   * Sends the specified message to the forwarder's destination(s) if it matches the specified\n   * type(s) and channel(s).\n   *\n   * @param {Message} message The [`Message`](Message) object to forward.\n   */\n  forward(message) {\n\n    // Abort if forwarding is currently suspended\n    if (this.suspended) return;\n\n    // Abort if this message type should not be forwarded\n    if (!this.types.includes(message.type)) return;\n\n    // Abort if this channel should not be forwarded\n    if (message.channel && !this.channels.includes(message.channel)) return;\n\n    // Forward\n    this.destinations.forEach(destination => {\n      if (WebMidi.validation && !(destination instanceof Output)) return;\n      destination.send(message);\n    });\n\n  }\n\n}\n"
  },
  {
    "path": "src/Input.js",
    "content": "import {Enumerations} from \"./Enumerations.js\";\nimport {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {Forwarder} from \"./Forwarder.js\";\nimport {InputChannel} from \"./InputChannel.js\";\nimport {Message} from \"./Message.js\";\nimport {Utilities} from \"./Utilities.js\";\nimport {WebMidi} from \"./WebMidi.js\";\n\n/**\n * The `Input` class represents a single MIDI input port. This object is automatically instantiated\n * by the library according to the host's MIDI subsystem and does not need to be directly\n * instantiated. Instead, you can access all `Input` objects by referring to the\n * [`WebMidi.inputs`](WebMidi#inputs) array. You can also retrieve inputs by using methods such as\n * [`WebMidi.getInputByName()`](WebMidi#getInputByName) and\n * [`WebMidi.getInputById()`](WebMidi#getInputById).\n *\n * Note that a single MIDI device may expose several inputs and/or outputs.\n *\n * **Important**: the `Input` class does not directly fire channel-specific MIDI messages\n * (such as [`noteon`](InputChannel#event:noteon) or\n * [`controlchange`](InputChannel#event:controlchange), etc.). The [`InputChannel`](InputChannel)\n * object does that. However, you can still use the\n * [`Input.addListener()`](#addListener) method to listen to channel-specific events on multiple\n * [`InputChannel`](InputChannel) objects at once.\n *\n * @fires Input#opened\n * @fires Input#disconnected\n * @fires Input#closed\n * @fires Input#midimessage\n *\n * @fires Input#sysex\n * @fires Input#timecode\n * @fires Input#songposition\n * @fires Input#songselect\n * @fires Input#tunerequest\n * @fires Input#clock\n * @fires Input#start\n * @fires Input#continue\n * @fires Input#stop\n * @fires Input#activesensing\n * @fires Input#reset\n *\n * @fires Input#unknownmidimessage\n *\n * @extends EventEmitter\n * @license Apache-2.0\n */\nexport class Input extends EventEmitter {\n\n  /**\n   * Creates an `Input` object.\n   *\n   * @param {MIDIInput} midiInput [`MIDIInput`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIInput)\n   * object as provided by the MIDI subsystem (Web MIDI API).\n   */\n  constructor(midiInput) {\n\n    super();\n\n    /**\n     * Reference to the actual MIDIInput object\n     * @private\n     */\n    this._midiInput = midiInput;\n\n    /**\n     * @type {number}\n     * @private\n     */\n    this._octaveOffset = 0;\n\n    /**\n     * Array containing the 16 [`InputChannel`](InputChannel) objects available for this `Input`. The\n     * channels are numbered 1 through 16.\n     *\n     * @type {InputChannel[]}\n     */\n    this.channels = [];\n    for (let i = 1; i <= 16; i++) this.channels[i] = new InputChannel(this, i);\n\n    /**\n     * @type {Forwarder[]}\n     * @private\n     */\n    this._forwarders = [];\n\n    // Setup listeners\n    this._midiInput.onstatechange = this._onStateChange.bind(this);\n    this._midiInput.onmidimessage = this._onMidiMessage.bind(this);\n\n  }\n\n  /**\n   * Destroys the `Input` by removing all listeners, emptying the [`channels`](#channels) array and\n   * unlinking the MIDI subsystem. This is mostly for internal use.\n   *\n   * @returns {Promise<void>}\n   */\n  async destroy() {\n    this.removeListener();\n    this.channels.forEach(ch => ch.destroy());\n    this.channels = [];\n    this._forwarders = [];\n    if (this._midiInput) {\n      this._midiInput.onstatechange = null;\n      this._midiInput.onmidimessage = null;\n    }\n    await this.close();\n    this._midiInput = null;\n  }\n\n  /**\n   * Executed when a `\"statechange\"` event occurs.\n   *\n   * @param e\n   * @private\n   */\n  _onStateChange(e) {\n\n    let event = {\n      timestamp: WebMidi.time,\n      target: this,\n      port: this // for consistency\n    };\n\n    if (e.port.connection === \"open\") {\n\n      /**\n       * Event emitted when the `Input` has been opened by calling the [`open()`]{@link #open}\n       * method.\n       *\n       * @event Input#opened\n       * @type {object}\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       * @property {string} type `opened`\n       * @property {Input} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       */\n      event.type = \"opened\";\n      this.emit(\"opened\", event);\n\n    } else if (e.port.connection === \"closed\" && e.port.state === \"connected\") {\n\n      /**\n       * Event emitted when the `Input` has been closed by calling the\n       * [`close()`]{@link #close} method.\n       *\n       * @event Input#closed\n       * @type {object}\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       * @property {string} type `closed`\n       * @property {Input} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       */\n      event.type = \"closed\";\n      this.emit(\"closed\", event);\n\n    } else if (e.port.connection === \"closed\" && e.port.state === \"disconnected\") {\n\n      /**\n       * Event emitted when the `Input` becomes unavailable. This event is typically fired\n       * when the MIDI device is unplugged.\n       *\n       * @event Input#disconnected\n       * @type {object}\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       * @property {string} type `disconnected`\n       * @property {Input} port Object with properties describing the {@link Input} that was\n       * disconnected. This is not the actual `Input` as it is no longer available.\n       * @property {Input} target The object that dispatched the event.\n       */\n      event.type = \"disconnected\";\n      event.port = {\n        connection: e.port.connection,\n        id: e.port.id,\n        manufacturer: e.port.manufacturer,\n        name: e.port.name,\n        state: e.port.state,\n        type: e.port.type\n      };\n      this.emit(\"disconnected\", event);\n\n    } else if (e.port.connection === \"pending\" && e.port.state === \"disconnected\") {\n      // I don't see the need to forward that...\n    } else {\n      console.warn(\"This statechange event was not caught: \", e.port.connection, e.port.state);\n    }\n\n  }\n\n  /**\n   * Executed when a `\"midimessage\"` event is received\n   * @param e\n   * @private\n   */\n  _onMidiMessage(e) {\n\n    // Create Message object from MIDI data\n    const message = new Message(e.data);\n\n    /**\n     * Event emitted when any MIDI message is received on an `Input`.\n     *\n     * @event Input#midimessage\n     *\n     * @type {object}\n     *\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Input} target The object that dispatched the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {string} type `midimessage`\n     *\n     * @since 2.1\n     */\n    const event = {\n      port: this,\n      target: this,\n      message: message,\n      timestamp: e.timeStamp,\n      type: \"midimessage\",\n\n      data: message.data,           // @deprecated (will be removed in v4)\n      rawData: message.data,        // @deprecated (will be removed in v4)\n      statusByte: message.data[0],  // @deprecated (will be removed in v4)\n      dataBytes: message.dataBytes  // @deprecated (will be removed in v4)\n    };\n\n    this.emit(\"midimessage\", event);\n\n    // Messages are forwarded to InputChannel if they are channel messages or parsed locally for\n    // system messages.\n    if (message.isSystemMessage) {           // system messages\n      this._parseEvent(event);\n    } else if (message.isChannelMessage) {   // channel messages\n      this.channels[message.channel]._processMidiMessageEvent(event);\n    }\n\n    // Forward message if forwarders have been defined\n    this._forwarders.forEach(forwarder => forwarder.forward(message));\n\n  }\n\n  /**\n   * @private\n   */\n  _parseEvent(e) {\n\n    // Make a shallow copy of the incoming event so we can use it as the new event.\n    const event = Object.assign({}, e);\n    event.type = event.message.type || \"unknownmidimessage\";\n\n    // Add custom property for 'songselect'\n    if (event.type === \"songselect\") {\n      event.song = e.data[1] + 1; // deprecated\n      event.value = e.data[1];\n      event.rawValue = event.value;\n    }\n\n    // Emit event\n    this.emit(event.type, event);\n\n  }\n\n  /**\n   * Opens the input for usage. This is usually unnecessary as the port is opened automatically when\n   * WebMidi is enabled.\n   *\n   * @returns {Promise<Input>} The promise is fulfilled with the `Input` object.\n   */\n  async open() {\n\n    // Explicitly opens the port for usage. This is not mandatory. When the port is not explicitly\n    // opened, it is implicitly opened (asynchronously) when assigning a listener to the\n    // `onmidimessage` property of the `MIDIInput`. We do it explicitly so that 'connected' events\n    // are dispatched immediately and that we are ready to listen.\n    try {\n      await this._midiInput.open();\n    } catch (err) {\n      return Promise.reject(err);\n    }\n\n    return Promise.resolve(this);\n\n  }\n\n  /**\n   * Closes the input. When an input is closed, it cannot be used to listen to MIDI messages until\n   * the input is opened again by calling [`Input.open()`](Input#open).\n   *\n   * **Note**: if what you want to do is stop events from being dispatched, you should use\n   * [`eventsSuspended`](#eventsSuspended) instead.\n   *\n   * @returns {Promise<Input>} The promise is fulfilled with the `Input` object\n   */\n  async close() {\n\n    // We close the port. This triggers a statechange event which, in turn, will emit the 'closed'\n    // event.\n    if (!this._midiInput) return Promise.resolve(this);\n\n    try {\n      await this._midiInput.close();\n    } catch (err) {\n      return Promise.reject(err);\n    }\n\n    return Promise.resolve(this);\n\n  }\n\n  /**\n   * @private\n   * @deprecated since v3.0.0 (moved to 'Utilities' class)\n   */\n  getChannelModeByNumber() {\n    if (WebMidi.validation) {\n      console.warn(\n        \"The 'getChannelModeByNumber()' method has been moved to the 'Utilities' class.\"\n      );\n    }\n  }\n\n  /**\n   * Adds an event listener that will trigger a function callback when the specified event is\n   * dispatched. The event usually is **input-wide** but can also be **channel-specific**.\n   *\n   * Input-wide events do not target a specific MIDI channel so it makes sense to listen for them\n   * at the `Input` level and not at the [`InputChannel`](InputChannel) level. Channel-specific\n   * events target a specific channel. Usually, in this case, you would add the listener to the\n   * [`InputChannel`](InputChannel) object. However, as a convenience, you can also listen to\n   * channel-specific events directly on an `Input`. This allows you to react to a channel-specific\n   * event no matter which channel it actually came through.\n   *\n   * When listening for an event, you simply need to specify the event name and the function to\n   * execute:\n   *\n   * ```javascript\n   * const listener = WebMidi.inputs[0].addListener(\"midimessage\", e => {\n   *   console.log(e);\n   * });\n   * ```\n   *\n   * Calling the function with an input-wide event (such as\n   * [`\"midimessage\"`]{@link #event:midimessage}), will return the [`Listener`](Listener) object\n   * that was created.\n   *\n   * If you call the function with a channel-specific event (such as\n   * [`\"noteon\"`]{@link InputChannel#event:noteon}), it will return an array of all\n   * [`Listener`](Listener) objects that were created (one for each channel):\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addListener(\"noteon\", someFunction);\n   * ```\n   *\n   * You can also specify which channels you want to add the listener to:\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addListener(\"noteon\", someFunction, {channels: [1, 2, 3]});\n   * ```\n   *\n   * In this case, `listeners` is an array containing 3 [`Listener`](Listener) objects. The order of\n   * the listeners in the array follows the order the channels were specified in.\n   *\n   * Note that, when adding channel-specific listeners, it is the [`InputChannel`](InputChannel)\n   * instance that actually gets a listener added and not the `Input` instance. You can check that\n   * by calling [`InputChannel.hasListener()`](InputChannel#hasListener()).\n   *\n   * There are 8 families of events you can listen to:\n   *\n   * 1. **MIDI System Common** Events (input-wide)\n   *\n   *    * [`songposition`]{@link Input#event:songposition}\n   *    * [`songselect`]{@link Input#event:songselect}\n   *    * [`sysex`]{@link Input#event:sysex}\n   *    * [`timecode`]{@link Input#event:timecode}\n   *    * [`tunerequest`]{@link Input#event:tunerequest}\n   *\n   * 2. **MIDI System Real-Time** Events (input-wide)\n   *\n   *    * [`clock`]{@link Input#event:clock}\n   *    * [`start`]{@link Input#event:start}\n   *    * [`continue`]{@link Input#event:continue}\n   *    * [`stop`]{@link Input#event:stop}\n   *    * [`activesensing`]{@link Input#event:activesensing}\n   *    * [`reset`]{@link Input#event:reset}\n   *\n   * 3. **State Change** Events (input-wide)\n   *\n   *    * [`opened`]{@link Input#event:opened}\n   *    * [`closed`]{@link Input#event:closed}\n   *    * [`disconnected`]{@link Input#event:disconnected}\n   *\n   * 4. **Catch-All** Events (input-wide)\n   *\n   *    * [`midimessage`]{@link Input#event:midimessage}\n   *    * [`unknownmidimessage`]{@link Input#event:unknownmidimessage}\n   *\n   * 5. **Channel Voice** Events (channel-specific)\n   *\n   *    * [`channelaftertouch`]{@link InputChannel#event:channelaftertouch}\n   *    * [`controlchange`]{@link InputChannel#event:controlchange}\n   *      * [`controlchange-controller0`]{@link InputChannel#event:controlchange-controller0}\n   *      * [`controlchange-controller1`]{@link InputChannel#event:controlchange-controller1}\n   *      * [`controlchange-controller2`]{@link InputChannel#event:controlchange-controller2}\n   *      * (...)\n   *      * [`controlchange-controller127`]{@link InputChannel#event:controlchange-controller127}\n   *    * [`keyaftertouch`]{@link InputChannel#event:keyaftertouch}\n   *    * [`noteoff`]{@link InputChannel#event:noteoff}\n   *    * [`noteon`]{@link InputChannel#event:noteon}\n   *    * [`pitchbend`]{@link InputChannel#event:pitchbend}\n   *    * [`programchange`]{@link InputChannel#event:programchange}\n   *\n   *    Note: you can listen for a specific control change message by using an event name like this:\n   *    `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   *    etc.\n   *\n   * 6. **Channel Mode** Events (channel-specific)\n   *\n   *    * [`allnotesoff`]{@link InputChannel#event:allnotesoff}\n   *    * [`allsoundoff`]{@link InputChannel#event:allsoundoff}\n   *    * [`localcontrol`]{@link InputChannel#event:localcontrol}\n   *    * [`monomode`]{@link InputChannel#event:monomode}\n   *    * [`omnimode`]{@link InputChannel#event:omnimode}\n   *    * [`resetallcontrollers`]{@link InputChannel#event:resetallcontrollers}\n   *\n   * 7. **NRPN** Events (channel-specific)\n   *\n   *    * [`nrpn`]{@link InputChannel#event:nrpn}\n   *    * [`nrpn-dataentrycoarse`]{@link InputChannel#event:nrpn-dataentrycoarse}\n   *    * [`nrpn-dataentryfine`]{@link InputChannel#event:nrpn-dataentryfine}\n   *    * [`nrpn-dataincrement`]{@link InputChannel#event:nrpn-dataincrement}\n   *    * [`nrpn-datadecrement`]{@link InputChannel#event:nrpn-datadecrement}\n   *\n   * 8. **RPN** Events (channel-specific)\n   *\n   *    * [`rpn`]{@link InputChannel#event:rpn}\n   *    * [`rpn-dataentrycoarse`]{@link InputChannel#event:rpn-dataentrycoarse}\n   *    * [`rpn-dataentryfine`]{@link InputChannel#event:rpn-dataentryfine}\n   *    * [`rpn-dataincrement`]{@link InputChannel#event:rpn-dataincrement}\n   *    * [`rpn-datadecrement`]{@link InputChannel#event:rpn-datadecrement}\n   *\n   * @param event {string | EventEmitter.ANY_EVENT} The type of the event.\n   *\n   * @param listener {function} A callback function to execute when the specified event is detected.\n   * This function will receive an event parameter object. For details on this object's properties,\n   * check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * An integer between 1 and 16 or an array of such integers representing the MIDI channel(s) to\n   * listen on. If no channel is specified, all channels will be used. This parameter is ignored for\n   * input-wide events.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @param {number} [options.remaining=Infinity] The number of times after which the callback\n   * should automatically be removed.\n   *\n   * @returns {Listener|Listener[]} If the event is input-wide, a single [`Listener`](Listener)\n   * object is returned. If the event is channel-specific, an array of all the\n   * [`Listener`](Listener) objects is returned (one for each channel).\n   */\n  addListener(event, listener, options = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (typeof options === \"function\") {\n        let channels = (listener != undefined) ? [].concat(listener) : undefined; // clone\n        listener = options;\n        options = {channels: channels};\n      }\n\n    }\n\n    // Check if the event is channel-specific or input-wide\n    if (Enumerations.CHANNEL_EVENTS.includes(event)) {\n\n      // If no channel defined, use all.\n      if (options.channels === undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n      let listeners = [];\n\n      Utilities.sanitizeChannels(options.channels).forEach(ch => {\n        listeners.push(this.channels[ch].addListener(event, listener, options));\n      });\n\n      return listeners;\n\n    } else {\n\n      return super.addListener(event, listener, options);\n\n    }\n\n  }\n\n  /**\n   * Adds a one-time event listener that will trigger a function callback when the specified event\n   * happens. The event can be **channel-bound** or **input-wide**. Channel-bound events are\n   * dispatched by [`InputChannel`]{@link InputChannel} objects and are tied to a specific MIDI\n   * channel while input-wide events are dispatched by the `Input` object itself and are not tied\n   * to a specific channel.\n   *\n   * Calling the function with an input-wide event (such as\n   * [`\"midimessage\"`]{@link #event:midimessage}), will return the [`Listener`](Listener) object\n   * that was created.\n   *\n   * If you call the function with a channel-specific event (such as\n   * [`\"noteon\"`]{@link InputChannel#event:noteon}), it will return an array of all\n   * [`Listener`](Listener) objects that were created (one for each channel):\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addOneTimeListener(\"noteon\", someFunction);\n   * ```\n   *\n   * You can also specify which channels you want to add the listener to:\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addOneTimeListener(\"noteon\", someFunction, {channels: [1, 2, 3]});\n   * ```\n   *\n   * In this case, the `listeners` variable contains an array of 3 [`Listener`](Listener) objects.\n   *\n   * The code above will add a listener for the `\"noteon\"` event and call `someFunction` when the\n   * event is triggered on MIDI channels `1`, `2` or `3`.\n   *\n   * Note that, when adding events to channels, it is the [`InputChannel`](InputChannel) instance\n   * that actually gets a listener added and not the `Input` instance.\n   *\n   * Note: if you want to add a listener to a single MIDI channel you should probably do so directly\n   * on the [`InputChannel`](InputChannel) object itself.\n   *\n   * There are 8 families of events you can listen to:\n   *\n   * 1. **MIDI System Common** Events (input-wide)\n   *\n   *    * [`songposition`]{@link Input#event:songposition}\n   *    * [`songselect`]{@link Input#event:songselect}\n   *    * [`sysex`]{@link Input#event:sysex}\n   *    * [`timecode`]{@link Input#event:timecode}\n   *    * [`tunerequest`]{@link Input#event:tunerequest}\n   *\n   * 2. **MIDI System Real-Time** Events (input-wide)\n   *\n   *    * [`clock`]{@link Input#event:clock}\n   *    * [`start`]{@link Input#event:start}\n   *    * [`continue`]{@link Input#event:continue}\n   *    * [`stop`]{@link Input#event:stop}\n   *    * [`activesensing`]{@link Input#event:activesensing}\n   *    * [`reset`]{@link Input#event:reset}\n   *\n   * 3. **State Change** Events (input-wide)\n   *\n   *    * [`opened`]{@link Input#event:opened}\n   *    * [`closed`]{@link Input#event:closed}\n   *    * [`disconnected`]{@link Input#event:disconnected}\n   *\n   * 4. **Catch-All** Events (input-wide)\n   *\n   *    * [`midimessage`]{@link Input#event:midimessage}\n   *    * [`unknownmidimessage`]{@link Input#event:unknownmidimessage}\n   *\n   * 5. **Channel Voice** Events (channel-specific)\n   *\n   *    * [`channelaftertouch`]{@link InputChannel#event:channelaftertouch}\n   *    * [`controlchange`]{@link InputChannel#event:controlchange}\n   *      * [`controlchange-controller0`]{@link InputChannel#event:controlchange-controller0}\n   *      * [`controlchange-controller1`]{@link InputChannel#event:controlchange-controller1}\n   *      * [`controlchange-controller2`]{@link InputChannel#event:controlchange-controller2}\n   *      * (...)\n   *      * [`controlchange-controller127`]{@link InputChannel#event:controlchange-controller127}\n   *    * [`keyaftertouch`]{@link InputChannel#event:keyaftertouch}\n   *    * [`noteoff`]{@link InputChannel#event:noteoff}\n   *    * [`noteon`]{@link InputChannel#event:noteon}\n   *    * [`pitchbend`]{@link InputChannel#event:pitchbend}\n   *    * [`programchange`]{@link InputChannel#event:programchange}\n   *\n   *    Note: you can listen for a specific control change message by using an event name like this:\n   *    `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   *    etc.\n   *\n   * 6. **Channel Mode** Events (channel-specific)\n   *\n   *    * [`allnotesoff`]{@link InputChannel#event:allnotesoff}\n   *    * [`allsoundoff`]{@link InputChannel#event:allsoundoff}\n   *    * [`localcontrol`]{@link InputChannel#event:localcontrol}\n   *    * [`monomode`]{@link InputChannel#event:monomode}\n   *    * [`omnimode`]{@link InputChannel#event:omnimode}\n   *    * [`resetallcontrollers`]{@link InputChannel#event:resetallcontrollers}\n   *\n   * 7. **NRPN** Events (channel-specific)\n   *\n   *    * [`nrpn`]{@link InputChannel#event:nrpn}\n   *    * [`nrpn-dataentrycoarse`]{@link InputChannel#event:nrpn-dataentrycoarse}\n   *    * [`nrpn-dataentryfine`]{@link InputChannel#event:nrpn-dataentryfine}\n   *    * [`nrpn-dataincrement`]{@link InputChannel#event:nrpn-dataincrement}\n   *    * [`nrpn-datadecrement`]{@link InputChannel#event:nrpn-datadecrement}\n   *\n   * 8. **RPN** Events (channel-specific)\n   *\n   *    * [`rpn`]{@link InputChannel#event:rpn}\n   *    * [`rpn-dataentrycoarse`]{@link InputChannel#event:rpn-dataentrycoarse}\n   *    * [`rpn-dataentryfine`]{@link InputChannel#event:rpn-dataentryfine}\n   *    * [`rpn-dataincrement`]{@link InputChannel#event:rpn-dataincrement}\n   *    * [`rpn-datadecrement`]{@link InputChannel#event:rpn-datadecrement}\n   *\n   * @param event {string} The type of the event.\n   *\n   * @param listener {function} A callback function to execute when the specified event is detected.\n   * This function will receive an event parameter object. For details on this object's properties,\n   * check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {number|number[]} [options.channels]  An integer between 1 and 16 or an array of\n   * such integers representing the MIDI channel(s) to listen on. This parameter is ignored for\n   * input-wide events.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @returns {Listener[]} An array of all [`Listener`](Listener) objects that were created.\n   */\n  addOneTimeListener(event, listener, options = {}) {\n    options.remaining = 1;\n    return this.addListener(event, listener, options);\n  }\n\n  /**\n   * This is an alias to the [Input.addListener()]{@link Input#addListener} method.\n   * @since 2.0.0\n   * @deprecated since v3.0\n   * @private\n   */\n  on(event, channel, listener, options) {\n    return this.addListener(event, channel, listener, options);\n  }\n\n  /**\n   * Checks if the specified event type is already defined to trigger the specified callback\n   * function. For channel-specific events, the function will return `true` only if all channels\n   * have the listener defined.\n   *\n   * @param event {string|Symbol} The type of the event.\n   *\n   * @param listener {function} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels]  An integer between 1 and 16 or an array of such\n   * integers representing the MIDI channel(s) to check. This parameter is ignored for input-wide\n   * events.\n   *\n   * @returns {boolean} Boolean value indicating whether or not the `Input` or\n   * [`InputChannel`](InputChannel) already has this listener defined.\n   */\n  hasListener(event, listener, options = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (typeof options === \"function\") {\n        let channels = [].concat(listener); // clone\n        listener = options;\n        options = {channels: channels};\n      }\n\n    }\n\n    if (Enumerations.CHANNEL_EVENTS.includes(event)) {\n\n      // If no channel defined, use all.\n      if (options.channels === undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n      return Utilities.sanitizeChannels(options.channels).every(ch => {\n        return this.channels[ch].hasListener(event, listener);\n      });\n\n    } else {\n      return super.hasListener(event, listener);\n    }\n\n  }\n\n  /**\n   * Removes the specified event listener. If no listener is specified, all listeners matching the\n   * specified event will be removed. If the event is channel-specific, the listener will be removed\n   * from all [`InputChannel`]{@link InputChannel} objects belonging to that channel. If no event is\n   * specified, all listeners for the `Input` as well as all listeners for all\n   * [`InputChannel`]{@link InputChannel} objects belonging to the `Input` will be removed.\n   *\n   * By default, channel-specific listeners will be removed from all\n   * [`InputChannel`]{@link InputChannel} objects unless the `options.channel` narrows it down.\n   *\n   * @param [type] {string} The type of the event.\n   *\n   * @param [listener] {function} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels]  An integer between 1 and 16 or an array of\n   * such integers representing the MIDI channel(s) to match. This parameter is ignored for\n   * input-wide events.\n   *\n   * @param {*} [options.context] Only remove the listeners that have this exact context.\n   *\n   * @param {number} [options.remaining] Only remove the listener if it has exactly that many\n   * remaining times to be executed.\n   */\n  removeListener(event, listener, options = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (typeof options === \"function\") {\n        let channels = [].concat(listener); // clone\n        listener = options;\n        options = {channels: channels};\n      }\n\n    }\n\n    if (options.channels === undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    // If the event is not specified, remove everything (channel-specific and input-wide)!\n    if (event == undefined) {\n      Utilities.sanitizeChannels(options.channels).forEach(ch => {\n        if (this.channels[ch]) this.channels[ch].removeListener();\n      });\n      return super.removeListener();\n    }\n\n    // If the event is specified, check if it's channel-specific or input-wide.\n    if (Enumerations.CHANNEL_EVENTS.includes(event)) {\n\n      Utilities.sanitizeChannels(options.channels).forEach(ch => {\n        this.channels[ch].removeListener(event, listener, options);\n      });\n\n    } else {\n\n      super.removeListener(event, listener, options);\n\n    }\n\n  }\n\n  /**\n   * Adds a forwarder that will forward all incoming MIDI messages matching the criteria to the\n   * specified [`Output`](Output) destination(s). This is akin to the hardware MIDI THRU port, with\n   * the added benefit of being able to filter which data is forwarded.\n   *\n   * @param {Output|Output[]|Forwarder} output An [`Output`](Output) object, a\n   * [`Forwarder`](Forwarder) object or an array of such objects, to forward messages to.\n   * @param {object} [options={}]\n   * @param {string|string[]} [options.types=(all messages)] A message type, or an array of such\n   * types (`noteon`, `controlchange`, etc.), that the message type must match in order to be\n   * forwarded. If this option is not specified, all types of messages will be forwarded. Valid\n   * messages are the ones found in either\n   * [`SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) or\n   * [`CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * A MIDI channel number or an array of channel numbers that the message must match in order to be\n   * forwarded. By default all MIDI channels are included (`1` to `16`).\n   *\n   * @returns {Forwarder} The [`Forwarder`](Forwarder) object created to handle the forwarding. This\n   * is useful if you wish to manipulate or remove the [`Forwarder`](Forwarder) later on.\n   */\n  addForwarder(output, options = {}) {\n\n    let forwarder;\n\n    // Unless 'output' is a forwarder, create a new forwarder\n    if (output instanceof Forwarder) {\n      forwarder = output;\n    } else {\n      forwarder = new Forwarder(output, options);\n    }\n\n    this._forwarders.push(forwarder);\n    return forwarder;\n\n  }\n\n  /**\n   * Removes the specified [`Forwarder`](Forwarder) object from the input.\n   *\n   * @param {Forwarder} forwarder The [`Forwarder`](Forwarder) to remove (the\n   * [`Forwarder`](Forwarder) object is returned when calling `addForwarder()`.\n   */\n  removeForwarder(forwarder) {\n    this._forwarders = this._forwarders.filter(item => item !== forwarder);\n  }\n\n  /**\n   * Checks whether the specified [`Forwarder`](Forwarder) object has already been attached to this\n   * input.\n   *\n   * @param {Forwarder} forwarder The [`Forwarder`](Forwarder) to check for (the\n   * [`Forwarder`](Forwarder) object is returned when calling [`addForwarder()`](#addForwarder).\n   * @returns {boolean}\n   */\n  hasForwarder(forwarder) {\n    return this._forwarders.includes(forwarder);\n  }\n\n  /**\n   * Name of the MIDI input.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get name() {\n    return this._midiInput.name;\n  }\n\n  /**\n   * ID string of the MIDI port. The ID is host-specific. Do not expect the same ID on different\n   * platforms. For example, Google Chrome and the Jazz-Plugin report completely different IDs for\n   * the same port.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get id() {\n    return this._midiInput.id;\n  }\n\n  /**\n   * Input port's connection state: `pending`, `open` or `closed`.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get connection() {\n    return this._midiInput.connection;\n  }\n\n  /**\n   * Name of the manufacturer of the device that makes this input port available.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get manufacturer() {\n    return this._midiInput.manufacturer;\n  }\n\n  /**\n   * An integer to offset the reported octave of incoming notes. By default, middle C (MIDI note\n   * number 60) is placed on the 4th octave (C4).\n   *\n   * If, for example, `octaveOffset` is set to 2, MIDI note number 60 will be reported as C6. If\n   * `octaveOffset` is set to -1, MIDI note number 60 will be reported as C3.\n   *\n   * Note that this value is combined with the global offset value defined in the\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) property (if any).\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  get octaveOffset() {\n    return this._octaveOffset;\n  }\n  set octaveOffset(value) {\n\n    if (this.validation) {\n      value = parseInt(value);\n      if (isNaN(value)) throw new TypeError(\"The 'octaveOffset' property must be an integer.\");\n    }\n\n    this._octaveOffset = value;\n\n  }\n\n  /**\n   * State of the input port: `connected` or `disconnected`.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get state() {\n    return this._midiInput.state;\n  }\n\n  /**\n   * The port type. In the case of the `Input` object, this is always: `input`.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get type() {\n    return this._midiInput.type;\n  }\n\n  /**\n   * @type {boolean}\n   * @private\n   * @deprecated since v3.0.0 (moved to 'InputChannel' class)\n   */\n  get nrpnEventsEnabled() {\n    if (WebMidi.validation) {\n      console.warn(\"The 'nrpnEventsEnabled' property has been moved to the 'InputChannel' class.\");\n    }\n    return false;\n  }\n\n}\n\n// Events that do not have code below them must be placed outside the class definition (?!)\n\n/**\n * Input-wide (system) event emitted when a **system exclusive** message has been received.\n * You should note that, to receive `sysex` events, you must call the\n * [`WebMidi.enable()`](WebMidi#enable()) method with the `sysex` option set to `true`:\n *\n * ```js\n * WebMidi.enable({sysex: true})\n *  .then(() => console.log(\"WebMidi has been enabled with sysex support.\"))\n * ```\n *\n * @event Input#sysex\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `sysex`\n *\n */\n\n/**\n * Input-wide (system) event emitted when a **time code quarter frame** message has been\n * received.\n *\n * @event Input#timecode\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `timecode`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **song position** message has been received.\n *\n * @event Input#songposition\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `songposition`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **song select** message has been received.\n *\n * @event Input#songselect\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} value Song (or sequence) number to select (0-127)\n * @property {string} rawValue Song (or sequence) number to select (0-127)\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **tune request** message has been received.\n *\n * @event Input#tunerequest\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `tunerequest`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **timing clock** message has been received.\n *\n * @event Input#clock\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `clock`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **start** message has been received.\n *\n * @event Input#start\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `start`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **continue** message has been received.\n *\n * @event Input#continue\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `continue`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **stop** message has been received.\n *\n * @event Input#stop\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `stop`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when an **active sensing** message has been received.\n *\n * @event Input#activesensing\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `activesensing`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when a **reset** message has been received.\n *\n * @event Input#reset\n *\n * @type {object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `reset`\n *\n * @since 2.1\n */\n\n/**\n * Input-wide (system) event emitted when an unknown MIDI message has been received. It could\n * be, for example, one of the undefined/reserved messages.\n *\n * @event Input#unknownmessage\n *\n * @type {Object}\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input} target The object that dispatched the event.\n * @property {Message} message A [`Message`](Message) object containing information about the\n * incoming MIDI message.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type `unknownmessage`\n *\n * @since 2.1\n */\n"
  },
  {
    "path": "src/InputChannel.js",
    "content": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {WebMidi} from \"./WebMidi.js\";\nimport {Utilities} from \"./Utilities.js\";\nimport {Note} from \"./Note.js\";\nimport {Enumerations} from \"./Enumerations.js\";\n\n/**\n * The `InputChannel` class represents a single MIDI input channel (1-16) from a single input\n * device. This object is derived from the host's MIDI subsystem and should not be instantiated\n * directly.\n *\n * All 16 `InputChannel` objects can be found inside the input's [`channels`](Input#channels)\n * property.\n *\n * @fires InputChannel#midimessage\n * @fires InputChannel#unknownmessage\n *\n * @fires InputChannel#noteoff\n * @fires InputChannel#noteon\n * @fires InputChannel#keyaftertouch\n * @fires InputChannel#programchange\n * @fires InputChannel#channelaftertouch\n * @fires InputChannel#pitchbend\n *\n * @fires InputChannel#allnotesoff\n * @fires InputChannel#allsoundoff\n * @fires InputChannel#localcontrol\n * @fires InputChannel#monomode\n * @fires InputChannel#omnimode\n * @fires InputChannel#resetallcontrollers\n *\n * @fires InputChannel#event:nrpn\n * @fires InputChannel#event:nrpn-dataentrycoarse\n * @fires InputChannel#event:nrpn-dataentryfine\n * @fires InputChannel#event:nrpn-dataincrement\n * @fires InputChannel#event:nrpn-datadecrement\n * @fires InputChannel#event:rpn\n * @fires InputChannel#event:rpn-dataentrycoarse\n * @fires InputChannel#event:rpn-dataentryfine\n * @fires InputChannel#event:rpn-dataincrement\n * @fires InputChannel#event:rpn-datadecrement\n *\n * @fires InputChannel#controlchange\n * @fires InputChannel#event:controlchange-controllerxxx\n * @fires InputChannel#event:controlchange-bankselectcoarse\n * @fires InputChannel#event:controlchange-modulationwheelcoarse\n * @fires InputChannel#event:controlchange-breathcontrollercoarse\n * @fires InputChannel#event:controlchange-footcontrollercoarse\n * @fires InputChannel#event:controlchange-portamentotimecoarse\n * @fires InputChannel#event:controlchange-dataentrycoarse\n * @fires InputChannel#event:controlchange-volumecoarse\n * @fires InputChannel#event:controlchange-balancecoarse\n * @fires InputChannel#event:controlchange-pancoarse\n * @fires InputChannel#event:controlchange-expressioncoarse\n * @fires InputChannel#event:controlchange-effectcontrol1coarse\n * @fires InputChannel#event:controlchange-effectcontrol2coarse\n * @fires InputChannel#event:controlchange-generalpurposecontroller1\n * @fires InputChannel#event:controlchange-generalpurposecontroller2\n * @fires InputChannel#event:controlchange-generalpurposecontroller3\n * @fires InputChannel#event:controlchange-generalpurposecontroller4\n * @fires InputChannel#event:controlchange-bankselectfine\n * @fires InputChannel#event:controlchange-modulationwheelfine\n * @fires InputChannel#event:controlchange-breathcontrollerfine\n * @fires InputChannel#event:controlchange-footcontrollerfine\n * @fires InputChannel#event:controlchange-portamentotimefine\n * @fires InputChannel#event:controlchange-dataentryfine\n * @fires InputChannel#event:controlchange-channelvolumefine\n * @fires InputChannel#event:controlchange-balancefine\n * @fires InputChannel#event:controlchange-panfine\n * @fires InputChannel#event:controlchange-expressionfine\n * @fires InputChannel#event:controlchange-effectcontrol1fine\n * @fires InputChannel#event:controlchange-effectcontrol2fine\n * @fires InputChannel#event:controlchange-damperpedal\n * @fires InputChannel#event:controlchange-portamento\n * @fires InputChannel#event:controlchange-sostenuto\n * @fires InputChannel#event:controlchange-softpedal\n * @fires InputChannel#event:controlchange-legatopedal\n * @fires InputChannel#event:controlchange-hold2\n * @fires InputChannel#event:controlchange-soundvariation\n * @fires InputChannel#event:controlchange-resonance\n * @fires InputChannel#event:controlchange-releasetime\n * @fires InputChannel#event:controlchange-attacktime\n * @fires InputChannel#event:controlchange-brightness\n * @fires InputChannel#event:controlchange-decaytime\n * @fires InputChannel#event:controlchange-vibratorate\n * @fires InputChannel#event:controlchange-vibratodepth\n * @fires InputChannel#event:controlchange-vibratodelay\n * @fires InputChannel#event:controlchange-generalpurposecontroller5\n * @fires InputChannel#event:controlchange-generalpurposecontroller6\n * @fires InputChannel#event:controlchange-generalpurposecontroller7\n * @fires InputChannel#event:controlchange-generalpurposecontroller8\n * @fires InputChannel#event:controlchange-portamentocontrol\n * @fires InputChannel#event:controlchange-highresolutionvelocityprefix\n * @fires InputChannel#event:controlchange-effect1depth\n * @fires InputChannel#event:controlchange-effect2depth\n * @fires InputChannel#event:controlchange-effect3depth\n * @fires InputChannel#event:controlchange-effect4depth\n * @fires InputChannel#event:controlchange-effect5depth\n * @fires InputChannel#event:controlchange-dataincrement\n * @fires InputChannel#event:controlchange-datadecrement\n * @fires InputChannel#event:controlchange-nonregisteredparameterfine\n * @fires InputChannel#event:controlchange-nonregisteredparametercoarse\n * @fires InputChannel#event:controlchange-registeredparameterfine\n * @fires InputChannel#event:controlchange-registeredparametercoarse\n * @fires InputChannel#event:controlchange-allsoundoff\n * @fires InputChannel#event:controlchange-resetallcontrollers\n * @fires InputChannel#event:controlchange-localcontrol\n * @fires InputChannel#event:controlchange-allnotesoff\n * @fires InputChannel#event:controlchange-omnimodeoff\n * @fires InputChannel#event:controlchange-omnimodeon\n * @fires InputChannel#event:controlchange-monomodeon\n * @fires InputChannel#event:controlchange-polymodeon\n * @fires InputChannel#event:\n *\n * @extends EventEmitter\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class InputChannel extends EventEmitter {\n\n  /**\n   * Creates an `InputChannel` object.\n   *\n   * @param {Input} input The [`Input`](Input) object this channel belongs to.\n   * @param {number} number The channel's MIDI number (1-16).\n   */\n  constructor(input, number) {\n\n    super();\n\n    /**\n     * @type {Input}\n     * @private\n     */\n    this._input = input;\n\n    /**\n     * @type {number}\n     * @private\n     */\n    this._number = number;\n\n    /**\n     * @type {number}\n     * @private\n     */\n    this._octaveOffset = 0;\n\n    /**\n     * An array of messages that form the current NRPN sequence\n     * @private\n     * @type {Message[]}\n     */\n    this._nrpnBuffer = [];\n\n    /**\n     * An array of messages that form the current RPN sequence\n     * @private\n     * @type {Message[]}\n     */\n    this._rpnBuffer = [];\n\n    /**\n     * Indicates whether events for **Registered Parameter Number** and **Non-Registered Parameter\n     * Number** should be dispatched. RPNs and NRPNs are composed of a sequence of specific\n     * **control change** messages. When a valid sequence of such control change messages is\n     * received, an [`rpn`](#event-rpn) or [`nrpn`](#event-nrpn) event will fire.\n     *\n     * If an invalid or out-of-order **control change** message is received, it will fall through\n     * the collector logic and all buffered **control change** messages will be discarded as\n     * incomplete.\n     *\n     * @type {boolean}\n     */\n    this.parameterNumberEventsEnabled = true;\n\n    /**\n     * Contains the current playing state of all MIDI notes of this channel (0-127). The state is\n     * `true` for a currently playing note and `false` otherwise.\n     * @type {boolean[]}\n     */\n    this.notesState = new Array(128).fill(false);\n\n  }\n\n  /**\n   * Destroys the `InputChannel` by removing all listeners and severing the link with the MIDI\n   * subsystem's input.\n   */\n  destroy() {\n    this._input = null;\n    this._number = null;\n    this._octaveOffset = 0;\n    this._nrpnBuffer = [];\n    this.notesState = new Array(128).fill(false);\n    this.parameterNumberEventsEnabled = false;\n    this.removeListener();\n  }\n\n  /**\n   * @param e MIDIMessageEvent\n   * @private\n   */\n  _processMidiMessageEvent(e) {\n\n    // Create and emit a new 'midimessage' event based on the incoming one\n    const event = Object.assign({}, e);\n    event.port = this.input;\n    event.target = this;\n    event.type = \"midimessage\";\n\n    /**\n     * Event emitted when a MIDI message of any kind is received by an `InputChannel`\n     *\n     * @event InputChannel#midimessage\n     *\n     * @type {object}\n     *\n     * @property {string} type `midimessage`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     */\n    this.emit(event.type, event);\n\n    // Parse the inbound event for regular MIDI messages\n    this._parseEventForStandardMessages(event);\n\n  }\n\n  /**\n   * Parses incoming channel events and emit standard MIDI message events (noteon, noteoff, etc.)\n   * @param e Event\n   * @private\n   */\n  _parseEventForStandardMessages(e) {\n\n    const event = Object.assign({}, e);\n    event.type = event.message.type || \"unknownmessage\";\n\n    const data1 = e.message.dataBytes[0];\n    const data2 = e.message.dataBytes[1];\n\n    if ( event.type === \"noteoff\" || (event.type === \"noteon\" && data2 === 0) ) {\n\n      this.notesState[data1] = false;\n      event.type = \"noteoff\"; // necessary for note on with 0 velocity\n\n      /**\n       * Event emitted when a **note off** MIDI message has been received on the channel.\n       *\n       * @event InputChannel#noteoff\n       *\n       * @type {object}\n       * @property {string} type `noteoff`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the incoming\n       * MIDI message.\n       * @property {number} timestamp The moment\n       * ([`DOMHighResTimeStamp`](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp))\n       * when the event occurred (in milliseconds since the navigation start of the document).\n       *\n       * @property {object} note A [`Note`](Note) object containing information such as note name,\n       * octave and release velocity.\n       * @property {number} value The release velocity amount expressed as a float between 0 and 1.\n       * @property {number} rawValue The release velocity amount expressed as an integer (between 0\n       * and 127).\n       */\n\n      // The object created when a noteoff event arrives is a Note with an attack velocity of 0.\n      event.note = new Note(\n        Utilities.offsetNumber(\n          data1, this.octaveOffset + this.input.octaveOffset + WebMidi.octaveOffset\n        ),\n        {\n          rawAttack: 0,\n          rawRelease: data2,\n        }\n      );\n\n      event.value = Utilities.from7bitToFloat(data2);\n      event.rawValue = data2;\n\n      // Those are kept for backwards-compatibility but are gone from the documentation. They will\n      // be removed in future versions (@deprecated).\n      event.velocity = event.note.release;\n      event.rawVelocity = event.note.rawRelease;\n\n    } else if (event.type === \"noteon\") {\n\n      this.notesState[data1] = true;\n\n      /**\n       * Event emitted when a **note on** MIDI message has been received.\n       *\n       * @event InputChannel#noteon\n       *\n       * @type {object}\n       * @property {string} type `noteon`\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} note A [`Note`](Note) object containing information such as note name,\n       * octave and release velocity.\n       * @property {number} value The attack velocity amount expressed as a float between 0 and 1.\n       * @property {number} rawValue The attack velocity amount expressed as an integer (between 0\n       * and 127).\n       */\n      event.note = new Note(\n        Utilities.offsetNumber(\n          data1, this.octaveOffset + this.input.octaveOffset + WebMidi.octaveOffset\n        ),\n        { rawAttack: data2 }\n      );\n\n      event.value = Utilities.from7bitToFloat(data2);\n      event.rawValue = data2;\n\n      // Those are kept for backwards-compatibility but are gone from the documentation. They will\n      // be removed in future versions (@deprecated).\n      event.velocity = event.note.attack;\n      event.rawVelocity = event.note.rawAttack;\n\n    } else if (event.type === \"keyaftertouch\") {\n\n      /**\n       * Event emitted when a **key-specific aftertouch** MIDI message has been received.\n       *\n       * @event InputChannel#keyaftertouch\n       *\n       * @type {object}\n       * @property {string} type `\"keyaftertouch\"`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} note A [`Note`](Note) object containing information such as note name\n       * and number.\n       * @property {number} value The aftertouch amount expressed as a float between 0 and 1.\n       * @property {number} rawValue The aftertouch amount expressed as an integer (between 0 and\n       * 127).\n       */\n      event.note = new Note(\n        Utilities.offsetNumber(\n          data1, this.octaveOffset + this.input.octaveOffset + WebMidi.octaveOffset\n        )\n      );\n\n      // Aftertouch value\n      event.value = Utilities.from7bitToFloat(data2);\n      event.rawValue = data2;\n\n      // @deprecated\n      event.identifier = event.note.identifier;\n      event.key = event.note.number;\n      event.rawKey = data1;\n\n    } else if (event.type === \"controlchange\") {\n\n      /**\n       * Event emitted when a **control change** MIDI message has been received.\n       *\n       * @event InputChannel#controlchange\n       *\n       * @type {object}\n       * @property {string} type `controlchange`\n       * @property {string} subtype The type of control change message that was received.\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n      event.controller = {\n        number: data1,\n        name: Enumerations.CONTROL_CHANGE_MESSAGES[data1].name,\n        description: Enumerations.CONTROL_CHANGE_MESSAGES[data1].description,\n        position: Enumerations.CONTROL_CHANGE_MESSAGES[data1].position,\n      };\n\n      event.subtype = event.controller.name || \"controller\" + data1;\n      event.value = Utilities.from7bitToFloat(data2);\n      event.rawValue = data2;\n\n      /**\n       * Event emitted when a **control change** MIDI message has been received and that message is\n       * targeting the controller numbered \"xxx\". Of course, \"xxx\" should be replaced by a valid\n       * controller number (0-127).\n       *\n       * @event InputChannel#controlchange-controllerxxx\n       *\n       * @type {object}\n       * @property {string} type `controlchange-controllerxxx`\n       * @property {string} subtype The type of control change message that was received.\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n      const numberedEvent = Object.assign({}, event);\n      numberedEvent.type = `${event.type}-controller${data1}`;\n      delete numberedEvent.subtype;\n      this.emit(numberedEvent.type, numberedEvent);\n\n      /**\n       * Event emitted when a **controlchange-bankselectcoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-bankselectcoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-bankselectcoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-modulationwheelcoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-modulationwheelcoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-modulationwheelcoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-breathcontrollercoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-breathcontrollercoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-breathcontrollercoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-footcontrollercoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-footcontrollercoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-footcontrollercoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-portamentotimecoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-portamentotimecoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-portamentotimecoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-dataentrycoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-dataentrycoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-dataentrycoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-volumecoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-volumecoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-volumecoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-balancecoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-balancecoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-balancecoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-pancoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-pancoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-pancoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-expressioncoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-expressioncoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-expressioncoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effectcontrol1coarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effectcontrol1coarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effectcontrol1coarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effectcontrol2coarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effectcontrol2coarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effectcontrol2coarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller1** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller1\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller1`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller2** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller2\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller2`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller3** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller3\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller3`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller4** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller4\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller4`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-bankselectfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-bankselectfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-bankselectfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-modulationwheelfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-modulationwheelfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-modulationwheelfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-breathcontrollerfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-breathcontrollerfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-breathcontrollerfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-footcontrollerfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-footcontrollerfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-footcontrollerfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-portamentotimefine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-portamentotimefine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-portamentotimefine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-dataentryfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-dataentryfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-dataentryfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-channelvolumefine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-channelvolumefine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-channelvolumefine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-balancefine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-balancefine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-balancefine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-panfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-panfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-panfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-expressionfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-expressionfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-expressionfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effectcontrol1fine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effectcontrol1fine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effectcontrol1fine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effectcontrol2fine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effectcontrol2fine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effectcontrol2fine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-damperpedal** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-damperpedal\n       *\n       * @type {object}\n       * @property {string} type `controlchange-damperpedal`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-portamento** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-portamento\n       *\n       * @type {object}\n       * @property {string} type `controlchange-portamento`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-sostenuto** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-sostenuto\n       *\n       * @type {object}\n       * @property {string} type `controlchange-sostenuto`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-softpedal** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-softpedal\n       *\n       * @type {object}\n       * @property {string} type `controlchange-softpedal`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-legatopedal** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-legatopedal\n       *\n       * @type {object}\n       * @property {string} type `controlchange-legatopedal`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-hold2** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-hold2\n       *\n       * @type {object}\n       * @property {string} type `controlchange-hold2`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-soundvariation** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-soundvariation\n       *\n       * @type {object}\n       * @property {string} type `controlchange-soundvariation`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-resonance** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-resonance\n       *\n       * @type {object}\n       * @property {string} type `controlchange-resonance`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-releasetime** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-releasetime\n       *\n       * @type {object}\n       * @property {string} type `controlchange-releasetime`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-attacktime** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-attacktime\n       *\n       * @type {object}\n       * @property {string} type `controlchange-attacktime`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-brightness** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-brightness\n       *\n       * @type {object}\n       * @property {string} type `controlchange-brightness`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-decaytime** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-decaytime\n       *\n       * @type {object}\n       * @property {string} type `controlchange-decaytime`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-vibratorate** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-vibratorate\n       *\n       * @type {object}\n       * @property {string} type `controlchange-vibratorate`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-vibratodepth** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-vibratodepth\n       *\n       * @type {object}\n       * @property {string} type `controlchange-vibratodepth`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-vibratodelay** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-vibratodelay\n       *\n       * @type {object}\n       * @property {string} type `controlchange-vibratodelay`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller5** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller5\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller5`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller6** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller6\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller6`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller7** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller7\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller7`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-generalpurposecontroller8** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-generalpurposecontroller8\n       *\n       * @type {object}\n       * @property {string} type `controlchange-generalpurposecontroller8`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-portamentocontrol** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-portamentocontrol\n       *\n       * @type {object}\n       * @property {string} type `controlchange-portamentocontrol`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-highresolutionvelocityprefix** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-highresolutionvelocityprefix\n       *\n       * @type {object}\n       * @property {string} type `controlchange-highresolutionvelocityprefix`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effect1depth** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effect1depth\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effect1depth`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effect2depth** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effect2depth\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effect2depth`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effect3depth** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effect3depth\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effect3depth`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effect4depth** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effect4depth\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effect4depth`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-effect5depth** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-effect5depth\n       *\n       * @type {object}\n       * @property {string} type `controlchange-effect5depth`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-dataincrement** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-dataincrement\n       *\n       * @type {object}\n       * @property {string} type `controlchange-dataincrement`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-datadecrement** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-datadecrement\n       *\n       * @type {object}\n       * @property {string} type `controlchange-datadecrement`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-nonregisteredparameterfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-nonregisteredparameterfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-nonregisteredparameterfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-nonregisteredparametercoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-nonregisteredparametercoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-nonregisteredparametercoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-registeredparameterfine** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-registeredparameterfine\n       *\n       * @type {object}\n       * @property {string} type `controlchange-registeredparameterfine`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-registeredparametercoarse** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-registeredparametercoarse\n       *\n       * @type {object}\n       * @property {string} type `controlchange-registeredparametercoarse`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-allsoundoff** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-allsoundoff\n       *\n       * @type {object}\n       * @property {string} type `controlchange-allsoundoff`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-resetallcontrollers** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-resetallcontrollers\n       *\n       * @type {object}\n       * @property {string} type `controlchange-resetallcontrollers`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-localcontrol** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-localcontrol\n       *\n       * @type {object}\n       * @property {string} type `controlchange-localcontrol`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-allnotesoff** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-allnotesoff\n       *\n       * @type {object}\n       * @property {string} type `controlchange-allnotesoff`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-omnimodeoff** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-omnimodeoff\n       *\n       * @type {object}\n       * @property {string} type `controlchange-omnimodeoff`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-omnimodeon** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-omnimodeon\n       *\n       * @type {object}\n       * @property {string} type `controlchange-omnimodeon`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-monomodeon** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-monomodeon\n       *\n       * @type {object}\n       * @property {string} type `controlchange-monomodeon`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      /**\n       * Event emitted when a **controlchange-polymodeon** MIDI message has been\n       * received.\n       *\n       * @event InputChannel#controlchange-polymodeon\n       *\n       * @type {object}\n       * @property {string} type `controlchange-polymodeon`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {object} controller\n       * @property {object} controller.number The number of the controller.\n       * @property {object} controller.name The usual name or function of the controller.\n       * @property {object} controller.description A user-friendly representation of the\n       * controller's default function\n       * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The value expressed as an integer (between 0 and 127).\n       */\n\n      const namedEvent = Object.assign({}, event);\n      namedEvent.type = `${event.type}-` + Enumerations.CONTROL_CHANGE_MESSAGES[data1].name;\n      delete namedEvent.subtype;\n\n      // Dispatch controlchange-\"function\" events only if the \"function\" is defined (not the generic\n      // controllerXXX nomenclature)\n      if (namedEvent.type.indexOf(\"controller\") !== 0) {\n        this.emit(namedEvent.type, namedEvent);\n      }\n\n      // Trigger channel mode message events (if appropriate)\n      if (event.message.dataBytes[0] >= 120) this._parseChannelModeMessage(event);\n\n      // Parse the inbound event to see if its part of an RPN/NRPN sequence\n      if (\n        this.parameterNumberEventsEnabled &&\n        this._isRpnOrNrpnController(event.message.dataBytes[0])\n      ) {\n        this._parseEventForParameterNumber(event);\n      }\n\n    } else if (event.type === \"programchange\") {\n\n      /**\n       * Event emitted when a **program change** MIDI message has been received.\n       *\n       * @event InputChannel#programchange\n       *\n       * @type {object}\n       * @property {string} type `programchange`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {number} value The value expressed as an integer between 0 and 127.\n       * @property {number} rawValue  The raw MIDI value expressed as an integer between 0 and 127.\n       */\n      event.value = data1;\n      event.rawValue = event.value;\n\n    } else if (event.type === \"channelaftertouch\") {\n\n      /**\n       * Event emitted when a control change MIDI message has been received.\n       *\n       * @event InputChannel#channelaftertouch\n       *\n       * @type {object}\n       * @property {string} type `channelaftertouch`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The raw MIDI value expressed as an integer between 0 and 127.\n       */\n      event.value = Utilities.from7bitToFloat(data1);\n      event.rawValue = data1;\n\n    } else if (event.type === \"pitchbend\") {\n\n      /**\n       * Event emitted when a pitch bend MIDI message has been received.\n       *\n       * @event InputChannel#pitchbend\n       *\n       * @type {object}\n       * @property {string} type `pitchbend`\n       *\n       * @property {InputChannel} target The object that dispatched the event.\n       * @property {Input} port The `Input` that triggered the event.\n       * @property {Message} message A [`Message`](Message) object containing information about the\n       * incoming MIDI message.\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       *\n       * @property {number} value The value expressed as a float between 0 and 1.\n       * @property {number} rawValue The raw MIDI value expressed as an integer (between 0 and\n       * 16383).\n       */\n      event.value = ((data2 << 7) + data1 - 8192) / 8192;\n      event.rawValue = (data2 << 7) + data1;\n\n    } else {\n      event.type = \"unknownmessage\";\n    }\n\n    this.emit(event.type, event);\n\n  }\n\n  /**\n   * @param e {Object}\n   * @private\n   */\n  _parseChannelModeMessage(e) {\n\n    // Make a shallow copy of the incoming event so we can use it as the new event.\n    const event = Object.assign({}, e);\n    event.type = event.controller.name;\n\n    /**\n     * Event emitted when an \"all sound off\" channel-mode MIDI message has been received.\n     *\n     * @event InputChannel#allsoundoff\n     *\n     * @type {object}\n     * @property {string} type `allsoundoff`\n     *\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     */\n\n    /**\n     * Event emitted when a \"reset all controllers\" channel-mode MIDI message has been received.\n     *\n     * @event InputChannel#resetallcontrollers\n     *\n     * @type {object}\n     *\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     */\n\n    /**\n     * Event emitted when a \"local control\" channel-mode MIDI message has been received. The value\n     * property of the event is set to either `true` (local control on) of `false` (local control\n     * off).\n     *\n     * @event InputChannel#localcontrol\n     *\n     * @type {object}\n     * @property {string} type `localcontrol`\n     *\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     *\n     * @property {boolean} value For local control on, the value is `true`. For local control off,\n     * the value is `false`.\n     * @property {boolean} rawValue For local control on, the value is `127`. For local control off,\n     * the value is `0`.\n     */\n    if (event.type === \"localcontrol\") {\n      event.value = event.message.data[2] === 127 ? true : false;\n      event.rawValue = event.message.data[2];\n    }\n\n    /**\n     * Event emitted when an \"all notes off\" channel-mode MIDI message has been received.\n     *\n     * @event InputChannel#allnotesoff\n     *\n     * @type {object}\n     * @property {string} type `allnotesoff`\n     *\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     */\n\n    /**\n     * Event emitted when an \"omni mode\" channel-mode MIDI message has been received. The value\n     * property of the event is set to either `true` (omni mode on) of `false` (omni mode off).\n     *\n     * @event InputChannel#omnimode\n     *\n     * @type {object}\n     * @property {string} type `\"omnimode\"`\n     *\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     *\n     * @property {boolean} value The value is `true` for omni mode on and false for omni mode off.\n     * @property {boolean} rawValue The raw MIDI value\n     */\n    if (event.type === \"omnimodeon\") {\n      event.type = \"omnimode\";\n      event.value = true;\n      event.rawValue = event.message.data[2];\n    } else if (event.type === \"omnimodeoff\") {\n      event.type = \"omnimode\";\n      event.value = false;\n      event.rawValue = event.message.data[2];\n    }\n\n\n    /**\n     * Event emitted when a \"mono/poly mode\" MIDI message has been received. The value property of\n     * the event is set to either `true` (mono mode on / poly mode off) or `false` (mono mode off /\n     * poly mode on).\n     *\n     * @event InputChannel#monomode\n     *\n     * @type {object}\n     * @property {string} type `monomode`\n     *\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     *\n     * @property {boolean} value The value is `true` for omni mode on and false for omni mode off.\n     * @property {boolean} rawValue The raw MIDI value\n     */\n    if (event.type === \"monomodeon\") {\n      event.type = \"monomode\";\n      event.value = true;\n      event.rawValue = event.message.data[2];\n    } else if (event.type === \"polymodeon\") {\n      event.type = \"monomode\";\n      event.value = false;\n      event.rawValue = event.message.data[2];\n    }\n\n    this.emit(event.type, event);\n\n  }\n\n  /**\n   * Parses inbound events to identify RPN/NRPN sequences.\n   * @param e Event\n   * @private\n   */\n  _parseEventForParameterNumber(event) {\n\n    // To make it more legible\n    const controller = event.message.dataBytes[0];\n    const value = event.message.dataBytes[1];\n\n    // A. Check if the message is the start of an RPN (101) or NRPN (99) parameter declaration.\n    if (controller === 99 || controller === 101) {\n\n      this._nrpnBuffer = [];\n      this._rpnBuffer = [];\n\n      if (controller === 99) {                          // 99\n        this._nrpnBuffer = [event.message];\n      } else {                                          // 101\n        // 127 is a reset so we ignore it\n        if (value !== 127) this._rpnBuffer = [event.message];\n      }\n\n    // B. Check if the message is the end of an RPN (100) or NRPN (98) parameter declaration.\n    } else if (controller === 98 || controller === 100) {\n\n      if (controller === 98) {                          // 98\n\n        // Flush the other buffer (they are mutually exclusive)\n        this._rpnBuffer = [];\n\n        // Check if we are in sequence\n        if (this._nrpnBuffer.length === 1) {\n          this._nrpnBuffer.push(event.message);\n        } else {\n          this._nrpnBuffer = []; // out of sequence\n        }\n\n      } else {                                          // 100\n\n        // Flush the other buffer (they are mutually exclusive)\n        this._nrpnBuffer = [];\n\n        // 127 is a reset so we ignore it\n        if (this._rpnBuffer.length === 1 && value !== 127) {\n          this._rpnBuffer.push(event.message);\n        } else {\n          this._rpnBuffer = []; // out of sequence or reset\n        }\n\n      }\n\n    // C. Check if the message is for data entry (6, 38, 96 or 97). Those messages trigger events.\n    } else if (\n      controller === 6 ||\n      controller === 38 ||\n      controller === 96 ||\n      controller === 97\n    ) {\n\n      if (this._rpnBuffer.length === 2) {\n        this._dispatchParameterNumberEvent(\n          \"rpn\",\n          this._rpnBuffer[0].dataBytes[1],\n          this._rpnBuffer[1].dataBytes[1],\n          event\n        );\n      } else if (this._nrpnBuffer.length === 2) {\n        this._dispatchParameterNumberEvent(\n          \"nrpn\",\n          this._nrpnBuffer[0].dataBytes[1],\n          this._nrpnBuffer[1].dataBytes[1],\n          event\n        );\n      } else {\n        this._nrpnBuffer = [];\n        this._rpnBuffer = [];\n      }\n\n    }\n\n  }\n\n  /**\n   * Indicates whether the specified controller can be part of an RPN or NRPN sequence\n   * @param controller\n   * @returns {boolean}\n   * @private\n   */\n  _isRpnOrNrpnController(controller) {\n\n    return controller === 6 ||\n      controller === 38 ||\n      controller === 96 ||\n      controller === 97 ||\n      controller === 98 ||\n      controller === 99 ||\n      controller === 100 ||\n      controller === 101;\n\n  }\n\n  /**\n   * @private\n   */\n  _dispatchParameterNumberEvent(type, paramMsb, paramLsb, e) {\n\n    type = type === \"nrpn\" ? \"nrpn\" : \"rpn\";\n\n    /**\n     * Event emitted when an **RPN data entry coarse** message is received on the input. The\n     * specific parameter to which the message applies can be found in the event's `parameter`\n     * property. It is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#rpn-dataentrycoarse\n     *\n     * @type {object}\n     *\n     * @property {string} type `rpn-dataentrycoarse`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when an **RPN data entry fine** message is received on the input. The\n     * specific parameter to which the message applies can be found in the event's `parameter`\n     * property. It is one of the ones defined in\n     * [`EnumerationsREGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#rpn-dataentryfine\n     *\n     * @type {object}\n     *\n     * @property {string} type `rpn-dataentryfine`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when an **RPN data increment** message is received on the input. The specific\n     * parameter to which the message applies can be found in the event's `parameter` property. It\n     * is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#rpn-dataincrement\n     *\n     * @type {object}\n     *\n     * @property {string} type `rpn-dataincrement`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when an **RPN data decrement** message is received on the input. The specific\n     * parameter to which the message applies can be found in the event's `parameter` property. It\n     * is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#rpn-datadecrement\n     *\n     * @type {object}\n     *\n     * @property {string} type `rpn-datadecrement`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when an **NRPN data entry coarse** message is received on the input. The\n     * specific parameter to which the message applies can be found in the event's `parameter`\n     * property. It is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#nrpn-dataentrycoarse\n     *\n     * @type {object}\n     *\n     * @property {string} type `nrpn-dataentrycoarse`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when an **NRPN data entry fine** message is received on the input. The\n     * specific parameter to which the message applies can be found in the event's `parameter`\n     * property. It is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#nrpn-dataentryfine\n     *\n     * @type {object}\n     *\n     * @property {string} type `nrpn-dataentryfine`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when an **NRPN data increment** message is received on the input. The specific\n     * parameter to which the message applies can be found in the event's `parameter` property. It\n     * is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#nrpn-dataincrement\n     *\n     * @type {object}\n     *\n     * @property {string} type `nrpn-dataincrement`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when an **NRPN data decrement** message is received on the input. The specific\n     * parameter to which the message applies can be found in the event's `parameter` property. It\n     * is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#nrpn-datadecrement\n     *\n     * @type {object}\n     *\n     * @property {string} type `nrpn-datadecrement`\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    const event = {\n      target: e.target,\n      timestamp: e.timestamp,\n      message: e.message,\n      parameterMsb: paramMsb,\n      parameterLsb: paramLsb,\n      value: Utilities.from7bitToFloat(e.message.dataBytes[1]),\n      rawValue: e.message.dataBytes[1],\n    };\n\n    // Identify the parameter (by name for RPN and by number for NRPN)\n    if (type === \"rpn\") {\n\n      event.parameter = Object.keys(Enumerations.REGISTERED_PARAMETERS).find(key => {\n        return Enumerations.REGISTERED_PARAMETERS[key][0] === paramMsb &&\n          Enumerations.REGISTERED_PARAMETERS[key][1] === paramLsb;\n      });\n\n    } else {\n      event.parameter = (paramMsb << 7) + paramLsb;\n    }\n\n    // Type and subtype\n    const subtype = Enumerations.CONTROL_CHANGE_MESSAGES[e.message.dataBytes[0]].name;\n\n    // Emit specific event\n    event.type = `${type}-${subtype}`;\n    this.emit(event.type, event);\n\n    // Begin Legacy Block (remove in v4)\n    const legacyEvent = Object.assign({}, event);\n    if (legacyEvent.type === \"nrpn-dataincrement\") {\n      legacyEvent.type = \"nrpn-databuttonincrement\";\n    } else if (legacyEvent.type === \"nrpn-datadecrement\") {\n      legacyEvent.type = \"nrpn-databuttondecrement\";\n    } else if (legacyEvent.type === \"rpn-dataincrement\") {\n      legacyEvent.type = \"rpn-databuttonincrement\";\n    } else if (legacyEvent.type === \"rpn-datadecrement\") {\n      legacyEvent.type = \"rpn-databuttondecrement\";\n    }\n    this.emit(legacyEvent.type, legacyEvent);\n    // End Legacy Block\n\n    /**\n     * Event emitted when any NRPN message is received on the input. There are four subtypes of NRPN\n     * messages:\n     *\n     *   * `nrpn-dataentrycoarse`\n     *   * `nrpn-dataentryfine`\n     *   * `nrpn-dataincrement`\n     *   * `nrpn-datadecrement`\n     *\n     * The parameter to which the message applies can be found in the event's `parameter` property.\n     *\n     * @event InputChannel#nrpn\n     *\n     * @type {object}\n     *\n     * @property {string} type `nrpn`\n     * @property {string} subtype The precise type of NRPN message that was received.\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {number} parameter The non-registered parameter number (0-16383)\n     * @property {number} parameterMsb The MSB portion of the non-registered parameter number\n     * (0-127)\n     * @property {number} parameterLsb: The LSB portion of the non-registered parameter number\n     * (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    /**\n     * Event emitted when any RPN message is received on the input. There are four subtypes of RPN\n     * messages:\n     *\n     *   * `rpn-dataentrycoarse`\n     *   * `rpn-dataentryfine`\n     *   * `rpn-dataincrement`\n     *   * `rpn-datadecrement`\n     *\n     * The parameter to which the message applies can be found in the event's `parameter` property.\n     * It is one of the ones defined in\n     * [`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n     *\n     * @event InputChannel#rpn\n     *\n     * @type {object}\n     *\n     * @property {string} type `rpn`\n     * @property {string} subtype The precise type of RPN message that was received.\n     * @property {InputChannel} target The object that dispatched the event.\n     * @property {Input} port The `Input` that triggered the event.\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {Message} message A [`Message`](Message) object containing information about the\n     * incoming MIDI message.\n     * @property {string} parameter The registered parameter's name\n     * @property {number} parameterMsb The MSB portion of the registered parameter (0-127)\n     * @property {number} parameterLsb: The LSB portion of the registered parameter (0-127)\n     * @property {number} value The received value as a normalized number between 0 and 1.\n     * @property {number} rawValue The value as received (0-127)\n     */\n\n    // Emit general event\n    event.type = type;\n    event.subtype = subtype;\n    this.emit(event.type, event);\n\n  }\n\n  /**\n   * @deprecated since version 3.\n   * @private\n   */\n  getChannelModeByNumber(number) {\n\n    if (WebMidi.validation) {\n      console.warn(\n        \"The 'getChannelModeByNumber()' method has been moved to the 'Utilities' class.\"\n      );\n      number = Math.floor(number);\n    }\n\n    return Utilities.getChannelModeByNumber(number);\n\n  }\n\n  /**\n   * @deprecated since version 3.\n   * @private\n   */\n  getCcNameByNumber(number) {\n\n    if (WebMidi.validation) {\n      console.warn(\n        \"The 'getCcNameByNumber()' method has been moved to the 'Utilities' class.\"\n      );\n      number = parseInt(number);\n      if ( !(number >= 0 && number <= 127) ) throw new RangeError(\"Invalid control change number.\");\n    }\n\n    return Utilities.getCcNameByNumber(number);\n\n  }\n\n  /**\n   * Returns the playing status of the specified note (`true` if the note is currently playing,\n   * `false` if it is not). The `note` parameter can be an unsigned integer (0-127), a note\n   * identifier (`\"C4\"`, `\"G#5\"`, etc.) or a [`Note`]{@link Note} object.\n   *\n   * IF the note is specified using an integer (0-127), no octave offset will be applied.\n   *\n   * @param {number|string|Note} note The note to get the state for. The\n   * [`octaveOffset`](#octaveOffset) (channel, input and global) will be factored in for note\n   * identifiers and [`Note`]{@link Note} objects.\n   * @returns {boolean}\n   * @since version 3.0.0\n   */\n  getNoteState(note) {\n\n    // If it's a note object, we simply use the identifier\n    if (note instanceof Note) note = note.identifier;\n\n    const number = Utilities.guessNoteNumber(\n      note,\n      WebMidi.octaveOffset + this.input.octaveOffset + this.octaveOffset\n    );\n\n    return this.notesState[number];\n\n  }\n\n  /**\n   * An integer to offset the reported octave of incoming note-specific messages (`noteon`,\n   * `noteoff` and `keyaftertouch`). By default, middle C (MIDI note number 60) is placed on the 4th\n   * octave (C4).\n   *\n   * If, for example, `octaveOffset` is set to 2, MIDI note number 60 will be reported as C6. If\n   * `octaveOffset` is set to -1, MIDI note number 60 will be reported as C3.\n   *\n   * Note that this value is combined with the global offset value defined by\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) object and with the value defined on the parent\n   * input object with [`Input.octaveOffset`](Input#octaveOffset).\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  get octaveOffset() {\n    return this._octaveOffset;\n  }\n  set octaveOffset(value) {\n\n    if (this.validation) {\n      value = parseInt(value);\n      if (isNaN(value)) throw new TypeError(\"The 'octaveOffset' property must be an integer.\");\n    }\n\n    this._octaveOffset = value;\n\n  }\n\n  /**\n   * The [`Input`](Input) this channel belongs to.\n   * @type {Input}\n   * @since 3.0\n   */\n  get input() {\n    return this._input;\n  }\n\n  /**\n   * This channel's MIDI number (1-16).\n   * @type {number}\n   * @since 3.0\n   */\n  get number() {\n    return this._number;\n  }\n\n  /**\n   * Whether RPN/NRPN events are parsed and dispatched.\n   * @type {boolean}\n   * @since 3.0\n   * @deprecated Use parameterNumberEventsEnabled instead.\n   * @private\n   */\n  get nrpnEventsEnabled() {\n    return this.parameterNumberEventsEnabled;\n  }\n  set nrpnEventsEnabled(value) {\n\n    if (this.validation) {\n      value = !!value;\n    }\n\n    this.parameterNumberEventsEnabled = value;\n\n  }\n\n}\n"
  },
  {
    "path": "src/Message.js",
    "content": "import {Utilities} from \"./Utilities.js\";\nimport {Enumerations} from \"./Enumerations.js\";\n\n/**\n * The `Message` class represents a single MIDI message. It has several properties that make it\n * easy to make sense of the binary data it contains.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Message {\n\n  /**\n   * Creates a new `Message` object from raw MIDI data.\n   *\n   * @param {Uint8Array} data The raw data of the MIDI message as a\n   * [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\n   * of integers between `0` and `255`.\n   */\n  constructor(data) {\n\n    /**\n     * A\n     * [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\n     * containing the bytes of the MIDI message. Each byte is an integer between `0` and `255`.\n     *\n     * @type {Uint8Array}\n     * @readonly\n     */\n    this.rawData = data;\n\n    /**\n     * An array containing all the bytes of the MIDI message. Each byte is an integer between `0`\n     * and `255`.\n     *\n     * @type {number[]}\n     * @readonly\n     */\n    this.data = Array.from(this.rawData);\n\n    /**\n     * The MIDI status byte of the message as an integer between `0` and `255`.\n     *\n     * @type {number}\n     * @readonly\n     */\n    this.statusByte = this.rawData[0];\n\n    /**\n     * A\n     * [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\n     * of the data byte(s) of the MIDI message. When the message is a system exclusive message\n     * (sysex), `rawDataBytes` explicitly excludes the manufacturer ID and the sysex end byte so\n     * only the actual data is included.\n     *\n     * @type {Uint8Array}\n     * @readonly\n     */\n    this.rawDataBytes = this.rawData.slice(1);\n\n    /**\n     * An array of the the data byte(s) of the MIDI message (as opposed to the status byte). When\n     * the message is a system exclusive message (sysex), `dataBytes` explicitly excludes the\n     * manufacturer ID and the sysex end byte so only the actual data is included.\n     *\n     * @type {number[]}\n     * @readonly\n     */\n    this.dataBytes = this.data.slice(1);\n\n    /**\n     * A boolean indicating whether the MIDI message is a channel-specific message.\n     *\n     * @type {boolean}\n     * @readonly\n     */\n    this.isChannelMessage = false;\n\n    /**\n     * A boolean indicating whether the MIDI message is a system message (not specific to a\n     * channel).\n     *\n     * @type {boolean}\n     * @readonly\n     */\n    this.isSystemMessage = false;\n\n    /**\n     * An integer identifying the MIDI command. For channel-specific messages, the value is 4-bit\n     * and will be between `8` and `14`. For system messages, the value will be between `240` and\n     * `255`.\n     *\n     * @type {number}\n     * @readonly\n     */\n    this.command = undefined;\n\n    /**\n     * The MIDI channel number (`1` - `16`) that the message is targeting. This is only for\n     * channel-specific messages. For system messages, this will be left `undefined`.\n     *\n     * @type {number}\n     * @readonly\n     */\n    this.channel = undefined;\n\n    /**\n     * When the message is a system exclusive message (sysex), this property contains an array with\n     * either 1 or 3 entries that identify the manufacturer targeted by the message.\n     *\n     * To know how to translate these entries into manufacturer names, check out the official list:\n     * https://www.midi.org/specifications-old/item/manufacturer-id-numbers\n     *\n     * @type {number[]}\n     * @readonly\n     */\n    this.manufacturerId = undefined;\n\n    /**\n     * The type of message as a string (`\"noteon\"`, `\"controlchange\"`, `\"sysex\"`, etc.)\n     *\n     * @type {string}\n     * @readonly\n     */\n    this.type = undefined;\n\n    // Assign values to property that vary according to whether they are channel-specific or system\n    if (this.statusByte < 240) {\n      this.isChannelMessage = true;\n      this.command = this.statusByte >> 4;\n      this.channel = (this.statusByte & 0b00001111) + 1;\n    } else {\n      this.isSystemMessage = true;\n      this.command = this.statusByte;\n    }\n\n    // Assign type (depending on whether the message is channel-specific or system)\n    if (this.isChannelMessage) {\n      this.type = Utilities.getPropertyByValue(Enumerations.CHANNEL_MESSAGES, this.command);\n    } else if (this.isSystemMessage) {\n      this.type = Utilities.getPropertyByValue(Enumerations.SYSTEM_MESSAGES, this.command);\n    }\n\n    // When the message is a sysex message, we add a manufacturer property and strip out the id from\n    // dataBytes and rawDataBytes.\n    if (this.statusByte === Enumerations.SYSTEM_MESSAGES.sysex) {\n\n      if (this.dataBytes[0] === 0) {\n        this.manufacturerId = this.dataBytes.slice(0, 3);\n        this.dataBytes = this.dataBytes.slice(3, this.rawDataBytes.length - 1);\n        this.rawDataBytes = this.rawDataBytes.slice(3, this.rawDataBytes.length - 1);\n      } else {\n        this.manufacturerId = [this.dataBytes[0]];\n        this.dataBytes = this.dataBytes.slice(1, this.dataBytes.length - 1);\n        this.rawDataBytes = this.rawDataBytes.slice(1, this.rawDataBytes.length - 1);\n      }\n\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "src/Note.js",
    "content": "import {WebMidi} from \"./WebMidi.js\";\nimport {Utilities} from \"./Utilities.js\";\n\n/**\n * The `Note` class represents a single musical note such as `\"D3\"`, `\"G#4\"`, `\"F-1\"`, `\"Gb7\"`, etc.\n *\n * `Note` objects can be played back on a single channel by calling\n * [`OutputChannel.playNote()`]{@link OutputChannel#playNote} or, on multiple channels of the same\n * output, by calling [`Output.playNote()`]{@link Output#playNote}.\n *\n * The note has [`attack`](#attack) and [`release`](#release) velocities set at `0.5` by default.\n * These can be changed by passing in the appropriate option. It is also possible to set a\n * system-wide default for attack and release velocities by using the\n * [`WebMidi.defaults`](WebMidi#defaults) property.\n *\n * If you prefer to work with raw MIDI values (`0` to `127`), you can use [`rawAttack`](#rawAttack) and\n * [`rawRelease`](#rawRelease) to both get and set the values.\n *\n * The note may have a [`duration`](#duration). If it does, playback will be automatically stopped\n * when the duration has elapsed by sending a `\"noteoff\"` event. By default, the duration is set to\n * `Infinity`. In this case, it will never stop playing unless explicitly stopped by calling a\n * method such as [`OutputChannel.stopNote()`]{@link OutputChannel#stopNote},\n * [`Output.stopNote()`]{@link Output#stopNote} or similar.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Note {\n\n  /**\n   * Creates a `Note` object.\n   *\n   * @param value {string|number} The value used to create the note. If an identifier string is used,\n   * it must start with the note letter, optionally followed by an accidental and followed by the\n   * octave number (`\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`, etc.). If a number is used, it must be an\n   * integer between 0 and 127. In this case, middle C is considered to be C4 (note number 60).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the note should be\n   * explicitly stopped.\n   *\n   * @param {number} [options.attack=0.5] The note's attack velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.release=0.5] The note's release velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawAttack=64] The note's attack velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawRelease=64] The note's release velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @throws {Error} Invalid note identifier\n   * @throws {RangeError} Invalid name value\n   * @throws {RangeError} Invalid accidental value\n   * @throws {RangeError} Invalid octave value\n   * @throws {RangeError} Invalid duration value\n   * @throws {RangeError} Invalid attack value\n   * @throws {RangeError} Invalid release value\n   */\n  constructor(value, options = {}) {\n\n    // Assign property defaults\n    this.duration = WebMidi.defaults.note.duration;\n    this.attack = WebMidi.defaults.note.attack;\n    this.release = WebMidi.defaults.note.release;\n\n    // Assign property values from options (validation occurs in setter)\n    if (options.duration != undefined) this.duration = options.duration;\n    if (options.attack != undefined) this.attack = options.attack;\n    if (options.rawAttack != undefined) this.attack = Utilities.from7bitToFloat(options.rawAttack);\n    if (options.release != undefined) this.release = options.release;\n    if (options.rawRelease != undefined) {\n      this.release = Utilities.from7bitToFloat(options.rawRelease);\n    }\n\n    // Assign note depending on the way it was specified (name or number)\n    if (Number.isInteger(value)) {\n      this.identifier = Utilities.toNoteIdentifier(value);\n    } else {\n      this.identifier = value;\n    }\n\n  }\n\n  /**\n   * The name, optional accidental and octave of the note, as a string.\n   * @type {string}\n   * @since 3.0.0\n   */\n  get identifier() {\n    return this._name + (this._accidental || \"\") + this._octave;\n  }\n  set identifier(value) {\n\n    const fragments = Utilities.getNoteDetails(value);\n\n    if (WebMidi.validation) {\n      if (!value) throw new Error(\"Invalid note identifier\");\n    }\n\n    this._name = fragments.name;\n    this._accidental = fragments.accidental;\n    this._octave = fragments.octave;\n\n  }\n\n  /**\n   * The name (letter) of the note. If you need the full name with octave and accidental, you can\n   * use the [`identifier`]{@link Note#identifier} property instead.\n   * @type {string}\n   * @since 3.0.0\n   */\n  get name() {\n    return this._name;\n  }\n  set name(value) {\n\n    if (WebMidi.validation) {\n      value = value.toUpperCase();\n      if (![\"C\", \"D\", \"E\", \"F\", \"G\", \"A\", \"B\"].includes(value)) {\n        throw new Error(\"Invalid name value\");\n      }\n    }\n\n    this._name = value;\n\n  }\n\n  /**\n   * The accidental (#, ##, b or bb) of the note.\n   * @type {string}\n   * @since 3.0.0\n   */\n  get accidental() {\n    return this._accidental;\n  }\n  set accidental(value) {\n\n    if (WebMidi.validation) {\n      value = value.toLowerCase();\n      if (![\"#\", \"##\", \"b\", \"bb\"].includes(value)) throw new Error(\"Invalid accidental value\");\n    }\n\n    this._accidental = value;\n\n  }\n\n  /**\n   * The octave of the note.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get octave() {\n    return this._octave;\n  }\n  set octave(value) {\n\n    if (WebMidi.validation) {\n      value = parseInt(value);\n      if (isNaN(value)) throw new Error(\"Invalid octave value\");\n    }\n\n    this._octave = value;\n\n  }\n\n  /**\n   * The duration of the note as a positive decimal number representing the number of milliseconds\n   * that the note should play for.\n   *\n   * @type {number}\n   * @since 3.0.0\n   */\n  get duration() {\n    return this._duration;\n  }\n  set duration(value) {\n\n    if (WebMidi.validation) {\n      value = parseFloat(value);\n      if (isNaN(value) || value === null || value < 0) {\n        throw new RangeError(\"Invalid duration value.\");\n      }\n    }\n\n    this._duration = value;\n\n  }\n\n  /**\n   * The attack velocity of the note as a float between 0 and 1.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get attack() {\n    return this._attack;\n  }\n  set attack(value) {\n\n    if (WebMidi.validation) {\n      value = parseFloat(value);\n      if (isNaN(value) || !(value >= 0 && value <= 1)) {\n        throw new RangeError(\"Invalid attack value.\");\n      }\n    }\n\n    this._attack = value;\n\n  }\n\n  /**\n   * The release velocity of the note as an integer between 0 and 1.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get release() {\n    return this._release;\n  }\n  set release(value) {\n\n    if (WebMidi.validation) {\n      value = parseFloat(value);\n      if (isNaN(value) || !(value >= 0 && value <= 1)) {\n        throw new RangeError(\"Invalid release value.\");\n      }\n    }\n\n    this._release = value;\n\n  }\n\n  /**\n   * The attack velocity of the note as a positive integer between 0 and 127.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get rawAttack() {\n    return Utilities.fromFloatTo7Bit(this._attack);\n  }\n  set rawAttack(value) {\n    this._attack = Utilities.from7bitToFloat(value);\n  }\n\n  /**\n   * The release velocity of the note as a positive integer between 0 and 127.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get rawRelease() {\n    return Utilities.fromFloatTo7Bit(this._release);\n  }\n  set rawRelease(value) {\n    this._release = Utilities.from7bitToFloat(value);\n  }\n\n  /**\n   * The MIDI number of the note (`0` - `127`). This number is derived from the note identifier\n   * using C4 as a reference for middle C.\n   *\n   * @type {number}\n   * @readonly\n   * @since 3.0.0\n   */\n  get number() {\n    return Utilities.toNoteNumber(this.identifier);\n  }\n\n  /**\n   * Returns a MIDI note number offset by octave and/or semitone. If the calculated value is less\n   * than 0, 0 will be returned. If the calculated value is more than 127, 127 will be returned. If\n   * an invalid value is supplied, 0 will be used.\n   *\n   * @param [octaveOffset] {number} An integer to offset the note number by octave.\n   * @param [semitoneOffset] {number} An integer to offset the note number by semitone.\n   * @returns {number} An integer between 0 and 127\n   */\n  getOffsetNumber(octaveOffset = 0, semitoneOffset = 0) {\n\n    if (WebMidi.validation) {\n      octaveOffset = parseInt(octaveOffset) || 0;\n      semitoneOffset = parseInt(semitoneOffset) || 0;\n    }\n\n    return Math.min(Math.max(this.number + (octaveOffset * 12) + semitoneOffset, 0), 127);\n\n  }\n\n}\n"
  },
  {
    "path": "src/Output.js",
    "content": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {OutputChannel} from \"./OutputChannel.js\";\nimport {Enumerations, Message, WebMidi} from \"./WebMidi.js\";\nimport {Utilities} from \"./Utilities.js\";\n\n/**\n * The `Output` class represents a single MIDI output port (not to be confused with a MIDI channel).\n * A port is made available by a MIDI device. A MIDI device can advertise several input and output\n * ports. Each port has 16 MIDI channels which can be accessed via the [`channels`](#channels)\n * property.\n *\n * The `Output` object is automatically instantiated by the library according to the host's MIDI\n * subsystem and should not be directly instantiated.\n *\n * You can access all available `Output` objects by referring to the\n * [`WebMidi.outputs`](WebMidi#outputs) array or by using methods such as\n * [`WebMidi.getOutputByName()`](WebMidi#getOutputByName) or\n * [`WebMidi.getOutputById()`](WebMidi#getOutputById).\n *\n * @fires Output#opened\n * @fires Output#disconnected\n * @fires Output#closed\n *\n * @extends EventEmitter\n * @license Apache-2.0\n */\nexport class Output extends EventEmitter {\n\n  /**\n   * Creates an `Output` object.\n   *\n   * @param {MIDIOutput} midiOutput [`MIDIOutput`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIOutput)\n   * object as provided by the MIDI subsystem.\n   */\n  constructor(midiOutput) {\n\n    super();\n\n    /**\n     * A reference to the `MIDIOutput` object\n     * @type {MIDIOutput}\n     * @private\n     */\n    this._midiOutput = midiOutput;\n\n    /**\n     * @type {number}\n     * @private\n     */\n    this._octaveOffset = 0;\n\n    /**\n     * Array containing the 16 [`OutputChannel`]{@link OutputChannel} objects available provided by\n     * this `Output`. The channels are numbered 1 through 16.\n     *\n     * @type {OutputChannel[]}\n     */\n    this.channels = [];\n    for (let i = 1; i <= 16; i++) this.channels[i] = new OutputChannel(this, i);\n\n    this._midiOutput.onstatechange = this._onStateChange.bind(this);\n\n  }\n\n  /**\n   * Destroys the `Output`. All listeners are removed, all channels are destroyed and the MIDI\n   * subsystem is unlinked.\n   * @returns {Promise<void>}\n   */\n  async destroy() {\n    this.removeListener();\n    this.channels.forEach(ch => ch.destroy());\n    this.channels = [];\n    if (this._midiOutput) this._midiOutput.onstatechange = null;\n    await this.close();\n    this._midiOutput = null;\n  }\n\n  /**\n   * @private\n   */\n  _onStateChange(e) {\n\n    let event = {\n      timestamp: WebMidi.time\n    };\n\n    if (e.port.connection === \"open\") {\n\n      /**\n       * Event emitted when the {@link Output} has been opened by calling the\n       * [open()]{@link Output#open} method.\n       *\n       * @event Output#opened\n       * @type {object}\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       * @property {string} type `\"opened\"`\n       * @property {Output} target The object to which the listener was originally added (`Output`).\n       * @property {Output} port The port that was opened\n       */\n      event.type = \"opened\";\n      event.target = this;\n      event.port = event.target; // for consistency\n      this.emit(\"opened\", event);\n\n    } else if (e.port.connection === \"closed\" && e.port.state === \"connected\") {\n\n      /**\n       * Event emitted when the {@link Output} has been closed by calling the\n       * [close()]{@link Output#close} method.\n       *\n       * @event Output#closed\n       * @type {object}\n       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       * @property {string} type `\"closed\"`\n       * @property {Output} target The object to which the listener was originally added (`Output`).\n       * @property {Output} port The port that was closed\n       */\n      event.type = \"closed\";\n      event.target = this;\n      event.port = event.target; // for consistency\n      this.emit(\"closed\", event);\n\n    } else if (e.port.connection === \"closed\" && e.port.state === \"disconnected\") {\n\n      /**\n       * Event emitted when the {@link Output} becomes unavailable. This event is typically fired\n       * when the MIDI device is unplugged.\n       *\n       * @event Output#disconnected\n       * @type {object}\n       * @property {number} timestamp The moment (DOMHighResTimeStamp0 when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       * @property {string} type `\"disconnected\"`\n       * @property {Output} target The object to which the listener was originally added (`Output`).\n       * @property {object} port Object with properties describing the {@link Output} that was\n       * disconnected. This is not the actual `Output` as it is no longer available.\n       */\n      event.type = \"disconnected\";\n      event.port = {\n        connection: e.port.connection,\n        id: e.port.id,\n        manufacturer: e.port.manufacturer,\n        name: e.port.name,\n        state: e.port.state,\n        type: e.port.type\n      };\n      this.emit(\"disconnected\", event);\n\n    } else if (e.port.connection === \"pending\" && e.port.state === \"disconnected\") {\n      // I don't see the need to forward that...\n    } else {\n      console.warn(\"This statechange event was not caught:\", e.port.connection, e.port.state);\n    }\n\n  }\n\n  /**\n   * Opens the output for usage. When the library is enabled, all ports are automatically opened.\n   * This method is only useful for ports that have been manually closed.\n   *\n   * @returns {Promise<Output>} The promise is fulfilled with the `Output` object.\n   */\n  async open() {\n\n    // Explicitly opens the port for usage. This is not mandatory. When the port is not explicitly\n    // opened, it is implicitly opened (asynchronously) when calling `send()` on the `MIDIOutput`.\n    // We do it explicitly so that 'connected' events are dispatched immediately and we are ready to\n    // send.\n    try {\n      await this._midiOutput.open();\n      return Promise.resolve(this);\n    } catch (err) {\n      return Promise.reject(err);\n    }\n\n  }\n\n  /**\n   * Closes the output connection. When an output is closed, it cannot be used to send MIDI messages\n   * until the output is opened again by calling [`open()`]{@link #open}. You can check\n   * the connection status by looking at the [`connection`]{@link #connection} property.\n   *\n   * @returns {Promise<void>}\n   */\n  async close() {\n\n    // We close the port. This triggers a 'statechange' event which we listen to to re-trigger the\n    // 'closed' event.\n    if (this._midiOutput) {\n      await this._midiOutput.close();\n    } else {\n      await Promise.resolve();\n    }\n\n  }\n\n  /**\n   * Sends a MIDI message on the MIDI output port. If no time is specified, the message will be\n   * sent immediately. The message should be an array of 8 bit unsigned integers (0-225), a\n   * [`Uint8Array`]{@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array}\n   * object or a [`Message`](Message) object.\n   *\n   * It is usually not necessary to use this method directly as you can use one of the simpler\n   * helper methods such as [`playNote()`](#playNote), [`stopNote()`](#stopNote),\n   * [`sendControlChange()`](#sendControlChange), etc.\n   *\n   * Details on the format of MIDI messages are available in the summary of\n   * [MIDI messages]{@link https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message}\n   * from the MIDI Manufacturers Association.\n   *\n   * @param message {number[]|Uint8Array|Message} An array of 8bit unsigned integers, a `Uint8Array`\n   * object (not available in Node.js) containing the message bytes or a `Message` object.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The first byte (status) must be an integer between 128 and 255.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @license Apache-2.0\n   */\n  send(message, options = {time: 0}, legacy = 0) {\n\n    // If a Message object is passed in we extract the message data (the jzz plugin used on Node.js\n    // does not support using Uint8Array).\n    if (message instanceof Message) {\n      message = Utilities.isNode ? message.data : message.rawData;\n    }\n\n    // If the data is a Uint8Array and we are on Node, we must convert it to array so it works with\n    // the jzz module.\n    if (message instanceof Uint8Array && Utilities.isNode) {\n      message = Array.from(message);\n    }\n\n    // Validation\n    if (WebMidi.validation) {\n\n      // If message is neither an array nor a Uint8Array, then we are in legacy mode\n      if (!Array.isArray(message) && !(message instanceof Uint8Array)) {\n        message = [message];\n        if (Array.isArray(options)) message = message.concat(options);\n        options = isNaN(legacy) ? {time: 0} : {time: legacy};\n      }\n\n      if (!(parseInt(message[0]) >= 128 && parseInt(message[0]) <= 255)) {\n        throw new RangeError(\"The first byte (status) must be an integer between 128 and 255.\");\n      }\n\n      message.slice(1).forEach(value => {\n        value = parseInt(value);\n        if (!(value >= 0 && value <= 255)) {\n          throw new RangeError(\"Data bytes must be integers between 0 and 255.\");\n        }\n      });\n\n      if (!options) options = {time: 0};\n\n    }\n\n    // Send message and return `Output` for chaining\n    this._midiOutput.send(message, Utilities.toTimestamp(options.time));\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI [**system exclusive**]{@link\n    * https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages}\n   * (*sysex*) message. There are two categories of system exclusive messages: manufacturer-specific\n   * messages and universal messages. Universal messages are further divided into three subtypes:\n   *\n   *   * Universal non-commercial (for research and testing): `0x7D`\n   *   * Universal non-realtime: `0x7E`\n   *   * Universal realtime: `0x7F`\n   *\n   * The method's first parameter (`identification`) identifies the type of message. If the value of\n   * `identification` is `0x7D` (125), `0x7E` (126) or `0x7F` (127), the message will be identified\n   * as a **universal non-commercial**, **universal non-realtime** or **universal realtime** message\n   * (respectively).\n   *\n   * If the `identification` value is an array or an integer between 0 and 124, it will be used to\n   * identify the manufacturer targeted by the message. The *MIDI Manufacturers Association*\n   * maintains a full list of\n   * [Manufacturer ID Numbers](https://www.midi.org/specifications-old/item/manufacturer-id-numbers).\n   *\n   * The `data` parameter should only contain the data of the message. When sending out the actual\n   * MIDI message, WEBMIDI.js will automatically prepend the data with the **sysex byte** (`0xF0`)\n   * and the identification byte(s). It will also automatically terminate the message with the\n   * **sysex end byte** (`0xF7`).\n   *\n   * To use the `sendSysex()` method, system exclusive message support must have been enabled. To\n   * do so, you must set the `sysex` option to `true` when calling\n   * [`WebMidi.enable()`]{@link WebMidi#enable}:\n   *\n   * ```js\n   * WebMidi.enable({sysex: true})\n   *   .then(() => console.log(\"System exclusive messages are enabled\");\n   * ```\n   *\n   * ##### Examples of manufacturer-specific system exclusive messages\n   *\n   * If you want to send a sysex message to a Korg device connected to the first output, you would\n   * use the following code:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex(0x42, [0x1, 0x2, 0x3, 0x4, 0x5]);\n   * ```\n   * In this case `0x42` is the ID of the manufacturer (Korg) and `[0x1, 0x2, 0x3, 0x4, 0x5]` is the\n   * data being sent.\n   *\n   * The parameters can be specified using any number notation (decimal, hex, binary, etc.).\n   * Therefore, the code above is equivalent to this code:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex(66, [1, 2, 3, 4, 5]);\n   * ```\n   *\n   * Some manufacturers are identified using 3 bytes. In this case, you would use a 3-position array\n   * as the first parameter. For example, to send the same sysex message to a\n   * *Native Instruments* device:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex([0x00, 0x21, 0x09], [0x1, 0x2, 0x3, 0x4, 0x5]);\n   * ```\n   *\n   * There is no limit for the length of the data array. However, it is generally suggested to keep\n   * system exclusive messages to 64Kb or less.\n   *\n   * ##### Example of universal system exclusive message\n   *\n   * If you want to send a universal sysex message, simply assign the correct identification number\n   * in the first parameter. Number `0x7D` (125) is for non-commercial, `0x7E` (126) is for\n   * non-realtime and `0x7F` (127) is for realtime.\n   *\n   * So, for example, if you wanted to send an identity request non-realtime message (`0x7E`), you\n   * could use the following:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex(0x7E, [0x7F, 0x06, 0x01]);\n   * ```\n   *\n   * For more details on the format of universal messages, consult the list of\n   * [universal sysex messages](https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages).\n   *\n   * @param {number|number[]} identification An unsigned integer or an array of three unsigned\n   * integers between `0` and `127` that either identify the manufacturer or sets the message to be\n   * a **universal non-commercial message** (`0x7D`), a **universal non-realtime message** (`0x7E`)\n   * or a **universal realtime message** (`0x7F`). The *MIDI Manufacturers Association* maintains a\n   * full list of\n   * [Manufacturer ID Numbers](https://www.midi.org/specifications-old/item/manufacturer-id-numbers).\n   *\n   * @param {number[]|Uint8Array} [data] A `Uint8Array` or an array of unsigned integers between `0`\n   * and `127`. This is the data you wish to transfer.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {DOMException} Failed to execute 'send' on 'MIDIOutput': System exclusive message is\n   * not allowed.\n   *\n   * @throws {TypeError} Failed to execute 'send' on 'MIDIOutput': The value at index x is greater\n   * than 0xFF.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendSysex(identification, data= [], options = {}) {\n\n    identification = [].concat(identification);\n\n    // Check if data is Uint8Array\n    if (data instanceof Uint8Array) {\n      const merged = new Uint8Array(1 + identification.length + data.length + 1);\n      merged[0] = Enumerations.SYSTEM_MESSAGES.sysex;\n      merged.set(Uint8Array.from(identification), 1);\n      merged.set(data, 1 + identification.length);\n      merged[merged.length - 1] = Enumerations.SYSTEM_MESSAGES.sysexend;\n      this.send(merged, {time: options.time});\n    } else {\n      const merged = identification.concat(data, Enumerations.SYSTEM_MESSAGES.sysexend);\n      this.send([Enumerations.SYSTEM_MESSAGES.sysex].concat(merged), {time: options.time});\n    }\n\n    return this;\n\n  };\n\n  /**\n   * Clears all MIDI messages that have been queued and scheduled but not yet sent.\n   *\n   * **Warning**: this method is defined in the\n   * [Web MIDI API specification](https://www.w3.org/TR/webmidi/#MIDIOutput) but has not been\n   * implemented by all browsers yet. You can follow\n   * [this issue](https://github.com/djipco/webmidi/issues/52) for more info.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  clear() {\n\n    if (this._midiOutput.clear) {\n\n      this._midiOutput.clear();\n\n    } else {\n\n      if (WebMidi.validation) {\n        console.warn(\n          \"The 'clear()' method has not yet been implemented in your environment.\"\n        );\n      }\n\n    }\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **timecode quarter frame** message. Please note that no processing is being done\n   * on the data. It is up to the developer to format the data according to the\n   * [MIDI Timecode](https://en.wikipedia.org/wiki/MIDI_timecode) format.\n   *\n   * @param value {number} The quarter frame message content (integer between 0 and 127).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendTimecodeQuarterFrame(value, options = {}) {\n\n    if (WebMidi.validation) {\n      value = parseInt(value);\n      if (isNaN(value) || !(value >= 0 && value <= 127)) {\n        throw new RangeError(\"The value must be an integer between 0 and 127.\");\n      }\n    }\n\n    this.send(\n      [\n        Enumerations.SYSTEM_MESSAGES.timecode,\n        value\n      ],\n      {time: options.time}\n    );\n\n    return this;\n\n  };\n\n  /**\n   * Sends a **song position** MIDI message. The value is expressed in MIDI beats (between `0` and\n   * `16383`) which are 16th note. Position `0` is always the start of the song.\n   *\n   * @param {number} [value=0] The MIDI beat to cue to (integer between `0` and `16383`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendSongPosition(value = 0, options = {}) {\n\n    // @todo allow passing in 2-entries array for msb/lsb\n\n    value = Math.floor(value) || 0;\n\n    var msb = (value >> 7) & 0x7F;\n    var lsb = value & 0x7F;\n\n    this.send(\n      [\n        Enumerations.SYSTEM_MESSAGES.songposition,\n        msb,\n        lsb\n      ],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **song select** MIDI message.\n   *\n   * @param {number} [value=0] The number of the song to select (integer between `0` and `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws The song number must be between 0 and 127.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendSongSelect(value = 0, options = {}) {\n\n    if (WebMidi.validation) {\n\n      value = parseInt(value);\n\n      if (isNaN(value) || !(value >= 0 && value <= 127)) {\n        throw new RangeError(\"The program value must be between 0 and 127\");\n      }\n\n    }\n\n    this.send(\n      [\n        Enumerations.SYSTEM_MESSAGES.songselect,\n        value\n      ],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **tune request** real-time message.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendTuneRequest(options = {}) {\n\n    this.send(\n      [Enumerations.SYSTEM_MESSAGES.tunerequest],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **clock** real-time message. According to the standard, there are 24 MIDI clocks\n   * for every quarter note.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendClock(options = {}) {\n\n    this.send(\n      [Enumerations.SYSTEM_MESSAGES.clock],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **start** real-time message. A MIDI Start message starts the playback of the current\n   * song at beat 0. To start playback elsewhere in the song, use the\n   * [`sendContinue()`]{@link #sendContinue} method.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendStart(options = {}) {\n\n    this.send(\n      [Enumerations.SYSTEM_MESSAGES.start],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **continue** real-time message. This resumes song playback where it was previously\n   * stopped or where it was last cued with a song position message. To start playback from the\n   * start, use the [`sendStart()`]{@link Output#sendStart}` method.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendContinue(options = {}) {\n\n    this.send(\n      [Enumerations.SYSTEM_MESSAGES.continue],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **stop** real-time message. This tells the device connected to this output to stop\n   * playback immediately (or at the scheduled time, if specified).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendStop(options = {}) {\n\n    this.send(\n      [Enumerations.SYSTEM_MESSAGES.stop],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends an **active sensing** real-time message. This tells the device connected to this port\n   * that the connection is still good. Active sensing messages are often sent every 300 ms if there\n   * was no other activity on the MIDI port.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendActiveSensing(options = {}) {\n\n    this.send(\n      [Enumerations.SYSTEM_MESSAGES.activesensing],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **reset** real-time message. This tells the device connected to this output that it\n   * should reset itself to a default state.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendReset(options = {}) {\n\n    this.send(\n      [Enumerations.SYSTEM_MESSAGES.reset],\n      {time: options.time}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  sendTuningRequest(options = {}) {\n\n    if (WebMidi.validation) {\n      console.warn(\n        \"The sendTuningRequest() method has been deprecated. Use sendTuningRequest() instead.\"\n      );\n    }\n\n    return this.sendTuneRequest(options);\n\n  }\n\n  /**\n   * Sends a MIDI **key aftertouch** message to the specified channel(s) at the scheduled time. This\n   * is a key-specific aftertouch. For a channel-wide aftertouch message, use\n   * [`setChannelAftertouch()`]{@link #setChannelAftertouch}.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) for which you are sending\n   * an aftertouch value. The notes can be specified by using a MIDI note number (`0` - `127`), a\n   * [`Note`](Note) object, a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the\n   * previous types. When using a note identifier, octave range must be between `-1` and `9`. The\n   * lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number\n   * `127`).\n   *\n   * @param [pressure=0.5] {number} The pressure level (between 0 and 1). An invalid pressure value\n   * will silently trigger the default behaviour. If the `rawValue` option is set to `true`, the\n   * pressure can be defined by using an integer between 0 and 127.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendKeyAftertouch(note, pressure, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendKeyAftertouch(note, pressure, options);\n    });\n\n    return this;\n\n  };\n\n  /**\n   * Sends a MIDI **control change** message to the specified channel(s) at the scheduled time. The\n   * control change message to send can be specified numerically (0-127) or by using one of the\n   * following common names:\n   *\n   * | Number | Name                          |\n   * |--------|-------------------------------|\n   * | 0      |`bankselectcoarse`             |\n   * | 1      |`modulationwheelcoarse`        |\n   * | 2      |`breathcontrollercoarse`       |\n   * | 4      |`footcontrollercoarse`         |\n   * | 5      |`portamentotimecoarse`         |\n   * | 6      |`dataentrycoarse`              |\n   * | 7      |`volumecoarse`                 |\n   * | 8      |`balancecoarse`                |\n   * | 10     |`pancoarse`                    |\n   * | 11     |`expressioncoarse`             |\n   * | 12     |`effectcontrol1coarse`         |\n   * | 13     |`effectcontrol2coarse`         |\n   * | 18     |`generalpurposeslider3`        |\n   * | 19     |`generalpurposeslider4`        |\n   * | 32     |`bankselectfine`               |\n   * | 33     |`modulationwheelfine`          |\n   * | 34     |`breathcontrollerfine`         |\n   * | 36     |`footcontrollerfine`           |\n   * | 37     |`portamentotimefine`           |\n   * | 38     |`dataentryfine`                |\n   * | 39     |`volumefine`                   |\n   * | 40     |`balancefine`                  |\n   * | 42     |`panfine`                      |\n   * | 43     |`expressionfine`               |\n   * | 44     |`effectcontrol1fine`           |\n   * | 45     |`effectcontrol2fine`           |\n   * | 64     |`holdpedal`                    |\n   * | 65     |`portamento`                   |\n   * | 66     |`sustenutopedal`               |\n   * | 67     |`softpedal`                    |\n   * | 68     |`legatopedal`                  |\n   * | 69     |`hold2pedal`                   |\n   * | 70     |`soundvariation`               |\n   * | 71     |`resonance`                    |\n   * | 72     |`soundreleasetime`             |\n   * | 73     |`soundattacktime`              |\n   * | 74     |`brightness`                   |\n   * | 75     |`soundcontrol6`                |\n   * | 76     |`soundcontrol7`                |\n   * | 77     |`soundcontrol8`                |\n   * | 78     |`soundcontrol9`                |\n   * | 79     |`soundcontrol10`               |\n   * | 80     |`generalpurposebutton1`        |\n   * | 81     |`generalpurposebutton2`        |\n   * | 82     |`generalpurposebutton3`        |\n   * | 83     |`generalpurposebutton4`        |\n   * | 91     |`reverblevel`                  |\n   * | 92     |`tremololevel`                 |\n   * | 93     |`choruslevel`                  |\n   * | 94     |`celestelevel`                 |\n   * | 95     |`phaserlevel`                  |\n   * | 96     |`dataincrement`                |\n   * | 97     |`datadecrement`                |\n   * | 98     |`nonregisteredparametercoarse` |\n   * | 99     |`nonregisteredparameterfine`   |\n   * | 100    |`registeredparametercoarse`    |\n   * | 101    |`registeredparameterfine`      |\n   * | 120    |`allsoundoff`                  |\n   * | 121    |`resetallcontrollers`          |\n   * | 122    |`localcontrol`                 |\n   * | 123    |`allnotesoff`                  |\n   * | 124    |`omnimodeoff`                  |\n   * | 125    |`omnimodeon`                   |\n   * | 126    |`monomodeon`                   |\n   * | 127    |`polymodeon`                   |\n   *\n   * Note: as you can see above, not all control change message have a matching name. This does not\n   * mean you cannot use the others. It simply means you will need to use their number (`0` - `127`)\n   * instead of their name. While you can still use them, numbers `120` to `127` are usually\n   * reserved for *channel mode* messages. See [`sendChannelMode()`]{@link #sendChannelMode} method\n   * for more info.\n   *\n   * To view a list of all available **control change** messages, please consult [Table 3 - Control\n   * Change Messages](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\n   * from the MIDI specification.\n   *\n   * @param controller {number|string} The MIDI controller name or number (0-127).\n   *\n   * @param [value=0] {number} The value to send (0-127).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} Controller numbers must be between 0 and 127.\n   * @throws {RangeError} Invalid controller name.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendControlChange(controller, value, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendControlChange(controller, value, options);\n    });\n\n    return this;\n\n  };\n\n  /**\n   * Sends a **pitch bend range** message to the specified channel(s) at the scheduled time so that\n   * they adjust the range used by their pitch bend lever. The range is specified by using the\n   * `semitones` and `cents` parameters. For example, setting the `semitones` parameter to `12`\n   * means that the pitch bend range will be 12 semitones above and below the nominal pitch.\n   *\n   * @param {number} [semitones=0] The desired adjustment value in semitones (between `0` and `127`).\n   * While nothing imposes that in the specification, it is very common for manufacturers to limit\n   * the range to 2 octaves (-12 semitones to 12 semitones).\n   *\n   * @param {number} [cents=0] The desired adjustment value in cents (integer between `0` and\n   * `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The msb value must be between 0 and 127.\n   * @throws {RangeError} The lsb value must be between 0 and 127.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendPitchBendRange(semitones= 0, cents = 0, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendPitchBendRange(semitones, cents, options);\n    });\n\n    return this;\n\n  }\n\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  setPitchBendRange(semitones = 0, cents = 0, channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The setPitchBendRange() method is deprecated. Use sendPitchBendRange() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendPitchBendRange(semitones, cents, options);\n\n  }\n\n  /**\n   * Sets the specified MIDI registered parameter to the desired value. The value is defined with\n   * up to two bytes of data (msb, lsb) that each can go from `0` to `127`.\n   *\n   * MIDI\n   * [registered parameters](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\n   * extend the original list of control change messages. The MIDI 1.0 specification lists only a\n   * limited number of them:\n   *\n   * | Numbers      | Function                 |\n   * |--------------|--------------------------|\n   * | (0x00, 0x00) | `pitchbendrange`         |\n   * | (0x00, 0x01) | `channelfinetuning`      |\n   * | (0x00, 0x02) | `channelcoarsetuning`    |\n   * | (0x00, 0x03) | `tuningprogram`          |\n   * | (0x00, 0x04) | `tuningbank`             |\n   * | (0x00, 0x05) | `modulationrange`        |\n   * | (0x3D, 0x00) | `azimuthangle`           |\n   * | (0x3D, 0x01) | `elevationangle`         |\n   * | (0x3D, 0x02) | `gain`                   |\n   * | (0x3D, 0x03) | `distanceratio`          |\n   * | (0x3D, 0x04) | `maximumdistance`        |\n   * | (0x3D, 0x05) | `maximumdistancegain`    |\n   * | (0x3D, 0x06) | `referencedistanceratio` |\n   * | (0x3D, 0x07) | `panspreadangle`         |\n   * | (0x3D, 0x08) | `rollangle`              |\n   *\n   * Note that the `tuningprogram` and `tuningbank` parameters are part of the *MIDI Tuning\n   * Standard*, which is not widely implemented.\n   *\n   * @param parameter {string|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (e.g. `[0x65, 0x64]`) that identify the\n   * registered parameter.\n   *\n   * @param [data=[]] {number|number[]} A single integer or an array of integers with a maximum\n   * length of 2 specifying the desired data.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendRpnValue(parameter, data, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendRpnValue(parameter, data, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  setRegisteredParameter(parameter, data = [], channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The setRegisteredParameter() method is deprecated. Use sendRpnValue() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendRpnValue(parameter, data, options);\n\n  }\n\n  /**\n   * Sends a MIDI **channel aftertouch** message to the specified channel(s). For key-specific\n   * aftertouch, you should instead use [`setKeyAftertouch()`]{@link #setKeyAftertouch}.\n   *\n   * @param [pressure=0.5] {number} The pressure level (between `0` and `1`). An invalid pressure\n   * value will silently trigger the default behaviour. If the `rawValue` option is set to `true`,\n   * the pressure can be defined by using an integer between `0` and `127`.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   * @since 3.0.0\n   */\n  sendChannelAftertouch(pressure, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendChannelAftertouch(pressure, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **pitch bend** message to the specified channel(s) at the scheduled time.\n   *\n   * The resulting bend is relative to the pitch bend range that has been defined. The range can be\n   * set with [`sendPitchBendRange()`]{@link #sendPitchBendRange}. So, for example, if the pitch\n   * bend range has been set to 12 semitones, using a bend value of `-1` will bend the note 1 octave\n   * below its nominal value.\n   *\n   * @param {number|number[]} value The intensity of the bend (between `-1.0` and `1.0`). A value of\n   * `0` means no bend. If an invalid value is specified, the nearest valid value will be used\n   * instead. If the `rawValue` option is set to `true`, the intensity of the bend can be defined by\n   * either using a single integer between `0` and `127` (MSB) or an array of two integers between\n   * `0` and `127` representing, respectively, the MSB (most significant byte) and the LSB (least\n   * significant byte). The MSB is expressed in semitones with `64` meaning no bend. A value lower\n   * than `64` bends downwards while a value higher than `64` bends upwards. The LSB is expressed\n   * in cents (1/100 of a semitone). An LSB of `64` also means no bend.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered as a float between `-1.0` and `1.0` (default) or as raw integer between `0` and\n   * 127` (or an array of 2 integers if using both MSB and LSB).\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendPitchBend(value, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendPitchBend(value, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **program change** message to the specified channel(s) at the scheduled time.\n   *\n   * @param {number} [program=0] The MIDI patch (program) number (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Failed to execute 'send' on 'MIDIOutput': The value at index 1 is greater\n   * than 0xFF.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendProgramChange(program = 0, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendProgramChange(program, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **modulation depth range** message to the specified channel(s) so that they adjust the\n   * depth of their modulation wheel's range. The range can be specified with the `semitones`\n   * parameter, the `cents` parameter or by specifying both parameters at the same time.\n   *\n   * @param [semitones=0] {number} The desired adjustment value in semitones (integer between\n   * 0 and 127).\n   *\n   * @param [cents=0] {number} The desired adjustment value in cents (integer between 0 and 127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The msb value must be between 0 and 127\n   * @throws {RangeError} The lsb value must be between 0 and 127\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendModulationRange(semitones, cents, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendModulationRange(semitones, cents, options);\n    });\n\n    return this;\n\n  };\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  setModulationRange(semitones = 0, cents = 0, channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The setModulationRange() method is deprecated. Use sendModulationRange() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendModulationRange(semitones, cents, options);\n\n  }\n\n  /**\n   * Sends a master tuning message to the specified channel(s). The value is decimal and must be\n   * larger than `-65` semitones and smaller than `64` semitones.\n   *\n   * Because of the way the MIDI specification works, the decimal portion of the value will be\n   * encoded with a resolution of 14bit. The integer portion must be between -64 and 63\n   * inclusively. This function actually generates two MIDI messages: a **Master Coarse Tuning** and\n   * a **Master Fine Tuning** RPN messages.\n   *\n   * @param [value=0.0] {number} The desired decimal adjustment value in semitones (-65 < x < 64)\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The value must be a decimal number between larger than -65 and smaller\n   * than 64.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendMasterTuning(value, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendMasterTuning(value, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  setMasterTuning(value, channel = {}, options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The setMasterTuning() method is deprecated. Use sendMasterTuning() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendMasterTuning(value, options);\n\n  }\n\n  /**\n   * Sets the MIDI tuning program to use. Note that the **Tuning Program** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param value {number} The desired tuning program (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The program value must be between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendTuningProgram(value, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendTuningProgram(value, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  setTuningProgram(value, channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The setTuningProgram() method is deprecated. Use sendTuningProgram() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendTuningProgram(value, options);\n\n  }\n\n  /**\n   * Sets the MIDI tuning bank to use. Note that the **Tuning Bank** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param {number} [value=0] The desired tuning bank (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The bank value must be between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendTuningBank(value= 0, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendTuningBank(value, options);\n    });\n\n    return this;\n\n  };\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  setTuningBank(parameter, channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The setTuningBank() method is deprecated. Use sendTuningBank() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendTuningBank(parameter, options);\n\n  }\n\n  /**\n   * Sends a MIDI **channel mode** message to the specified channel(s). The channel mode message to\n   * send can be specified numerically or by using one of the following common names:\n   *\n   * |  Type                |Number| Shortcut Method                                               |\n   * | ---------------------|------|-------------------------------------------------------------- |\n   * | `allsoundoff`        | 120  | [`sendAllSoundOff()`]{@link #sendAllSoundOff}                 |\n   * | `resetallcontrollers`| 121  | [`sendResetAllControllers()`]{@link #sendResetAllControllers} |\n   * | `localcontrol`       | 122  | [`sendLocalControl()`]{@link #sendLocalControl}               |\n   * | `allnotesoff`        | 123  | [`sendAllNotesOff()`]{@link #sendAllNotesOff}                 |\n   * | `omnimodeoff`        | 124  | [`sendOmniMode(false)`]{@link #sendOmniMode}                  |\n   * | `omnimodeon`         | 125  | [`sendOmniMode(true)`]{@link #sendOmniMode}                   |\n   * | `monomodeon`         | 126  | [`sendPolyphonicMode(\"mono\")`]{@link #sendPolyphonicMode}     |\n   * | `polymodeon`         | 127  | [`sendPolyphonicMode(\"poly\")`]{@link #sendPolyphonicMode}     |\n   *\n   * Note: as you can see above, to make it easier, all channel mode messages also have a matching\n   * helper method.\n   *\n   * It should also be noted that, per the MIDI specification, only `localcontrol` and `monomodeon`\n   * may require a value that's not zero. For that reason, the `value` parameter is optional and\n   * defaults to 0.\n   *\n   * @param {number|string} command The numerical identifier of the channel mode message (integer\n   * between 120-127) or its name as a string.\n   *\n   * @param {number} [value=0] The value to send (integer between 0-127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Invalid channel mode message name.\n   * @throws {RangeError} Channel mode controller numbers must be between 120 and 127.\n   * @throws {RangeError} Value must be an integer between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   */\n  sendChannelMode(command, value = 0, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendChannelMode(command, value, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends an **all sound off** channel mode message. This will silence all sounds playing on that\n   * channel but will not prevent new sounds from being triggered.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output}\n   *\n   * @since 3.0.0\n   */\n  sendAllSoundOff(options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendAllSoundOff(options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends an **all notes off** channel mode message. This will make all currently playing notes\n   * fade out just as if their key had been released. This is different from the\n   * [`sendAllSoundOff()`]{@link #sendAllSoundOff} method which mutes all sounds immediately.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output}\n   *\n   * @since 3.0.0\n   */\n  sendAllNotesOff(options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendAllNotesOff(options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **reset all controllers** channel mode message. This resets all controllers, such as\n   * the pitch bend, to their default value.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output}\n   */\n  sendResetAllControllers(options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendResetAllControllers(options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sets the polyphonic mode. In `poly` mode (usually the default), multiple notes can be played\n   * and heard at the same time. In `mono` mode, only one note will be heard at once even if\n   * multiple notes are being played.\n   *\n   * @param mode {string} The mode to use: `mono` or `poly`.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendPolyphonicMode(mode, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendPolyphonicMode(mode, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Turns local control on or off. Local control is usually enabled by default. If you disable it,\n   * the instrument will no longer trigger its own sounds. It will only send the MIDI messages to\n   * its out port.\n   *\n   * @param [state=false] {boolean} Whether to activate local control (`true`) or disable it\n   * (`false`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendLocalControl(state, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendLocalControl(state, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sets OMNI mode to **on** or **off** for the specified channel(s). MIDI's OMNI mode causes the\n   * instrument to respond to messages from all channels.\n   *\n   * It should be noted that support for OMNI mode is not as common as it used to be.\n   *\n   * @param [state] {boolean} Whether to activate OMNI mode (`true`) or not (`false`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Invalid channel mode message name.\n   * @throws {RangeError} Channel mode controller numbers must be between 120 and 127.\n   * @throws {RangeError} Value must be an integer between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendOmniMode(state, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendOmniMode(state, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sets a non-registered parameter to the specified value. The NRPN is selected by passing a\n   * two-position array specifying the values of the two control bytes. The value is specified by\n   * passing a single integer (most cases) or an array of two integers.\n   *\n   * NRPNs are not standardized in any way. Each manufacturer is free to implement them any way\n   * they see fit. For example, according to the Roland GS specification, you can control the\n   * **vibrato rate** using NRPN (`1`, `8`). Therefore, to set the **vibrato rate** value to `123`\n   * you would use:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendNrpnValue([1, 8], 123);\n   * ```\n   *\n   * You probably want to should select a channel so the message is not sent to all channels. For\n   * instance, to send to channel `1` of the first output port, you would use:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendNrpnValue([1, 8], 123, 1);\n   * ```\n   *\n   * In some rarer cases, you need to send two values with your NRPN messages. In such cases, you\n   * would use a 2-position array. For example, for its **ClockBPM** parameter (`2`, `63`), Novation\n   * uses a 14-bit value that combines an MSB and an LSB (7-bit values). So, for example, if the\n   * value to send was `10`, you could use:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendNrpnValue([2, 63], [0, 10], 1);\n   * ```\n   *\n   * For further implementation details, refer to the manufacturer's documentation.\n   *\n   * @param parameter {number[]} A two-position array specifying the two control bytes (`0x63`,\n   * `0x62`) that identify the non-registered parameter.\n   *\n   * @param [data=[]] {number|number[]} An integer or an array of integers with a length of 1 or 2\n   * specifying the desired data.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The control value must be between 0 and 127.\n   * @throws {RangeError} The msb value must be between 0 and 127\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendNrpnValue(parameter, data, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendNrpnValue(parameter, data, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  setNonRegisteredParameter(parameter, data = [], channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The setNonRegisteredParameter() method is deprecated. Use sendNrpnValue() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendNrpnValue(parameter, data, options);\n\n  }\n\n  /**\n   * Increments the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this method:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendRpnIncrement(parameter, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendRpnIncrement(parameter, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  incrementRegisteredParameter(parameter, channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The incrementRegisteredParameter() method is deprecated. Use sendRpnIncrement() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendRpnIncrement(parameter, options);\n\n  }\n\n  /**\n   * Decrements the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this method:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws TypeError The specified parameter is not available.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendRpnDecrement(parameter, options = {}) {\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendRpnDecrement(parameter, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0\n   */\n  decrementRegisteredParameter(parameter, channel = \"all\", options = {}) {\n\n    if (WebMidi.validation) {\n\n      console.warn(\n        \"The decrementRegisteredParameter() method is deprecated. Use sendRpnDecrement() instead.\"\n      );\n\n      options.channels = channel;\n      if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    }\n\n    return this.sendRpnDecrement(parameter, options);\n\n  }\n\n  /**\n   * Sends a **note off** message for the specified MIDI note number on the specified channel(s).\n   * The first parameter is the note to stop. It can be a single value or an array of the following\n   * valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`,\n   * `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range\n   * must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest\n   * note is `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendNoteOff(note, options= {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendNoteOff(note, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **note off** message for the specified MIDI note number on the specified channel(s).\n   * The first parameter is the note to stop. It can be a single value or an array of the following\n   * valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`,\n   * `Db7`) or an array of the previous types. When using a note identifier, octave range must be\n   * between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is\n   * `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  stopNote(note, options) {\n    return this.sendNoteOff(note, options);\n  }\n\n  /**\n   * Plays a note or an array of notes on one or more channels of this output. If you intend to play\n   * notes on a single channel, you should probably use\n   * [`OutputChannel.playNote()`](OutputChannel#playNote) instead.\n   *\n   * The first parameter is the note to play. It can be a single value or an array of the following\n   * valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`]{@link Note} object\n   *\n   * The `playNote()` method sends a **note on** MIDI message for all specified notes on all\n   * specified channels. If no channel is specified, it will send to all channels. If a `duration`\n   * is set in the `options` parameter or in the [`Note`]{@link Note} object's\n   * [`duration`]{@link Note#duration} property, it will also schedule a **note off** message to end\n   * the note after said duration. If no `duration` is set, the note will simply play until a\n   * matching **note off** message is sent with [`stopNote()`]{@link #stopNote}.\n   *\n   * The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the durations and velocities defined in the\n   * [`Note`]{@link Note} objects have precedence over the ones specified via the method's `options`\n   * parameter.\n   *\n   * **Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to play. The notes can be\n   * specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a\n   * [`Note`]{@link Note} object or an array of the previous types. When using a note identifier,\n   * octave range must be between -1 and 9. The lowest note is C-1 (MIDI note number `0`) and the\n   * highest note is G9 (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.duration=undefined] The number of milliseconds after which a\n   * **note off** message will be scheduled. If left undefined, only a **note on** message is sent.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`). If the `rawAttack` option is also defined, it will have priority. An invalid velocity\n   * value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The attack velocity at which to play the note (between\n   * `0` and `127`). This has priority over the `attack` property. An invalid velocity value will\n   * silently trigger the default of 64.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note (between `0`\n   * and `1`). If the `rawRelease` option is also defined, it will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`. This is only used with the\n   * **note off** event triggered when `options.duration` is set.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note (between `0`\n   * and `127`). This has priority over the `release` property. An invalid velocity value will\n   * silently trigger the default of 64. This is only used with the **note off** event triggered\n   * when `options.duration` is set.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  playNote(note, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy-compatibility warnings\n      if (options.rawVelocity) {\n        console.warn(\"The 'rawVelocity' option is deprecated. Use 'rawAttack' instead.\");\n      }\n\n      if (options.velocity) {\n        console.warn(\"The 'velocity' option is deprecated. Use 'velocity' instead.\");\n      }\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].playNote(note, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **note on** message for the specified MIDI note number on the specified channel(s). The\n   * first parameter is the number. It can be a single value or an array of the following valid\n   * values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   *  The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * **Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`,\n   * `Db7`) or an array of the previous types. When using a note identifier, octave range must be\n   * between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is\n   * `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`).  If the `rawAttack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The velocity at which to release the note (between `0`\n   * and `127`). If the `attack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendNoteOn(note, options = {}, legacy = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy compatibility\n      if (Array.isArray(options) || Number.isInteger(options) || options === \"all\") {\n        const channels = options;\n        options = legacy;\n        options.channels = channels;\n        if (options.channels === \"all\") options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n      }\n\n    }\n\n    if (options.channels == undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n\n    // This actually supports passing a Note object even if, semantically, this does not make sense.\n    Utilities.sanitizeChannels(options.channels).forEach(ch => {\n      this.channels[ch].sendNoteOn(note, options);\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Name of the MIDI output.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get name() {\n    return this._midiOutput.name;\n  }\n\n  /**\n   * ID string of the MIDI output. The ID is host-specific. Do not expect the same ID on different\n   * platforms. For example, Google Chrome and the Jazz-Plugin report completely different IDs for\n   * the same port.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get id() {\n    return this._midiOutput.id;\n  }\n\n  /**\n   * Output port's connection state: `pending`, `open` or `closed`.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get connection() {\n    return this._midiOutput.connection;\n  }\n\n  /**\n   * Name of the manufacturer of the device that makes this output port available.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get manufacturer() {\n    return this._midiOutput.manufacturer;\n  }\n\n  /**\n   * State of the output port: `connected` or `disconnected`.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get state() {\n    return this._midiOutput.state;\n  }\n\n  /**\n   * Type of the output port (it will always be: `output`).\n   *\n   * @type {string}\n   * @readonly\n   */\n  get type() {\n    return this._midiOutput.type;\n  }\n\n  /**\n   * An integer to offset the octave of outgoing notes. By default, middle C (MIDI note number 60)\n   * is placed on the 4th octave (C4).\n   *\n   * Note that this value is combined with the global offset value defined in\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) (if any).\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  get octaveOffset() {\n    return this._octaveOffset;\n  }\n  set octaveOffset(value) {\n\n    if (this.validation) {\n      value = parseInt(value);\n      if (isNaN(value)) throw new TypeError(\"The 'octaveOffset' property must be an integer.\");\n    }\n\n    this._octaveOffset = value;\n\n  }\n\n}\n"
  },
  {
    "path": "src/OutputChannel.js",
    "content": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {WebMidi} from \"./WebMidi.js\";\nimport {Utilities} from \"./Utilities.js\";\nimport {Enumerations} from \"./Enumerations.js\";\n\n/**\n * The `OutputChannel` class represents a single output MIDI channel. `OutputChannel` objects are\n * provided by an [`Output`](Output) port which, itself, is made available by a device. The\n * `OutputChannel` object is derived from the host's MIDI subsystem and should not be instantiated\n * directly.\n *\n * All 16 `OutputChannel` objects can be found inside the parent output's\n * [`channels`]{@link Output#channels} property.\n *\n * @param {Output} output The [`Output`](Output) this channel belongs to.\n * @param {number} number The MIDI channel number (`1` - `16`).\n *\n * @extends EventEmitter\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class OutputChannel extends EventEmitter {\n\n  /**\n   * Creates an `OutputChannel` object.\n   *\n   * @param {Output} output The [`Output`](Output) this channel belongs to.\n   * @param {number} number The MIDI channel number (`1` - `16`).\n   */\n  constructor(output, number) {\n\n    super();\n\n    /**\n     * @type {Output}\n     * @private\n     */\n    this._output = output;\n\n    /**\n     * @type {number}\n     * @private\n     */\n    this._number = number;\n\n    /**\n     * @type {number}\n     * @private\n     */\n    this._octaveOffset = 0;\n\n  }\n\n  /**\n   * Unlinks the MIDI subsystem, removes all listeners attached to the channel and nulls the channel\n   * number. This method is mostly for internal use. It has not been prefixed with an underscore\n   * since it is called by other objects such as the `Output` object.\n   *\n   * @private\n   */\n  destroy() {\n    this._output = null;\n    this._number = null;\n    this._octaveOffset = 0;\n    this.removeListener();\n  }\n\n  /**\n   * Sends a MIDI message on the MIDI output port. If no time is specified, the message will be\n   * sent immediately. The message should be an array of 8-bit unsigned integers (`0` - `225`),\n   * a\n   * [`Uint8Array`]{@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array}\n   * object or a [`Message`](Message) object.\n   *\n   * It is usually not necessary to use this method directly as you can use one of the simpler\n   * helper methods such as [`playNote()`](#playNote), [`stopNote()`](#stopNote),\n   * [`sendControlChange()`](#sendControlChange), etc.\n   *\n   * Details on the format of MIDI messages are available in the summary of\n   * [MIDI messages]{@link https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message}\n   * from the MIDI Manufacturers Association.\n   *\n   * @param message {number[]|Uint8Array|Message} A `Message` object, an array of 8-bit unsigned\n   * integers or a `Uint8Array` object (not available in Node.js) containing the message bytes.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The first byte (status) must be an integer between 128 and 255.\n   *\n   * @throws {RangeError} Data bytes must be integers between 0 and 255.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  send(message, options = {time: 0}) {\n    this.output.send(message, options);\n    return this;\n  }\n\n  /**\n   * Sends a MIDI **key aftertouch** message at the scheduled time. This is a key-specific\n   * aftertouch. For a channel-wide aftertouch message, use\n   * [`sendChannelAftertouch()`]{@link #sendChannelAftertouch}.\n   *\n   * @param target {number|Note|string|number[]|Note[]|string[]} The note(s) for which you are sending\n   * an aftertouch value. The notes can be specified by using a MIDI note number (`0` - `127`), a\n   * [`Note`](Note) object, a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the\n   * previous types. When using a note identifier, octave range must be between `-1` and `9`. The\n   * lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number\n   * `127`).\n   *\n   * When using a note identifier, the octave value will be offset by the local\n   * [`octaveOffset`](#octaveOffset) and by\n   * [`Output.octaveOffset`](Output#octaveOffset) and [`WebMidi.octaveOffset`](WebMidi#octaveOffset)\n   * (if those values are not `0`). When using a key number, `octaveOffset` values are ignored.\n   *\n   * @param [pressure=0.5] {number} The pressure level (between `0` and `1`). An invalid pressure\n   * value will silently trigger the default behaviour. If the `rawValue` option is set to `true`,\n   * the pressure is defined by using an integer between `0` and `127`.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   * @throws RangeError Invalid key aftertouch value.\n   */\n  sendKeyAftertouch(target, pressure, options = {}) {\n\n    if (WebMidi.validation) {\n\n      // Legacy support\n      if (options.useRawValue) options.rawValue = options.useRawValue;\n\n      if (isNaN(parseFloat(pressure))) {\n        throw new RangeError(\"Invalid key aftertouch value.\");\n      }\n      if (options.rawValue) {\n        if (!(pressure >= 0 && pressure <= 127 && Number.isInteger(pressure))) {\n          throw new RangeError(\"Key aftertouch raw value must be an integer between 0 and 127.\");\n        }\n      } else {\n        if (!(pressure >= 0 && pressure <= 1)) {\n          throw new RangeError(\"Key aftertouch value must be a float between 0 and 1.\");\n        }\n      }\n\n    }\n\n    // Normalize pressure to integer\n    if (!options.rawValue) pressure = Utilities.fromFloatTo7Bit(pressure);\n\n    // Plot total offset\n    const offset = WebMidi.octaveOffset + this.output.octaveOffset + this.octaveOffset;\n\n    // Make sure we are dealing with an array\n    if (!Array.isArray(target)) target = [target];\n\n    Utilities.buildNoteArray(target).forEach(n => {\n      this.send(\n        [\n          (Enumerations.CHANNEL_MESSAGES.keyaftertouch << 4) + (this.number - 1),\n          n.getOffsetNumber(offset),\n          pressure\n        ],\n        {time: Utilities.toTimestamp(options.time)}\n      );\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **control change** message to the channel at the scheduled time. The control\n   * change message to send can be specified numerically (`0` to `127`) or by using one of the\n   * following common names:\n   *\n   * | Number | Name                          |\n   * |--------|-------------------------------|\n   * | 0      |`bankselectcoarse`             |\n   * | 1      |`modulationwheelcoarse`        |\n   * | 2      |`breathcontrollercoarse`       |\n   * | 4      |`footcontrollercoarse`         |\n   * | 5      |`portamentotimecoarse`         |\n   * | 6      |`dataentrycoarse`              |\n   * | 7      |`volumecoarse`                 |\n   * | 8      |`balancecoarse`                |\n   * | 10     |`pancoarse`                    |\n   * | 11     |`expressioncoarse`             |\n   * | 12     |`effectcontrol1coarse`         |\n   * | 13     |`effectcontrol2coarse`         |\n   * | 18     |`generalpurposeslider3`        |\n   * | 19     |`generalpurposeslider4`        |\n   * | 32     |`bankselectfine`               |\n   * | 33     |`modulationwheelfine`          |\n   * | 34     |`breathcontrollerfine`         |\n   * | 36     |`footcontrollerfine`           |\n   * | 37     |`portamentotimefine`           |\n   * | 38     |`dataentryfine`                |\n   * | 39     |`volumefine`                   |\n   * | 40     |`balancefine`                  |\n   * | 42     |`panfine`                      |\n   * | 43     |`expressionfine`               |\n   * | 44     |`effectcontrol1fine`           |\n   * | 45     |`effectcontrol2fine`           |\n   * | 64     |`holdpedal`                    |\n   * | 65     |`portamento`                   |\n   * | 66     |`sustenutopedal`               |\n   * | 67     |`softpedal`                    |\n   * | 68     |`legatopedal`                  |\n   * | 69     |`hold2pedal`                   |\n   * | 70     |`soundvariation`               |\n   * | 71     |`resonance`                    |\n   * | 72     |`soundreleasetime`             |\n   * | 73     |`soundattacktime`              |\n   * | 74     |`brightness`                   |\n   * | 75     |`soundcontrol6`                |\n   * | 76     |`soundcontrol7`                |\n   * | 77     |`soundcontrol8`                |\n   * | 78     |`soundcontrol9`                |\n   * | 79     |`soundcontrol10`               |\n   * | 80     |`generalpurposebutton1`        |\n   * | 81     |`generalpurposebutton2`        |\n   * | 82     |`generalpurposebutton3`        |\n   * | 83     |`generalpurposebutton4`        |\n   * | 91     |`reverblevel`                  |\n   * | 92     |`tremololevel`                 |\n   * | 93     |`choruslevel`                  |\n   * | 94     |`celestelevel`                 |\n   * | 95     |`phaserlevel`                  |\n   * | 96     |`dataincrement`                |\n   * | 97     |`datadecrement`                |\n   * | 98     |`nonregisteredparametercoarse` |\n   * | 99     |`nonregisteredparameterfine`   |\n   * | 100    |`registeredparametercoarse`    |\n   * | 101    |`registeredparameterfine`      |\n   * | 120    |`allsoundoff`                  |\n   * | 121    |`resetallcontrollers`          |\n   * | 122    |`localcontrol`                 |\n   * | 123    |`allnotesoff`                  |\n   * | 124    |`omnimodeoff`                  |\n   * | 125    |`omnimodeon`                   |\n   * | 126    |`monomodeon`                   |\n   * | 127    |`polymodeon`                   |\n   *\n   * As you can see above, not all control change message have a matching name. This does not mean\n   * you cannot use the others. It simply means you will need to use their number\n   * (`0` to `127`) instead of their name. While you can still use them, numbers `120` to `127` are\n   * usually reserved for *channel mode* messages. See\n   * [`sendChannelMode()`]{@link OutputChannel#sendChannelMode} method for more info.\n   *\n   * To view a detailed list of all available **control change** messages, please consult \"Table 3 -\n   * Control Change Messages\" from the [MIDI Messages](\n   * https://www.midi.org/specifications/item/table-3-control-change-messages-data-bytes-2)\n   * specification.\n   *\n   * **Note**: messages #0-31 (MSB) are paired with messages #32-63 (LSB). For example, message #1\n   * (`modulationwheelcoarse`) can be accompanied by a second control change message for\n   * `modulationwheelfine` to achieve a greater level of precision. if you want to specify both MSB\n   * and LSB for messages between `0` and `31`, you can do so by passing a 2-value array as the\n   * second parameter.\n   *\n   * @param {number|string} controller The MIDI controller name or number (`0` - `127`).\n   *\n   * @param {number|number[]} value The value to send (0-127). You can also use a two-position array\n   * for controllers 0 to 31. In this scenario, the first value will be sent as usual and the second\n   * value will be sent to the matching LSB controller (which is obtained by adding 32 to the first\n   * controller)\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} Controller numbers must be between 0 and 127.\n   * @throws {RangeError} Invalid controller name.\n   * @throws {TypeError} The value array must have a length of 2.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   * @license Apache-2.0\n   * @since 3.0.0\n   */\n  sendControlChange(controller, value, options = {}) {\n\n    if (typeof controller === \"string\") {\n      controller = Utilities.getCcNumberByName(controller);\n    }\n\n    if (!Array.isArray(value)) value = [value];\n\n    if (WebMidi.validation) {\n\n      if (controller === undefined) {\n        throw new TypeError(\n          \"Control change must be identified with a valid name or an integer between 0 and 127.\"\n        );\n      }\n\n      if (!Number.isInteger(controller) || !(controller >= 0 && controller <= 127)) {\n        throw new TypeError(\"Control change number must be an integer between 0 and 127.\");\n      }\n\n      value = value.map(item => {\n        const output = Math.min(Math.max(parseInt(item), 0), 127);\n        if (isNaN(output)) throw new TypeError(\"Values must be integers between 0 and 127\");\n        return output;\n      });\n\n      if (value.length === 2 && controller >= 32) {\n        throw new TypeError(\"To use a value array, the controller must be between 0 and 31\");\n      }\n\n    }\n\n    value.forEach((item, index) => {\n\n      this.send(\n        [\n          (Enumerations.CHANNEL_MESSAGES.controlchange << 4) + (this.number - 1),\n          controller + (index * 32),\n          value[index]\n        ],\n        {time: Utilities.toTimestamp(options.time)}\n      );\n\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Selects a MIDI non-registered parameter so it is affected by upcoming data entry, data\n   * increment and data decrement messages.\n   *\n   * @param parameter {number[]} A two-position array specifying the two control bytes that identify\n   * the registered parameter. The NRPN MSB (99 or 0x63) is a position 0. The NRPN LSB (98 or 0x62)\n   * is at position 1.\n   *\n   * @private\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time] If `time` is a string prefixed with `\"+\"` and followed by\n   * a number, the message will be delayed by that many milliseconds. If the value is a number, the\n   * operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  _selectNonRegisteredParameter(parameter, options = {}) {\n\n    // parameter[0] = Math.floor(parameter[0]);\n    // if (!(parameter[0] >= 0 && parameter[0] <= 127)) {\n    //   throw new RangeError(\"The control63 value must be between 0 and 127.\");\n    // }\n    //\n    // parameter[1] = Math.floor(parameter[1]);\n    // if (!(parameter[1] >= 0 && parameter[1] <= 127)) {\n    //   throw new RangeError(\"The control62 value must be between 0 and 127.\");\n    // }\n\n    this.sendControlChange(0x63, parameter[0], options);\n    this.sendControlChange(0x62, parameter[1], options);\n\n    return this;\n\n  }\n\n  /**\n   * Deselects the currently active MIDI registered parameter so it is no longer affected by data\n   * entry, data increment and data decrement messages.\n   *\n   * Current best practice recommends doing that after each call to\n   * [_setCurrentParameter()]{@link #_setCurrentParameter}.\n   *\n   * @private\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time] If `time` is a string prefixed with `\"+\"` and followed by\n   * a number, the message will be delayed by that many milliseconds. If the value is a number, the\n   * operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  _deselectRegisteredParameter(options = {}) {\n    this.sendControlChange(0x65, 0x7F, options);\n    this.sendControlChange(0x64, 0x7F, options);\n    return this;\n  }\n\n  /**\n   * Deselects the currently active MIDI non-registered parameter so it is no longer affected by\n   * data entry, data increment and data decrement messages.\n   *\n   * @private\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time] If `time` is a string prefixed with `\"+\"` and followed by\n   * a number, the message will be delayed by that many milliseconds. If the value is a number, the\n   * operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  _deselectNonRegisteredParameter(options = {}) {\n    this.sendControlChange(0x65, 0x7F, options);\n    this.sendControlChange(0x64, 0x7F, options);\n    return this;\n  }\n\n  /**\n   * Selects a MIDI registered parameter so it is affected by upcoming data entry, data increment\n   * and data decrement messages.\n   *\n   * @private\n   *\n   * @param parameter {number[]} A two-position array of integers specifying the two control bytes\n   * (0x65, 0x64) that identify the registered parameter. The integers must be between 0 and 127.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time] If `time` is a string prefixed with `\"+\"` and followed by\n   * a number, the message will be delayed by that many milliseconds. If the value is a number, the\n   * operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  _selectRegisteredParameter(parameter, options = {}) {\n    this.sendControlChange(0x65, parameter[0], options);\n    this.sendControlChange(0x64, parameter[1], options);\n    return this;\n  }\n\n  /**\n   * Sets the value of the currently selected MIDI registered parameter.\n   *\n   * @private\n   *\n   * @param data {number|number[]}\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time] If `time` is a string prefixed with `\"+\"` and followed by\n   * a number, the message will be delayed by that many milliseconds. If the value is a number, the\n   * operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  _setCurrentParameter(data, options = {}) {\n\n    data = [].concat(data);\n\n    // MSB\n    // data[0] = parseInt(data[0]);\n    // if (!isNaN(data[0]) && data[0] >= 0 && data[0] <= 127) {\n    this.sendControlChange(0x06, data[0], options);\n    // } else {\n    //   throw new RangeError(\"The msb value must be between 0 and 127.\");\n    // }\n\n    if (data.length < 2) return this;\n\n    // LSB\n    // data[1] = parseInt(data[1]);\n\n    // if (!isNaN(data[1]) && data[1] >= 0 && data[1] <= 127) {\n    this.sendControlChange(0x26, data[1], options);\n    // } else {\n    //   throw new RangeError(\"The lsb value must be between 0 and 127.\");\n    // }\n\n    return this;\n\n  }\n\n  /**\n   * Decrements the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this function:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws TypeError The specified registered parameter is invalid.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendRpnDecrement(parameter, options = {}) {\n\n    if (!Array.isArray(parameter)) parameter = Enumerations.REGISTERED_PARAMETERS[parameter];\n\n    if (WebMidi.validation) {\n\n      if (parameter === undefined) {\n        throw new TypeError(\"The specified registered parameter is invalid.\");\n      }\n\n      let valid = false;\n\n      Object.getOwnPropertyNames(Enumerations.REGISTERED_PARAMETERS).forEach(p => {\n        if (\n          Enumerations.REGISTERED_PARAMETERS[p][0] === parameter[0] &&\n          Enumerations.REGISTERED_PARAMETERS[p][1] === parameter[1]\n        ) {\n          valid = true;\n        }\n      });\n\n      if (!valid) throw new TypeError(\"The specified registered parameter is invalid.\");\n\n    }\n\n    this._selectRegisteredParameter(parameter, options);\n    this.sendControlChange(0x61, 0, options);\n    this._deselectRegisteredParameter(options);\n\n    return this;\n\n  }\n\n  /**\n   * Increments the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this function:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws TypeError The specified registered parameter is invalid.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendRpnIncrement(parameter, options = {}) {\n\n    if (!Array.isArray(parameter)) parameter = Enumerations.REGISTERED_PARAMETERS[parameter];\n\n    if (WebMidi.validation) {\n\n      if (parameter === undefined) {\n        throw new TypeError(\"The specified registered parameter is invalid.\");\n      }\n\n      let valid = false;\n\n      Object.getOwnPropertyNames(Enumerations.REGISTERED_PARAMETERS).forEach(p => {\n        if (\n          Enumerations.REGISTERED_PARAMETERS[p][0] === parameter[0] &&\n          Enumerations.REGISTERED_PARAMETERS[p][1] === parameter[1]\n        ) {\n          valid = true;\n        }\n      });\n\n      if (!valid) throw new TypeError(\"The specified registered parameter is invalid.\");\n\n    }\n\n    this._selectRegisteredParameter(parameter, options);\n    this.sendControlChange(0x60, 0, options);\n    this._deselectRegisteredParameter(options);\n\n    return this;\n\n  }\n\n  /**\n   * Plays a note or an array of notes on the channel. The first parameter is the note to play. It\n   * can be a single value or an array of the following valid values:\n   *\n   *  - A [`Note`]{@link Note} object\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note name, followed by the octave (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *\n   * The `playNote()` method sends a **note on** MIDI message for all specified notes. If a\n   * `duration` is set in the `options` parameter or in the [`Note`]{@link Note} object's\n   * [`duration`]{@link Note#duration} property, it will also schedule a **note off** message\n   * to end the note after said duration. If no `duration` is set, the note will simply play until\n   * a matching **note off** message is sent with [`stopNote()`]{@link OutputChannel#stopNote} or\n   * [`sendNoteOff()`]{@link OutputChannel#sendNoteOff}.\n   *\n   *  The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the durations and velocities defined in the\n   * [`Note`]{@link Note} objects have precedence over the ones specified via the method's `options`\n   * parameter.\n   *\n   * **Note**: per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to play. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`,\n   * `F-1`, `Db7`), a [`Note`]{@link Note} object or an array of the previous types. When using a\n   * note identifier, the octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI\n   * note number `0`) and the highest note is `G9` (MIDI note number `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration] A positive decimal number larger than `0` representing the\n   * number of milliseconds to wait before sending a **note off** message. If invalid or left\n   * undefined, only a **note on** message will be sent.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`). If the `rawAttack` option is also defined, it will have priority. An invalid velocity\n   * value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The attack velocity at which to play the note (between\n   * `0` and `127`). This has priority over the `attack` property. An invalid velocity value will\n   * silently trigger the default of 64.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note (between `0`\n   * and `1`). If the `rawRelease` option is also defined, it will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`. This is only used with the\n   * **note off** event triggered when `options.duration` is set.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note (between `0`\n   * and `127`). This has priority over the `release` property. An invalid velocity value will\n   * silently trigger the default of 64. This is only used with the **note off** event triggered\n   * when `options.duration` is set.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  playNote(note, options = {}) {\n\n    // Send note on and, optionally, note off message (if duration is a positive number)\n    this.sendNoteOn(note, options);\n\n    const notes = Array.isArray(note) ? note : [note];\n\n    for(let note of notes) {\n      if (parseInt(note.duration) > 0) {\n        const noteOffOptions = {\n          time: (Utilities.toTimestamp(options.time) || WebMidi.time) + parseInt(note.duration),\n          release: note.release,\n          rawRelease: note.rawRelease\n        };\n        this.sendNoteOff(note, noteOffOptions);\n      } else if (parseInt(options.duration) > 0) {\n        const noteOffOptions = {\n          time: (Utilities.toTimestamp(options.time) || WebMidi.time) + parseInt(options.duration),\n          release: options.release,\n          rawRelease: options.rawRelease\n        };\n        this.sendNoteOff(note, noteOffOptions);\n      }\n    }\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **note off** message for the specified notes on the channel. The first parameter is the\n   * note. It can be a single value or an array of the following valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note name, followed by the octave (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`]{@link Note} object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the release velocity defined in the\n   * [`Note`]{@link Note} objects has precedence over the one specified via the method's `options`\n   * parameter.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a\n   * [`Note`]{@link Note} object or an array of the previous types. When using a note name, octave\n   * range must be between -1 and 9. The lowest note is C-1 (MIDI note number 0) and the highest\n   * note is G9 (MIDI note number 127).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendNoteOff(note, options = {}) {\n\n    if (WebMidi.validation) {\n\n      if (\n        options.rawRelease != undefined &&\n        !(options.rawRelease >= 0 && options.rawRelease <= 127)\n      ) {\n        throw new RangeError(\"The 'rawRelease' option must be an integer between 0 and 127\");\n      }\n\n      if (options.release != undefined && !(options.release >= 0 && options.release <= 1)) {\n        throw new RangeError(\"The 'release' option must be an number between 0 and 1\");\n      }\n\n      // Legacy compatibility warnings\n      if (options.rawVelocity) {\n        options.rawRelease = options.velocity;\n        console.warn(\"The 'rawVelocity' option is deprecated. Use 'rawRelease' instead.\");\n      }\n      if (options.velocity) {\n        options.release = options.velocity;\n        console.warn(\"The 'velocity' option is deprecated. Use 'attack' instead.\");\n      }\n\n    }\n\n    let nVelocity = 64;\n\n    if (options.rawRelease != undefined) {\n      nVelocity = options.rawRelease;\n    } else {\n      if (!isNaN(options.release)) nVelocity = Math.round(options.release * 127);\n    }\n\n    // Plot total octave offset\n    const offset = WebMidi.octaveOffset + this.output.octaveOffset + this.octaveOffset;\n\n    Utilities.buildNoteArray(note, {rawRelease: parseInt(nVelocity)}).forEach(n => {\n      this.send(\n        [\n          (Enumerations.CHANNEL_MESSAGES.noteoff << 4) + (this.number - 1),\n          n.getOffsetNumber(offset),\n          n.rawRelease,\n        ],\n        {time: Utilities.toTimestamp(options.time)}\n      );\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **note off** message for the specified MIDI note number. The first parameter is the\n   * note to stop. It can be a single value or an array of the following valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`,\n   * `Db7`) or an array of the previous types. When using a note identifier, octave range must be\n   * between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is\n   * `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  stopNote(note, options = {}) {\n    return this.sendNoteOff(note, options);\n  }\n\n  /**\n   * Sends a **note on** message for the specified note(s) on the channel. The first parameter is\n   * the note. It can be a single value or an array of the following valid values:\n   *\n   *  - A [`Note`]{@link Note} object\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *\n   *  When passing a [`Note`]{@link Note}object or a note name, the `octaveOffset` will be applied.\n   *  This is not the case when using a note number. In this case, we assume you know exactly which\n   *  MIDI note number should be sent out.\n   *\n   * The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the attack velocity defined in the\n   * [`Note`]{@link Note} objects has precedence over the one specified via the method's `options`\n   * parameter. Also, the `duration` is ignored. If you want to also send a **note off** message,\n   * use the [`playNote()`]{@link #playNote} method instead.\n   *\n   * **Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to play. The notes can be\n   * specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a\n   * [`Note`]{@link Note} object or an array of the previous types.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`).  If the `rawAttack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The velocity at which to release the note (between `0`\n   * and `127`). If the `attack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `64`.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendNoteOn(note, options = {}) {\n\n    if (WebMidi.validation) {\n\n      if (options.rawAttack != undefined && !(options.rawAttack >= 0 && options.rawAttack <= 127)) {\n        throw new RangeError(\"The 'rawAttack' option must be an integer between 0 and 127\");\n      }\n\n      if (options.attack != undefined && !(options.attack >= 0 && options.attack <= 1)) {\n        throw new RangeError(\"The 'attack' option must be an number between 0 and 1\");\n      }\n\n      // Legacy compatibility warnings\n      if (options.rawVelocity) {\n        options.rawAttack = options.velocity;\n        options.rawRelease = options.release;\n        console.warn(\"The 'rawVelocity' option is deprecated. Use 'rawAttack' or 'rawRelease'.\");\n      }\n      if (options.velocity) {\n        options.attack = options.velocity;\n        console.warn(\"The 'velocity' option is deprecated. Use 'attack' instead.\");\n      }\n\n    }\n\n    let nVelocity = 64;\n\n    if (options.rawAttack != undefined) {\n      nVelocity = options.rawAttack;\n    } else {\n      if (!isNaN(options.attack)) nVelocity = Math.round(options.attack * 127);\n    }\n\n    // Plot total octave offset\n    const offset = WebMidi.octaveOffset + this.output.octaveOffset + this.octaveOffset;\n\n    Utilities.buildNoteArray(note, {rawAttack: nVelocity}).forEach(n => {\n      this.send(\n        [\n          (Enumerations.CHANNEL_MESSAGES.noteon << 4) + (this.number - 1),\n          n.getOffsetNumber(offset),\n          n.rawAttack\n        ],\n        {time: Utilities.toTimestamp(options.time)}\n      );\n    });\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **channel mode** message. The channel mode message to send can be specified\n   * numerically or by using one of the following common names:\n   *\n   * |  Type                |Number| Shortcut Method                                               |\n   * | ---------------------|------|-------------------------------------------------------------- |\n   * | `allsoundoff`        | 120  | [`sendAllSoundOff()`]{@link #sendAllSoundOff}                 |\n   * | `resetallcontrollers`| 121  | [`sendResetAllControllers()`]{@link #sendResetAllControllers} |\n   * | `localcontrol`       | 122  | [`sendLocalControl()`]{@link #sendLocalControl}               |\n   * | `allnotesoff`        | 123  | [`sendAllNotesOff()`]{@link #sendAllNotesOff}                 |\n   * | `omnimodeoff`        | 124  | [`sendOmniMode(false)`]{@link #sendOmniMode}                  |\n   * | `omnimodeon`         | 125  | [`sendOmniMode(true)`]{@link #sendOmniMode}                   |\n   * | `monomodeon`         | 126  | [`sendPolyphonicMode(\"mono\")`]{@link #sendPolyphonicMode}     |\n   * | `polymodeon`         | 127  | [`sendPolyphonicMode(\"poly\")`]{@link #sendPolyphonicMode}     |\n   *\n   * **Note**: as you can see above, to make it easier, all channel mode messages also have a matching\n   * helper method.\n   *\n   * It should be noted that, per the MIDI specification, only `localcontrol` and `monomodeon` may\n   * require a value that's not zero. For that reason, the `value` parameter is optional and\n   * defaults to 0.\n   *\n   * @param {number|string} command The numerical identifier of the channel mode message (integer\n   * between `120` and `127`) or its name as a string.\n   *\n   * @param {number} [value=0] The value to send (integer between `0` - `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendChannelMode(command, value = 0, options = {}) {\n\n    // Normalize command to integer\n    if (typeof command === \"string\") command = Enumerations.CHANNEL_MODE_MESSAGES[command];\n\n    if (WebMidi.validation) {\n\n      if (command === undefined) {\n        throw new TypeError(\"Invalid channel mode message name or number.\");\n      }\n\n      if (isNaN(command) || !(command >= 120 && command <= 127)) {\n        throw new TypeError(\"Invalid channel mode message number.\");\n      }\n\n      if (isNaN(parseInt(value)) || value < 0 || value > 127) {\n        throw new RangeError(\"Value must be an integer between 0 and 127.\");\n      }\n\n    }\n\n    this.send(\n      [\n        (Enumerations.CHANNEL_MESSAGES.controlchange << 4) + (this.number - 1),\n        command,\n        value\n      ],\n      {time: Utilities.toTimestamp(options.time)}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sets OMNI mode to `\"on\"` or `\"off\"`. MIDI's OMNI mode causes the instrument to respond to\n   * messages from all channels.\n   *\n   * It should be noted that support for OMNI mode is not as common as it used to be.\n   *\n   * @param [state=true] {boolean} Whether to activate OMNI mode (`true`) or not (`false`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Invalid channel mode message name.\n   * @throws {RangeError} Channel mode controller numbers must be between 120 and 127.\n   * @throws {RangeError} Value must be an integer between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendOmniMode(state, options = {}) {\n\n    if (state === undefined || state) {\n      this.sendChannelMode(\"omnimodeon\", 0, options);\n    } else {\n      this.sendChannelMode(\"omnimodeoff\", 0, options);\n    }\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **channel aftertouch** message. For key-specific aftertouch, you should instead\n   * use [`sendKeyAftertouch()`]{@link #sendKeyAftertouch}.\n   *\n   * @param [pressure] {number} The pressure level (between `0` and `1`). If the `rawValue` option\n   * is set to `true`, the pressure can be defined by using an integer between `0` and `127`.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   * @throws RangeError Invalid channel aftertouch value.\n   */\n  sendChannelAftertouch(pressure, options = {}) {\n\n    if (WebMidi.validation) {\n\n      if (isNaN(parseFloat(pressure))) {\n        throw new RangeError(\"Invalid channel aftertouch value.\");\n      }\n\n      if (options.rawValue) {\n        if (!(pressure >= 0 && pressure <= 127 && Number.isInteger(pressure))) {\n          throw new RangeError(\n            \"Channel aftertouch raw value must be an integer between 0 and 127.\")\n          ;\n        }\n      } else {\n        if (!(pressure >= 0 && pressure <= 1)) {\n          throw new RangeError(\"Channel aftertouch value must be a float between 0 and 1.\");\n        }\n      }\n\n    }\n\n    // Normalize pressure to integer\n    if (!options.rawValue) pressure = Utilities.fromFloatTo7Bit(pressure);\n\n    this.send(\n      [\n        (Enumerations.CHANNEL_MESSAGES.channelaftertouch << 4) + (this.number - 1),\n        Math.round(pressure)\n      ],\n      {time: Utilities.toTimestamp(options.time)}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **master tuning** message. The value is decimal and must be larger than -65 semitones\n   * and smaller than 64 semitones.\n   *\n   * Because of the way the MIDI specification works, the decimal portion of the value will be\n   * encoded with a resolution of 14bit. The integer portion must be between -64 and 63\n   * inclusively. This function actually generates two MIDI messages: a **Master Coarse Tuning** and\n   * a **Master Fine Tuning** RPN messages.\n   *\n   * @param [value=0.0] {number} The desired decimal adjustment value in semitones (-65 < x < 64)\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The value must be a decimal number between larger than -65 and smaller\n   * than 64.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendMasterTuning(value, options = {}) {\n\n    // @todo allow passing value as msb/lsb pair (the same as pitch bend range)\n\n    value = parseFloat(value) || 0.0;\n\n    if (WebMidi.validation) {\n\n      if (!(value > -65 && value < 64)) {\n        throw new RangeError(\n          \"The value must be a decimal number larger than -65 and smaller than 64.\"\n        );\n      }\n\n    }\n\n    let coarse = Math.floor(value) + 64;\n    let fine = value - Math.floor(value);\n\n    // Calculate MSB and LSB for fine adjustment (14bit resolution)\n    fine = Math.round((fine + 1) / 2 * 16383);\n    let msb = (fine >> 7) & 0x7F;\n    let lsb = fine & 0x7F;\n\n    this.sendRpnValue(\"channelcoarsetuning\", coarse, options);\n    this.sendRpnValue(\"channelfinetuning\", [msb, lsb], options);\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **modulation depth range** message to adjust the depth of the modulation wheel's range.\n   * The range can be specified with the `semitones` parameter, the `cents` parameter or by\n   * specifying both parameters at the same time.\n   *\n   * @param {number} semitones The desired adjustment value in semitones (integer between 0 and\n   * 127).\n   *\n   * @param {number} [cents=0] The desired adjustment value in cents (integer between 0 and 127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendModulationRange(semitones, cents, options = {}) {\n\n    // @todo allow passing value as msb/lsb pair (the same as pitch bend range)\n    // when passing a single argument, semitones and cents shoud be combined\n\n    if (WebMidi.validation) {\n\n      if (!Number.isInteger(semitones) || !(semitones >= 0 && semitones <= 127)) {\n        throw new RangeError(\"The semitones value must be an integer between 0 and 127.\");\n      }\n\n      if (!(cents == undefined) && (!Number.isInteger(cents) || !(cents >= 0 && cents <= 127))) {\n        throw new RangeError(\"If specified, the cents value must be an integer between 0 and 127.\");\n      }\n\n    }\n\n    // Default value for cents\n    if (!(cents >= 0 && cents <= 127)) cents = 0;\n\n    this.sendRpnValue(\"modulationrange\", [semitones, cents], options);\n\n    return this;\n\n  }\n\n  /**\n   * Sets a non-registered parameter (NRPN) to the specified value. The NRPN is selected by passing\n   * in a two-position array specifying the values of the two control bytes. The value is specified\n   * by passing in a single integer (most cases) or an array of two integers.\n   *\n   * NRPNs are not standardized in any way. Each manufacturer is free to implement them any way\n   * they see fit. For example, according to the Roland GS specification, you can control the\n   * **vibrato rate** using NRPN (1, 8). Therefore, to set the **vibrato rate** value to **123** you\n   * would use:\n   *\n   * ```js\n   * WebMidi.outputs[0].channels[0].sendNrpnValue([1, 8], 123);\n   * ```\n   *\n   * In some rarer cases, you need to send two values with your NRPN messages. In such cases, you\n   * would use a 2-position array. For example, for its **ClockBPM** parameter (2, 63), Novation\n   * uses a 14-bit value that combines an MSB and an LSB (7-bit values). So, for example, if the\n   * value to send was 10, you could use:\n   *\n   * ```js\n   * WebMidi.outputs[0].channels[0].sendNrpnValue([2, 63], [0, 10]);\n   * ```\n   *\n   * For further implementation details, refer to the manufacturer's documentation.\n   *\n   * @param nrpn {number[]} A two-position array specifying the two control bytes (0x63,\n   * 0x62) that identify the non-registered parameter.\n   *\n   * @param [data=[]] {number|number[]} An integer or an array of integers with a length of 1 or 2\n   * specifying the desired data.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The control value must be between 0 and 127.\n   * @throws {RangeError} The msb value must be between 0 and 127\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendNrpnValue(nrpn, data, options = {}) {\n\n    data = [].concat(data);\n\n    if (WebMidi.validation) {\n\n      if (!Array.isArray(nrpn) || !Number.isInteger(nrpn[0]) || !Number.isInteger(nrpn[1])) {\n        throw new TypeError(\"The specified NRPN is invalid.\");\n      }\n\n      if (!(nrpn[0] >= 0 && nrpn[0] <= 127)) {\n        throw new RangeError(\"The first byte of the NRPN must be between 0 and 127.\");\n      }\n\n      if (!(nrpn[1] >= 0 && nrpn[1] <= 127)) {\n        throw new RangeError(\"The second byte of the NRPN must be between 0 and 127.\");\n      }\n\n      data.forEach(value => {\n        if (!(value >= 0 && value <= 127)) {\n          throw new RangeError(\"The data bytes of the NRPN must be between 0 and 127.\");\n        }\n      });\n\n    }\n\n    this._selectNonRegisteredParameter(nrpn, options);\n    this._setCurrentParameter(data, options);\n    this._deselectNonRegisteredParameter(options);\n\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **pitch bend** message at the scheduled time. The resulting bend is relative to\n   * the pitch bend range that has been defined. The range can be set with\n   * [`sendPitchBendRange()`]{@link #sendPitchBendRange}. So, for example, if the pitch\n   * bend range has been set to 12 semitones, using a bend value of -1 will bend the note 1 octave\n   * below its nominal value.\n   *\n   * @param {number|number[]} [value] The intensity of the bend (between -1.0 and 1.0). A value of\n   * zero means no bend. If the `rawValue` option is set to `true`, the intensity of the bend can be\n   * defined by either using a single integer between 0 and 127 (MSB) or an array of two integers\n   * between 0 and 127 representing, respectively, the MSB (most significant byte) and the LSB\n   * (least significant byte). The MSB is expressed in semitones with `64` meaning no bend. A value\n   * lower than `64` bends downwards while a value higher than `64` bends upwards. The LSB is\n   * expressed in cents (1/100 of a semitone). An LSB of `64` also means no bend.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered as a float between -1.0 and 1.0 (default) or as raw integer between 0 and 127 (or\n   * an array of 2 integers if using both MSB and LSB).\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendPitchBend(value, options = {}) {\n\n    // @todo standardize the way msb/lsb are passed in\n\n    if (WebMidi.validation) {\n\n      if (options.rawValue && Array.isArray(value)) {\n\n        if (!(value[0] >= 0 && value[0] <= 127)) {\n          throw new RangeError(\"The pitch bend MSB must be an integer between 0 and 127.\");\n        }\n        if (!(value[1] >= 0 && value[1] <= 127)) {\n          throw new RangeError(\"The pitch bend LSB must be an integer between 0 and 127.\");\n        }\n\n      } else if (options.rawValue && !Array.isArray(value)) {\n\n        if (!(value >= 0 && value <= 127)) {\n          throw new RangeError(\"The pitch bend MSB must be an integer between 0 and 127.\");\n        }\n\n      } else {\n\n        if (isNaN(value) || value === null) {\n          throw new RangeError(\"Invalid pitch bend value.\");\n        }\n\n        if (!(value >= -1 && value <= 1)) {\n          throw new RangeError(\"The pitch bend value must be a float between -1 and 1.\");\n        }\n\n      }\n\n    }\n\n    let msb = 0;\n    let lsb = 0;\n\n    // Calculate MSB and LSB for both scenarios\n    if (options.rawValue && Array.isArray(value)) {\n      msb = value[0];\n      lsb = value[1];\n    } else if (options.rawValue && !Array.isArray(value)) {\n      msb = value;\n    } else {\n      const result = Utilities.fromFloatToMsbLsb((value + 1) / 2); // b/c value is -1 to 1\n      msb = result.msb;\n      lsb = result.lsb;\n    }\n\n    this.send(\n      [\n        (Enumerations.CHANNEL_MESSAGES.pitchbend << 4) + (this.number - 1),\n        lsb,\n        msb\n      ],\n      {time: Utilities.toTimestamp(options.time)}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sends a **pitch bend range** message at the scheduled time to adjust the range used by the\n   * pitch bend lever. The range is specified by using the `semitones` and `cents` parameters. For\n   * example, setting the `semitones` parameter to `12` means that the pitch bend range will be 12\n   * semitones above and below the nominal pitch.\n   *\n   * @param semitones {number} The desired adjustment value in semitones (between 0 and 127). While\n   * nothing imposes that in the specification, it is very common for manufacturers to limit the\n   * range to 2 octaves (-12 semitones to 12 semitones).\n   *\n   * @param [cents=0] {number} The desired adjustment value in cents (integer between 0-127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The semitones value must be an integer between 0 and 127.\n   * @throws {RangeError} The cents value must be an integer between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendPitchBendRange(semitones, cents, options = {}) {\n\n    // @todo use single value as parameter or pair of msb/lsb\n\n    if (WebMidi.validation) {\n\n      if (!Number.isInteger(semitones) || !(semitones >= 0 && semitones <= 127)) {\n        throw new RangeError(\"The semitones value must be an integer between 0 and 127.\");\n      }\n\n      if (!Number.isInteger(cents) || !(cents >= 0 && cents <= 127)) {\n        throw new RangeError(\"The cents value must be an integer between 0 and 127.\");\n      }\n\n    }\n\n    this.sendRpnValue(\"pitchbendrange\", [semitones, cents], options);\n    return this;\n\n  }\n\n  /**\n   * Sends a MIDI **program change** message at the scheduled time.\n   *\n   * @param [program=1] {number} The MIDI patch (program) number (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Failed to execute 'send' on 'MIDIOutput': The value at index 1 is greater\n   * than 0xFF.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   */\n  sendProgramChange(program, options = {}) {\n\n    program = parseInt(program) || 0;\n\n    if (WebMidi.validation) {\n\n      if (!(program >= 0 && program <= 127)) {\n        throw new RangeError(\"The program number must be between 0 and 127.\");\n      }\n\n    }\n\n    this.send(\n      [\n        (Enumerations.CHANNEL_MESSAGES.programchange << 4) + (this.number - 1),\n        program\n      ],\n      {time: Utilities.toTimestamp(options.time)}\n    );\n\n    return this;\n\n  }\n\n  /**\n   * Sets the specified MIDI registered parameter to the desired value. The value is defined with\n   * up to two bytes of data (msb, lsb) that each can go from 0 to 127.\n   *\n   * MIDI\n   * [registered parameters](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\n   * extend the original list of control change messages. The MIDI 1.0 specification lists only a\n   * limited number of them:\n   *\n   * | Numbers      | Function                 |\n   * |--------------|--------------------------|\n   * | (0x00, 0x00) | `pitchbendrange`         |\n   * | (0x00, 0x01) | `channelfinetuning`      |\n   * | (0x00, 0x02) | `channelcoarsetuning`    |\n   * | (0x00, 0x03) | `tuningprogram`          |\n   * | (0x00, 0x04) | `tuningbank`             |\n   * | (0x00, 0x05) | `modulationrange`        |\n   * | (0x3D, 0x00) | `azimuthangle`           |\n   * | (0x3D, 0x01) | `elevationangle`         |\n   * | (0x3D, 0x02) | `gain`                   |\n   * | (0x3D, 0x03) | `distanceratio`          |\n   * | (0x3D, 0x04) | `maximumdistance`        |\n   * | (0x3D, 0x05) | `maximumdistancegain`    |\n   * | (0x3D, 0x06) | `referencedistanceratio` |\n   * | (0x3D, 0x07) | `panspreadangle`         |\n   * | (0x3D, 0x08) | `rollangle`              |\n   *\n   * Note that the **Tuning Program** and **Tuning Bank** parameters are part of the *MIDI Tuning\n   * Standard*, which is not widely implemented.\n   *\n   * @param rpn {string|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (e.g. `[0x65, 0x64]`) that identify the\n   * registered parameter.\n   *\n   * @param [data=[]] {number|number[]} An single integer or an array of integers with a maximum\n   * length of 2 specifying the desired data.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendRpnValue(rpn, data, options = {}) {\n\n    if (!Array.isArray(rpn)) rpn = Enumerations.REGISTERED_PARAMETERS[rpn];\n\n    if (WebMidi.validation) {\n\n      if (!Number.isInteger(rpn[0]) || !Number.isInteger(rpn[1])) {\n        throw new TypeError(\"The specified NRPN is invalid.\");\n      }\n\n      if (!(rpn[0] >= 0 && rpn[0] <= 127)) {\n        throw new RangeError(\"The first byte of the RPN must be between 0 and 127.\");\n      }\n\n      if (!(rpn[1] >= 0 && rpn[1] <= 127)) {\n        throw new RangeError(\"The second byte of the RPN must be between 0 and 127.\");\n      }\n\n      [].concat(data).forEach(value => {\n        if (!(value >= 0 && value <= 127)) {\n          throw new RangeError(\"The data bytes of the RPN must be between 0 and 127.\");\n        }\n      });\n\n    }\n\n    this._selectRegisteredParameter(rpn, options);\n    this._setCurrentParameter(data, options);\n    this._deselectRegisteredParameter(options);\n\n    return this;\n\n  }\n\n  /**\n   * Sets the MIDI tuning bank to use. Note that the **Tuning Bank** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param value {number} The desired tuning bank (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The bank value must be between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendTuningBank(value, options = {}) {\n\n    if (WebMidi.validation) {\n\n      if (!Number.isInteger(value) || !(value >= 0 && value <= 127)) {\n        throw new RangeError(\"The tuning bank number must be between 0 and 127.\");\n      }\n\n    }\n\n    this.sendRpnValue(\"tuningbank\", value, options);\n    return this;\n\n  }\n\n  /**\n   * Sets the MIDI tuning program to use. Note that the **Tuning Program** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param value {number} The desired tuning program (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The program value must be between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendTuningProgram(value, options = {}) {\n\n    if (WebMidi.validation) {\n\n      if (!Number.isInteger(value) || !(value >= 0 && value <= 127)) {\n        throw new RangeError(\"The tuning program number must be between 0 and 127.\");\n      }\n\n    }\n\n    this.sendRpnValue(\"tuningprogram\", value, options);\n    return this;\n\n  }\n\n  /**\n   * Turns local control on or off. Local control is usually enabled by default. If you disable it,\n   * the instrument will no longer trigger its own sounds. It will only send the MIDI messages to\n   * its out port.\n   *\n   * @param [state=false] {boolean} Whether to activate local control (`true`) or disable it\n   * (`false`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendLocalControl(state, options = {}) {\n    if (state) {\n      return this.sendChannelMode(\"localcontrol\", 127, options);\n    } else {\n      return this.sendChannelMode(\"localcontrol\", 0, options);\n    }\n  }\n\n  /**\n   * Sends an **all notes off** channel mode message. This will make all currently playing notes\n   * fade out just as if their key had been released. This is different from the\n   * [`sendAllSoundOff()`]{@link #sendAllSoundOff} method which mutes all sounds immediately.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendAllNotesOff(options = {}) {\n    return this.sendChannelMode(\"allnotesoff\", 0, options);\n  }\n\n  /**\n   * Sends an **all sound off** channel mode message. This will silence all sounds playing on that\n   * channel but will not prevent new sounds from being triggered.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendAllSoundOff(options = {}) {\n    return this.sendChannelMode(\"allsoundoff\", 0, options);\n  }\n\n  /**\n   * Sends a **reset all controllers** channel mode message. This resets all controllers, such as\n   * the pitch bend, to their default value.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendResetAllControllers(options = {}) {\n    return this.sendChannelMode(\"resetallcontrollers\", 0, options);\n  }\n\n  /**\n   * Sets the polyphonic mode. In `\"poly\"` mode (usually the default), multiple notes can be played\n   * and heard at the same time. In `\"mono\"` mode, only one note will be heard at once even if\n   * multiple notes are being played.\n   *\n   * @param {string} [mode=poly] The mode to use: `\"mono\"` or `\"poly\"`.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendPolyphonicMode(mode, options = {}) {\n    if (mode === \"mono\") {\n      return this.sendChannelMode(\"monomodeon\", 0, options);\n    } else {\n      return this.sendChannelMode(\"polymodeon\", 0, options);\n    }\n  }\n\n  /**\n   * An integer to offset the reported octave of outgoing note-specific messages (`noteon`,\n   * `noteoff` and `keyaftertouch`). By default, middle C (MIDI note number 60) is placed on the 4th\n   * octave (C4).\n   *\n   * Note that this value is combined with the global offset value defined in\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) and with the parent value defined in\n   * [`Output.octaveOffset`]{@link Output#octaveOffset}.\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  get octaveOffset() {\n    return this._octaveOffset;\n  }\n  set octaveOffset(value) {\n\n    if (this.validation) {\n      value = parseInt(value);\n      if (isNaN(value)) throw new TypeError(\"The 'octaveOffset' property must be an integer.\");\n    }\n\n    this._octaveOffset = value;\n\n  }\n\n  /**\n   * The parent [`Output`]{@link Output} this channel belongs to.\n   * @type {Output}\n   * @since 3.0\n   */\n  get output() {\n    return this._output;\n  }\n\n  /**\n   * This channel's MIDI number (`1` - `16`).\n   * @type {number}\n   * @since 3.0\n   */\n  get number() {\n    return this._number;\n  }\n\n}\n"
  },
  {
    "path": "src/Utilities.js",
    "content": "import {Note} from \"./Note.js\";\nimport {WebMidi} from \"./WebMidi.js\";\nimport {Enumerations} from \"./Enumerations.js\";\n\n/**\n * The `Utilities` class contains general-purpose utility methods. All methods are static and\n * should be called using the class name. For example: `Utilities.getNoteDetails(\"C4\")`.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Utilities {\n\n  /**\n   * Returns a MIDI note number matching the identifier passed in the form of a string. The\n   * identifier must include the octave number. The identifier also optionally include a sharp (#),\n   * a double sharp (##), a flat (b) or a double flat (bb) symbol. For example, these are all valid\n   * identifiers: C5, G4, D#-1, F0, Gb7, Eb-1, Abb4, B##6, etc.\n   *\n   * When converting note identifiers to numbers, C4 is considered to be middle C (MIDI note number\n   * 60) as per the scientific pitch notation standard.\n   *\n   * The resulting note number can be offset by using the `octaveOffset` parameter.\n   *\n   * @param identifier {string} The identifier in the form of a letter, followed by an optional \"#\",\n   * \"##\", \"b\" or \"bb\" followed by the octave number. For exemple: C5, G4, D#-1, F0, Gb7, Eb-1,\n   * Abb4, B##6, etc.\n   *\n   * @param {number} [octaveOffset=0] A integer to offset the octave by.\n   *\n   * @returns {number} The MIDI note number (an integer between 0 and 127).\n   *\n   * @throws RangeError Invalid 'octaveOffset' value\n   *\n   * @throws TypeError Invalid note identifier\n   *\n   * @license Apache-2.0\n   * @since 3.0.0\n   * @static\n   */\n  static toNoteNumber(identifier, octaveOffset = 0) {\n\n    // Validation\n    octaveOffset = octaveOffset == undefined ? 0 : parseInt(octaveOffset);\n    if (isNaN(octaveOffset)) throw new RangeError(\"Invalid 'octaveOffset' value\");\n    if (typeof identifier !== \"string\") identifier = \"\";\n\n    const fragments = this.getNoteDetails(identifier);\n    if (!fragments) throw new TypeError(\"Invalid note identifier\");\n\n    const notes = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 };\n    let result = (fragments.octave + 1 + octaveOffset) * 12;\n    result += notes[fragments.name];\n\n    if (fragments.accidental) {\n      if (fragments.accidental.startsWith(\"b\")) {\n        result -= fragments.accidental.length;\n      } else {\n        result += fragments.accidental.length;\n      }\n    }\n\n    if (result < 0 || result > 127) throw new RangeError(\"Invalid octaveOffset value\");\n\n    return result;\n\n  }\n\n  /**\n   * Given a proper note identifier (`C#4`, `Gb-1`, etc.) or a valid MIDI note number (0-127), this\n   * method returns an object containing broken down details about the specified note (uppercase\n   * letter, accidental and octave).\n   *\n   * When a number is specified, the translation to note is done using a value of 60 for middle C\n   * (C4 = middle C).\n   *\n   * @param value {string|number} A note identifier A  atring (\"C#4\", \"Gb-1\", etc.) or a MIDI note\n   * number (0-127).\n   *\n   * @returns {{accidental: string, identifier: string, name: string, octave: number }}\n   *\n   * @throws TypeError Invalid note identifier\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static getNoteDetails(value) {\n\n    if (Number.isInteger(value)) value = this.toNoteIdentifier(value);\n\n    const matches = value.match(/^([CDEFGAB])(#{0,2}|b{0,2})(-?\\d+)$/i);\n    if (!matches) throw new TypeError(\"Invalid note identifier\");\n\n    const name = matches[1].toUpperCase();\n    const octave = parseInt(matches[3]);\n    let accidental = matches[2].toLowerCase();\n    accidental = accidental === \"\" ? undefined : accidental;\n\n    const fragments = {\n      accidental: accidental,\n      identifier: name + (accidental || \"\") + octave,\n      name: name,\n      octave: octave\n    };\n\n    return fragments;\n\n  }\n\n  /**\n   * Returns a sanitized array of valid MIDI channel numbers (1-16). The parameter should be a\n   * single integer or an array of integers.\n   *\n   * For backwards-compatibility, passing `undefined` as a parameter to this method results in all\n   * channels being returned (1-16). Otherwise, parameters that cannot successfully be parsed to\n   * integers between 1 and 16 are silently ignored.\n   *\n   * @param [channel] {number|number[]} An integer or an array of integers to parse as channel\n   * numbers.\n   *\n   * @returns {number[]} An array of 0 or more valid MIDI channel numbers.\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static sanitizeChannels(channel) {\n\n    let channels;\n\n    if (WebMidi.validation) {\n\n      if (channel === \"all\") { // backwards-compatibility\n        channels = [\"all\"];\n      } else if (channel === \"none\") { // backwards-compatibility\n        return [];\n      }\n\n    }\n\n    if (!Array.isArray(channel)) {\n      channels = [channel];\n    } else {\n      channels = channel;\n    }\n\n    // In order to preserve backwards-compatibility, we let this assignment as it is.\n    if (channels.indexOf(\"all\") > -1) {\n      channels = Enumerations.MIDI_CHANNEL_NUMBERS;\n    }\n\n    return channels\n      .map(function(ch) {\n        return parseInt(ch);\n      })\n      .filter(function(ch) {\n        return (ch >= 1 && ch <= 16);\n      });\n\n  }\n\n  /**\n   * Returns a valid timestamp, relative to the navigation start of the document, derived from the\n   * `time` parameter. If the parameter is a string starting with the \"+\" sign and followed by a\n   * number, the resulting timestamp will be the sum of the current timestamp plus that number. If\n   * the parameter is a positive number, it will be returned as is. Otherwise, false will be\n   * returned.\n   *\n   * @param [time] {number|string} The time string (e.g. `\"+2000\"`) or number to parse\n   * @return {number|false} A positive number or `false` (if the time cannot be converted)\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static toTimestamp(time) {\n\n    let value = false;\n\n    const parsed = parseFloat(time);\n    if (isNaN(parsed)) return false;\n\n    if (typeof time === \"string\" && time.substring(0, 1) === \"+\") {\n      if (parsed >= 0) value = WebMidi.time + parsed;\n    } else {\n      if (parsed >= 0) value = parsed;\n    }\n\n    return value;\n\n  }\n\n  /**\n   * Returns a valid MIDI note number (0-127) given the specified input. The input usually is a\n   * string containing a note identifier (`\"C3\"`, `\"F#4\"`, `\"D-2\"`, `\"G8\"`, etc.). If an integer\n   * between 0 and 127 is passed, it will simply be returned as is (for convenience). Other strings\n   * will be parsed for integer value, if possible.\n   *\n   * If the input is an identifier, the resulting note number is offset by the `octaveOffset`\n   * parameter. For example, if you pass in \"C4\" (note number 60) and the `octaveOffset` value is\n   * -2, the resulting MIDI note number will be 36.\n   *\n   * @param input {string|number} A string or number to extract the MIDI note number from.\n   * @param octaveOffset {number} An integer to offset the octave by\n   *\n   * @returns {number|false} A valid MIDI note number (0-127) or `false` if the input could not\n   * successfully be parsed to a note number.\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static guessNoteNumber(input, octaveOffset) {\n\n    // Validate and, if necessary, assign default\n    octaveOffset = parseInt(octaveOffset) || 0;\n\n    let output = false;\n\n    // Check input type\n    if (Number.isInteger(input) && input >= 0 && input <= 127) {        // uint\n      output = parseInt(input);\n    } else if (parseInt(input) >= 0 && parseInt(input) <= 127) {        // float or uint as string\n      output = parseInt(input);\n    } else if (typeof input === \"string\" || input instanceof String) {  // string\n      try {\n        output = this.toNoteNumber(input.trim(), octaveOffset);\n      } catch (e) {\n        return false;\n      }\n    }\n\n    return output;\n\n  }\n\n  /**\n   * Returns an identifier string representing a note name (with optional accidental) followed by an\n   * octave number. The octave can be offset by using the `octaveOffset` parameter.\n   *\n   * @param {number} number The MIDI note number to convert to a note identifier\n   * @param {number} octaveOffset An offset to apply to the resulting octave\n   *\n   * @returns {string}\n   *\n   * @throws RangeError Invalid note number\n   * @throws RangeError Invalid octaveOffset value\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static toNoteIdentifier(number, octaveOffset) {\n\n    number = parseInt(number);\n    if (isNaN(number) || number < 0 || number > 127) throw new RangeError(\"Invalid note number\");\n\n    octaveOffset = octaveOffset == undefined ? 0 : parseInt(octaveOffset);\n    if (isNaN(octaveOffset)) throw new RangeError(\"Invalid octaveOffset value\");\n\n    const notes = [\"C\", \"C#\", \"D\", \"D#\", \"E\", \"F\", \"F#\", \"G\", \"G#\", \"A\", \"A#\", \"B\"];\n    const octave = Math.floor(number / 12 - 1) + octaveOffset;\n    return notes[number % 12] + octave.toString();\n\n  }\n\n  /**\n   * Converts the `input` parameter to a valid [`Note`]{@link Note} object. The input usually is an\n   * unsigned integer (0-127) or a note identifier (`\"C4\"`, `\"G#5\"`, etc.). If the input is a\n   * [`Note`]{@link Note} object, it will be returned as is.\n   *\n   * If the input is a note number or identifier, it is possible to specify options by providing the\n   * `options` parameter.\n   *\n   * @param [input] {number|string|Note}\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the note should\n   * be explicitly stopped.\n   *\n   * @param {number} [options.attack=0.5] The note's attack velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.release=0.5] The note's release velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawAttack=64] The note's attack velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawRelease=64] The note's release velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.octaveOffset=0] An integer to offset the octave by. **This is only\n   * used when the input value is a note identifier.**\n   *\n   * @returns {Note}\n   *\n   * @throws TypeError The input could not be parsed to a note\n   *\n   * @since version 3.0.0\n   * @static\n   */\n  static buildNote(input, options= {}) {\n\n    options.octaveOffset = parseInt(options.octaveOffset) || 0;\n\n    // If it's already a Note, we're done\n    if (input instanceof Note) return input;\n\n    let number = this.guessNoteNumber(input, options.octaveOffset);\n\n    if (number === false) { // We use a comparison b/c the note can be 0 (which equates to false)\n      throw new TypeError(`The input could not be parsed as a note (${input})`);\n    }\n\n    // If we got here, we have a proper note number. Before creating the new note, we strip out\n    // 'octaveOffset' because it has already been factored in when calling guessNoteNumber().\n    options.octaveOffset = undefined;\n    return new Note(number, options);\n\n  }\n\n  /**\n   * Converts an input value, which can be an unsigned integer (0-127), a note identifier, a\n   * [`Note`]{@link Note}  object or an array of the previous types, to an array of\n   * [`Note`]{@link Note}  objects.\n   *\n   * [`Note`]{@link Note}  objects are returned as is. For note numbers and identifiers, a\n   * [`Note`]{@link Note} object is created with the options specified. An error will be thrown when\n   * encountering invalid input.\n   *\n   * Note: if both the `attack` and `rawAttack` options are specified, the later has priority. The\n   * same goes for `release` and `rawRelease`.\n   *\n   * @param [notes] {number|string|Note|number[]|string[]|Note[]}\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the note should\n   * be explicitly stopped.\n   *\n   * @param {number} [options.attack=0.5] The note's attack velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.release=0.5] The note's release velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawAttack=64] The note's attack velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawRelease=64] The note's release velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.octaveOffset=0] An integer to offset the octave by. **This is only\n   * used when the input value is a note identifier.**\n   *\n   * @returns {Note[]}\n   *\n   * @throws TypeError An element could not be parsed as a note.\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static buildNoteArray(notes, options = {}) {\n\n    let result = [];\n    if (!Array.isArray(notes)) notes = [notes];\n\n    notes.forEach(note => {\n      result.push(this.buildNote(note, options));\n    });\n\n    return result;\n\n  }\n\n  /**\n   * Returns a number between 0 and 1 representing the ratio of the input value divided by 127 (7\n   * bit). The returned value is restricted between 0 and 1 even if the input is greater than 127 or\n   * smaller than 0.\n   *\n   * Passing `Infinity` will return `1` and passing `-Infinity` will return `0`. Otherwise, when the\n   * input value cannot be converted to an integer, the method returns 0.\n   *\n   * @param value {number} A positive integer between 0 and 127 (inclusive)\n   * @returns {number} A number between 0 and 1 (inclusive)\n   * @static\n   */\n  static from7bitToFloat(value) {\n    if (value === Infinity) value = 127;\n    value = parseInt(value) || 0;\n    return Math.min(Math.max(value / 127, 0), 1);\n  }\n\n  /**\n   * Returns an integer between 0 and 127 which is the result of multiplying the input value by\n   * 127. The input value should be a number between 0 and 1 (inclusively). The returned value is\n   * restricted between 0 and 127 even if the input is greater than 1 or smaller than 0.\n   *\n   * Passing `Infinity` will return `127` and passing `-Infinity` will return `0`. Otherwise, when\n   * the input value cannot be converted to a number, the method returns 0.\n   *\n   * @param value {number} A positive float between 0 and 1 (inclusive)\n   * @returns {number} A number between 0 and 127 (inclusive)\n   * @static\n   */\n  static fromFloatTo7Bit(value) {\n    if (value === Infinity) value = 1;\n    value = parseFloat(value) || 0;\n    return Math.min(Math.max(Math.round(value * 127), 0), 127);\n  }\n\n  /**\n   * Combines and converts MSB and LSB values (0-127) to a float between 0 and 1. The returned value\n   * is within between 0 and 1 even if the result is greater than 1 or smaller than 0.\n   *\n   * @param msb {number} The most significant byte as a integer between 0 and 127.\n   * @param [lsb=0] {number} The least significant byte as a integer between 0 and 127.\n   * @returns {number} A float between 0 and 1.\n   */\n  static fromMsbLsbToFloat(msb, lsb = 0) {\n\n    if (WebMidi.validation) {\n      msb = Math.min(Math.max(parseInt(msb) || 0, 0), 127);\n      lsb = Math.min(Math.max(parseInt(lsb) || 0, 0), 127);\n    }\n\n    const value = ((msb << 7) + lsb) / 16383;\n    return Math.min(Math.max(value, 0), 1);\n\n  }\n\n  /**\n   * Extracts 7bit MSB and LSB values from the supplied float.\n   *\n   * @param value {number} A float between 0 and 1\n   * @returns {{lsb: number, msb: number}}\n   */\n  static fromFloatToMsbLsb(value) {\n\n    if (WebMidi.validation) {\n      value = Math.min(Math.max(parseFloat(value) || 0, 0), 1);\n    }\n\n    const multiplied = Math.round(value * 16383);\n\n    return {\n      msb: multiplied >> 7,\n      lsb: multiplied & 0x7F\n    };\n\n  }\n\n  /**\n   * Returns the supplied MIDI note number offset by the requested octave and semitone values. If\n   * the calculated value is less than 0, 0 will be returned. If the calculated value is more than\n   * 127, 127 will be returned. If an invalid offset value is supplied, 0 will be used.\n   *\n   * @param number {number} The MIDI note to offset as an integer between 0 and 127.\n   * @param octaveOffset {number} An integer to offset the note by (in octave)\n   * @param octaveOffset {number} An integer to offset the note by (in semitones)\n   * @returns {number} An integer between 0 and 127\n   *\n   * @throws {Error} Invalid note number\n   * @static\n   */\n  static offsetNumber(number, octaveOffset = 0, semitoneOffset = 0) {\n\n    if (WebMidi.validation) {\n      number = parseInt(number);\n      if (isNaN(number)) throw new Error(\"Invalid note number\");\n      octaveOffset = parseInt(octaveOffset) || 0;\n      semitoneOffset = parseInt(semitoneOffset) || 0;\n    }\n\n    return Math.min(Math.max(number + (octaveOffset * 12) + semitoneOffset, 0), 127);\n\n  }\n\n  /**\n   * Returns the name of the first property of the supplied object whose value is equal to the one\n   * supplied. If nothing is found, `undefined` is returned.\n   *\n   * @param object {object} The object to look for the property in.\n   * @param value {*} Any value that can be expected to be found in the object's properties.\n   * @returns {string|undefined} The name of the matching property or `undefined` if nothing is\n   * found.\n   * @static\n   */\n  static getPropertyByValue(object, value) {\n    return Object.keys(object).find(key => object[key] === value);\n  }\n\n  /**\n   * Returns the name of a control change message matching the specified number (0-127). Some valid\n   * control change numbers do not have a specific name or purpose assigned in the MIDI\n   * [spec](https://midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2).\n   * In these cases, the method returns `controllerXXX` (where XXX is the number).\n   *\n   * @param {number} number An integer (0-127) representing the control change message\n   * @returns {string|undefined} The matching control change name or `undefined` if no match was\n   * found.\n   *\n   * @static\n   */\n  static getCcNameByNumber(number) {\n\n    if (WebMidi.validation) {\n      number = parseInt(number);\n      if (!(number >= 0 && number <= 127)) return undefined;\n    }\n\n    return Enumerations.CONTROL_CHANGE_MESSAGES[number].name;\n\n  }\n\n  /**\n   * Returns the number of a control change message matching the specified name.\n   *\n   * @param {string} name A string representing the control change message\n   * @returns {string|undefined} The matching control change number or `undefined` if no match was\n   * found.\n   *\n   * @since 3.1\n   * @static\n   */\n  static getCcNumberByName(name) {\n    let message = Enumerations.CONTROL_CHANGE_MESSAGES.find(element => element.name === name);\n    if (message) {\n      return message.number;\n    } else {\n      // Legacy (remove in v4)\n      return Enumerations.MIDI_CONTROL_CHANGE_MESSAGES[name];\n    }\n  }\n\n  /**\n   * Returns the channel mode name matching the specified number. If no match is found, the function\n   * returns `false`.\n   *\n   * @param {number} number An integer representing the channel mode message (120-127)\n   * @returns {string|false} The name of the matching channel mode or `false` if no match could be\n   * found.\n   *\n   * @since 2.0.0\n   */\n  static getChannelModeByNumber(number) {\n\n    if ( !(number >= 120 && number <= 127) ) return false;\n\n    for (let cm in Enumerations.CHANNEL_MODE_MESSAGES) {\n\n      if (\n        Enumerations.CHANNEL_MODE_MESSAGES.hasOwnProperty(cm) &&\n        number === Enumerations.CHANNEL_MODE_MESSAGES[cm]\n      ) {\n        return cm;\n      }\n\n    }\n\n    return false;\n\n  }\n\n  /**\n   * Indicates whether the execution environment is Node.js (`true`) or not (`false`)\n   * @type {boolean}\n   */\n  static get isNode() {\n    return typeof process !== \"undefined\" &&\n      process.versions != null &&\n      process.versions.node != null;\n  }\n\n  /**\n   * Indicates whether the execution environment is a browser (`true`) or not (`false`)\n   * @type {boolean}\n   */\n  static get isBrowser() {\n    return typeof window !== \"undefined\" && typeof window.document !== \"undefined\";\n  }\n\n}\n"
  },
  {
    "path": "src/WebMidi.js",
    "content": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {Input} from \"./Input.js\";\nimport {Output} from \"./Output.js\";\nimport {Utilities} from \"./Utilities.js\";\nimport {Enumerations} from \"./Enumerations.js\";\n\n/*START-CJS*/\n\n// This code will only be included in the CJS version (CommonJS).\n\n/*\n\ncoud we use this instead of eval():\n\nlet jzz = await Object.getPrototypeOf(async function() {}).constructor(`\n  let jzz = await import(\"jzz\");\n  return jzz.default;\n`)();\n\n */\n\n// If this code is executed by Node.js then we must import the `jzz` module. I import it in this\n// convoluted way to prevent Webpack from automatically bundling it in browser bundles where it\n// isn't needed.\nif (Utilities.isNode) {\n\n  // Some environments may have both Node.js and browser runtimes (Electron, NW.js, React Native,\n  // etc.) so we also check for the presence of the window.navigator property.\n  try {\n    window.navigator;\n  } catch (err) {\n    let jzz;\n    eval('jzz = require(\"jzz\")');\n    if (!global.navigator) global.navigator = {}; // for Node.js prior to v21\n    Object.assign(global.navigator, jzz);\n  }\n\n  // The `performance` module appeared in Node.js v8.5.0 but has started to be automatically\n  // imported only in v16+.\n  try {\n    performance;\n  } catch (err) {\n    let performance;\n    eval('performance = require(\"perf_hooks\").performance');\n    global.performance = performance;\n  }\n\n}\n\n/*END-CJS*/\n/**\n * The `WebMidi` object makes it easier to work with the low-level Web MIDI API. Basically, it\n * simplifies sending outgoing MIDI messages and reacting to incoming MIDI messages.\n *\n * When using the WebMidi.js library, you should know that the `WebMidi` class has already been\n * instantiated. You cannot instantiate it yourself. If you use the **IIFE** version, you should\n * simply use the global object called `WebMidi`. If you use the **CJS** (CommonJS) or **ESM** (ES6\n * module) version, you get an already-instantiated object when you import the module.\n *\n * @fires WebMidi#connected\n * @fires WebMidi#disabled\n * @fires WebMidi#disconnected\n * @fires WebMidi#enabled\n * @fires WebMidi#error\n * @fires WebMidi#midiaccessgranted\n * @fires WebMidi#portschanged\n *\n * @extends EventEmitter\n * @license Apache-2.0\n */\nclass WebMidi extends EventEmitter {\n\n  /**\n   * The WebMidi class is a singleton and you cannot instantiate it directly. It has already been\n   * instantiated for you.\n   */\n  constructor() {\n\n    super();\n\n    /**\n     * Object containing system-wide default values that can be changed to customize how the library\n     * works.\n     *\n     * @type {object}\n     *\n     * @property {object}  defaults.note - Default values relating to note\n     * @property {number}  defaults.note.attack - A number between 0 and 127 representing the\n     * default attack velocity of notes. Initial value is 64.\n     * @property {number}  defaults.note.release - A number between 0 and 127 representing the\n     * default release velocity of notes. Initial value is 64.\n     * @property {number}  defaults.note.duration - A number representing the default duration of\n     * notes (in seconds). Initial value is Infinity.\n     */\n    this.defaults = {\n      note: {\n        attack: Utilities.from7bitToFloat(64),\n        release: Utilities.from7bitToFloat(64),\n        duration: Infinity\n      }\n    };\n\n    /**\n     * The [`MIDIAccess`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess)\n     * instance used to talk to the lower-level Web MIDI API. This should not be used directly\n     * unless you know what you are doing.\n     *\n     * @type {MIDIAccess}\n     * @readonly\n     */\n    this.interface = null;\n\n    /**\n     * Indicates whether argument validation and backwards-compatibility checks are performed\n     * throughout the WebMidi.js library for object methods and property setters.\n     *\n     * This is an advanced setting that should be used carefully. Setting `validation` to `false`\n     * improves performance but should only be done once the project has been thoroughly tested with\n     * `validation` turned on.\n     *\n     * @type {boolean}\n     */\n    this.validation = true;\n\n    /**\n     * Array of all (Input) objects\n     * @type {Input[]}\n     * @private\n     */\n    this._inputs = [];\n\n    /**\n     * Array of disconnected [`Input`](Input) objects. This is used when inputs are plugged back in\n     * to retain their previous state.\n     * @type {Input[]}\n     * @private\n     */\n    this._disconnectedInputs = [];\n\n    /**\n     * Array of all [`Output`](Output) objects\n     * @type {Output[]}\n     * @private\n     */\n    this._outputs = [];\n\n    /**\n     * Array of disconnected [`Output`](Output) objects. This is used when outputs are plugged back\n     * in to retain their previous state.\n     * @type {Output[]}\n     * @private\n     */\n    this._disconnectedOutputs = [];\n\n    /**\n     * Array of statechange events to process. These events must be parsed synchronously so they do\n     * not override each other.\n     *\n     * @type {string[]}\n     * @private\n     */\n    this._stateChangeQueue = [];\n\n    /**\n     * @type {number}\n     * @private\n     */\n    this._octaveOffset = 0;\n\n  }\n\n  /**\n   * Checks if the Web MIDI API is available in the current environment and then tries to connect to\n   * the host's MIDI subsystem. This is an asynchronous operation and it causes a security prompt to\n   * be displayed to the user.\n   *\n   * To enable the use of MIDI system exclusive messages, the `sysex` option should be set to\n   * `true`. However, under some environments (e.g. Jazz-Plugin), the `sysex` option is ignored\n   * and system exclusive messages are always enabled. You can check the\n   * [`sysexEnabled`](#sysexEnabled) property to confirm.\n   *\n   * To enable access to software synthesizers available on the host, you would set the `software`\n   * option to `true`. However, this option is only there to future-proof the library as support for\n   * software synths has not yet been implemented in any browser (as of September 2021).\n   *\n   * By the way, if you call the [`enable()`](#enable) method while WebMidi.js is already enabled,\n   * the callback function will be executed (if any), the promise will resolve but the events\n   * ([`\"midiaccessgranted\"`](#event:midiaccessgranted), [`\"connected\"`](#event:connected) and\n   * [`\"enabled\"`](#event:enabled)) will not be fired.\n   *\n   * There are 3 ways to execute code after `WebMidi` has been enabled:\n   *\n   * - Pass a callback function in the `options`\n   * - Listen to the [`\"enabled\"`](#event:enabled) event\n   * - Wait for the promise to resolve\n   *\n   * In order, this is what happens towards the end of the enabling process:\n   *\n   * 1. [`\"midiaccessgranted\"`](#event:midiaccessgranted) event is triggered once the user has\n   * granted access to use MIDI.\n   * 2. [`\"connected\"`](#event:connected) events are triggered (for each available input and output)\n   * 3. [`\"enabled\"`](#event:enabled) event is triggered when WebMidi.js is fully ready\n   * 4. specified callback (if any) is executed\n   * 5. promise is resolved and fulfilled with the `WebMidi` object.\n   *\n   * **Important note**: starting with Chrome v77, a page using Web MIDI API must be hosted on a\n   * secure origin (`https://`, `localhost` or `file:///`) and the user will always be prompted to\n   * authorize the operation (no matter if the `sysex` option is `true` or not).\n   *\n   * ##### Example\n   * ```js\n   * // Enabling WebMidi and using the promise\n   * WebMidi.enable().then(() => {\n   *   console.log(\"WebMidi.js has been enabled!\");\n   * })\n   * ```\n   *\n   * @param [options] {object}\n   *\n   * @param [options.callback] {function} A function to execute once the operation completes. This\n   * function will receive an `Error` object if enabling the Web MIDI API failed.\n   *\n   * @param [options.sysex=false] {boolean} Whether to enable MIDI system exclusive messages or not.\n   *\n   * @param [options.validation=true] {boolean} Whether to enable library-wide validation of method\n   * arguments and setter values. This is an advanced setting that should be used carefully. Setting\n   * [`validation`](#validation) to `false` improves performance but should only be done once the\n   * project has been thoroughly tested with [`validation`](#validation)  turned on.\n   *\n   * @param [options.software=false] {boolean} Whether to request access to software synthesizers on\n   * the host system. This is part of the spec but has not yet been implemented by most browsers as\n   * of April 2020.\n   *\n   * @param [options.requestMIDIAccessFunction] {function} A custom function to use to return\n   * the MIDIAccess object. This is useful if you want to use a polyfill for the Web MIDI API\n   * or if you want to use a custom implementation of the Web MIDI API - probably for testing\n   * purposes.\n   *\n   * @async\n   *\n   * @returns {Promise.<WebMidi>} The promise is fulfilled with the `WebMidi` object for\n   * chainability\n   *\n   * @throws {Error} The Web MIDI API is not supported in your environment.\n   * @throws {Error} Jazz-Plugin must be installed to use WebMIDIAPIShim.\n   */\n  async enable(options = {}, legacy = false) {\n\n    /*START-ESM*/\n\n    // This block is stripped out of the IIFE and CJS versions where it isn't needed.\n\n    // If this code is executed by Node.js in \"module\" mode (when \"type\": \"module\" is used in the\n    // package.json file), then we must import the `jzz` module. I import it in this convoluted way\n    // to prevent Webpack from automatically bundling it in browser bundles where it isn't needed.\n    if (Utilities.isNode) {\n\n      // Some environments may have both Node.js and browser runtimes (Electron, NW.js, React\n      // Native, etc.) so we also check for the presence of the window.navigator property.\n      try {\n        window.navigator;\n      } catch (err) {\n        let jzz = await Object.getPrototypeOf(async function() {}).constructor(`\n        let jzz = await import(\"jzz\");\n        return jzz.default;\n        `)();\n        if (!global.navigator) global.navigator = {}; // for Node.js prior to v21\n        Object.assign(global.navigator, jzz);\n      }\n\n      // The `performance` module appeared in Node.js v8.5.0 but has started to be automatically\n      // imported only in v16+.\n      try {\n        performance;\n      } catch (err) {\n        global.performance = await Object.getPrototypeOf(async function() {}).constructor(`\n        let perf_hooks = await import(\"perf_hooks\");\n        return perf_hooks.performance;\n        `)();\n      }\n\n    }\n\n    /*END-ESM*/\n\n    this.validation = (options.validation !== false);\n\n    if (this.validation) {\n      // Backwards-compatibility. Previous syntax was: enable(callback, sysex)\n      if (typeof options === \"function\") options = {callback: options, sysex: legacy};\n      if (legacy) options.sysex = true;\n    }\n\n    // If already enabled, trigger callback and resolve promise but do not dispatch events.\n    if (this.enabled) {\n      if (typeof options.callback === \"function\") options.callback();\n      return Promise.resolve();\n    }\n\n    /**\n     * Event emitted when an error occurs trying to enable `WebMidi`\n     *\n     * @event WebMidi#error\n     * @type {object}\n     * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in\n     * milliseconds since the navigation start of the document).\n     * @property {WebMidi} target The object that triggered the event\n     * @property {string} type `error`\n     * @property {*} error Actual error that occurred\n     */\n    const errorEvent = {\n      timestamp: this.time,\n      target: this,\n      type: \"error\",\n      error: undefined\n    };\n\n    /**\n     * Event emitted once the MIDI interface has been successfully created (which implies user has\n     * granted access to MIDI).\n     *\n     * @event WebMidi#midiaccessgranted\n     * @type {object}\n     * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in milliseconds\n     * since the navigation start of the document).\n     * @property {WebMidi} target The object that triggered the event\n     * @property {string} type `midiaccessgranted`\n     */\n    const midiAccessGrantedEvent = {\n      timestamp: this.time,\n      target: this,\n      type: \"midiaccessgranted\"\n    };\n\n    /**\n     * Event emitted once `WebMidi` has been fully enabled\n     *\n     * @event WebMidi#enabled\n     * @type {object}\n     * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in milliseconds\n     * since the navigation start of the document).\n     * @property {WebMidi} target The object that triggered the event\n     * @property {string} type `\"enabled\"`\n     */\n    const enabledEvent = {\n      timestamp: this.time,\n      target: this,\n      type: \"enabled\"\n    };\n\n    // Request MIDI access (this is where the prompt will appear)\n    try {\n      if (typeof options.requestMIDIAccessFunction === \"function\") {\n        this.interface = await options.requestMIDIAccessFunction(\n          {sysex: options.sysex, software: options.software}\n        );\n      } else {\n        this.interface = await navigator.requestMIDIAccess(\n          {sysex: options.sysex, software: options.software}\n        );\n      }\n    } catch(err) {\n      errorEvent.error = err;\n      this.emit(\"error\", errorEvent);\n      if (typeof options.callback === \"function\") options.callback(err);\n      return Promise.reject(err);\n    }\n\n    // Now that the Web MIDI API interface has been created, we trigger the 'midiaccessgranted'\n    // event. This allows the developer an occasion to assign listeners on 'connected' events.\n    this.emit(\"midiaccessgranted\", midiAccessGrantedEvent);\n\n    // We setup the state change listener before creating the ports so that it properly catches the\n    // the ports' `connected` events\n    this.interface.onstatechange = this._onInterfaceStateChange.bind(this);\n\n    // Update inputs and outputs (this is where `Input` and `Output` objects are created).\n    try {\n      await this._updateInputsAndOutputs();\n    } catch (err) {\n      errorEvent.error = err;\n      this.emit(\"error\", errorEvent);\n      if (typeof options.callback === \"function\") options.callback(err);\n      return Promise.reject(err);\n    }\n\n    // If we make it here, the ports have been successfully created, so we trigger the 'enabled'\n    // event.\n    this.emit(\"enabled\", enabledEvent);\n\n    // Execute the callback (if any) and resolve the promise with 'this' (for chainability)\n    if (typeof options.callback === \"function\") options.callback();\n    return Promise.resolve(this);\n\n  }\n\n  /**\n   * Completely disables **WebMidi.js** by unlinking the MIDI subsystem's interface and closing all\n   * [`Input`](Input) and [`Output`](Output) objects that may have been opened. This also means that\n   * listeners added to [`Input`](Input) objects, [`Output`](Output) objects or to `WebMidi` itself\n   * are also destroyed.\n   *\n   * @async\n   * @returns {Promise<Array>}\n   *\n   * @throws {Error} The Web MIDI API is not supported by your environment.\n   *\n   * @since 2.0.0\n   */\n  async disable() {\n\n    // This needs to be done right away to prevent racing conditions in listeners while the inputs\n    // are being destroyed.\n    if (this.interface) this.interface.onstatechange = undefined;\n\n    return this._destroyInputsAndOutputs().then(() => {\n\n      if (navigator && typeof navigator.close === \"function\") navigator.close(); // jzz\n\n      this.interface = null; // also resets enabled, sysexEnabled\n\n      /**\n       * Event emitted once `WebMidi` has been successfully disabled.\n       *\n       * @event WebMidi#disabled\n       * @type {object}\n       * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in\n       * milliseconds since the navigation start of the document).\n       * @property {WebMidi} target The object that triggered the event\n       * @property {string} type `\"disabled\"`\n       */\n      let event = {\n        timestamp: this.time,\n        target: this,\n        type: \"disabled\"\n      };\n\n      // Finally, trigger the 'disabled' event and then remove all listeners.\n      this.emit(\"disabled\", event);\n      this.removeListener();\n\n    });\n\n  };\n\n  /**\n   * Returns the [`Input`](Input) object that matches the specified ID string or `false` if no\n   * matching input is found. As per the Web MIDI API specification, IDs are strings (not integers).\n   *\n   * Please note that IDs change from one host to another. For example, Chrome does not use the same\n   * kind of IDs as Jazz-Plugin.\n   *\n   * @param id {string} The ID string of the input. IDs can be viewed by looking at the\n   * [`WebMidi.inputs`](WebMidi#inputs) array. Even though they sometimes look like integers, IDs\n   * are strings.\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected input\n   *\n   * @returns {Input} An [`Input`](Input) object matching the specified ID string or `undefined`\n   * if no matching input can be found.\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getInputById(id, options = {disconnected: false}) {\n\n    if (this.validation) {\n      if (!this.enabled) throw new Error(\"WebMidi is not enabled.\");\n      if (!id) return;\n    }\n\n    if (options.disconnected) {\n      for (let i = 0; i < this._disconnectedInputs.length; i++) {\n        if (\n          this._disconnectedInputs[i]._midiInput &&\n          this._disconnectedInputs[i].id === id.toString()\n        ) return this._disconnectedInputs[i];\n      }\n    } else {\n      for (let i = 0; i < this.inputs.length; i++) {\n        if (\n          this.inputs[i]._midiInput &&\n          this.inputs[i].id === id.toString()\n        ) return this.inputs[i];\n      }\n    }\n\n  };\n\n  /**\n   * Returns the first [`Input`](Input) object whose name **contains** the specified string. Note\n   * that the port names change from one environment to another. For example, Chrome does not report\n   * input names in the same way as the Jazz-Plugin does.\n   *\n   * @param name {string} The non-empty string to look for within the name of MIDI inputs (such as\n   * those visible in the [inputs](WebMidi#inputs) array).\n   *\n   * @returns {Input} The [`Input`](Input) that was found or `undefined` if no input contained the\n   * specified name.\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected input\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getInputByName(name, options = {disconnected: false}) {\n\n    if (this.validation) {\n      if (!this.enabled) throw new Error(\"WebMidi is not enabled.\");\n      if (!name) return;\n      name = name.toString();\n    }\n\n    if (options.disconnected) {\n      for (let i = 0; i < this._disconnectedInputs.length; i++) {\n        if (~this._disconnectedInputs[i].name.indexOf(name)) return this._disconnectedInputs[i];\n      }\n    } else {\n      for (let i = 0; i < this.inputs.length; i++) {\n        if (~this.inputs[i].name.indexOf(name)) return this.inputs[i];\n      }\n    }\n\n  };\n\n  /**\n   * Returns the first [`Output`](Output) object whose name **contains** the specified string. Note\n   * that the port names change from one environment to another. For example, Chrome does not report\n   * input names in the same way as the Jazz-Plugin does.\n   *\n   * @param name {string} The non-empty string to look for within the name of MIDI inputs (such as\n   * those visible in the [`outputs`](#outputs) array).\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected output\n   *\n   * @returns {Output} The [`Output`](Output) that was found or `undefined` if no output matched\n   * the specified name.\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getOutputByName(name, options = {disconnected: false}) {\n\n    if (this.validation) {\n      if (!this.enabled) throw new Error(\"WebMidi is not enabled.\");\n      if (!name) return;\n      name = name.toString();\n    }\n\n    if (options.disconnected) {\n      for (let i = 0; i < this._disconnectedOutputs.length; i++) {\n        if (~this._disconnectedOutputs[i].name.indexOf(name)) return this._disconnectedOutputs[i];\n      }\n    } else {\n      for (let i = 0; i < this.outputs.length; i++) {\n        if (~this.outputs[i].name.indexOf(name)) return this.outputs[i];\n      }\n    }\n\n  };\n\n  /**\n   * Returns the [`Output`](Output) object that matches the specified ID string or `false` if no\n   * matching output is found. As per the Web MIDI API specification, IDs are strings (not\n   * integers).\n   *\n   * Please note that IDs change from one host to another. For example, Chrome does not use the same\n   * kind of IDs as Jazz-Plugin.\n   *\n   * @param id {string} The ID string of the port. IDs can be viewed by looking at the\n   * [`WebMidi.outputs`](WebMidi#outputs) array.\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected output\n   *\n   * @returns {Output} An [`Output`](Output) object matching the specified ID string. If no\n   * matching output can be found, the method returns `undefined`.\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getOutputById(id, options = {disconnected: false}) {\n\n    if (this.validation) {\n      if (!this.enabled) throw new Error(\"WebMidi is not enabled.\");\n      if (!id) return;\n    }\n\n    if (options.disconnected) {\n      for (let i = 0; i < this._disconnectedOutputs.length; i++) {\n        if (\n          this._disconnectedOutputs[i]._midiOutput &&\n          this._disconnectedOutputs[i].id === id.toString()\n        ) return this._disconnectedOutputs[i];\n      }\n    } else {\n      for (let i = 0; i < this.outputs.length; i++) {\n        if (\n          this.outputs[i]._midiOutput &&\n          this.outputs[i].id === id.toString()\n        ) return this.outputs[i];\n      }\n    }\n\n  };\n\n  /**\n   * @private\n   * @deprecated since version 3.0.0, use Utilities.toNoteNumber() instead.\n   */\n  noteNameToNumber(name) {\n    if (this.validation) {\n      console.warn(\n        \"The noteNameToNumber() method is deprecated. Use \" +\n        \"Utilities.toNoteNumber() instead.\"\n      );\n    }\n    return Utilities.toNoteNumber(name, this.octaveOffset);\n  }\n\n  /**\n   * @private\n   * @deprecated since 3.0.0, use Utilities.getNoteDetails() instead.\n   */\n  getOctave(number) {\n\n    if (this.validation) {\n      console.warn(\"The getOctave()is deprecated. Use Utilities.getNoteDetails() instead\");\n      number = parseInt(number);\n    }\n\n    if (!isNaN(number) && number >= 0 && number <= 127) {\n      return Utilities.getNoteDetails(Utilities.offsetNumber(number, this.octaveOffset)).octave;\n    } else {\n      return false;\n    }\n\n  }\n\n  /**\n   * @private\n   * @deprecated since 3.0.0, use Utilities.sanitizeChannels() instead.\n   */\n  sanitizeChannels(channel) {\n\n    if (this.validation) {\n      console.warn(\"The sanitizeChannels() method has been moved to the utilities class.\");\n    }\n\n    return Utilities.sanitizeChannels(channel);\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0.0, use Utilities.sanitizeChannels() instead.\n   */\n  toMIDIChannels(channel) {\n\n    if (this.validation) {\n      console.warn(\n        \"The toMIDIChannels() method has been deprecated. Use Utilities.sanitizeChannels() instead.\"\n      );\n    }\n\n    return Utilities.sanitizeChannels(channel);\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0.0, use Utilities.guessNoteNumber() instead.\n   */\n  guessNoteNumber(input) {\n\n    if (this.validation) {\n      console.warn(\n        \"The guessNoteNumber() method has been deprecated. Use Utilities.guessNoteNumber() instead.\"\n      );\n    }\n\n    return Utilities.guessNoteNumber(input, this.octaveOffset);\n\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0.0, use Utilities.buildNoteArray() instead.\n   */\n  getValidNoteArray(notes, options = {}) {\n    if (this.validation) {\n      console.warn(\n        \"The getValidNoteArray() method has been moved to the Utilities.buildNoteArray()\"\n      );\n    }\n    return Utilities.buildNoteArray(notes, options);\n  }\n\n  /**\n   * @private\n   * @deprecated since version 3.0.0, use Utilities.toTimestamp() instead.\n   */\n  convertToTimestamp(time) {\n\n    if (this.validation) {\n      console.warn(\n        \"The convertToTimestamp() method has been moved to Utilities.toTimestamp().\"\n      );\n    }\n\n    return Utilities.toTimestamp(time);\n\n  }\n\n  /**\n   * @return {Promise<void>}\n   * @private\n   */\n  async _destroyInputsAndOutputs() {\n\n    let promises = [];\n\n    this.inputs.forEach(input => promises.push(input.destroy()));\n    this.outputs.forEach(output => promises.push(output.destroy()));\n\n    return Promise.all(promises).then(() => {\n      this._inputs = [];\n      this._outputs = [];\n    });\n\n  }\n\n  /**\n   * @private\n   */\n  _onInterfaceStateChange(e) {\n\n    // If WebMidi is no longer enabled, silently ignore the event. Methods like getOutputById()\n    // and getInputById() throw when WebMidi is disabled, and since this handler is triggered by a\n    // browser event, client code cannot catch those errors — causing unrecoverable crashes (#546).\n    if (!this.enabled) return;\n\n    this._updateInputsAndOutputs();\n\n    /**\n     * Event emitted when an [`Input`](Input) or [`Output`](Output) port is connected or\n     * disconnected. This event is typically fired whenever a MIDI device is plugged in or\n     * unplugged. Please note that it may fire several times if a device possesses multiple inputs\n     * and/or outputs (which is often the case).\n     *\n     * @event WebMidi#portschanged\n     * @type {object}\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred\n     * (in milliseconds since the navigation start of the document).\n     * @property {string} type `portschanged`\n     * @property {WebMidi} target The object to which the listener was originally added (`WebMidi`)\n     * @property {Input|Output} port The [`Input`](Input) or [`Output`](Output) object that\n     * triggered the event.\n     *\n     * @since 3.0.2\n     */\n\n    /**\n     * Event emitted when an [`Input`](Input) or [`Output`](Output) becomes available. This event is\n     * typically fired whenever a MIDI device is plugged in. Please note that it may fire several\n     * times if a device possesses multiple inputs and/or outputs (which is often the case).\n     *\n     * @event WebMidi#connected\n     * @type {object}\n     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred\n     * (in milliseconds since the navigation start of the document).\n     * @property {string} type `connected`\n     * @property {WebMidi} target The object to which the listener was originally added (`WebMidi`)\n     * @property {Input|Output} port The [`Input`](Input) or [`Output`](Output) object that\n     * triggered the event.\n     */\n\n    /**\n     * Event emitted when an [`Input`](Input) or [`Output`](Output) becomes unavailable. This event\n     * is typically fired whenever a MIDI device is unplugged. Please note that it may fire several\n     * times if a device possesses multiple inputs and/or outputs (which is often the case).\n     *\n     * @event WebMidi#disconnected\n     * @type {object}\n     * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in milliseconds\n     * since the navigation start of the document).\n     * @property {string} type `disconnected`\n     * @property {WebMidi} target The object to which the listener was originally added (`WebMidi`)\n     * @property {Input|Output} port The [`Input`](Input) or [`Output`](Output) object that\n     * triggered the event.\n     */\n    let event = {\n      timestamp: e.timeStamp,\n      type: e.port.state,\n      target: this\n    };\n\n    // We check if \"connection\" is \"open\" because connected events are also triggered with\n    // \"connection=closed\"\n    if (e.port.state === \"connected\" && e.port.connection === \"open\") {\n\n      if (e.port.type === \"output\") {\n        event.port = this.getOutputById(e.port.id);\n      } else if (e.port.type === \"input\") {\n        event.port = this.getInputById(e.port.id);\n      }\n\n      // Emit \"connected\" event\n      this.emit(e.port.state, event);\n\n      // Make a shallow copy of the event so we can use it for the \"portschanged\" event\n      const portsChangedEvent = Object.assign({}, event);\n      portsChangedEvent.type = \"portschanged\";\n      this.emit(portsChangedEvent.type, portsChangedEvent);\n\n    // We check if \"connection\" is \"pending\" because we do not always get the \"closed\" event\n    } else if (e.port.state === \"disconnected\" && e.port.connection === \"pending\") {\n\n      if (e.port.type === \"input\") {\n        event.port = this.getInputById(e.port.id, {disconnected: true});\n      } else if (e.port.type === \"output\") {\n        event.port = this.getOutputById(e.port.id, {disconnected: true});\n      }\n\n      // Emit \"disconnected\" event\n      this.emit(e.port.state, event);\n\n      // Make a shallow copy of the event so we can use it for the \"portschanged\" event\n      const portsChangedEvent = Object.assign({}, event);\n      portsChangedEvent.type = \"portschanged\";\n      this.emit(portsChangedEvent.type, portsChangedEvent);\n\n    }\n\n  };\n\n  /**\n   * @private\n   */\n  async _updateInputsAndOutputs() {\n\n    return Promise.all([\n      this._updateInputs(),\n      this._updateOutputs()\n    ]);\n\n  };\n\n  /**\n   * @private\n   */\n  async _updateInputs() {\n\n    // We must check for the existence of this.interface because it might have been closed via\n    // WebMidi.disable().\n    if (!this.interface) return;\n\n    // Check for items to remove from the existing array (because they are no longer being reported\n    // by the MIDI back-end).\n    for (let i = this._inputs.length - 1; i >= 0; i--) {\n      const current = this._inputs[i];\n      const inputs = Array.from(this.interface.inputs.values());\n      if (! inputs.find(input => input === current._midiInput)) {\n        // Instead of destroying removed inputs, we stash them in case they come back (which is the\n        // case when the computer goes to sleep and is later brought back online).\n        this._disconnectedInputs.push(current);\n        this._inputs.splice(i, 1);\n      }\n    }\n\n    // Array to hold pending promises from trying to open all input ports\n    let promises = [];\n\n    // Add new inputs (if not already present)\n    this.interface.inputs.forEach(nInput => {\n\n      // Check if the input is currently absent from the 'inputs' array.\n      if (! this._inputs.find(input => input._midiInput === nInput) ) {\n\n        // If the input has previously been stashed away, reuse it. If not, create a new one.\n        let input = this._disconnectedInputs.find(input => input._midiInput === nInput);\n        if (!input) input = new Input(nInput);\n        this._inputs.push(input);\n        promises.push(input.open());\n\n      }\n\n    });\n\n    // Return a promise that resolves when all promises have resolved\n    return Promise.all(promises);\n\n  };\n\n  /**\n   * @private\n   */\n  async _updateOutputs() {\n\n    // We must check for the existence of this.interface because it might have been closed via\n    // WebMidi.disable().\n    if (!this.interface) return;\n\n    // Check for items to remove from the existing array (because they are no longer being reported\n    // by the MIDI back-end).\n    for (let i = this._outputs.length - 1; i >= 0; i--) {\n      const current = this._outputs[i];\n      const outputs = Array.from(this.interface.outputs.values());\n      if (! outputs.find(output => output === current._midiOutput)) {\n        // Instead of destroying removed inputs, we stash them in case they come back (which is the\n        // case when the computer goes to sleep and is later brought back online).\n        this._disconnectedOutputs.push(current);\n        this._outputs.splice(i, 1);\n      }\n    }\n\n    // Array to hold pending promises from trying to open all output ports\n    let promises = [];\n\n    // Add new outputs (if not already present)\n    this.interface.outputs.forEach(nOutput => {\n\n      // Check if the output is currently absent from the 'outputs' array.\n      if (! this._outputs.find(output => output._midiOutput === nOutput) ) {\n\n        // If the output has previously been stashed away, reuse it. If not, create a new one.\n        let output = this._disconnectedOutputs.find(output => output._midiOutput === nOutput);\n        if (!output) output = new Output(nOutput);\n        this._outputs.push(output);\n        promises.push(output.open());\n\n      }\n\n    });\n\n    // Return a promise that resolves when all sub-promises have resolved\n    return Promise.all(promises);\n\n  };\n\n  // injectPluginMarkup(parent) {\n  //\n  //   // Silently ignore on Node.js\n  //   if (Utilities.isNode) return;\n  //\n  //   // Default to <body> if no parent is specified\n  //   if (!(parent instanceof Element) && !(parent instanceof HTMLDocument)) {\n  //     parent = document.body;\n  //   }\n  //\n  //   // IE10 needs this:\n  //   // <meta http-equiv=\"X-UA-Compatible\" content=\"requiresActiveX=true\"/>\n  //\n  //   // Create markup and add to parent\n  //   const obj = document.createElement(\"object\");\n  //   obj.classid = \"CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90\"; // IE\n  //   if (!obj.isJazz) obj.type = \"audio/x-jazz\";                 // Standards-compliant\n  //   obj.style.visibility = \"hidden\";\n  //   obj.style.width = obj.style.height = \"0px\";\n  //   parent.appendChild(obj);\n  //\n  // }\n\n  /**\n   * Indicates whether access to the host's MIDI subsystem is active or not.\n   *\n   * @readonly\n   * @type {boolean}\n   */\n  get enabled() {\n    return this.interface !== null;\n  }\n\n  /**\n   * An array of all currently available MIDI inputs.\n   *\n   * @readonly\n   * @type {Input[]}\n   */\n  get inputs() {\n    return this._inputs;\n  }\n\n  /**\n   * @private\n   * @deprecated\n   */\n  get isNode() {\n\n    if (this.validation) {\n      console.warn(\"WebMidi.isNode has been deprecated. Use Utilities.isNode instead.\");\n    }\n\n    return Utilities.isNode;\n\n  }\n\n  /**\n   * @private\n   * @deprecated\n   */\n  get isBrowser() {\n\n    if (this.validation) {\n      console.warn(\"WebMidi.isBrowser has been deprecated. Use Utilities.isBrowser instead.\");\n    }\n\n    return Utilities.isBrowser;\n\n  }\n\n  /**\n   * An integer to offset the octave of notes received from external devices or sent to external\n   * devices.\n   *\n   * When a MIDI message comes in on an input channel the reported note name will be offset. For\n   * example, if the `octaveOffset` is set to `-1` and a [`\"noteon\"`](InputChannel#event:noteon)\n   * message with MIDI number 60 comes in, the note will be reported as C3 (instead of C4).\n   *\n   * By the same token, when [`OutputChannel.playNote()`](OutputChannel#playNote) is called, the\n   * MIDI note number being sent will be offset. If `octaveOffset` is set to `-1`, the MIDI note\n   * number sent will be 72 (instead of 60).\n   *\n   * @type {number}\n   *\n   * @since 2.1\n   */\n  get octaveOffset() {\n    return this._octaveOffset;\n  }\n  set octaveOffset(value) {\n\n    if (this.validation) {\n      value = parseInt(value);\n      if (isNaN(value)) throw new TypeError(\"The 'octaveOffset' property must be an integer.\");\n    }\n\n    this._octaveOffset = value;\n\n  }\n\n  /**\n   * An array of all currently available MIDI outputs as [`Output`](Output) objects.\n   *\n   * @readonly\n   * @type {Output[]}\n   */\n  get outputs() {\n    return this._outputs;\n  }\n\n  /**\n   * Indicates whether the environment provides support for the Web MIDI API or not.\n   *\n   * **Note**: in environments that do not offer built-in MIDI support, this will report `true` if\n   * the\n   * [`navigator.requestMIDIAccess`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess)\n   * function is available. For example, if you have installed WebMIDIAPIShim.js but no plugin, this\n   * property will be `true` even though actual support might not be there.\n   *\n   * @readonly\n   * @type {boolean}\n   */\n  get supported() {\n    return (typeof navigator !== \"undefined\" && !!navigator.requestMIDIAccess);\n  }\n\n  /**\n   * Indicates whether MIDI system exclusive messages have been activated when WebMidi.js was\n   * enabled via the [`enable()`](#enable) method.\n   *\n   * @readonly\n   * @type boolean\n   */\n  get sysexEnabled() {\n    return !!(this.interface && this.interface.sysexEnabled);\n  }\n\n  /**\n   * The elapsed time, in milliseconds, since the time\n   * [origin](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin).\n   * Said simply, it is the number of milliseconds that passed since the page was loaded. Being a\n   * floating-point number, it has sub-millisecond accuracy. According to the\n   * [documentation](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp), the\n   * time should be accurate to 5 µs (microseconds). However, due to various constraints, the\n   * browser might only be accurate to one millisecond.\n   *\n   * Note: `WebMidi.time` is simply an alias to `performance.now()`.\n   *\n   * @type {DOMHighResTimeStamp}\n   * @readonly\n   */\n  get time() {\n    return performance.now();\n  }\n\n  /**\n   * The version of the library as a [semver](https://semver.org/) string.\n   *\n   * @readonly\n   * @type string\n   */\n  get version() {\n    return \"[VI]{version}[/VI]\";\n  }\n\n  /**\n   * The flavour of the library. Can be one of:\n   *\n   * * `esm`: ECMAScript Module\n   * * `cjs`: CommonJS Module\n   * * `iife`: Immediately-Invoked Function Expression\n   *\n   * @readonly\n   * @type string\n   * @since 3.0.25\n   */\n  get flavour() {\n    return \"__flavour__\"; // will be replaced during bundling by the correct identifier\n  }\n\n  /**\n   * @private\n   * @deprecated since 3.0.0. Use Enumerations.CHANNEL_EVENTS instead.\n   */\n  get CHANNEL_EVENTS() {\n    if (this.validation) {\n      console.warn(\n        \"The CHANNEL_EVENTS enum has been moved to Enumerations.CHANNEL_EVENTS.\"\n      );\n    }\n    return Enumerations.CHANNEL_EVENTS;\n  }\n\n  /**\n   * @private\n   * @deprecated since 3.0.0. Use Enumerations.SYSTEM_MESSAGES instead.\n   */\n  get MIDI_SYSTEM_MESSAGES() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_SYSTEM_MESSAGES enum has been moved to \" +\n        \"Enumerations.SYSTEM_MESSAGES.\"\n      );\n    }\n\n    return Enumerations.SYSTEM_MESSAGES;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since 3.0.0. Use Enumerations.CHANNEL_MODE_MESSAGES instead\n   */\n  get MIDI_CHANNEL_MODE_MESSAGES() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_CHANNEL_MODE_MESSAGES enum has been moved to \" +\n        \"Enumerations.CHANNEL_MODE_MESSAGES.\"\n      );\n    }\n\n    return Enumerations.CHANNEL_MODE_MESSAGES;\n\n  }\n\n  /**\n   * @private\n   * @deprecated since 3.0.0. Use Enumerations.CONTROL_CHANGE_MESSAGES instead.\n   */\n  get MIDI_CONTROL_CHANGE_MESSAGES() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_CONTROL_CHANGE_MESSAGES enum has been replaced by the \" +\n        \"Enumerations.CONTROL_CHANGE_MESSAGES array.\"\n      );\n    }\n\n    return Enumerations.MIDI_CONTROL_CHANGE_MESSAGES;\n\n  }\n\n  /**\n   * @deprecated since 3.0.0. Use Enumerations.REGISTERED_PARAMETERS instead.\n   * @private\n   */\n  get MIDI_REGISTERED_PARAMETER() {\n\n    if (this.validation) {\n      console.warn(\n        \"The MIDI_REGISTERED_PARAMETER enum has been moved to \" +\n        \"Enumerations.REGISTERED_PARAMETERS.\"\n      );\n    }\n\n    return Enumerations.REGISTERED_PARAMETERS;\n\n  }\n\n  /**\n   * @deprecated since 3.0.0.\n   * @private\n   */\n  get NOTES() {\n\n    if (this.validation) {\n      console.warn(\"The NOTES enum has been deprecated.\");\n    }\n\n    return [\"C\", \"C#\", \"D\", \"D#\", \"E\", \"F\", \"F#\", \"G\", \"G#\", \"A\", \"A#\", \"B\"];\n\n  }\n\n}\n\n// Export singleton instance of WebMidi class. The 'constructor' is nulled so that it cannot be used\n// to instantiate a new WebMidi object or extend it. However, it is not freezed so it remains\n// extensible (properties can be added at will).\nconst wm = new WebMidi();\nwm.constructor = null;\n\nexport {Enumerations} from \"./Enumerations.js\";\nexport {Forwarder} from \"./Forwarder.js\";\nexport {Input} from \"./Input.js\";\nexport {InputChannel} from \"./InputChannel.js\";\nexport {Message} from \"./Message.js\";\nexport {Note} from \"./Note.js\";\nexport {Output} from \"./Output.js\";\nexport {OutputChannel} from \"./OutputChannel.js\";\nexport {Utilities} from \"./Utilities.js\";\nexport {wm as WebMidi};\n\n"
  },
  {
    "path": "test/Enumerations.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst {Enumerations} = require(\"../dist/cjs/webmidi.cjs.js\");\n\n// VERIFIED\ndescribe(\"Enumerations Object\", function() {\n\n  describe(\"get CHANNEL_MESSAGES()\", function() {\n    it(\"should return an object with valid properties\", function() {\n      expect(Enumerations.CHANNEL_MESSAGES.pitchbend).to.equal(0xE);\n    });\n  });\n\n  describe(\"get MIDI_REGISTERED_PARAMETER()\", function() {\n    it(\"should return an object with valid properties\", function() {\n      expect(Enumerations.REGISTERED_PARAMETERS.rollangle[0]).to.equal(0x3D);\n      expect(Enumerations.REGISTERED_PARAMETERS.rollangle[1]).to.equal(0x08);\n    });\n  });\n\n  describe(\"get CONTROL_CHANGE_MESSAGES()\", function() {\n    it(\"should return an an array with valid properties\", function() {\n      expect(Enumerations.CONTROL_CHANGE_MESSAGES[73].name).to.equal(\"attacktime\");\n    });\n  });\n\n  // Legacy (remove in v4)\n  describe(\"get MIDI_CONTROL_CHANGE_MESSAGES()\", function() {\n    it(\"should return an object with valid properties\", function() {\n      expect(Enumerations.MIDI_CONTROL_CHANGE_MESSAGES.registeredparameterfine).to.equal(101);\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/Forwarder.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst {Message, WebMidi, Note, Forwarder} = require(\"../dist/cjs/webmidi.cjs.js\");\nconst midi = require(\"@julusian/midi\");\n\n// The virtual port is an \"external\" device so an input is seen as an output by WebMidi. To avoid\n// confusion, the naming scheme adopts WebMidi's perspective.\nlet VIRTUAL_OUTPUT = new midi.Input();\nlet VIRTUAL_OUTPUT_NAME = \"Virtual Output\";\nlet WEBMIDI_OUTPUT;\n\ndescribe(\"Forwarder Object\", function() {\n\n  before(function () {\n    VIRTUAL_OUTPUT.openVirtualPort(VIRTUAL_OUTPUT_NAME);\n    VIRTUAL_OUTPUT.ignoreTypes(false, false, false); // enable sysex, timing & active sensing\n  });\n\n  after(function () {\n    VIRTUAL_OUTPUT.closePort();\n  });\n\n  beforeEach(\"Check support and enable WebMidi.js\", async function () {\n    await WebMidi.enable({sysex: true});\n    WEBMIDI_OUTPUT = WebMidi.getOutputByName(VIRTUAL_OUTPUT_NAME);\n  });\n\n  afterEach(\"Disable WebMidi.js\", async function () {\n    await WebMidi.disable();\n  });\n\n  describe(\"constructor()\", function() {\n\n    it(\"should throw when an invalid destination is specified\", function() {\n\n      // Arrange\n      const items = [\n        \"abc\",\n        123,\n        new Note(64),\n      ];\n\n      // Act\n\n      // Assert\n      items.forEach(item => {\n        expect(() => new Forwarder(item)).to.throw(TypeError);\n      });\n\n    });\n\n    it(\"should throw when an invalid type is specified\", function() {\n\n      // Arrange\n      const types = [\n        \"abc\",\n        123,\n      ];\n\n      // Act\n\n      // Assert\n      types.forEach(type => {\n        expect(() => {\n          new Forwarder(WEBMIDI_OUTPUT, {types: type});\n        }).to.throw(TypeError);\n      });\n\n    });\n\n    it(\"should throw when an invalid channel is specified\", function() {\n\n      // Arrange\n      const items = [\n        0,\n        -1,\n        17,\n      ];\n\n      // Act\n\n      // Assert\n      items.forEach(item => {\n        expect(() => {\n          new Forwarder(WEBMIDI_OUTPUT, {channels: item});\n        }).to.throw(TypeError);\n      });\n\n    });\n\n  });\n\n  describe(\"forward()\", function() {\n\n    it(\"should forward the message to the forwarder's destination(s)\", function(done) {\n\n      // Arrange\n      const data = [0x90, 64, 127];\n      const msg = new Message(data);\n      const forwarder = new Forwarder(WEBMIDI_OUTPUT);\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      forwarder.forward(msg);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(msg.data);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n\n    });\n\n    it(\"should not forward messages when 'suspended' is true\", function(done) {\n\n      // Arrange\n      const data = [0x90, 64, 127];\n      const msg = new Message(data);\n      const forwarder = new Forwarder(WEBMIDI_OUTPUT);\n      forwarder.suspended = true;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      forwarder.forward(msg);\n\n      const id = setTimeout(() => {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }, 250);\n\n      // Assert\n      function assert(deltaTime, message) {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        clearTimeout(id);\n        done(new Error(\"Callback should not have been triggered. \" + message));\n      }\n\n    });\n\n    it(\"should not forward message if it does not match channel\", function(done) {\n\n      // Arrange\n      const msg1 = new Message([0x90, 64, 127]); // note on on ch. 1\n      const msg2 = new Message([0x94, 64, 127]); // note on on ch. 5\n      const msg3 = new Message([0x9A, 64, 127]); // note on on ch. 11\n      const msg4 = new Message([0x9F, 64, 127]); // note on on ch. 16\n      const channel = 10;\n      const forwarder = new Forwarder(WEBMIDI_OUTPUT, {channels: channel});\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      const id = setTimeout(() => {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }, 250);\n\n      forwarder.forward(msg1);\n      forwarder.forward(msg2);\n      forwarder.forward(msg3);\n      forwarder.forward(msg4);\n\n      // Assert\n      function assert(deltaTime, message) {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        clearTimeout(id);\n        done(new Error(\"Callback should not have been triggered. \" + message));\n      }\n\n    });\n\n    it(\"should not forward message if it does not match type\", function(done) {\n\n      // Arrange\n      const target = {name: \"pitchbend\", number: 0xE0}; // pitchbend\n      const messages = [\n        0x80,\n        0x90,\n        0xA0,\n        0xB0,\n        0xC0,\n        0xD0\n      ];\n      const forwarder = new Forwarder(WEBMIDI_OUTPUT, {types: target.name});\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      const id = setTimeout(() => {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }, 250);\n\n      messages.forEach(i => {\n        forwarder.forward(new Message([i, 64, 127]));\n      });\n\n      // Assert\n      function assert(deltaTime, message) {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        clearTimeout(id);\n        done(new Error(\"Callback should not have been triggered. \" + message));\n      }\n\n    });\n\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/Input.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst sinon = require(\"sinon\");\nconst {WebMidi, Enumerations, Forwarder} = require(\"../dist/cjs/webmidi.cjs.js\");\n\n// The virtual port is an \"external\" device so an output is seen as an input by WebMidi. To avoid\n// confusion, the naming scheme adopts WebMidi's perspective.\nlet VIRTUAL_INPUT = new midi.Output();\nlet VIRTUAL_INPUT_NAME = \"Virtual Input\";\nlet WEBMIDI_INPUT;\n\n// The virtual port is an \"external\" device so an input is seen as an output by WebMidi. To avoid\n// confusion, the naming scheme adopts WebMidi's perspective.\nlet VIRTUAL_OUTPUT = new midi.Input();\nlet VIRTUAL_OUTPUT_NAME = \"Virtual Output\";\nlet WEBMIDI_OUTPUT;\n\ndescribe(\"Input Object\", function() {\n\n  before(function () {\n\n    VIRTUAL_INPUT.openVirtualPort(VIRTUAL_INPUT_NAME);\n\n    VIRTUAL_OUTPUT.openVirtualPort(VIRTUAL_OUTPUT_NAME);\n    VIRTUAL_OUTPUT.ignoreTypes(false, false, false); // enable sysex, timing & active sensing\n\n  });\n\n  after(function () {\n    VIRTUAL_INPUT.closePort();\n    VIRTUAL_OUTPUT.closePort();\n  });\n\n  beforeEach(\"Check support and enable WebMidi.js\", async function () {\n    await WebMidi.enable({sysex: true});\n    WEBMIDI_INPUT = WebMidi.getInputByName(VIRTUAL_INPUT_NAME);\n    WEBMIDI_OUTPUT = WebMidi.getOutputByName(VIRTUAL_OUTPUT_NAME);\n  });\n\n  afterEach(\"Disable WebMidi.js\", async function () {\n    await WebMidi.disable();\n  });\n\n  it(\"should dispatch events when receiving sysex messages (normal)\", function (done) {\n\n    // Arrange\n    let data = [\n      Enumerations.MIDI_SYSTEM_MESSAGES.sysex,\n      0x42, // Korg\n      1,    // data\n      2,    // data\n      3,    // data\n      Enumerations.MIDI_SYSTEM_MESSAGES.sysexend\n    ];\n    WEBMIDI_INPUT.addListener(\"sysex\", assert);\n\n    // Act\n    VIRTUAL_INPUT.sendMessage(data);\n\n    // Assert\n    function assert(e) {\n      expect(e.message.data).to.have.ordered.members(data);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch events when receiving system common MIDI messages (normal)\", function (done) {\n\n    // Arrange\n    let items = [\n      {type: \"timecode\", data: [0xF1, 0x36]},\n      {type: \"songposition\", data: [12, 34]},\n      {type: \"songselect\", data: [123]},\n      {type: \"tunerequest\", data: []}\n    ];\n    let index = 0;\n\n    items.forEach(item => {\n      WEBMIDI_INPUT.addListener(item.type, assert);\n    });\n\n    // Act\n    items.forEach(item => {\n      VIRTUAL_INPUT.sendMessage(\n        [Enumerations.MIDI_SYSTEM_MESSAGES[item.type]].concat(item.data)\n      );\n    });\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(items[index].type);\n      index++;\n      if (index >= items.length) done();\n    }\n\n  });\n\n  it(\"should dispatch events when receiving system common MIDI messages (legacy)\", function (done) {\n\n    // Arrange\n    let items = [\n      {type: \"timecode\", data: [0xF1, 0x36]},\n      {type: \"songposition\", data: [12, 34]},\n      {type: \"songselect\", data: [123]},\n      {type: \"tunerequest\", data: []}\n    ];\n    let index = 0;\n\n    items.forEach(item => {\n      WEBMIDI_INPUT.addListener(item.type, undefined, assert);\n    });\n\n    // Act\n    items.forEach(item => {\n      VIRTUAL_INPUT.sendMessage(\n        [Enumerations.MIDI_SYSTEM_MESSAGES[item.type]].concat(item.data)\n      );\n    });\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(items[index].type);\n      index++;\n      if (index >= items.length) done();\n    }\n\n  });\n\n  it(\"should dispatch events when receiving realtime MIDI messages (normal)\", function (done) {\n\n    // Arrange\n    let events = [\n      \"clock\",\n      \"start\",\n      \"continue\",\n      \"stop\",\n      \"activesensing\",\n      \"reset\"\n    ];\n    let index = 0;\n\n    events.forEach(event => {\n      WEBMIDI_INPUT.addListener(event, assert);\n    });\n\n    // Act\n    events.forEach(event => {\n      VIRTUAL_INPUT.sendMessage(\n        [Enumerations.MIDI_SYSTEM_MESSAGES[event]]\n      );\n    });\n\n    // Assert\n    function assert(e) {\n      let event = events[index];\n      expect(e.data).to.have.ordered.members([Enumerations.MIDI_SYSTEM_MESSAGES[event]]);\n      index++;\n      if (index >= events.length) done();\n    }\n\n  });\n\n  it(\"should dispatch events when receiving realtime MIDI messages (legacy)\", function (done) {\n\n    // Arrange\n    let events = [\n      \"clock\",\n      \"start\",\n      \"continue\",\n      \"stop\",\n      \"activesensing\",\n      \"reset\"\n    ];\n    let index = 0;\n\n    events.forEach(event => {\n      WEBMIDI_INPUT.addListener(event, undefined, assert);\n    });\n\n    // Act\n    events.forEach(event => {\n      VIRTUAL_INPUT.sendMessage(\n        [Enumerations.MIDI_SYSTEM_MESSAGES[event]]\n      );\n    });\n\n    // Assert\n    function assert(e) {\n      let event = events[index];\n      expect(e.data).to.have.ordered.members([Enumerations.MIDI_SYSTEM_MESSAGES[event]]);\n      index++;\n      if (index >= events.length) done();\n    }\n\n  });\n\n  it(\"should dispatch midimessage event when receiving any messages (normal)\", function (done) {\n\n    // Arrange\n    let messages = [\n      [0x80, 48, 87],     // Note off\n      [0x90, 52, 64],     // Note on\n      [0xA0, 60, 83],     // Key pressure\n      [0xB0, 67, 92],     // Control change\n      [0xC0, 88],         // Program change\n      [0xD0, 93],         // Program change\n      [0xE0, 95, 101],    // Pitch bend\n      [250]               // Start\n    ];\n    let index = 0;\n\n    WEBMIDI_INPUT.addListener(\"midimessage\", assert);\n\n    // Act\n    messages.forEach(msg => {\n      VIRTUAL_INPUT.sendMessage(msg);\n    });\n\n    // Assert\n    function assert(e) {\n      expect(e.data).to.have.ordered.members(messages[index]);\n      index++;\n      if (index >= messages.length) done();\n    }\n\n  });\n\n  it(\"should dispatch midimessage event when receiving any messages (legacy)\", function (done) {\n\n    // Arrange\n    let messages = [\n      [0x80, 48, 87],     // Note off\n      [0x90, 52, 64],     // Note on\n      [0xA0, 60, 83],     // Key pressure\n      [0xB0, 67, 92],     // Control change\n      [0xC0, 88],         // Program change\n      [0xD0, 93],         // Program change\n      [0xE0, 95, 101],    // Pitch bend\n      [250]               // Start\n    ];\n    let index = 0;\n\n    WEBMIDI_INPUT.addListener(\"midimessage\", undefined, assert);\n\n    // Act\n    messages.forEach(msg => {\n      VIRTUAL_INPUT.sendMessage(msg);\n    });\n\n    // Assert\n    function assert(e) {\n      expect(e.data).to.have.ordered.members(messages[index]);\n      index++;\n      if (index >= messages.length) done();\n    }\n\n  });\n\n  it(\"should trigger 'opened' event\", function (done) {\n\n    // Arrange\n    const input = WebMidi.getInputByName(VIRTUAL_INPUT_NAME);\n    input.addListener(\"opened\", () => done());\n    input.addListener(\"closed\", () => input.open());\n\n    // Act\n    input.close();\n\n  });\n\n  it(\"should trigger 'closed' event\", function (done) {\n\n    // Arrange\n    const input = WebMidi.getInputByName(VIRTUAL_INPUT_NAME);\n    input.addListener(\"closed\", () => done());\n\n    // Act\n    input.close();\n\n  });\n\n  it(\"should trigger 'disconnected' event\"); // below does not work...\n  // it.only(\"should trigger 'disconnected' event\", function (done) {\n  //\n  //   // Arrange\n  //   const input = WebMidi.getInputByName(VIRTUAL_INPUT_NAME);\n  //   input.addListener(\"disconnected\", () => done());\n  //\n  //   // Act\n  //   VIRTUAL_INPUT.closePort();\n  //\n  //   setTimeout(() => {\n  //     VIRTUAL_INPUT.openVirtualPort(VIRTUAL_INPUT_NAME);\n  //   }, 100);\n  //\n  // });\n\n  describe(\"addListener()\", function() {\n\n    it(\"should add listeners to all specified channels (normal)\", function() {\n\n      // Arrange\n      let l1 = () => {};\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let event = \"noteon\";\n\n      // Act\n      WEBMIDI_INPUT.addListener(event, l1, {channels: channels});\n\n      // Assert\n      WEBMIDI_INPUT.channels.forEach(ch => {\n        expect(ch.hasListener(event, l1)).to.be.true;\n      });\n\n      expect(\n        WEBMIDI_INPUT.hasListener(event, l1, {channels: channels})\n      ).to.be.true;\n\n    });\n\n    it(\"should add listeners to all specified channels (legacy)\", function() {\n\n      // Arrange\n      let l1 = () => {};\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let event = \"noteon\";\n\n      // Act\n      WEBMIDI_INPUT.addListener(event, channels, l1);\n\n      // Assert\n      WEBMIDI_INPUT.channels.forEach(ch => {\n        expect(ch.hasListener(event, l1)).to.be.true;\n      });\n\n      expect(\n        WEBMIDI_INPUT.hasListener(event, channels, l1)\n      ).to.be.true;\n\n    });\n\n    it(\"should add listeners for input-wide messages (normal)\", function() {\n\n      // Arrange\n      let l1 = () => {};\n\n      // Act\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(key => {\n        WEBMIDI_INPUT.addListener(key, l1);\n      });\n\n      // Assert\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(key => {\n        expect(WEBMIDI_INPUT.hasListener(key, l1)).to.be.true;\n      });\n\n    });\n\n    it(\"should add listeners for input-wide messages (legacy)\", function() {\n\n      // Arrange\n      let l1 = () => {};\n\n      // Act\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(key => {\n        WEBMIDI_INPUT.addListener(key, undefined, l1);\n      });\n\n      // Assert\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(key => {\n        expect(WEBMIDI_INPUT.hasListener(key, undefined, l1)).to.be.true;\n      });\n\n    });\n\n    it(\"should throw error for invalid event types (normal)\", function() {\n\n      // Arrange\n      let values = [undefined, null, \"\", NaN, [], {}, 0];\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n\n      //  Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(event) {\n        expect(() => {\n          WEBMIDI_INPUT.addListener(event, () => {}, {channels: channels});\n        }).to.throw(TypeError);\n      }\n\n    });\n\n    it(\"should throw error for invalid event types (legacy)\", function() {\n\n      // Arrange\n      let values = [undefined, null, \"\", NaN, [], {}, 0];\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n\n      //  Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(event) {\n        expect(() => {\n          WEBMIDI_INPUT.addListener(event, channels, () => {});\n        }).to.throw(TypeError);\n      }\n\n    });\n\n    it(\"should add listener to all channels when none is specified (normal)\", function () {\n      // Arrange\n\n      // Act\n      const listener = WEBMIDI_INPUT.addListener(\"noteon\", () => {});\n\n      // Assert\n      expect(listener.length).to.equal(16);\n\n    });\n\n    it(\"should add listener to all channels when none is specified (legacy)\", function () {\n\n      // Arrange\n\n      // Act\n      const listener = WEBMIDI_INPUT.addListener(\"noteon\", undefined, () => {});\n\n      // Assert\n      expect(listener.length).to.equal(16);\n\n    });\n\n    it(\"should return a single 'Listener' for system messages (normal)\", function() {\n\n      // Arrange\n      let callbacks = [];\n      let listeners = [];\n\n      // Act\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(function(key, index) {\n        callbacks[index] = () => {};\n        listeners[index] = WEBMIDI_INPUT.addListener(key, callbacks[index]);\n      });\n\n      // Assert\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(function(key, index) {\n        expect(Array.isArray(listeners[index])).to.be.false;\n        expect(listeners[index].callback === callbacks[index]).to.be.true;\n      });\n\n    });\n\n    it(\"should return a single 'Listener' for system messages (normal)\", function() {\n\n      // Arrange\n      let callbacks = [];\n      let listeners = [];\n\n      // Act\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(function(key, index) {\n        callbacks[index] = () => {};\n        listeners[index] = WEBMIDI_INPUT.addListener(key, undefined, callbacks[index]);\n      });\n\n      // Assert\n      Object.keys(Enumerations.MIDI_SYSTEM_MESSAGES).forEach(function(key, index) {\n        expect(Array.isArray(listeners[index])).to.be.false;\n        expect(listeners[index].callback === callbacks[index]).to.be.true;\n      });\n\n    });\n\n    it(\"should return an array of 'Listener' objects for channel messages (normal)\", function() {\n\n      // Arrange\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let callbacks = [];\n      let listeners = [];\n\n      Object.keys(Enumerations.CHANNEL_MESSAGES).forEach((key, index) => {\n        callbacks[index] = () => {};\n        listeners[index] = WEBMIDI_INPUT.addListener(key, callbacks[index], {channels: channels});\n      });\n\n      Object.keys(Enumerations.CHANNEL_MESSAGES).forEach((key, index) => {\n        expect(listeners[index].length).to.equal(channels.length);\n        expect(listeners[index][0].callback === callbacks[index]).to.be.true;\n      });\n\n    });\n\n    it(\"should return an array of 'Listener' objects for channel messages (legacy)\", function() {\n\n      // Arrange\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let callbacks = [];\n      let listeners = [];\n\n      Object.keys(Enumerations.CHANNEL_MESSAGES).forEach((key, index) => {\n        callbacks[index] = () => {};\n        listeners[index] = WEBMIDI_INPUT.addListener(key, channels, callbacks[index]);\n      });\n\n      Object.keys(Enumerations.CHANNEL_MESSAGES).forEach((key, index) => {\n        expect(listeners[index].length).to.equal(channels.length);\n        expect(listeners[index][0].callback === callbacks[index]).to.be.true;\n      });\n\n    });\n\n    it(\"should throw if channels are specified but listener not a function (normal)\", function() {\n\n      // Arrange\n      let event = \"noteon\";\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n\n      expect(() => {\n        WEBMIDI_INPUT.addListener(event, undefined, {channels: channels});\n      }).to.throw(TypeError);\n\n    });\n\n    it(\"should throw if channels are specified but listener not a function (legacy)\", function() {\n\n      // Arrange\n      let event = \"noteon\";\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n\n      expect(() => {\n        WEBMIDI_INPUT.addListener(event, channels);\n      }).to.throw(Error);\n\n    });\n\n    it(\"should ignore invalid channels (normal)\", function() {\n\n      // Arrange\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, \"\", NaN, [], {}, 0];\n      let channels = valid.concat(invalid);\n\n      // Act\n      let listeners = WEBMIDI_INPUT.addListener(\"noteon\", () => {}, {channels: channels});\n\n      // Assert\n      expect(listeners.length).to.equal(valid.length);\n\n    });\n\n    it(\"should ignore invalid channels (legacy)\", function() {\n\n      // Arrange\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, \"\", NaN, [], {}, 0];\n      let channels = valid.concat(invalid);\n\n      // Act\n      let listeners = WEBMIDI_INPUT.addListener(\"noteon\", channels, () => {});\n\n      // Assert\n      expect(listeners.length).to.equal(valid.length);\n\n    });\n\n  });\n\n  describe(\"addOneTimeListener()\", function() {\n\n    it(\"should properly call 'addListener()' with appropriate parameters\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_INPUT.channels[1], \"addListener\");\n      let event = \"noteon\";\n      let listener = () => {};\n      let options = {\n        channels: 1,\n        context: {},\n        prepend: true,\n        duration: 1000,\n        arguments: [1, 2, 3]\n      };\n\n      // Act\n      WEBMIDI_INPUT.addOneTimeListener(event, listener, options);\n\n      // Assert\n      let args = spy.args[0];\n      expect(args[0]).to.equal(event);\n      expect(args[1]).to.equal(listener);\n      expect(args[2].context).to.equal(options.context);\n      expect(args[2].prepend).to.equal(options.prepend);\n      expect(args[2].duration).to.equal(options.duration);\n      expect(args[2].arguments).to.equal(options.arguments);\n      expect(args[2].remaining).to.equal(1);\n\n    });\n\n    it(\"should call 'addListener()' for all specified channels\", function () {\n\n      // Arrange\n      let event = \"noteon\";\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let listener = () => {};\n      let spies = [];\n\n      // Act\n      channels.forEach(ch => {\n        spies.push(sinon.spy(WEBMIDI_INPUT.channels[ch], \"addListener\"));\n      });\n\n      WEBMIDI_INPUT.addOneTimeListener(event, listener, {channels: channels});\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.args[0][2].remaining).to.equal(1);\n        expect(spy.calledOnce).to.be.true;\n      });\n\n    });\n\n  });\n\n  describe(\"constructor()\", function() {\n\n    it(\"should set up all 'InputChannel' objects\", function() {\n\n      expect(WEBMIDI_INPUT.channels.length).to.equal(16+1);\n\n      for (let i = 1; i <= 16; i++) {\n        expect(WEBMIDI_INPUT.channels[i].number).to.equal(i);\n      }\n\n    });\n\n  });\n\n  describe(\"close()\", function() {\n\n    it(\"should close the port\", async function () {\n\n      // Act\n      await WEBMIDI_INPUT.close();\n\n      // Assert\n      expect(WEBMIDI_INPUT.connection).to.equal(\"closed\");\n\n    });\n\n  });\n\n  describe(\"destroy()\", function() {\n\n    it(\"should destroy the 'Input'\", async function() {\n\n      // Act\n      await WEBMIDI_INPUT.destroy();\n\n      // Assert\n      try {\n        WEBMIDI_INPUT.name;\n      } catch (e) {\n        await Promise.resolve();\n      }\n\n      if (WEBMIDI_INPUT.channels.length !== 0) return Promise.reject();\n      if (WEBMIDI_INPUT.hasListener() === true) return Promise.reject();\n\n    });\n\n  });\n\n  describe(\"hasListener()\", function() {\n\n    it(\"should call channel's 'hasListener()' for all specified channels (normal)\", function () {\n\n      // Arrange\n      let event = \"noteon\";\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let listener = () => {};\n      let spies = [];\n\n      // Act\n      channels.forEach(ch => {\n        spies.push(sinon.spy(WEBMIDI_INPUT.channels[ch], \"hasListener\"));\n      });\n\n      WEBMIDI_INPUT.addListener(event, listener, {channels: channels});\n      WEBMIDI_INPUT.hasListener(event, listener, {channels: channels});\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnce).to.be.true;\n      });\n\n    });\n\n    it(\"should call channel's 'hasListener()' for all specified channels (legacy)\", function () {\n\n      // Arrange\n      let event = \"noteon\";\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let listener = () => {};\n      let spies = [];\n\n      // Act\n      channels.forEach(ch => {\n        spies.push(sinon.spy(WEBMIDI_INPUT.channels[ch], \"hasListener\"));\n      });\n\n      WEBMIDI_INPUT.addListener(event, channels, listener);\n      WEBMIDI_INPUT.hasListener(event, channels, listener);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnce).to.be.true;\n      });\n\n    });\n\n    it(\"should call input's 'hasListener()' for all input-wide events\", function () {\n\n      // Arrange\n      let event = \"clock\";\n      let listener = () => {};\n      let spy = sinon.spy(WEBMIDI_INPUT, \"hasListener\");\n\n      // Act\n      WEBMIDI_INPUT.addListener(event, listener);\n      WEBMIDI_INPUT.hasListener(event, listener);\n\n      // Assert\n      expect(spy.calledOnce).to.be.true;\n      expect(spy.args[0][0]).to.equal(event);\n      expect(spy.args[0][1]).to.equal(listener);\n\n    });\n\n  });\n\n  describe(\"open()\", async function() {\n\n    it(\"should open the port\", async function () {\n\n      // Act\n      await WEBMIDI_INPUT.close();\n      await WEBMIDI_INPUT.open();\n\n      // Assert\n      expect(WEBMIDI_INPUT.connection).to.equal(\"open\");\n\n    });\n\n  });\n\n  describe(\"removeListener()\", function() {\n\n    it(\"should call channel's 'removeListener()' for all specified channels (normal)\", function () {\n\n      // Arrange\n      let event = \"noteon\";\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let listener = () => {};\n      let spies = [];\n\n      // Act\n      channels.forEach(ch => {\n        spies.push(sinon.spy(WEBMIDI_INPUT.channels[ch], \"removeListener\"));\n      });\n\n      WEBMIDI_INPUT.removeListener(event, listener, {channels: channels});\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnce).to.be.true;\n      });\n\n    });\n\n    it(\"should call channel's 'removeListener()' for all specified channels (legacy)\", function () {\n\n      // Arrange\n      let event = \"noteon\";\n      let channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let listener = () => {};\n      let spies = [];\n\n      // Act\n      channels.forEach(ch => {\n        spies.push(sinon.spy(WEBMIDI_INPUT.channels[ch], \"removeListener\"));\n      });\n\n      WEBMIDI_INPUT.removeListener(event, channels, listener);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnce).to.be.true;\n      });\n\n    });\n\n    it(\"should call input's 'removeListener()' for all input-wide events (normal)\", function () {\n\n      // Arrange\n      let event = \"clock\";\n      let listener = () => {};\n      let spy = sinon.spy(WEBMIDI_INPUT, \"removeListener\");\n\n      // Act\n      WEBMIDI_INPUT.removeListener(event, listener);\n\n      // Assert\n      expect(spy.calledOnce).to.be.true;\n      expect(spy.args[0][0]).to.equal(event);\n      expect(spy.args[0][1]).to.equal(listener);\n\n    });\n\n    it(\"should call input's 'removeListener()' for all input-wide events (legacy)\", function () {\n\n      // Arrange\n      let event = \"clock\";\n      let listener = () => {};\n      let spy = sinon.spy(WEBMIDI_INPUT, \"removeListener\");\n\n      // Act\n      WEBMIDI_INPUT.removeListener(event, undefined, listener);\n\n      // Assert\n      expect(spy.calledOnce).to.be.true;\n      expect(spy.args[0][0]).to.equal(event);\n      expect(spy.args[0][2]).to.equal(listener);\n\n    });\n\n  });\n\n  describe(\"addForwarder()\", function() {\n\n    it(\"should add a forwarder and return it\", function() {\n\n      // Arrange\n\n      // Act\n      const forwarder = WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT);\n\n      // Assert\n      expect(WEBMIDI_INPUT.hasForwarder(forwarder)).to.be.true;\n\n    });\n\n    it(\"should forward message to specified output\", function(done) {\n\n      // Arrange\n      const data = [0x90, 64, 127];\n      WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT);\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      VIRTUAL_INPUT.sendMessage(data);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(data);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should forward message to specified output (with channel filter)\", function(done) {\n\n      // Arrange\n      let target = [0x99, 64, 127]; // noteon on channel 10\n      const data = [\n        [0x90, 64, 127], // noteon on channel 1\n        [0x81, 64, 127], // noteoff on channel 2\n        target,\n      ];\n      WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT, {channels: 10});\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      data.forEach(item => VIRTUAL_INPUT.sendMessage(item));\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(target);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should not forward message to filtered outputs (with channel filter)\", function(done) {\n\n      // Arrange\n      const channel = 10;\n      WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT, {channels: channel});\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]\n        .filter(x => x !== channel).forEach(i => {\n          VIRTUAL_INPUT.sendMessage([0x90 + i - 1, 64, 127]);\n        });\n\n      // Assert\n      const id = setTimeout(() => {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }, 750);\n\n      function assert(deltaTime, message) {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        clearTimeout(id);\n        done(new Error(\"Callback should not have been triggered. \" + message));\n      }\n\n    });\n\n    it(\"should forward message to specified output (with type filter)\", function(done) {\n\n      // Arrange\n      let type = \"controlchange\";\n      const target = [0xB2, 64, 127];\n      const data = [\n        [0x90, 64, 127],  // noteon on channel 1\n        [0x81, 64, 127],  // noteoff on channel 2\n        target            // control change on channel 3\n      ];\n      WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT, {types: type});\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      data.forEach(item => VIRTUAL_INPUT.sendMessage(item));\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(target);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should not forward message to filtered outputs (with type filter)\", function(done) {\n\n      // Arrange\n      const target = {name: \"pitchbend\", number: 0xE0}; // pitchbend\n      const messages = [\n        0x80,\n        0x90,\n        0xA0,\n        0xB0,\n        0xC0,\n        0xD0,\n        0xE0\n      ];\n      WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT, {types: target.name});\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      messages.filter(x => x !== target.number).forEach(i => {\n        VIRTUAL_INPUT.sendMessage([i, 64, 127]);\n      });\n\n      // Assert\n      const id = setTimeout(() => {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }, 750);\n\n      function assert(deltaTime, message) {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        clearTimeout(id);\n        done(new Error(\"Callback should not have been triggered. \" + message));\n      }\n\n    });\n\n  });\n\n  describe(\"removeForwarder()\", function() {\n\n    it(\"should remove specified forwarder\", function() {\n\n      // Arrange\n      const forwarder = WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT);\n\n      // Act\n      WEBMIDI_INPUT.removeForwarder(forwarder);\n\n      // Assert\n      expect(WEBMIDI_INPUT.hasForwarder(forwarder)).to.be.false;\n\n    });\n\n  });\n\n  describe(\"hasForwarder()\", function() {\n\n    it(\"should correctly report prsence of forwarder\", function() {\n\n      // Arrange\n      const forwarder1 = WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT);\n      const forwarder2 = WEBMIDI_INPUT.addForwarder(WEBMIDI_OUTPUT);\n\n      // Act\n      WEBMIDI_INPUT.removeForwarder(forwarder1);\n\n      // Assert\n      expect(WEBMIDI_INPUT.hasForwarder(forwarder1)).to.be.false;\n      expect(WEBMIDI_INPUT.hasForwarder(forwarder2)).to.be.true;\n      expect(WEBMIDI_INPUT.hasForwarder(new Forwarder(WEBMIDI_OUTPUT))).to.be.false;\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/InputChannel.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst {WebMidi, Utilities, Enumerations, Note} = require(\"../dist/cjs/webmidi.cjs.js\");\n\n// Create virtual MIDI input port. Being an external device, the virtual device's output is seen as\n// an input from WebMidi's perspective. To avoid confusion, the property names adopt WebMidi's point\n// of view.\nlet VIRTUAL_INPUT = {\n  PORT: new midi.Output(),\n  NAME: \"Virtual input\"\n};\n\nlet WEBMIDI_INPUT;\n\ndescribe(\"InputChannel Object\", function() {\n\n  before(function () {\n    VIRTUAL_INPUT.PORT.openVirtualPort(VIRTUAL_INPUT.NAME);\n  });\n\n  after(function () {\n    VIRTUAL_INPUT.PORT.closePort();\n  });\n\n  beforeEach(\"Check support and enable\", async function () {\n    await WebMidi.enable();\n    WEBMIDI_INPUT = WebMidi.getInputByName(VIRTUAL_INPUT.NAME);\n  });\n\n  afterEach(\"Disable WebMidi.js\", async function () {\n    await WebMidi.disable();\n    WEBMIDI_INPUT = undefined;\n  });\n\n  it(\"should dispatch 'midimessage' events for all channel voice MIDI messages\", function (done) {\n\n    // Arrange\n    let event = \"midimessage\";\n    let messages = [\n      [0x80, 48, 87],     // Note off\n      [0x90, 52, 64],     // Note on\n      [0xA0, 60, 83],     // Key pressure\n      [0xB0, 67, 92],     // Control change\n      [0xC0, 88],         // Program change\n      [0xD0, 93],         // Channel aftertouch\n      [0xE0, 95, 101],    // Pitch bend\n      [0xB0, 120, 0],     // All sound off\n      [0xB0, 121, 0],     // reset all controllers\n      [0xB0, 122, 0],     // Local control off\n      [0xB0, 122, 127],   // Local control on\n      [0xB0, 123, 0],     // All notes off\n      [0xB0, 126, 0],     // Mono mode on (poly mode off)\n      [0xB0, 127, 0]      // Mono mode off (poly mode on)\n    ];\n    let channel = WEBMIDI_INPUT.channels[1];\n    let index = 0;\n    channel.addListener(event, assert);\n\n    // Act\n    messages.forEach(message => {\n      VIRTUAL_INPUT.PORT.sendMessage(message);\n    });\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(e.data).to.have.ordered.members(messages[index]);\n\n      index++;\n      if (index >= messages.length) done();\n\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'noteoff' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"noteoff\";\n    let velocity = 87;\n    let status = 0x80;\n    let index = 0;\n    channel.addListener(event, assert);\n\n    // Act\n    for (let i = 0; i <= 127; i++) {\n      VIRTUAL_INPUT.PORT.sendMessage([status, i, velocity]);\n    }\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(e.note.identifier).to.equal(Utilities.toNoteIdentifier(index));\n      expect(e.note.attack).to.equal(0); // the note must have an attack of 0 to be a noteoff\n      expect(e.note.rawRelease).to.equal(velocity);\n      expect(e.note.duration).to.equal(WebMidi.defaults.note.duration);\n      expect(e.value).to.equal(Utilities.from7bitToFloat(velocity));\n      expect(e.rawValue).to.equal(velocity);\n      expect(e.target).to.equal(channel);\n\n      index++;\n      if (index > 127) done();\n\n    }\n\n  });\n\n  it(\"should dispatch 'noteoff' event when receiving 'noteon' with 0 velocity\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"noteoff\";\n    let velocity = 0;\n    let status = 0x90; // note on\n    let index = 0;\n    channel.addListener(event, assert);\n\n    // Act\n    for (let i = 0; i <= 127; i++) {\n      VIRTUAL_INPUT.PORT.sendMessage([status, i, velocity]);\n    }\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(e.note.identifier).to.equal(Utilities.toNoteIdentifier(index));\n      expect(e.note.attack).to.equal(0); // the note must have an attack of 0 to be a noteoff\n      expect(e.note.rawRelease).to.equal(velocity);\n      expect(e.note.duration).to.equal(WebMidi.defaults.note.duration);\n      expect(e.value).to.equal(Utilities.from7bitToFloat(velocity));\n      expect(e.rawValue).to.equal(velocity);\n      expect(e.target).to.equal(channel);\n\n      index++;\n      if (index > 127) done();\n\n    }\n\n  });\n\n  it(\"should report correct octave for 'noteoff' event (with octaveOffset)\", function (done) {\n\n    // Arrange\n    const channel = WEBMIDI_INPUT.channels[1];\n    const event = \"noteoff\";\n    const message = [0x80, 60, 64];\n    WebMidi.octaveOffset = -1;\n    WEBMIDI_INPUT.octaveOffset = -1;\n    channel.octaveOffset = -1;\n    const offset = WebMidi.octaveOffset + WEBMIDI_INPUT.octaveOffset + channel.octaveOffset;\n    const result = 4 + offset;\n\n    // Act\n    channel.addListener(event, assert);\n    VIRTUAL_INPUT.PORT.sendMessage(message);\n\n    // Assert\n    function assert(e) {\n      expect(e.note.octave).to.equal(result);\n      WebMidi.octaveOffset = 0;\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'noteon' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"noteon\";\n    let velocity = 64;\n    let status = 0x90;\n    let index = 0;\n\n    channel.addListener(event, assert);\n\n    // Act\n    for (let i = 0; i <= 127; i++) {\n      VIRTUAL_INPUT.PORT.sendMessage([status, i, velocity]);\n    }\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(Utilities.toNoteNumber(e.note.identifier)).to.equal(index);\n      expect(e.rawValue).to.equal(velocity);\n      expect(e.target).to.equal(channel);\n\n      index++;\n      if (index > 127) done();\n\n    }\n\n  });\n\n  it(\"should report correct octave in 'noteon' event (with octaveOffset)\", function (done) {\n\n    // Arrange\n    const channel = WEBMIDI_INPUT.channels[1];\n    const event = \"noteon\";\n    const message = [0x90, 60, 64];\n    WebMidi.octaveOffset = -1;\n    WEBMIDI_INPUT.octaveOffset = -1;\n    channel.octaveOffset = -1;\n    const result = 4 + WebMidi.octaveOffset + WEBMIDI_INPUT.octaveOffset + channel.octaveOffset;\n\n    // Act\n    channel.addListener(event, assert);\n    VIRTUAL_INPUT.PORT.sendMessage(message);\n\n    // Assert\n    function assert(e) {\n      expect(e.note.octave).to.equal(result);\n      WebMidi.octaveOffset = 0;\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'keyaftertouch' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"keyaftertouch\";\n    let status = 0xA0;\n    let velocity = 73;\n    let index = 0;\n    channel.addListener(event, assert);\n\n    // Act\n    for (let i = 0; i <= 127; i++) {\n      VIRTUAL_INPUT.PORT.sendMessage([status, i, velocity]);\n    }\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(e.rawKey).to.equal(index);\n      expect(e.rawValue).to.equal(velocity);\n      expect(e.target).to.equal(channel);\n\n      index++;\n      if (index > 127) done();\n\n    }\n\n  });\n\n  it(\"should report correct octave in 'keyaftertouch' event (with octaveOffset)\", function (done) {\n\n    // Arrange\n    const channel = WEBMIDI_INPUT.channels[1];\n    const event = \"keyaftertouch\";\n    const message = [0xA0, 60, 64];\n    WebMidi.octaveOffset = 1;\n    channel.input.octaveOffset = 1;\n    channel.octaveOffset = 1;\n    const offset = WebMidi.octaveOffset + channel.octaveOffset + channel.input.octaveOffset;\n    const result = 4 + offset;\n\n    // Act\n    channel.addListener(event, assert);\n    VIRTUAL_INPUT.PORT.sendMessage(message);\n\n    // Assert\n    function assert(e) {\n      expect(e.note.octave).to.equal(result);\n      WebMidi.octaveOffset = 0;\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'controlchange' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"controlchange\";\n    let status = 0xB0;\n    let value = 123;\n    let index = 0;\n    channel.addListener(event, assert);\n\n    // Act\n    for (let i = 0; i <= 127; i++) {\n      VIRTUAL_INPUT.PORT.sendMessage([status, i, value]);\n    }\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.controller.number).to.equal(index);\n      expect(e.rawValue).to.equal(value);\n      expect(e.target).to.equal(channel);\n\n      index++;\n      if (index > 127) done();\n\n    }\n\n  });\n\n  it(\"should dispatch event for all 'controlchange' numbered subtypes\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"controlchange\";\n    let status = 0xB0;\n    let value = 123;\n\n    for (let i = 0; i <= 127; i++) {\n      channel.addListener(`${event}-controller${i}`, e => {\n        assert(e, i);\n      });\n    }\n\n    // Act\n    for (let i = 0; i <= 127; i++) {\n      VIRTUAL_INPUT.PORT.sendMessage([status, i, value]);\n    }\n\n    // Assert\n    function assert(e, index) {\n      expect(e.type).to.equal(`${event}-controller${index}`);\n      expect(e.controller.number).to.equal(index);\n      expect(e.controller.name).to.equal(Enumerations.CONTROL_CHANGE_MESSAGES[index].name);\n      expect(e.controller.description)\n        .to.equal(Enumerations.CONTROL_CHANGE_MESSAGES[index].description);\n      expect(e.controller.position)\n        .to.equal(Enumerations.CONTROL_CHANGE_MESSAGES[index].position);\n      expect(e.rawValue).to.equal(value);\n      expect(e.target).to.equal(channel);\n      if (index === 127) done();\n    }\n\n  });\n\n  it(\"should dispatch event for all 'controlchange' named subtypes\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"controlchange\";\n    let status = 0xB0;\n    let value = 123;\n\n    for (let i = 0; i <= 127; i++) {\n\n      const name = Enumerations.CONTROL_CHANGE_MESSAGES[i].name;\n      if (name.indexOf(\"controller\") !== 0) {\n        channel.addListener(`${event}-` + name, e => {\n          assert(e, i);\n        });\n      }\n\n    }\n\n    // Act\n    for (let i = 0; i <= 127; i++) {\n      VIRTUAL_INPUT.PORT.sendMessage([status, i, value]);\n    }\n\n    // Assert\n    function assert(e, index) {\n      expect(e.type).to.equal(`${event}-` + Enumerations.CONTROL_CHANGE_MESSAGES[index].name);\n      expect(e.controller.number).to.equal(index);\n      expect(e.controller.name).to.equal(Enumerations.CONTROL_CHANGE_MESSAGES[index].name);\n      expect(e.controller.description)\n        .to.equal(Enumerations.CONTROL_CHANGE_MESSAGES[index].description);\n      expect(e.controller.position)\n        .to.equal(Enumerations.CONTROL_CHANGE_MESSAGES[index].position);\n      expect(e.rawValue).to.equal(value);\n      expect(e.target).to.equal(channel);\n      if (index === 127) done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'program change' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"programchange\";\n    let status = 0xC0;\n    let value = 19;\n    channel.addListener(event, assert);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, value]);\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.value).to.equal(value);\n      expect(e.rawValue).to.equal(value);\n      expect(e.target).to.equal(channel);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'channel aftertouch' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"channelaftertouch\";\n    let status = 0xD0;\n    let value = 114;\n    channel.addListener(event, assert);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, value]);\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.rawValue).to.equal(value);\n      expect(e.target).to.equal(channel);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'pitchbend' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"pitchbend\";\n    let status = 0xE0;\n    let lsb = 6;\n    let msb = 89;\n    channel.addListener(event, assert);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, lsb, msb]);\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.data[1]).to.equal(lsb);\n      expect(e.data[2]).to.equal(msb);\n      expect(e.target).to.equal(channel);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'all sound off' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"allsoundoff\";\n    let status = 0xB0;\n    let mode = 120;\n    channel.addListener(event, assert);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, mode, 0]);\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.target).to.equal(channel);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'reset all controllers' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"resetallcontrollers\";\n    let status = 0xB0;\n    let mode = 121;\n    channel.addListener(event, assert);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, mode, 0]);\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.target).to.equal(channel);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'local control' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"localcontrol\";\n    let status = 0xB0;\n    let mode = 122;\n    channel.addListener(event, assert);\n    let index = 0;\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, mode, 0]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, mode, 127]);\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(e.target).to.equal(channel);\n\n      if (index === 0) {\n        expect(e.value).to.be.false;\n      } else if (index === 1) {\n        expect(e.value).to.be.true;\n        done();\n      }\n\n      index++;\n\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'all notes off' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"allnotesoff\";\n    let status = 0xB0;\n    let mode = 123;\n    channel.addListener(event, assert);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, mode, 0]);\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.target).to.equal(channel);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'monomode/polymode' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"monomode\";\n    let status = 0xB0;\n    channel.addListener(event, assert);\n    let index = 0;\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 126, 0]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 127, 0]);\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(e.target).to.equal(channel);\n\n      if (index === 0) {\n        expect(e.value).to.be.true;\n      } else if (index === 1) {\n        expect(e.value).to.be.false;\n        done();\n      }\n\n      index++;\n\n    }\n\n  });\n\n  it(\"should dispatch event for inbound 'omni mode on/off' MIDI message\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"omnimode\";\n    let status = 0xB0;\n    channel.addListener(event, assert);\n    let index = 0;\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 124, 0]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 125, 0]);\n\n    // Assert\n    function assert(e) {\n\n      expect(e.type).to.equal(event);\n      expect(e.target).to.equal(channel);\n\n      if (index === 0) {\n        expect(e.value).to.be.false;\n      } else if (index === 1) {\n        expect(e.value).to.be.true;\n        done();\n      }\n\n      index++;\n\n    }\n\n  });\n\n  it(\"should dispatch general and specific events for nrpn-dataentrycoarse\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"nrpn\";\n    let subtype = \"dataentrycoarse\";\n    let status = 0xB0;      // control change\n    let parameterMsb = 12;\n    let parameterLsb = 34;\n    let value = 56;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 6, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch nrpn-dataentryfine\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"nrpn\";\n    let subtype = \"dataentryfine\";\n    let status = 0xB0;      // control change\n    let parameterMsb = 12;\n    let parameterLsb = 34;\n    let value = 56;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 38, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch nrpn-dataincrement\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"nrpn\";\n    let subtype = \"dataincrement\";\n    let status = 0xB0;      // control change\n    let parameterMsb = 12;\n    let parameterLsb = 34;\n    let value = 56;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 96, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch nrpn-datadecrement\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"nrpn\";\n    let subtype = \"datadecrement\";\n    let status = 0xB0;      // control change\n    let parameterMsb = 12;\n    let parameterLsb = 34;\n    let value = 56;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 97, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      done();\n    }\n\n  });\n\n  it(\"should ignore out-of-order NRPN messages\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"nrpn\";\n    let subtype = \"datadecrement\";\n    let status = 0xB0;      // control change\n    let parameterMsb = 12;\n    let parameterLsb = 34;\n    let value = 56;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 97, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch rpn-dataentrycoarse\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"rpn\";\n    let subtype = \"dataentrycoarse\";\n    let status = 0xB0;      // control change\n    let parameter = \"pitchbendrange\";\n    let parameterMsb = 0;\n    let parameterLsb = 0;\n    let value = 123;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 6, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch rpn-dataentryfine\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"rpn\";\n    let subtype = \"dataentryfine\";\n    let status = 0xB0;      // control change\n    let parameter = \"channelfinetuning\";\n    let parameterMsb = 0;\n    let parameterLsb = 1;\n    let value = 123;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 38, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch rpn-dataincrement\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"rpn\";\n    let subtype = \"dataincrement\";\n    let status = 0xB0;      // control change\n    let parameter = \"tuningbank\";\n    let parameterMsb = 0;\n    let parameterLsb = 4;\n    let value = 123;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 96, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch rpn-datadecrement\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"rpn\";\n    let subtype = \"datadecrement\";\n    let status = 0xB0;      // control change\n    let parameter = \"referencedistanceratio\";\n    let parameterMsb = 0x3D;\n    let parameterLsb = 0x06;\n    let value = 123;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n    channel.addListener(event, assert2);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 97, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n    }\n    function assert2(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n      done();\n    }\n\n  });\n\n  it(\"should ignore out-of-order RPN messages\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"rpn\";\n    let subtype = \"datadecrement\";\n    let status = 0xB0;      // control change\n    let parameter = \"referencedistanceratio\";\n    let parameterMsb = 0x3D;\n    let parameterLsb = 0x06;\n    let value = 123;\n\n    channel.addListener(event, assert);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 97, 456]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 97, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert(e) {\n      expect(e.type).to.equal(event);\n      expect(e.subtype).to.equal(subtype);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n      done();\n    }\n\n  });\n\n\n  it(\"should dispatch nrpn-databuttonincrement (legacy)\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"nrpn\";\n    let subtype = \"databuttonincrement\";\n    let status = 0xB0;      // control change\n    let parameterMsb = 12;\n    let parameterLsb = 34;\n    let value = 56;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 96, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch nrpn-databuttondecrement (legacy)\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"nrpn\";\n    let subtype = \"databuttondecrement\";\n    let status = 0xB0;      // control change\n    let parameterMsb = 12;\n    let parameterLsb = 34;\n    let value = 56;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 99, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 98, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 97, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch rpn-databuttonincrement (legacy)\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"rpn\";\n    let subtype = \"databuttonincrement\";\n    let status = 0xB0;      // control change\n    let parameter = \"tuningbank\";\n    let parameterMsb = 0;\n    let parameterLsb = 4;\n    let value = 123;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 96, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n      done();\n    }\n\n  });\n\n  it(\"should dispatch rpn-databuttondecrement (legacy)\", function (done) {\n\n    // Arrange\n    let channel = WEBMIDI_INPUT.channels[1];\n    let event = \"rpn\";\n    let subtype = \"databuttondecrement\";\n    let status = 0xB0;      // control change\n    let parameter = \"referencedistanceratio\";\n    let parameterMsb = 0x3D;\n    let parameterLsb = 0x06;\n    let value = 123;\n\n    channel.addListener(`${event}-${subtype}`, assert1);\n\n    // Act\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, parameterMsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, parameterLsb]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 97, value]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 101, 127]);\n    VIRTUAL_INPUT.PORT.sendMessage([status, 100, 127]);\n\n    // Assert\n    function assert1(e) {\n      expect(e.type).to.equal(`${event}-${subtype}`);\n      expect(e.rawValue).to.equal(value);\n      expect(e.parameter).to.equal(parameter);\n      done();\n    }\n\n  });\n\n\n  describe(\"destroy()\", function () {\n\n    it(\"should set input and channel number to null\", function () {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n\n      // Act\n      channel.destroy();\n\n      // Assert\n      expect(channel.input).to.be.null;\n      expect(channel.number).to.be.null;\n\n    });\n\n    it(\"should remove all listeners\", function () {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n      channel.addListener(\"test\", () => {});\n\n      // Act\n      channel.destroy();\n\n      // Assert\n      expect(channel.hasListener()).to.be.false;\n\n    });\n\n  });\n\n  describe(\"getChannelModeByNumber()\", function () {\n\n    it(\"should return string for valid channel mode numbers\", function () {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n      let results = [];\n\n      // Act\n      for (let cc in Enumerations.CHANNEL_MODE_MESSAGES) {\n        let number = Enumerations.CHANNEL_MODE_MESSAGES[cc];\n        results.push(channel.getChannelModeByNumber(number));\n      }\n\n      // Assert\n      results.forEach(result => {\n        expect(result).to.be.a(\"string\");\n      });\n\n    });\n\n    it(\"should return 'false' for numbers with no match\", function () {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n      let values = [\n        -1,\n        0,\n        119,\n        128\n      ];\n      let results = [];\n\n      // Act\n      values.forEach(value => {\n        results.push(channel.getChannelModeByNumber(value));\n      });\n\n      // Assert\n      results.forEach(result => {\n        expect(result).to.be.false;\n      });\n\n    });\n\n  });\n\n  describe(\"getCcNameByNumber()\", function () {\n\n    it(\"should throw error for invalid control change numbers\", function () {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n      let values = [\n        -1,\n        // 120,\n        // \"test\",\n        // undefined,\n        // NaN,\n        // null\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          channel.getCcNameByNumber(value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return string for valid control change numbers\", function () {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n      let results = [];\n\n      // Act\n      for (let cc in Enumerations.CONTROL_CHANGE_MESSAGES) {\n        let number = Enumerations.CONTROL_CHANGE_MESSAGES[cc].number;\n        results.push(channel.getCcNameByNumber(number));\n      }\n\n      // Assert\n      results.forEach(result => {\n        expect(result).to.be.a(\"string\");\n      });\n\n    });\n\n  });\n\n  describe(\"getNoteState()\", function () {\n\n    it(\"should return correct play state\", function (done) {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n      let status = 0x90; // note on on channel 1\n      let event = \"noteon\";\n      let velocity = 127;\n      let note = 64;\n\n      channel.addListener(event, assert);\n\n      // Act\n      VIRTUAL_INPUT.PORT.sendMessage([status, note, velocity]);\n\n      // Assert\n      function assert() {\n\n        for (let i = 0; i < 128; i++) {\n          if (i === note) {\n            expect(channel.getNoteState(note)).to.equal(true);\n            expect(channel.getNoteState(new Note(i))).to.equal(true);\n            expect(channel.getNoteState(Utilities.toNoteIdentifier(i))).to.equal(true);\n          } else {\n            expect(channel.getNoteState(i)).to.equal(false);\n            expect(channel.getNoteState(new Note(i))).to.equal(false);\n            expect(channel.getNoteState(Utilities.toNoteIdentifier(i))).to.equal(false);\n          }\n        }\n\n        done();\n\n      }\n\n    });\n\n    it(\"should return correct play state (with octaveOffset\", function (done) {\n\n      // Arrange\n      let channel = WEBMIDI_INPUT.channels[1];\n      let event = \"noteon\";\n      let velocity = 127;\n      let index = 0;\n      let status = 0x90;\n\n      WebMidi.octaveOffset = 1;\n      WEBMIDI_INPUT.octaveOffset = 1;\n      channel.octaveOffset = 1;\n\n      let notes = [\n        {identifier: \"C-4\", number: 0},\n        {identifier: \"C1\", number: 60},\n        {identifier: \"G6\", number: 127}\n      ];\n\n      channel.addListener(event, assert);\n\n      // Act\n      notes.forEach(note => {\n        VIRTUAL_INPUT.PORT.sendMessage([status, note.number, velocity]);\n      });\n\n      // Assert\n      function assert() {\n\n        index++;\n        if (index < notes.length) return;\n\n        notes.forEach(note => {\n          expect(channel.getNoteState(new Note(note.identifier))).to.equal(true);\n        });\n\n        done();\n\n      }\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/Message.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst {Message, Enumerations} = require(\"../dist/cjs/webmidi.cjs.js\");\n\ndescribe(\"Message Object\", function() {\n\n  describe(\"constructor()\", function() {\n\n    it(\"should correctly set the type of message for system messages\", function() {\n\n      // Arrange\n      let data = new Uint8Array(3);\n\n      const items = [\n        {type: \"sysex\", status: 240},\n        {type: \"timecode\", status: 241},\n        {type: \"songposition\", status: 242},\n        {type: \"songselect\", status: 243},\n        {type: \"tunerequest\", status: 246},\n        {type: \"sysexend\", status: 247},\n        {type: \"clock\", status: 248},\n        {type: \"start\", status: 250},\n        {type: \"continue\", status: 251},\n        {type: \"stop\", status: 252},\n        {type: \"activesensing\", status: 254},\n        {type: \"reset\", status: 255}\n      ];\n\n      // Act\n      items.forEach(item => {\n        data[0] = item.status;\n        const message = new Message(data);\n        expect(message.isSystemMessage).to.be.true;\n        expect(message.isChannelMessage).to.be.false;\n        expect(message.type).to.equal(item.type);\n      });\n\n    });\n\n    it(\"should correctly set properties for channel messages\", function() {\n\n      // Arrange\n      const items = [\n        {data: [0x8, 0, 127], type: \"noteoff\"},\n        {data: [0x9, 32, 96], type: \"noteon\"},\n        {data: [0xA, 64, 64], type: \"keyaftertouch\"},\n        {data: [0xB, 96, 32], type: \"controlchange\"},\n        {data: [0xC, 0], type: \"programchange\"},\n        {data: [0xD, 64], type: \"channelaftertouch\"},\n        {data: [0xE, 32, 64], type: \"pitchbend\"}\n      ];\n      const channel = 10;\n\n      // Act\n      items.forEach(item => {\n        item.data[0] = (item.data[0] << 4) + channel - 1;\n        const message = new Message(item.data);\n        expect(message.isSystemMessage).to.be.false;\n        expect(message.isChannelMessage).to.be.true;\n        expect(message.type).to.equal(item.type);\n        expect(message.rawData).to.equal(item.data);\n      });\n\n    });\n\n    it(\"should correctly set properties for sysex with basic manufacturer code\", function() {\n\n      // Arrange\n      let data = new Uint8Array(6);\n      data[0] = Enumerations.MIDI_SYSTEM_MESSAGES.sysex;     // sysex\n      data[1] = 0x42;                                   // Korg\n      data[2] = 1;                                      // Some data\n      data[3] = 2;                                      // Some data\n      data[4] = 3;                                      // Some data\n      data[5] = Enumerations.MIDI_SYSTEM_MESSAGES.sysexend;  // sysex end\n\n      // Act\n      const message = new Message(data);\n\n      // Assert\n      expect(message.manufacturerId).to.have.ordered.members([data[1]]);\n      expect(message.dataBytes).to.have.ordered.members([data[2], data[3], data[4]]);\n\n    });\n\n    it(\"should correctly set properties for sysex with extended manufacturer code\", function() {\n\n      // Arrange\n      let data = new Uint8Array(8);\n      data[0] = Enumerations.MIDI_SYSTEM_MESSAGES.sysex;     // sysex\n      data[1] = 0;                                      // MOTU (byte 1)\n      data[2] = 0;                                      // MOTU (byte 2)\n      data[3] = 0x3B;                                   // MOTU (byte 3)\n      data[4] = 1;                                      // Some data\n      data[5] = 2;                                      // Some data\n      data[6] = 3;                                      // Some data\n      data[7] = Enumerations.MIDI_SYSTEM_MESSAGES.sysexend;  // sysex end\n\n      // Act\n      const message = new Message(data);\n\n      // Assert\n      expect(message.manufacturerId).to.have.ordered.members([data[1], data[2], data[3]]);\n      expect(message.dataBytes).to.have.ordered.members([data[4], data[5], data[6]]);\n\n    });\n\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/Note.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst {WebMidi, Note, Utilities} = require(\"../dist/cjs/webmidi.cjs.js\");\n\ndescribe(\"Note Object\", function() {\n\n  describe(\"constructor()\", function () {\n\n    it(\"should return 'Note' with defaults when using number and no options\", function() {\n\n      // Arrange\n      let notes = [];\n\n      // Act\n      for (let i = 0; i <= 127; i++) notes.push(new Note(i));\n\n      // Assert\n      notes.forEach((note, i) => {\n\n        const compare = Utilities.getNoteDetails(Utilities.toNoteIdentifier(i));\n\n        expect(note).to.be.an.instanceof(Note);\n        expect(note.attack).to.equal(WebMidi.defaults.note.attack);\n        expect(note.release).to.equal(WebMidi.defaults.note.release);\n        expect(note.duration).to.equal(WebMidi.defaults.note.duration);\n\n        expect(note.name).to.equal(compare.name);\n        expect(note.accidental).to.equal(compare.accidental);\n        expect(note.octave).to.equal(compare.octave);\n\n      });\n\n    });\n\n    it(\"should return 'Note' with defaults when using identifier and no options\", function() {\n\n      // Arrange\n      let values = [\n        \"C-1\",\n        \"D#0\",\n        \"E##1\",\n        \"Fb2\",\n        \"Fbb3\",\n        \"G9\",\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n\n        const note = new Note(value);\n\n        expect(note).to.be.an.instanceof(Note);\n        expect(note.attack).to.equal(WebMidi.defaults.note.attack);\n        expect(note.release).to.equal(WebMidi.defaults.note.release);\n        expect(note.duration).to.equal(WebMidi.defaults.note.duration);\n\n        expect(note.identifier).to.equal(value);\n        expect(note.name).to.equal(Utilities.getNoteDetails(value).name);\n        expect(note.accidental).to.equal(Utilities.getNoteDetails(value).accidental);\n        expect(note.octave).to.equal(Utilities.getNoteDetails(value).octave);\n\n      }\n\n    });\n\n    it(\"should return 'Note' with correct props when using number and options\", function() {\n\n      // Arrange\n      let notes = [];\n      let options = {duration: 12.34, attack: 0.26, release: 0.79};\n\n      // Act\n      for (let i = 0; i <= 127; i++) notes.push(new Note(i, options));\n\n      // Assert\n      notes.forEach(note => {\n        expect(note).to.be.an.instanceof(Note);\n        expect(note.attack).to.equal(options.attack);\n        expect(note.rawAttack).to.equal(Utilities.fromFloatTo7Bit(options.attack));\n        expect(note.release).to.equal(options.release);\n        expect(note.rawRelease).to.equal(Utilities.fromFloatTo7Bit(options.release));\n        expect(note.duration).to.equal(options.duration);\n      });\n\n    });\n\n    it(\"should return 'Note' with correct props when using identifier and options\", function() {\n\n      // Arrange\n      let values = [\n        \"C-1\",\n        \"D#0\",\n        \"E##1\",\n        \"Fb2\",\n        \"Fbb3\",\n        \"G9\",\n      ];\n      let options = {duration: 12.34, attack: 0.13, release: 0.98};\n\n      // Act\n      values.forEach(value => {\n        assert(new Note(value, options));\n      });\n\n      // Assert\n      function assert(note) {\n        expect(note).to.be.an.instanceof(Note);\n        expect(note.attack).to.equal(options.attack);\n        expect(note.rawAttack).to.equal(Utilities.fromFloatTo7Bit(options.attack));\n        expect(note.release).to.equal(options.release);\n        expect(note.rawRelease).to.equal(Utilities.fromFloatTo7Bit(options.release));\n        expect(note.duration).to.equal(options.duration);\n      }\n\n    });\n\n    it(\"should prioritize rawAttack and rawRelease when defined\", function() {\n\n      // Arrange\n      const identifier = \"C4\";\n      let options = {attack: 0.12, rawAttack: 100, release: 0.98, rawRelease: 5};\n\n      // Act\n      const note = new Note(identifier, options);\n\n      // Assert\n      expect(note.attack).to.equal(Utilities.from7bitToFloat(options.rawAttack));\n      expect(note.release).to.equal(Utilities.from7bitToFloat(options.rawRelease));\n\n    });\n\n    it(\"should ignore 'octaveOffset' for notes specified by identifier\", function() {\n\n      // Arrange\n      let triplets = [\n        {number: 0, octaveOffset: -1, identifier: \"C0\"},\n        {number: 0, octaveOffset: 2, identifier: \"C0\"},\n        {number: 60, octaveOffset: -3, identifier: \"C4\"},\n        {number: 60, octaveOffset: 4, identifier: \"C4\"},\n        {number: 127, octaveOffset: -5, identifier: \"G9\"},\n        {number: 127, octaveOffset: 6, identifier: \"G9\"}\n      ];\n\n      // Act\n      triplets.forEach(assert);\n\n      // Assert\n      function assert(triplet) {\n        const note = new Note(triplet.identifier, {octaveOffset: triplet.octaveOffset});\n        expect(note.identifier).to.equal(triplet.identifier);\n      }\n\n    });\n\n    it(\"should throw when using invalid value\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        Infinity,\n        -Infinity,\n        \"0\",\n        \"127\",\n        undefined,\n        null,\n        \"X2\",\n        \"Cbbb4\",\n        \"test\",\n        [],\n        {},\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(function() {\n          new Note(value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw when using invalid duration\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        -Infinity,\n        \"test\",\n        [],\n        -1.5,\n        -0.6,\n        {},\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(function() {\n          new Note(\"B3\", {duration: value});\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should throw when using invalid attack or release\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        2,\n        -Infinity,\n        \"test\",\n        [],\n        {},\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n\n        expect(function() {\n          new Note(\"Db5\", {attack: value});\n        }).to.throw(RangeError);\n\n        expect(function() {\n          new Note(\"E##5\", {release: value});\n        }).to.throw(RangeError);\n\n      }\n\n    });\n\n    it(\"should throw when using invalid rawAttack or rawRelease\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        -Infinity,\n        \"test\",\n        [],\n        {},\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n\n        expect(function() {\n          new Note(\"Db5\", {attack: value});\n        }).to.throw(RangeError);\n\n        expect(function() {\n          new Note(\"E##5\", {release: value});\n        }).to.throw(RangeError);\n\n      }\n\n    });\n\n  });\n\n  describe(\"getOffsetNumber()\", function () {\n\n    it(\"should use 0 if octaveOffset is invalid\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        Infinity,\n        -Infinity,\n        \"abc\",\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(note.getOffsetNumber(value)).to.equal(note.number);\n      }\n\n    });\n\n    it(\"should use 0 if semitoneOffset is invalid\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        Infinity,\n        -Infinity,\n        \"abc\",\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(note.getOffsetNumber(0, value)).to.equal(note.number);\n      }\n\n    });\n\n    it(\"should cap returned value at 127\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let pairs = [\n        {octaveOffset: 0, semitoneOffset: 1000},\n        {octaveOffset: 100, semitoneOffset: 0},\n        {octaveOffset: 20, semitoneOffset: 20},\n      ];\n\n      // Act\n      pairs.forEach(assert);\n\n      // Assert\n      function assert(pair) {\n        expect(note.getOffsetNumber(pair.octaveOffset, pair.semitoneOffset)).to.equal(127);\n      }\n\n    });\n\n    it(\"should return 0 for values smaller than 0\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let pairs = [\n        {octaveOffset: 0, semitoneOffset: -43},\n        {octaveOffset: -20, semitoneOffset: 0},\n        {octaveOffset: -20, semitoneOffset: -20},\n      ];\n\n      // Act\n      pairs.forEach(assert);\n\n      // Assert\n      function assert(pair) {\n        expect(note.getOffsetNumber(pair.octaveOffset, pair.semitoneOffset)).to.equal(0);\n      }\n\n    });\n\n  });\n\n  describe(\"set identifier()\", function () {\n\n    it(\"should throw error if setting to an invalid value\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        -1,\n        128,\n        Infinity,\n        -Infinity,\n        \"0\",\n        \"127\",\n        undefined,\n        null,\n        \"X2\",\n        \"Cbbb4\",\n        \"test\",\n        [],\n        {},\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => note.identifier = value).to.throw(Error);\n      }\n\n    });\n\n  });\n\n  describe(\"set attack()\", function () {\n\n    it(\"should throw error if setting to an invalid value\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        -1,\n        2,\n        -Infinity,\n        \"test\",\n        [],\n        {},\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => note.attack = value).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should assign correct value to 'rawAttack'\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        {value: 0, rawValue: 0},\n        {value: 0.25, rawValue: 32},\n        {value: 0.5, rawValue: 64},\n        {value: 0.75, rawValue: 95},\n        {value: 1, rawValue: 127},\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        note.attack = item.value;\n        expect(note.rawAttack).to.equal(item.rawValue);\n      }\n\n    });\n\n  });\n\n  describe(\"set rawAttack()\", function () {\n\n    it(\"should assign correct value to 'attack'\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        {value: 0, rawValue: 0},\n        {value: 0.25, rawValue: 32},\n        {value: 0.5, rawValue: 64},\n        {value: 0.75, rawValue: 95},\n        {value: 1, rawValue: 127},\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        note.rawAttack = item.rawValue;\n        expect(note.attack).to.be.within(item.value - 0.005, item.value + 0.005);\n      }\n\n    });\n\n  });\n\n  describe(\"set release()\", function () {\n\n    it(\"should throw error if setting to an invalid value\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        -1,\n        2,\n        -Infinity,\n        \"test\",\n        [],\n        {},\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => note.release = value).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should assign correct value to 'rawRelease'\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        {value: 0, rawValue: 0},\n        {value: 0.25, rawValue: 32},\n        {value: 0.5, rawValue: 64},\n        {value: 0.75, rawValue: 95},\n        {value: 1, rawValue: 127},\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        note.release = item.value;\n        expect(note.rawRelease).to.equal(item.rawValue);\n      }\n\n    });\n\n  });\n\n  describe(\"set rawRelease()\", function () {\n\n    it(\"should assign correct value to 'release'\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        {value: 0, rawValue: 0},\n        {value: 0.25, rawValue: 32},\n        {value: 0.5, rawValue: 64},\n        {value: 0.75, rawValue: 95},\n        {value: 1, rawValue: 127},\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        note.rawRelease = item.rawValue;\n        expect(note.release).to.be.within(item.value - 0.005, item.value + 0.005);\n      }\n\n    });\n\n  });\n\n  describe(\"set duration()\", function () {\n\n    it(\"should throw error if invalid duration is specified\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        -1,\n        -Infinity,\n        \"test\",\n        [],\n        -1.5,\n        -0.6,\n        {},\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => note.duration = value).to.throw();\n      }\n\n    });\n\n  });\n\n  describe(\"set name()\", function () {\n\n    it(\"should throw error if invalid name is specified\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        -1,\n        -Infinity,\n        \"test\",\n        \"H\",\n        [],\n        -1.5,\n        -0.6,\n        {},\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => note.name = value).to.throw();\n      }\n\n    });\n\n  });\n\n  describe(\"set accidental()\", function () {\n\n    it(\"should throw error if invalid accidental is specified\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        -1,\n        \"###\",\n        \"bbb\",\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => note.accidental = value).to.throw();\n      }\n\n    });\n\n  });\n\n  describe(\"set octave()\", function () {\n\n    it(\"should throw error if invalid octave is specified\", function() {\n\n      // Arrange\n      let note = new Note(42);\n      let values = [\n        Infinity,\n        -Infinity,\n        \"abc\",\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => note.octave = value).to.throw();\n      }\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/Output.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst sinon = require(\"sinon\");\nconst {WebMidi, Message} = require(\"../dist/cjs/webmidi.cjs.js\");\n\n// The virtual port is an \"external\" device so an input is seen as an output by WebMidi. To avoid\n// confusion, the naming scheme adopts WebMidi's perspective.\nlet VIRTUAL_OUTPUT = new midi.Input();\nlet VIRTUAL_OUTPUT_NAME = \"Virtual Output\";\nlet WEBMIDI_OUTPUT;\n\ndescribe(\"Output Object\", function() {\n\n  before(function () {\n    VIRTUAL_OUTPUT.openVirtualPort(VIRTUAL_OUTPUT_NAME);\n    VIRTUAL_OUTPUT.ignoreTypes(false, false, false); // enable sysex, timing & active sensing\n  });\n\n  after(function () {\n    VIRTUAL_OUTPUT.closePort();\n  });\n\n  beforeEach(\"Check support and enable WebMidi.js\", async function () {\n    await WebMidi.enable();\n    WEBMIDI_OUTPUT = WebMidi.getOutputByName(VIRTUAL_OUTPUT_NAME);\n  });\n\n  afterEach(\"Disable WebMidi.js\", async function () {\n    await WebMidi.disable();\n  });\n\n  describe(\"clear()\", function () {\n\n    it(\"should return 'Output' object for chaining\", function() {\n      expect(WEBMIDI_OUTPUT.clear()).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"close()\", function () {\n\n    it(\"should close connection\", async function() {\n\n      // Act\n      await WEBMIDI_OUTPUT.close();\n\n      // Assert\n      expect(WEBMIDI_OUTPUT.connection).to.equal(\"closed\");\n\n    });\n\n  });\n\n  describe(\"sendRpnDecrement()\", function () {\n\n    it(\"should trigger the channel method for all valid channels\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let rpn = \"pitchbendrange\";\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendRpnDecrement\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendRpnDecrement(rpn, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(rpn, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the 'Output' object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendRpnDecrement(\"pitchbendrange\")\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"destroy()\", function () {\n\n    it(\"should destroy the 'Output'\", async function() {\n\n      // Act\n      await WEBMIDI_OUTPUT.destroy();\n\n      // Assert\n      try {\n        WEBMIDI_OUTPUT.name;\n      } catch (e) {\n        await Promise.resolve();\n      }\n\n      if (WEBMIDI_OUTPUT.channels.length !== 0) return Promise.reject();\n      if (WEBMIDI_OUTPUT.hasListener() === true) return Promise.reject();\n\n    });\n\n  });\n\n  describe(\"sendRpnIncrement()\", function () {\n\n    it(\"should trigger the channel method for all valid channels\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let rpn = \"pitchbendrange\";\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendRpnIncrement\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendRpnIncrement(rpn, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(rpn, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the 'Output' object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendRpnIncrement(\"pitchbendrange\")\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"open()\", function () {\n\n    it(\"should open connection\", async function() {\n\n      // Act\n      await WEBMIDI_OUTPUT.close();\n      await WEBMIDI_OUTPUT.open();\n\n      // Assert\n      expect(WEBMIDI_OUTPUT.connection).to.equal(\"open\");\n\n    });\n\n  });\n\n  describe(\"playNote()\", function () {\n\n    it(\"should trigger channel method for all valid channels [legacy]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n      let note = 60;\n      WEBMIDI_OUTPUT.channels.forEach(ch => spies.push(sinon.spy(ch, \"playNote\")));\n\n      // Act\n      WEBMIDI_OUTPUT.playNote(note, valid.concat(invalid), options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(note, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let note = 60;\n      WEBMIDI_OUTPUT.channels.forEach(ch => spies.push(sinon.spy(ch, \"playNote\")));\n\n      // Act\n      WEBMIDI_OUTPUT.playNote(note, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(note, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.playNote(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n    it(\"should send immediately if no valid time is found\", function (done) {\n\n      // Arrange\n      let note = 64;\n      let sent = WebMidi.time;\n      let timestamps = [-1, 0, -Infinity, undefined, null, WebMidi.time, NaN];\n      let index = 0;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      timestamps.forEach(\n        stamp => WEBMIDI_OUTPUT.playNote(note, {channel: 1, time: stamp})\n      );\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify([144, 64, 64])) {\n\n          expect(WebMidi.time - sent).to.be.within(-5, 15);\n          index++;\n\n          if (index === timestamps.length) {\n            VIRTUAL_OUTPUT.removeAllListeners();\n            done();\n          }\n\n        }\n\n      }\n\n    });\n\n    it(\"should properly send note off according to specified relative time\", function (done) {\n\n      // Arrange\n      let note = 64;\n      let channel = 1;\n      let expected = [128, note, 64]; // 128 = note off on ch 1\n      let delay = 50;\n      let timestamp = \"+\" + delay;\n      let duration = 50;\n      let sent = WebMidi.time;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.playNote(note, {channels: channel, time: timestamp, duration: duration});\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(expected)) {\n          expect(WebMidi.time - sent - delay - duration).to.be.within(-5, 10);\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly send note off according to specified absolute time\", function (done) {\n\n      // Arrange\n      let note = 64;\n      let channel = 1;\n      let expected = [128, note, 64]; // 128 = note off on ch 1\n      let delay = 50;\n      let timestamp = WebMidi.time + delay;\n      let duration = 50;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.playNote(note, {channels: channel, time: timestamp, duration: duration});\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(expected)) {\n          expect(WebMidi.time - timestamp - duration).to.be.within(-5, 10);\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly send note off when no time is specified\", function (done) {\n\n      // Arrange\n      let note = 64;\n      let channel = 1;\n      let expected = [128, note, 64]; // 128 = note off on ch 1\n      let duration = 50;\n      let sent = WebMidi.time;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.playNote(note, {channels: channel, duration: duration});\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(expected)) {\n          expect(WebMidi.time - sent - duration).to.be.within(-5, 10);\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n  });\n\n  describe(\"sendResetAllControllers()\", function () {\n\n    it(\"should trigger channel method for all valid channels [legacy]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendResetAllControllers\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendResetAllControllers(valid.concat(invalid), options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendResetAllControllers\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendResetAllControllers(options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendResetAllControllers()\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"send()\", function () {\n\n    it(\"should throw error if 'status' byte is invalid (legacy)\", function() {\n\n      // Arrange\n      let values = [\"xxx\", NaN, 127, 256, undefined, null, -1, 0, {}];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.send(value);\n        }).to.throw(RangeError);\n      };\n\n    });\n\n    it(\"should throw error if 'status' byte is invalid\", function() {\n\n      // Arrange\n      const uint8Array1 = new Uint8Array(1);\n      uint8Array1[0] = 127;\n      const uint8Array2 = new Uint8Array(1);\n      uint8Array2[0] = 0;\n\n      let messages = [\n        [\"xxx\"],\n        [NaN],\n        [127],\n        [256],\n        [undefined],\n        [null],\n        [-1],\n        [0],\n        [{}],\n        uint8Array1,\n        uint8Array2\n      ];\n\n      // Act\n      messages.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.send(value);\n        }).to.throw(RangeError);\n      };\n\n    });\n\n    it(\"should throw error if 'data' bytes are invalid (legacy)\", function() {\n\n      // Arrange\n      let values = [\n        [\"xxx\"],\n        [-1],\n        [256],\n        [NaN],\n        [null],\n        [Infinity]\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.send(0x90, value);\n        }).to.throw(RangeError);\n      };\n\n    });\n\n    it(\"should throw error if 'data' bytes are invalid\", function() {\n\n      // Arrange\n      let messages = [\n        [0x90, \"xxx\"],\n        [0x90, -1],\n        [0x90, 256],\n        [0x90, NaN],\n        [0x90, null],\n        [0x90, Infinity]\n      ];\n\n      // Act\n      messages.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.send(value);\n        }).to.throw(RangeError);\n      };\n\n    });\n\n    it(\"should throw error if message is incomplete (legacy)\", function() {\n\n      // Arrange\n      let values = [\n        undefined,\n        NaN,\n        []\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.send(0x90, value);\n        }).to.throw(TypeError);\n      };\n\n    });\n\n    it(\"should throw error if message is incomplete\", function() {\n\n      // Arrange\n      const uint8Array = new Uint8Array(1);\n      uint8Array[0] = 0x90;\n\n      let messages = [\n        [0x90],\n        uint8Array\n      ];\n\n      // Act\n      messages.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.send(value);\n        }).to.throw(TypeError);\n      };\n\n    });\n\n    it(\"should return the Output object for method chaining (legacy)\", function() {\n      expect(\n        WEBMIDI_OUTPUT.send(144, [64, 64])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.send([144, 64, 64])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n    it(\"should actually send the message defined by array (legacy)\", function(done) {\n\n      // Arrange\n      let expected = [0x90, 60, 127]; // Note on: channel 0 (144), note number (60), velocity (127)\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(\n        expected[0],\n        [expected[1], expected[2]]\n      );\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should actually send the message defined by array (normal)\", function(done) {\n\n      // Arrange\n      let message = [0x90, 60, 127]; // Note on: channel 0 (144), note number (60), velocity (127)\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(message);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(message);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should actually send the message defined by Uint8Array (normal)\", function(done) {\n\n      // Arrange\n      const data = [0x90, 60, 127];\n      const uint8array = Uint8Array.from(data); // Note on + channel 0, note 60, velocity (127)\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(uint8array);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(data);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should actually send the message defined by Message (normal)\", function(done) {\n\n      // Arrange\n      const data = Uint8Array.from([0x90, 60, 127]); // Note on + ch. 1, number (60), velocity (127)\n      const message = new Message(data);\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(message);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(message);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should send immediately if no valid timestamp is found (legaqy) \", function (done) {\n\n      // Arrange\n      let status = 144;\n      let data = [13, 0];\n      let sent = WebMidi.time;\n      let timestamps = [\n        -1,\n        0,\n        -Infinity,\n        undefined,\n        null,\n        NaN\n      ];\n      let index = 0;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      timestamps.forEach(\n        stamp => WEBMIDI_OUTPUT.send(status, data, stamp)\n      );\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify([].concat(status, data))) {\n\n          expect(WebMidi.time - sent).to.be.within(0, 5);\n          index++;\n\n          if (index === timestamps.length) {\n            VIRTUAL_OUTPUT.removeAllListeners();\n            done();\n          }\n\n        }\n\n      }\n\n    });\n\n    it(\"should send immediately if no valid timestamp is found\", function (done) {\n\n      // Arrange\n      let data = [144, 13, 0];\n      let sent = WebMidi.time;\n      let timestamps = [\n        {time: -1},\n        {time: -Infinity},\n        {time: undefined},\n        {time: null},\n        {time: NaN},\n        {}\n      ];\n      let index = 0;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      timestamps.forEach(\n        stamp => WEBMIDI_OUTPUT.send(data, stamp)\n      );\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(data)) {\n\n          expect(WebMidi.time - sent).to.be.within(0, 5);\n          index++;\n\n          if (index === timestamps.length) {\n            VIRTUAL_OUTPUT.removeAllListeners();\n            done();\n          }\n\n        }\n\n      }\n\n    });\n\n    it(\"should schedule message according to absolute timestamp (legacy)\", function (done) {\n\n      // Arrange\n      let status = 144;\n      let data = [10, 0];\n      let target = WebMidi.time + 100;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(status, data, target);\n\n      // Assert\n      function assert() {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        expect(WebMidi.time - target).to.be.within(-5, 10);\n        done();\n      }\n\n    });\n\n    it(\"should schedule message according to absolute timestamp\", function (done) {\n\n      // Arrange\n      let message = [144, 10, 0];\n      let target = WebMidi.time + 100;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(message, {time: target});\n\n      // Assert\n      function assert() {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        expect(WebMidi.time - target).to.be.within(-5, 10);\n        done();\n      }\n\n    });\n\n    it(\"should schedule message according to relative timestamp (legacy)\", function (done) {\n\n      // Arrange\n      let status = 144;\n      let data = [10, 0];\n      let offset = \"+100\";\n      let target = WebMidi.time + 100;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(status, data, offset);\n\n      // Assert\n      function assert() {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        expect(WebMidi.time - target).to.be.within(-5, 10);\n        done();\n      }\n\n    });\n\n    it(\"should schedule message according to relative timestamp\", function (done) {\n\n      // Arrange\n      let message = [144, 10, 0];\n      let offset = \"+100\";\n      let target = WebMidi.time + 100;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.send(message, {time: offset});\n\n      // Assert\n      function assert() {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        expect(WebMidi.time - target).to.be.within(-5, 10);\n        done();\n      }\n\n    });\n\n  });\n\n  describe(\"sendActiveSensing()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 254) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (254)\n      WEBMIDI_OUTPUT.sendActiveSensing();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendActiveSensing(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendChannelAftertouch() [legacy]\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n      let value = 0.5;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendChannelAftertouch\"));\n      });\n\n      WEBMIDI_OUTPUT.sendChannelAftertouch(value, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendChannelAftertouch(0.5)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendChannelMode()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendChannelMode\"));\n      });\n\n      WEBMIDI_OUTPUT.sendChannelMode(\"allnotesoff\", 42, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(\"allnotesoff\", 42, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = 42;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendChannelMode\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendChannelMode(\"allnotesoff\", value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(\"allnotesoff\", value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendChannelMode(\"allnotesoff\", 42, [1])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendClock()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      // Arrange\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.sendClock();\n\n      // Assert\n      function assert(deltaTime, message) {\n        if (message[0] === 248) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      }\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendClock()\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendContinue()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 251) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (251)\n      WEBMIDI_OUTPUT.sendContinue();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendContinue()\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendControlChange()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendControlChange\"));\n      });\n\n      WEBMIDI_OUTPUT.sendControlChange(60, 64, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(60, 64, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let note = 60;\n      let value = 64;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendControlChange\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendControlChange(note, value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(note, value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendControlChange(60, 64, [1])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendKeyAftertouch()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let options = {time: 123, channels: valid};\n      let pressure = 0.75;\n      let note = 60;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendKeyAftertouch\"));\n      });\n\n      WEBMIDI_OUTPUT.sendKeyAftertouch(note, pressure, options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(note, pressure, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendKeyAftertouch(60, 0.75)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendNoteOff()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendNoteOff\"));\n      });\n\n      WEBMIDI_OUTPUT.sendNoteOff(60, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(60, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let note = 60;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendNoteOff\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendNoteOff(note, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(note, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendNoteOff(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendNoteOn()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendNoteOn\"));\n      });\n\n      WEBMIDI_OUTPUT.sendNoteOn(60, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(60, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let note = 60;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendNoteOn\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendNoteOn(note, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(note, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendNoteOn(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendPitchBend() [legacy]\", function () {\n\n    it(\"should trigger the channel method for all valid channels\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n      let bend = 0.5;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendPitchBend\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendPitchBend(bend, valid.concat(invalid), options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(bend, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n\n      // Arrange\n      let bend = 0.5;\n      let channel = 1;\n\n      // Assert\n      expect(\n        WEBMIDI_OUTPUT.sendPitchBend(bend, channel)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendProgramChange() [legacy]\", function () {\n\n    it(\"should trigger the channel method for all valid channels\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendProgramChange\"));\n      });\n\n      WEBMIDI_OUTPUT.sendProgramChange(60, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(60, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendProgramChange(64, [1])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendReset()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 255) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (255)\n      WEBMIDI_OUTPUT.sendReset();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendReset(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendSongPosition() [legacy]\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 242) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (242)\n      WEBMIDI_OUTPUT.sendSongPosition();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendSongPosition(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendSongSelect() [legacy]\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 243) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (243)\n      WEBMIDI_OUTPUT.sendSongSelect(42);\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendSongSelect(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendStart()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 250) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (250)\n      WEBMIDI_OUTPUT.sendStart();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendStart(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendStop()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 252) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (252)\n      WEBMIDI_OUTPUT.sendStop();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendStop(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendSysex()\", function () {\n\n    it(\"should throw error if 'sysex' was not enabled\", function() {\n      expect(() => {\n        WEBMIDI_OUTPUT.sendSysex(66, [1, 2, 3, 4, 5]);\n      }).to.throw();\n    });\n\n    it(\"should actually send the message if defined by array\", async function() {\n\n      await WebMidi.disable();\n      await WebMidi.enable({sysex: true});\n      WEBMIDI_OUTPUT = WebMidi.getOutputByName(VIRTUAL_OUTPUT_NAME);\n\n      await new Promise(resolve => {\n\n        VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n\n          if (\n            message[0] === 240 &&     // sysex star byte\n            message[1] === 0x42 &&    // Korg\n            message[2] === 0x1 &&\n            message[3] === 0x2 &&\n            message[4] === 0x3 &&\n            message[5] === 247        // sysex end byte\n          ) {\n            VIRTUAL_OUTPUT.removeAllListeners();\n            resolve();\n          }\n\n        });\n\n        // Sysex (240...247)\n        WEBMIDI_OUTPUT.sendSysex(0x42, [0x1, 0x2, 0x3]);\n\n      });\n\n\n    });\n\n    it(\"should actually send the message if defined by Uint8Array\", async function() {\n\n      // Arrange\n      await WebMidi.disable();\n      await WebMidi.enable({sysex: true});\n      WEBMIDI_OUTPUT = WebMidi.getOutputByName(VIRTUAL_OUTPUT_NAME);\n      const data = Uint8Array.from([0x1, 0x2, 0x3]);\n\n      // Act\n      await new Promise(resolve => {\n\n        VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n\n          if (\n            message[0] === 240 &&     // sysex star byte\n            message[1] === 0x42 &&    // Korg\n            message[2] === 0x1 &&\n            message[3] === 0x2 &&\n            message[4] === 0x3 &&\n            message[5] === 247        // sysex end byte\n          ) {\n            VIRTUAL_OUTPUT.removeAllListeners();\n            resolve();\n          }\n\n        });\n\n        // Sysex (240...247)\n        WEBMIDI_OUTPUT.sendSysex(0x42, data);\n\n      });\n\n\n    });\n\n    it(\"should return the Output object for method chaining\", async function() {\n      await WebMidi.disable();\n      await WebMidi.enable({sysex: true});\n      WEBMIDI_OUTPUT = WebMidi.getOutputByName(VIRTUAL_OUTPUT_NAME);\n      expect(\n        WEBMIDI_OUTPUT.sendSysex(66, [1, 2, 3, 4, 5])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendTimecodeQuarterFrame()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 241 && message[1] === 42) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (251)\n      WEBMIDI_OUTPUT.sendTimecodeQuarterFrame(42);\n\n    });\n\n    it(\"should throw error on invalid data\", function() {\n\n      [255, 9999999, undefined, null, [], {}].forEach(value => {\n        expect(() => {\n          WEBMIDI_OUTPUT.sendTimecodeQuarterFrame(value);\n        }).to.throw();\n      });\n\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendTimecodeQuarterFrame(0)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendTuneRequest()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 246) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (246)\n      WEBMIDI_OUTPUT.sendTuneRequest();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendTuneRequest()\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendTuningRequest() [legacy]\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 246) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (246)\n      WEBMIDI_OUTPUT.sendTuningRequest();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendTuningRequest()\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendChannelAftertouch()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n      let value = 0.5;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendChannelAftertouch\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendChannelAftertouch(value, valid.concat(invalid), options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = 0.5;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendChannelAftertouch\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendChannelAftertouch(value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n\n      // Arrange\n      let value = 0.5;\n\n      // Assert\n      expect(\n        WEBMIDI_OUTPUT.sendChannelAftertouch(value)\n      ).to.equal(WEBMIDI_OUTPUT);\n\n    });\n\n  });\n\n  describe(\"sendKeyAftertouch()\", function () {\n\n    it(\"should trigger the channel method for all valid channels\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let note = 60;\n      let value = 0.5;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendKeyAftertouch\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendKeyAftertouch(note, value, valid.concat(invalid), options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWith(note, value)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let note = 60;\n      let value = 0.5;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendKeyAftertouch\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendKeyAftertouch(note, value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWith(note, value)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n\n      // Arrange\n      let note = 60;\n      let value = 0.5;\n\n      // Assert\n      expect(\n        WEBMIDI_OUTPUT.sendKeyAftertouch(note, value)\n      ).to.equal(WEBMIDI_OUTPUT);\n\n    });\n\n  });\n\n  describe(\"sendLocalControl()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendLocalControl\"));\n      });\n\n      WEBMIDI_OUTPUT.sendLocalControl(true, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(true, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendLocalControl\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendLocalControl(true, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(true, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendLocalControl(true)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendMasterTuning()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendMasterTuning\"));\n      });\n\n      WEBMIDI_OUTPUT.setMasterTuning(42, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(42, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = 42;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendMasterTuning\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendMasterTuning(value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendMasterTuning(true)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendModulationRange()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendModulationRange\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.setModulationRange(42, 24, valid.concat(invalid), options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(42, 24, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let semitones = 42;\n      let cents = 24;\n\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendModulationRange\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendModulationRange(semitones, cents, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(semitones, cents, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendModulationRange(5)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendNrpnValue()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendNrpnValue\"));\n      });\n\n      WEBMIDI_OUTPUT.setNonRegisteredParameter([2, 63], [0, 10], valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly([2, 63], [0, 10], options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let parameter = [2, 63];\n      let data = [0, 10];\n\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendNrpnValue\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendNrpnValue(parameter, data, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(parameter, data, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendNrpnValue([2, 63], [0, 10])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendOmniMode()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendOmniMode\"));\n      });\n\n      WEBMIDI_OUTPUT.sendOmniMode(true, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(true, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = true;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendOmniMode\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendOmniMode(value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendOmniMode(true)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendProgramChange()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendProgramChange\"));\n      });\n\n      WEBMIDI_OUTPUT.sendProgramChange(60, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(60, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = 60;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendProgramChange\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendProgramChange(value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendProgramChange(64, [1])\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendPitchBend()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendPitchBend\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendPitchBend(0.5, valid.concat(invalid), options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(0.5, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = 0.5;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendPitchBend\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendPitchBend(value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n\n      // Arrange\n      let value = -0.5;\n\n      // Assert\n      expect(\n        WEBMIDI_OUTPUT.sendPitchBend(value)\n      ).to.equal(WEBMIDI_OUTPUT);\n\n    });\n\n  });\n\n  describe(\"sendPitchBendRange()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendPitchBendRange\"));\n      });\n\n      WEBMIDI_OUTPUT.setPitchBendRange(42, 24, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(42, 24, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let semitones = 42;\n      let cents = 24;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendPitchBendRange\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendPitchBendRange(semitones, cents, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(semitones, cents, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendPitchBendRange(42, 24)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendPolyphonicMode()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendPolyphonicMode\"));\n      });\n\n      WEBMIDI_OUTPUT.sendPolyphonicMode(\"mono\", valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(\"mono\", options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = \"mono\";\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendPolyphonicMode\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendPolyphonicMode(value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendPolyphonicMode(\"mono\")\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendProgramChange()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendProgramChange\"));\n      });\n\n      WEBMIDI_OUTPUT.sendProgramChange(42, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(42, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = 42;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendProgramChange\"));\n      });\n\n      WEBMIDI_OUTPUT.sendProgramChange(value, options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendProgramChange(42)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendRpnValue()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendRpnValue\"));\n      });\n\n      WEBMIDI_OUTPUT.setRegisteredParameter(\"modulationrange\", 42, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(\"modulationrange\", 42, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let parameter = \"modulationrange\";\n      let value = 42;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendRpnValue\"));\n      });\n\n      WEBMIDI_OUTPUT.sendRpnValue(parameter, value, options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(parameter, value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendRpnValue(\"modulationrange\", 42)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendSongSelect()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 243) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (243)\n      WEBMIDI_OUTPUT.sendSongSelect(42);\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendSongSelect(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendSongPosition()\", function () {\n\n    it(\"should actually send the message\", function(done) {\n\n      VIRTUAL_OUTPUT.on(\"message\", (deltaTime, message) => {\n        if (message[0] === 242) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      });\n\n      // Note on (242)\n      WEBMIDI_OUTPUT.sendSongPosition();\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendActiveSensing(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendTuningBank()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendTuningBank\"));\n      });\n\n      WEBMIDI_OUTPUT.setTuningBank(60, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(60, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let bank = 60;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendTuningBank\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendTuningBank(bank, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(bank, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendTuningBank(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendTuningProgram()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendTuningProgram\"));\n      });\n\n      WEBMIDI_OUTPUT.setTuningProgram(60, valid.concat(invalid), options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(60, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n      let value = 60;\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendTuningProgram\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendTuningProgram(value, options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(value, options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendTuningProgram(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"stopNote()\", function () {\n\n    it(\"should call 'sendNoteOff()' method\", function() {\n\n      // Arrange\n      let note = 60;\n      let options = {time: 0};\n      let spy = sinon.spy(WEBMIDI_OUTPUT, \"sendNoteOff\");\n\n      // Act\n      WEBMIDI_OUTPUT.stopNote(note, options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(note, options)).to.be.true;\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.stopNote(64)\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendAllNotesOff()\", function () {\n\n    it(\"should trigger the channel method for all valid channels\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendAllNotesOff\"));\n      });\n\n      WEBMIDI_OUTPUT.sendAllNotesOff(options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendAllNotesOff\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendAllNotesOff(options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendAllNotesOff()\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n  describe(\"sendAllSoundOff()\", function () {\n\n    it(\"should trigger the channel method for all valid channels [legacy]\", function() {\n\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendAllSoundOff\"));\n      });\n\n      WEBMIDI_OUTPUT.sendAllSoundOff(options);\n\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should trigger the channel method for all valid channels [normal]\", function() {\n\n      // Arrange\n      let spies = [];\n      let valid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];\n      let invalid = [undefined, null, NaN, -1, \"\", Infinity, -Infinity];\n      let options = {time: 123, channels: valid.concat(invalid)};\n\n      WEBMIDI_OUTPUT.channels.forEach(ch => {\n        spies.push(sinon.spy(ch, \"sendAllSoundOff\"));\n      });\n\n      // Act\n      WEBMIDI_OUTPUT.sendAllSoundOff(options);\n\n      // Assert\n      spies.forEach(spy => {\n        expect(spy.calledOnceWithExactly(options)).to.be.true;\n        spy.restore();\n      });\n\n    });\n\n    it(\"should return the Output object for method chaining\", function() {\n      expect(\n        WEBMIDI_OUTPUT.sendAllSoundOff()\n      ).to.equal(WEBMIDI_OUTPUT);\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/OutputChannel.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst sinon = require(\"sinon\");\nconst {WebMidi, Note, Utilities, Message, Enumerations} = require(\"../dist/cjs/webmidi.cjs.js\");\n\n// The virtual port is an \"external\" device so an input is seen as an output by WebMidi. To avoid\n// confusion, the naming scheme adopts WebMidi's perspective.\nlet VIRTUAL_OUTPUT = new midi.Input();\nlet VIRTUAL_OUTPUT_NAME = \"Virtual Output\";\nlet WEBMIDI_OUTPUT;\n\n/**\n * Caution: the tests below are executed against the \"development\" version of the library. The\n * development version, throws more errors than the production version for performance reasons.\n */\n\ndescribe(\"OutputChannel Object\", function() {\n\n  before(function () {\n    VIRTUAL_OUTPUT.openVirtualPort(VIRTUAL_OUTPUT_NAME);\n    VIRTUAL_OUTPUT.ignoreTypes(false, false, false); // enable sysex, timing & active sensing\n  });\n\n  after(function () {\n    VIRTUAL_OUTPUT.closePort();\n  });\n\n  beforeEach(\"Check support and enable\", async function () {\n    await WebMidi.enable();\n    WEBMIDI_OUTPUT = WebMidi.getOutputByName(VIRTUAL_OUTPUT_NAME);\n  });\n\n  afterEach(\"Disable WebMidi.js\", async function () {\n    await WebMidi.disable();\n  });\n\n  describe(\"sendRpnDecrement()\", function () {\n\n    it(\"should throw error if parameter, specified by name, is invalid\", function () {\n\n      // Arrange\n      let invalid = [\"xxx\", undefined, null, \"\"];\n\n      // Act\n      invalid.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(value);\n        }).to.throw(TypeError);\n      }\n\n    });\n\n    it(\"should throw error if parameter, specified by array, is invalid\", function () {\n\n      // Arrange\n      let values = [\n        [],\n        [-1, -1],\n        [0x3d, 1111],\n        undefined,\n        null\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(value);\n        }).to.throw();\n      };\n\n    });\n\n    it(\"should send correct MIDI messages for all valid named parameters\", function(done) {\n\n      // Arrange\n      let index = 0;\n\n      let parameters = [\n        {name: \"pitchbendrange\", value: [0x00, 0x00]},\n        {name: \"channelfinetuning\", value: [0x00, 0x01]},\n        {name: \"channelcoarsetuning\", value: [0x00, 0x02]},\n        {name: \"tuningprogram\", value: [0x00, 0x03]},\n        {name: \"tuningbank\", value: [0x00, 0x04]},\n        {name: \"modulationrange\", value: [0x00, 0x05]},\n        {name: \"azimuthangle\", value: [0x3D, 0x00]},\n        {name: \"elevationangle\", value: [0x3D, 0x01]},\n        {name: \"gain\", value: [0x3D, 0x02]},\n        {name: \"distanceratio\", value: [0x3D, 0x03]},\n        {name: \"maximumdistance\", value: [0x3D, 0x04]},\n        {name: \"maximumdistancegain\", value: [0x3D, 0x05]},\n        {name: \"referencedistanceratio\", value: [0x3D, 0x06]},\n        {name: \"panspreadangle\", value: [0x3D, 0x07]},\n        {name: \"rollangle\", value: [0x3D, 0x08]},\n      ];\n\n      let expected = [];\n\n      parameters.forEach(param => {\n        expected.push([176, 101, param.value[0]]);  // select rpn\n        expected.push([176, 100, param.value[1]]);  // select rpn\n        expected.push([176, 97, 0]);                // decrement\n        expected.push([176, 101, 127]);             // deselect rpn\n        expected.push([176, 100, 127]);             // deselect rpn\n      });\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      parameters.forEach(param => {\n        WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(param.name);\n      });\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct MIDI messages for all valid array parameters\", function(done) {\n\n      // Arrange\n      let index = 0;\n\n      let parameters = [\n        {name: \"pitchbendrange\", value: [0x00, 0x00]},\n        {name: \"channelfinetuning\", value: [0x00, 0x01]},\n        {name: \"channelcoarsetuning\", value: [0x00, 0x02]},\n        {name: \"tuningprogram\", value: [0x00, 0x03]},\n        {name: \"tuningbank\", value: [0x00, 0x04]},\n        {name: \"modulationrange\", value: [0x00, 0x05]},\n        {name: \"azimuthangle\", value: [0x3D, 0x00]},\n        {name: \"elevationangle\", value: [0x3D, 0x01]},\n        {name: \"gain\", value: [0x3D, 0x02]},\n        {name: \"distanceratio\", value: [0x3D, 0x03]},\n        {name: \"maximumdistance\", value: [0x3D, 0x04]},\n        {name: \"maximumdistancegain\", value: [0x3D, 0x05]},\n        {name: \"referencedistanceratio\", value: [0x3D, 0x06]},\n        {name: \"panspreadangle\", value: [0x3D, 0x07]},\n        {name: \"rollangle\", value: [0x3D, 0x08]},\n      ];\n\n      let expected = [];\n\n      parameters.forEach(param => {\n        expected.push([176, 101, param.value[0]]);  // select rpn\n        expected.push([176, 100, param.value[1]]);  // select rpn\n        expected.push([176, 97, 0]);                // decrement\n        expected.push([176, 101, 127]);             // deselect rpn\n        expected.push([176, 100, 127]);             // deselect rpn\n      });\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      parameters.forEach(param => {\n        WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(param.value);\n      });\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly call '_selectRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_selectRegisteredParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(\"pitchbendrange\", options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(\n          Enumerations.REGISTERED_PARAMETERS[\"pitchbendrange\"],\n          options\n        )\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call 'sendControlChange()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendControlChange\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(\"pitchbendrange\", options);\n\n      // Assert\n      expect(\n        spy.calledWithExactly(0x61, 0, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call '_deselectRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_deselectRegisteredParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(\"pitchbendrange\", options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(options)\n      ).to.be.true;\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendRpnDecrement(\"pitchbendrange\")\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"destroy()\", function () {\n\n    it(\"should set output and channel number to null\", function () {\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].destroy();\n\n      // Assert\n      expect(WEBMIDI_OUTPUT.channels[1].output).to.be.null;\n      expect(WEBMIDI_OUTPUT.channels[1].number).to.be.null;\n\n    });\n\n    it(\"should remove all listeners\", function () {\n\n      // Arrange\n      WEBMIDI_OUTPUT.channels[1].addListener(\"test\", () => {});\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].destroy();\n\n      // Assert\n      expect(WEBMIDI_OUTPUT.channels[1].hasListener()).to.be.false;\n\n    });\n\n  });\n\n  describe(\"sendRpnIncrement()\", function () {\n\n    it(\"should throw error if registered parameter, specified by name, is invalid\", function () {\n\n      // Arrange\n      let values = [\"xxx\", undefined, null, \"\"];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(value);\n        }).to.throw(TypeError);\n      }\n\n    });\n\n    it(\"should throw error if registered parameter, specified by array, is invalid\", function () {\n\n      // Arrange\n      let values = [\n        [],\n        [-1, -1],\n        [0x3d, 1111]\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(value);\n        }).to.throw();\n      };\n\n    });\n\n    it(\"should send correct MIDI messages for all valid named parameters\", function(done) {\n\n      // Arrange\n      let index = 0;\n\n      let parameters = [\n        {name: \"pitchbendrange\", value: [0x00, 0x00]},\n        {name: \"channelfinetuning\", value: [0x00, 0x01]},\n        {name: \"channelcoarsetuning\", value: [0x00, 0x02]},\n        {name: \"tuningprogram\", value: [0x00, 0x03]},\n        {name: \"tuningbank\", value: [0x00, 0x04]},\n        {name: \"modulationrange\", value: [0x00, 0x05]},\n        {name: \"azimuthangle\", value: [0x3D, 0x00]},\n        {name: \"elevationangle\", value: [0x3D, 0x01]},\n        {name: \"gain\", value: [0x3D, 0x02]},\n        {name: \"distanceratio\", value: [0x3D, 0x03]},\n        {name: \"maximumdistance\", value: [0x3D, 0x04]},\n        {name: \"maximumdistancegain\", value: [0x3D, 0x05]},\n        {name: \"referencedistanceratio\", value: [0x3D, 0x06]},\n        {name: \"panspreadangle\", value: [0x3D, 0x07]},\n        {name: \"rollangle\", value: [0x3D, 0x08]},\n      ];\n\n      let expected = [];\n\n      parameters.forEach(param => {\n        expected.push([176, 101, param.value[0]]);  // select rpn\n        expected.push([176, 100, param.value[1]]);  // select rpn\n        expected.push([176, 96, 0]);                // increment\n        expected.push([176, 101, 127]);             // deselect rpn\n        expected.push([176, 100, 127]);             // deselect rpn\n      });\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      parameters.forEach(param => {\n        WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(param.name);\n      });\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct MIDI messages for all valid array parameters\", function(done) {\n\n      // Arrange\n      let index = 0;\n\n      let parameters = [\n        {name: \"pitchbendrange\", value: [0x00, 0x00]},\n        {name: \"channelfinetuning\", value: [0x00, 0x01]},\n        {name: \"channelcoarsetuning\", value: [0x00, 0x02]},\n        {name: \"tuningprogram\", value: [0x00, 0x03]},\n        {name: \"tuningbank\", value: [0x00, 0x04]},\n        {name: \"modulationrange\", value: [0x00, 0x05]},\n        {name: \"azimuthangle\", value: [0x3D, 0x00]},\n        {name: \"elevationangle\", value: [0x3D, 0x01]},\n        {name: \"gain\", value: [0x3D, 0x02]},\n        {name: \"distanceratio\", value: [0x3D, 0x03]},\n        {name: \"maximumdistance\", value: [0x3D, 0x04]},\n        {name: \"maximumdistancegain\", value: [0x3D, 0x05]},\n        {name: \"referencedistanceratio\", value: [0x3D, 0x06]},\n        {name: \"panspreadangle\", value: [0x3D, 0x07]},\n        {name: \"rollangle\", value: [0x3D, 0x08]},\n      ];\n\n      let expected = [];\n\n      parameters.forEach(param => {\n        expected.push([176, 101, param.value[0]]);  // select rpn\n        expected.push([176, 100, param.value[1]]);  // select rpn\n        expected.push([176, 96, 0]);                // increment\n        expected.push([176, 101, 127]);             // deselect rpn\n        expected.push([176, 100, 127]);             // deselect rpn\n      });\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      parameters.forEach(param => {\n        WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(param.value);\n      });\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly call '_selectRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_selectRegisteredParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(\"pitchbendrange\", options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(\n          Enumerations.REGISTERED_PARAMETERS[\"pitchbendrange\"],\n          options\n        )\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call 'sendControlChange()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendControlChange\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(\"pitchbendrange\", options);\n\n      // Assert\n      expect(\n        spy.calledWithExactly(0x60, 0, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call '_deselectRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_deselectRegisteredParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(\"pitchbendrange\", options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(options)\n      ).to.be.true;\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendRpnIncrement(\"pitchbendrange\")\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"playNote()\", function () {\n\n    it(\"should call 'sendNoteOn()' with correct parameters\", function () {\n\n      // Arrange\n      let note = \"G5\";\n      let options = {time: 10, attack: 0.5, rawAttack: 127};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendNoteOn\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].playNote(note, options);\n\n      // Assert\n      expect(spy.calledOnceWith(note)).to.be.true;\n      expect(spy.args[0][0]).to.equal(note);\n      expect(spy.args[0][1].time).to.equal(options.time);\n      expect(spy.args[0][1].attack).to.equal(options.attack);\n      expect(spy.args[0][1].rawAttack).to.equal(options.rawAttack);\n\n    });\n\n    it(\"should call 'sendNoteOff()' with correct parameters if duration is valid\", function () {\n\n      // Arrange\n      let note = \"G5\";\n      let options = {time: 10, duration: 20, release: 0.5, rawRelease: 127};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendNoteOff\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].playNote(note, options);\n\n      // Assert\n      expect(spy.calledOnceWith(note)).to.be.true;\n      expect(spy.args[0][0]).to.equal(note);\n      expect(spy.args[0][1].time).to.equal(options.time + options.duration);\n      expect(spy.args[0][1].release).to.equal(options.release);\n      expect(spy.args[0][1].rawRelease).to.equal(options.rawRelease);\n\n    });\n\n    it(\"shouldn't call 'sendNoteOff()' if duration is invalid\", function () {\n\n      // Arrange\n      let values = [0, undefined, null, \"\", NaN, Infinity, -Infinity, -1];\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendNoteOff\");\n\n      // Act\n      values.forEach(value => {\n        WEBMIDI_OUTPUT.channels[1].playNote(\"C3\", {duration: value});\n      });\n\n      // Assert\n      expect(spy.callCount).to.equal(0);\n\n    });\n\n    it(\"should call 'sendNoteOn()' with correct parameters if passed a Note\", function () {\n\n      // Arrange\n      let note = new Note(\"C4\", { duration: 250, attack: 0.5, rawAttack: 127 });\n      let options = { time: 10 };\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendNoteOff\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].playNote(note, options);\n\n      // Assert\n      expect(spy.calledOnceWith(note)).to.be.true;\n      expect(spy.args[0][0]).to.equal(note);\n      expect(spy.args[0][0].attack).to.equal(note.attack);\n      expect(spy.args[0][0].rawAttack).to.equal(note.rawAttack);\n      expect(spy.args[0][1].time).to.equal(options.time + note.duration);\n    });\n\n    it(\"should call 'sendNoteOff()' for each note if parameter is Note[]\", function () {\n\n      // Arrange\n      let note = new Note(\"C4\", { duration: 250 });\n      let notes = [\n        note,\n        new Note(\"D#2\", { duration: 500 }),\n        new Note(\"F3\", { duration: 250 })\n      ];\n\n      let options = {};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendNoteOff\");\n\n      // // Act\n      WEBMIDI_OUTPUT.channels[1].playNote(notes, options);\n\n      // // Assert\n      expect(spy.callCount).to.equal(3);\n      expect(spy.args[0][0]).to.equal(note);\n      // Hard to get exact timing values, assert in future\n      expect(spy.args[0][1].time).to.greaterThan(WebMidi.time);\n      expect(spy.args[0][1].release).to.equal(note.release);\n      expect(spy.args[0][1].rawRelease).to.equal(note.rawRelease);\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].playNote(\"C3\")\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n    it(\"should properly send note off according to specified relative time\", function (done) {\n\n      // Arrange\n      let note = 64;\n      let channel = 1;\n      let expected = [128, note, 64]; // 128 = note off on ch 1\n      let delay = 100;\n      let timestamp = \"+\" + delay;\n      let duration = 100;\n      let sent = WebMidi.time;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].playNote(note, {time: timestamp, duration: duration});\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(expected)) {\n          expect(WebMidi.time - sent - delay - duration).to.be.within(-5, 10);\n          VIRTUAL_OUTPUT.removeListener(\"message\", assert);\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly send note off according to specified absolute time\", function (done) {\n\n      // Arrange\n      let note = 64;\n      let channel = 1;\n      let expected = [128, note, 64]; // 128 = note off on ch 1\n      let delay = 100;\n      let timestamp = WebMidi.time + delay;\n      let duration = 100;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].playNote(note, {time: timestamp, duration: duration});\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(expected)) {\n          expect(WebMidi.time - timestamp - duration).to.be.within(-5, 10);\n          VIRTUAL_OUTPUT.removeListener(\"message\", assert);\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly send note off when no time is specified\", function (done) {\n\n      // Arrange\n      let note = 64;\n      let channel = 1;\n      let expected = [128, note, 64]; // 128 = note off on ch 1\n      let duration = 100;\n      let sent = WebMidi.time;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].playNote(note, {duration: duration});\n\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(expected)) {\n          expect(WebMidi.time - sent - duration).to.be.within(-5, 15);\n          VIRTUAL_OUTPUT.removeListener(\"message\", assert);\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should call 'sendNoteOff()' if duration in Note object is valid\", function (done) {\n\n      // Arrange\n      let note = new Note(\"C4\", { duration: 250 });\n      let channel = WEBMIDI_OUTPUT.channels[1];\n      let expected = [128, note.number, 64]; // 128 = note off on ch 1\n      let sent = WebMidi.time;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      channel.playNote(note);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(expected)) {\n          expect(WebMidi.time - sent - note.duration).to.be.within(-5, 10);\n          VIRTUAL_OUTPUT.removeListener(\"message\", assert);\n          done();\n        }\n\n      }\n\n    });\n\n  });\n\n  describe(\"sendResetAllControllers()\", function () {\n\n    it(\"should properly call 'sendChannelMode()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendResetAllControllers(options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"resetallcontrollers\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI message\", function(done) {\n\n      // Arrange\n      let expected = [176, 121, 0];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendResetAllControllers();\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeListener(\"message\", assert);\n        done();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendResetAllControllers()\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"send()\", function () {\n\n    it(\"should throw error when invalid status is provided\", function () {\n\n      // Arrange\n      const uint8Array1 = new Uint8Array(1);\n      uint8Array1[0] = 127;\n      const uint8Array2 = new Uint8Array(1);\n      uint8Array2[0] = 0;\n\n      let messages = [\n        [\"xxx\"],\n        [NaN],\n        [127],\n        [256],\n        [undefined],\n        [null],\n        [-1],\n        [0],\n        [{}],\n        uint8Array1,\n        uint8Array2\n      ];\n\n      // Act\n      messages.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].send(value);\n        }).to.throw(RangeError);\n      };\n\n    });\n\n    it(\"should throw error when invalid data is provided\", function () {\n\n      // Arrange\n      let messages = [\n        [0x90, \"xxx\"],\n        [0x90, -1],\n        [0x90, 256],\n        [0x90, NaN],\n        [0x90, null],\n        [0x90, Infinity]\n      ];\n\n      // Act\n      messages.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].send(value);\n        }).to.throw(RangeError);\n      };\n\n    });\n\n    it(\"should actually send MIDI message specified by array\", function (done) {\n\n      // Arrange\n      let message = [0x90, 60, 127]; // Note on: channel 0 (144), note number (60), velocity (127)\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].send(message);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(message);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    // We cannot test sending with Uint8Array because it is not supported in Node.js\n    it(\"should actually send MIDI message specified by Uint8Array\");\n\n    it(\"should actually send MIDI message specified by Message object\", function (done) {\n\n      // Arrange\n      let data = Uint8Array.from([0x90, 60, 127]); // Note on: ch 0, number (60), velocity (127)\n      const message = new Message(data);\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].send(message);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(message);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should throw error if message is incomplete\", function() {\n\n      // Arrange\n      const uint8Array = new Uint8Array(1);\n      uint8Array[0] = 0x90;\n\n      let messages = [\n        [0x90],\n        uint8Array\n      ];\n\n      // Act\n      messages.forEach(assert);\n\n      // Assert\n      function assert(value){\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].send(value);\n        }).to.throw(TypeError);\n      };\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].send([144, 127, 127])\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n    it(\"should send immediately if no valid timestamp is found\", function (done) {\n\n      // Arrange\n      let data = [144, 13, 0];\n      let sent = WebMidi.time;\n      let timestamps = [\n        {time: -1},\n        {time: -Infinity},\n        {time: undefined},\n        {time: null},\n        {time: NaN},\n        {}\n      ];\n      let index = 0;\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      timestamps.forEach(\n        stamp => WEBMIDI_OUTPUT.channels[1].send(data, stamp)\n      );\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (JSON.stringify(message) == JSON.stringify(data)) {\n\n          expect(WebMidi.time - sent).to.be.within(0, 5);\n          index++;\n\n          if (index === timestamps.length) {\n            VIRTUAL_OUTPUT.removeAllListeners();\n            done();\n          }\n\n        }\n\n      }\n\n    });\n\n    it(\"should schedule message according to absolute timestamp\", function (done) {\n\n      // Arrange\n      let message = [144, 10, 0];\n      let target = WebMidi.time + 100;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].send(message, {time: target});\n\n      // Assert\n      function assert() {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        expect(WebMidi.time - target).to.be.within(-5, 10);\n        done();\n      }\n\n    });\n\n    it(\"should schedule message according to relative timestamp\", function (done) {\n\n      // Arrange\n      let message = [144, 10, 0];\n      let offset = \"+100\";\n      let target = WebMidi.time + 100;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].send(message, {time: offset});\n\n      // Assert\n      function assert() {\n        VIRTUAL_OUTPUT.removeAllListeners();\n        expect(WebMidi.time - target).to.be.within(-5, 10);\n        done();\n      }\n\n    });\n\n  });\n\n  describe(\"sendChannelMode()\", function () {\n\n    it(\"should properly send MIDI message when using message numbers\", function(done) {\n\n      // Arrange\n      let index = 120;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      for (let i = 120; i < 128; i++) WEBMIDI_OUTPUT.channels[1].sendChannelMode(i, 0);\n      index = 120;\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members([176, index, 0]);\n        index++;\n\n        if (index >= 128) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should actually send MIDI message when using message names\", function(done) {\n\n      // Arrange\n      let index = 120;\n      let names = [\n        \"allsoundoff\",\n        \"resetallcontrollers\",\n        \"localcontrol\",\n        \"allnotesoff\",\n        \"omnimodeoff\",\n        \"omnimodeon\",\n        \"monomodeon\",\n        \"polymodeon\"\n      ];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      names.forEach(name => WEBMIDI_OUTPUT.channels[1].sendChannelMode(name, 0));\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members([176, index, 0]);\n        index++;\n\n        if (index >= 128) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid name is passed\", function () {\n\n      // Arrange\n      let values = [\n        \"xxx\",\n        0,\n        1,\n        -1,\n        undefined,\n        Infinity,\n        null\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendChannelMode(value, 0);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw error when invalid value is passed\", function () {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        NaN,\n        null,\n        Infinity,\n        -Infinity\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendChannelMode(120, value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendChannelMode(120, 0)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendControlChange()\", function () {\n\n    it(\"should properly send MIDI message when using controller numbers\", function(done) {\n\n      // Arrange\n      let index = 0;\n      const max = 127;\n\n      // Act\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n      for (let i = 0; i <= max; i++) WEBMIDI_OUTPUT.channels[1].sendControlChange(i, 123);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[0]).to.equal(176);\n        expect(message[1]).to.equal(index);\n        index++;\n\n        if (index > max) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly send 2 MIDI message when using value array\", function(done) {\n\n      // Arrange\n      let index = 0;\n\n      // Act\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n      WEBMIDI_OUTPUT.channels[1].sendControlChange(0, [12, 34]);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        if (index === 0) {\n          expect(message[0]).to.equal(0xB0);  // control change on channel 1\n          expect(message[1]).to.equal(0);     // bankselectcoarse\n          expect(message[2]).to.equal(12);    // bankselectcoarse\n          index++;\n        } else {\n          expect(message[0]).to.equal(0xB0);  // control change on channel 1\n          expect(message[1]).to.equal(32);    // bankselectfine\n          expect(message[2]).to.equal(34);    // bankselectcoarse\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly send MIDI message when using controller names\", function(done) {\n\n      // Arrange\n      let index = 0;\n      let map = [\n        [\"bankselectcoarse\", 0],\n        [\"modulationwheelcoarse\", 1],\n        [\"breathcontrollercoarse\", 2],\n        [\"footcontrollercoarse\", 4],\n        [\"portamentotimecoarse\", 5],\n        [\"dataentrycoarse\", 6],\n        [\"volumecoarse\", 7],\n        [\"balancecoarse\", 8],\n        [\"pancoarse\", 10],\n        [\"expressioncoarse\", 11],\n        [\"effectcontrol1coarse\", 12],\n        [\"effectcontrol2coarse\", 13],\n        [\"generalpurposecontroller1\", 16],\n        [\"generalpurposecontroller2\", 17],\n        [\"generalpurposecontroller3\", 18],\n        [\"generalpurposecontroller4\", 19],\n        [\"bankselectfine\", 32],\n        [\"modulationwheelfine\", 33],\n        [\"breathcontrollerfine\", 34],\n        [\"footcontrollerfine\", 36],\n        [\"portamentotimefine\", 37],\n        [\"dataentryfine\", 38],\n        [\"channelvolumefine\", 39],\n        [\"balancefine\", 40],\n        [\"panfine\", 42],\n        [\"expressionfine\", 43],\n        [\"effectcontrol1fine\", 44],\n        [\"effectcontrol2fine\", 45],\n        [\"damperpedal\", 64],\n        [\"portamento\", 65],\n        [\"sostenuto\", 66],\n        [\"softpedal\", 67],\n        [\"legatopedal\", 68],\n        [\"hold2\", 69],\n        [\"soundvariation\", 70],\n        [\"resonance\", 71],\n        [\"releasetime\", 72],\n        [\"attacktime\", 73],\n        [\"brightness\", 74],\n        [\"decaytime\", 75],\n        [\"vibratorate\", 76],\n        [\"vibratodepth\", 77],\n        [\"vibratodelay\", 78],\n        [\"controller79\", 79],\n        [\"generalpurposecontroller5\", 80],\n        [\"generalpurposecontroller6\", 81],\n        [\"generalpurposecontroller7\", 82],\n        [\"generalpurposecontroller8\", 83],\n        [\"portamentocontrol\", 84],\n        [\"highresolutionvelocityprefix\", 88],\n        [\"effect1depth\", 91],\n        [\"effect2depth\", 92],\n        [\"effect3depth\", 93],\n        [\"effect4depth\", 94],\n        [\"effect5depth\", 95],\n        [\"dataincrement\", 96],\n        [\"datadecrement\", 97],\n        [\"nonregisteredparameterfine\", 98],\n        [\"nonregisteredparametercoarse\", 99],\n        [\"registeredparameterfine\", 100],\n        [\"registeredparametercoarse\", 101],\n\n        [\"allsoundoff\", 120],\n        [\"resetallcontrollers\", 121],\n        [\"localcontrol\", 122],\n        [\"allnotesoff\", 123],\n        [\"omnimodeoff\", 124],\n        [\"omnimodeon\", 125],\n        [\"monomodeon\", 126],\n        [\"polymodeon\", 127]\n      ];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      map.forEach(pair => WEBMIDI_OUTPUT.channels[1].sendControlChange(pair[0], 123));\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[0]).to.equal(176);\n        expect(message[1]).to.equal(map[index][1]);\n        index++;\n\n        if (index >= map.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should properly send MIDI message for deprecated names (legacy)\", function(done) {\n\n      // Arrange\n      let index = 0;\n      let map = [\n        [\"generalpurposeslider1\", 16],\n        [\"generalpurposeslider2\", 17],\n        [\"generalpurposeslider3\", 18],\n        [\"generalpurposeslider4\", 19],\n        [\"volumefine\", 39],\n        [\"holdpedal\", 64],\n        [\"sustenutopedal\", 66],\n        [\"hold2pedal\", 69],\n        [\"soundreleasetime\", 72],\n        [\"soundattacktime\", 73],\n        [\"soundcontrol6\", 75],\n        [\"soundcontrol7\", 76],\n        [\"soundcontrol8\", 77],\n        [\"soundcontrol9\", 78],\n        [\"soundcontrol10\", 79],\n        [\"generalpurposebutton1\", 80],\n        [\"generalpurposebutton2\", 81],\n        [\"generalpurposebutton3\", 82],\n        [\"generalpurposebutton4\", 83],\n        [\"portamentocontrol\", 84],\n        [\"highresolutionvelocityprefix\", 88],\n        [\"reverblevel\", 91],\n        [\"tremololevel\", 92],\n        [\"choruslevel\", 93],\n        [\"celestelevel\", 94],\n        [\"phaserlevel\", 95],\n        [\"databuttonincrement\", 96],\n        [\"databuttondecrement\", 97],\n        [\"nonregisteredparameterfine\", 98],\n        [\"nonregisteredparametercoarse\", 99],\n        [\"registeredparameterfine\", 100],\n        [\"registeredparametercoarse\", 101],\n      ];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      map.forEach(pair => WEBMIDI_OUTPUT.channels[1].sendControlChange(pair[0], 123));\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[0]).to.equal(176);\n        expect(message[1]).to.equal(map[index][1]);\n        index++;\n\n        if (index >= map.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid controller name is specified\", function () {\n\n      // Arrange\n      let values = [\n        \"xxx\"\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendControlChange(value, 123);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw error when invalid controller number is specified\", function () {\n\n      // Arrange\n      let values = [\n        -1,\n        128\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendControlChange(value, 123);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw error when invalid value is specified\", function () {\n\n      // Arrange\n      let invalid = [\n        NaN,\n        undefined,\n        null\n      ];\n\n      // Act\n      invalid.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendControlChange(0, value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendControlChange(\"bankselectcoarse\", 0)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendNoteOff()\", function () {\n\n    it(\"should send MIDI message when specifying note by number\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let index = 0;\n      let options = {time: 0};\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      for (let i = 0; i <= 127; i++) WEBMIDI_OUTPUT.channels[channel].sendNoteOff(i, options);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message[0]).to.equal(128);\n        expect(message[1]).to.equal(index);\n        index++;\n        if (index > 127) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      }\n\n    });\n\n    it(\"should send MIDI message when specifying note by name\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let notes = [\"C-1\", \"C3\", \"G5\", \"G9\"];\n      let index = 0;\n      let options = {time: 0};\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      notes.forEach(note => WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note, options));\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[0]).to.equal(128);\n        expect(message[1]).to.equal(Utilities.toNoteNumber(notes[index]));\n\n        index++;\n\n        if (index >= notes.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send MIDI message when specifying note by Note object\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let notes = [new Note(\"C-1\"), new Note(\"C3\"), new Note(\"G5\"), new Note(\"G9\")];\n      let index = 0;\n      let options = {time: 0};\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      notes.forEach(note => WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note, options));\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[0]).to.equal(128);\n        expect(message[1]).to.equal(notes[index].number);\n        index++;\n\n        if (index >= notes.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct release velocity when specified with normal value\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let note = 0;\n      let options = {release: 1};\n      let expected = [128, 0, 127];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note, options);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should send correct release velocity when specified with raw value\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let note = 0;\n      let options = {release: 1, rawRelease: 83};\n      let expected = [128, 0, options.rawRelease];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note, options);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should call 'send' method with correct parameters when using 'rawRelease'\", function () {\n\n      // Arrange\n      let channel = 1;\n      let note = 60;\n      let options = {time: 10, release: 0.5, rawRelease: 127};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note, options);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(note);\n      expect(args[0][2]).to.equal(options.rawRelease);\n      expect(args[1].time).to.equal(options.time);\n\n    });\n\n    it(\"should call 'send' method with correct parameters when using 'attack'\", function () {\n\n      // Arrange\n      let channel = 1;\n      let note = 60;\n      let options = {time: 10, release: 0.5};\n      let expectedRawRelease = 64;\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note, options);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(note);\n      expect(args[0][2]).to.equal(expectedRawRelease);\n      expect(args[1].time).to.equal(options.time);\n\n    });\n\n    it(\"should call 'send' with correct 'octaveOffset' when using number\", function () {\n\n      // Arrange\n      const channel = 1;\n\n      WebMidi.octaveOffset = -1;\n      WEBMIDI_OUTPUT.octaveOffset = -1;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = -1;\n\n      const offset = WebMidi.octaveOffset +\n        WEBMIDI_OUTPUT.octaveOffset +\n        WEBMIDI_OUTPUT.channels[channel].octaveOffset;\n\n      const inputNumber = 60;\n      const outputNumber = inputNumber + offset * 12;\n      const spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOff(inputNumber);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(outputNumber);\n\n      WebMidi.octaveOffset = 0;\n      WEBMIDI_OUTPUT.octaveOffset = 0;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 0;\n\n    });\n\n    it(\"should call 'send' with correct 'octaveOffset' when using identifier\", function () {\n\n      // Arrange\n      const channel = 1;\n\n      WebMidi.octaveOffset = 1;\n      WEBMIDI_OUTPUT.octaveOffset = 1;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 1;\n\n      const offset = WebMidi.octaveOffset +\n        WEBMIDI_OUTPUT.octaveOffset +\n        WEBMIDI_OUTPUT.channels[channel].octaveOffset;\n\n      const note = new Note(\"C4\");\n      const outputNumber = note.number + offset * 12;\n      const spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(outputNumber);\n\n      WebMidi.octaveOffset = 0;\n      WEBMIDI_OUTPUT.octaveOffset = 0;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 0;\n\n    });\n\n    it(\"should call 'send' with correct 'octaveOffset' when using Note\", function () {\n\n      // Arrange\n      const channel = 1;\n\n      WebMidi.octaveOffset = 1;\n      WEBMIDI_OUTPUT.octaveOffset = 1;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 1;\n\n      const offset = WebMidi.octaveOffset +\n        WEBMIDI_OUTPUT.octaveOffset +\n        WEBMIDI_OUTPUT.channels[channel].octaveOffset;\n\n      const note = new Note(60);\n      const outputNumber = note.number + offset * 12;\n      const spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOff(note);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(outputNumber);\n\n      WebMidi.octaveOffset = 0;\n      WEBMIDI_OUTPUT.octaveOffset = 0;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 0;\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendNoteOff(0)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendNoteOn()\", function () {\n\n    it(\"should send MIDI message when specifying note by number\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let index = 0;\n      let options = {time: 0};\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      for (let i = 0; i <= 127; i++) {\n        WEBMIDI_OUTPUT.channels[channel].sendNoteOn(i, options);\n      }\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message[0]).to.equal(144);\n        expect(message[1]).to.equal(index);\n        index++;\n        if (index > 127) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      }\n\n    });\n\n    it(\"should send MIDI message when specifying note by name\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let notes = [\"C-1\", \"C3\", \"G5\", \"G9\"];\n      let index = 0;\n      let options = {time: 0};\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      notes.forEach(note => WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note, options));\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[0]).to.equal(144);\n        expect(message[1]).to.equal(Utilities.toNoteNumber(notes[index]));\n\n        index++;\n\n        if (index >= notes.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send MIDI message when specifying note by Note object\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let notes = [new Note(\"C-1\"), new Note(\"C3\"), new Note(\"G5\"), new Note(\"G9\")];\n      let index = 0;\n      let options = {time: 0};\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      notes.forEach(note => WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note, options));\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[0]).to.equal(144);\n        expect(message[1]).to.equal(notes[index].number);\n        index++;\n\n        if (index >= notes.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct attack velocity when specified with normal value\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let note = 0;\n      let options = {attack: 1};\n      let expected = [144, 0, 127];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note, options);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should send correct attack velocity when specified with raw value\", function(done) {\n\n      // Arrange\n      let channel = 1;\n      let note = 0;\n      let options = {attack: 1, rawAttack: 98};\n      let expected = [144, 0, options.rawAttack];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note, options);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should call 'send' method with correct parameters when using 'rawAttack'\", function () {\n\n      // Arrange\n      let channel = 1;\n      let note = 60;\n      let options = {time: 10, attack: 0.5, rawAttack: 127};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note, options);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(note);\n      expect(args[0][2]).to.equal(options.rawAttack);\n      expect(args[1].time).to.equal(options.time);\n\n    });\n\n    it(\"should call 'send' method with correct parameters when using 'attack'\", function () {\n\n      // Arrange\n      let channel = 1;\n      let note = 60;\n      let options = {time: 10, attack: 0.5};\n      let expectedRawAttack = 64;\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note, options);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(note);\n      expect(args[0][2]).to.equal(expectedRawAttack);\n      expect(args[1].time).to.equal(options.time);\n\n    });\n\n    it(\"should call 'send' with correct 'octaveOffset' when using number\", function () {\n\n      // Arrange\n      const channel = 1;\n\n      WebMidi.octaveOffset = 1;\n      WEBMIDI_OUTPUT.octaveOffset = 1;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 1;\n\n      const offset = WebMidi.octaveOffset +\n        WEBMIDI_OUTPUT.octaveOffset +\n        WEBMIDI_OUTPUT.channels[channel].octaveOffset;\n\n      const inputNumber = 60;\n      const outputNumber = inputNumber + offset * 12;\n      const spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOn(inputNumber);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(outputNumber);\n\n      WebMidi.octaveOffset = 0;\n      WEBMIDI_OUTPUT.octaveOffset = 0;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 0;\n\n    });\n\n    it(\"should call 'send' with correct 'octaveOffset' when using identifier\", function () {\n\n      // Arrange\n      const channel = 1;\n\n      WebMidi.octaveOffset = 1;\n      WEBMIDI_OUTPUT.octaveOffset = 1;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 1;\n\n      const offset = WebMidi.octaveOffset +\n        WEBMIDI_OUTPUT.octaveOffset +\n        WEBMIDI_OUTPUT.channels[channel].octaveOffset;\n\n      const note = new Note(\"C4\");\n      const outputNumber = note.number + offset * 12;\n      const spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(outputNumber);\n\n      WebMidi.octaveOffset = 0;\n      WEBMIDI_OUTPUT.octaveOffset = 0;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 0;\n\n    });\n\n    it(\"should call 'send' with correct 'octaveOffset' when using Note\", function () {\n\n      // Arrange\n      const channel = 1;\n\n      WebMidi.octaveOffset = 1;\n      WEBMIDI_OUTPUT.octaveOffset = 1;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 1;\n\n      const offset = WebMidi.octaveOffset +\n        WEBMIDI_OUTPUT.octaveOffset +\n        WEBMIDI_OUTPUT.channels[channel].octaveOffset;\n\n      const note = new Note(60);\n      const outputNumber = note.number + offset * 12;\n      const spy = sinon.spy(WEBMIDI_OUTPUT.channels[channel], \"send\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].sendNoteOn(note);\n\n      // Assert\n      let args = spy.args[0];\n      expect(spy.calledOnce).to.be.true;\n      expect(args[0][1]).to.equal(outputNumber);\n\n      WebMidi.octaveOffset = 0;\n      WEBMIDI_OUTPUT.octaveOffset = 0;\n      WEBMIDI_OUTPUT.channels[channel].octaveOffset = 0;\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendNoteOn(0)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendChannelAftertouch()\", function () {\n\n    it(\"should send correct MIDI message when using float (0-1)\", function(done) {\n\n      // Arrange\n      let index = 0;\n      let values = [0, 0.5, 1];\n      let expected = [\n        [208, 0],\n        [208, 64],\n        [208, 127],\n      ];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      values.forEach(value => WEBMIDI_OUTPUT.channels[1].sendChannelAftertouch(value));\n\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct MIDI message when using integer (0-127)\", function(done) {\n\n      // Arrange\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      for (let i = 0; i < 128; i++) {\n        WEBMIDI_OUTPUT.channels[1].sendChannelAftertouch(i, {rawValue: true});\n      }\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message[1]).to.equal(index++);\n\n        if (index >= 128) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid pressure is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        2,\n        undefined,\n        null,\n        \"test\"\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendChannelAftertouch(value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should throw error when invalid raw pressure is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        undefined,\n        null,\n        \"test\"\n      ];\n      let options = {rawValue: true};\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendChannelAftertouch(value, options);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendChannelAftertouch(1)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendKeyAftertouch()\", function () {\n\n    it(\"should send correct MIDI message when using note number\", function(done) {\n\n      // Arrange\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      for (let i = 0; i <= 127; i++) WEBMIDI_OUTPUT.channels[1].sendKeyAftertouch(i, 0.5);\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members([160, index, 64]);\n        index++;\n\n        if (index >= 127) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct MIDI message when using note identifier\", function(done) {\n\n      // Arrange\n      let index = 0;\n      const value = 0.5;\n      const items = [\n        {identifier: \"C-1\", number: 0},\n        {identifier: \"C4\", number: 60},\n        {identifier: \"G9\", number: 127},\n      ];\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      items.forEach(item => {\n        WEBMIDI_OUTPUT.channels[1].sendKeyAftertouch(item.identifier, value);\n      });\n\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(\n          message\n        ).to.have.ordered.members([160, items[index].number, Utilities.fromFloatTo7Bit(value)]);\n\n        index++;\n\n        if (index >= items.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct message when offset is used with note number\", function(done) {\n\n      // Arrange\n      WebMidi.octaveOffset = -1;\n      WEBMIDI_OUTPUT.octaveOffset = -1;\n      WEBMIDI_OUTPUT.channels[1].octaveOffset = -1;\n      const offset = WebMidi.octaveOffset + WEBMIDI_OUTPUT.octaveOffset +\n        WEBMIDI_OUTPUT.channels[1].octaveOffset;\n      let index = Math.abs(offset) * 12;\n      const max = 127;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      for (let i = index; i <= max; i++) WEBMIDI_OUTPUT.channels[1].sendKeyAftertouch(i, 0.5);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members([160, index + offset * 12, 64]);\n        index++;\n\n        if (index >= max) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          WebMidi.octaveOffset = 0;\n          WEBMIDI_OUTPUT.octaveOffset = 0;\n          WEBMIDI_OUTPUT.channels[1].octaveOffset = 0;\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct message when offset is used with note identifier\", function(done) {\n\n      // Arrange\n      WebMidi.octaveOffset = 1;\n      WEBMIDI_OUTPUT.octaveOffset = 1;\n      WEBMIDI_OUTPUT.channels[1].octaveOffset = 1;\n\n      let index = 0;\n      const value = 0.5;\n\n      const items = [\n        {identifier: \"C-1\", number: 36},\n        {identifier: \"C4\", number: 96}\n      ];\n\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      items.forEach(item => {\n        WEBMIDI_OUTPUT.channels[1].sendKeyAftertouch(item.identifier, value);\n      });\n\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(\n          [160, items[index].number, Utilities.fromFloatTo7Bit(value)]\n        );\n\n        index++;\n\n        if (index >= items.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          WebMidi.octaveOffset = 0;\n          WEBMIDI_OUTPUT.octaveOffset = 0;\n          WEBMIDI_OUTPUT.channels[1].octaveOffset = 0;\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid pressure is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        2,\n        undefined,\n        null,\n        \"test\"\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendKeyAftertouch(64, value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should throw error when invalid raw pressure is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        undefined,\n        null,\n        \"test\"\n      ];\n      let options = {rawValue: true};\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendKeyAftertouch(64, value, options);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendKeyAftertouch(64, 0.5)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendLocalControl()\", function () {\n\n    it(\"should call 'sendChannelMode' method with correct parameter for 'true'\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendLocalControl(true, options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"localcontrol\", 127, options)).to.be.true;\n\n    });\n\n    it(\"should call 'sendChannelMode' method with correct parameter for 'false'\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendLocalControl(false, options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"localcontrol\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI message\", function(done) {\n\n      // Arrange\n      let expected = [176, 122, 0];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendLocalControl();\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendLocalControl()\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendMasterTuning()\", function () {\n\n    it(\"should call 'sendRpnValue' method for coarse and fine tuning\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendRpnValue\");\n      let options = {time: 0};\n      let value = 12.5;\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendMasterTuning(value, options);\n\n      // Assert\n      expect(spy.calledWith(\"channelcoarsetuning\")).to.be.true;\n      expect(spy.calledWith(\"channelfinetuning\")).to.be.true;\n      expect(spy.calledTwice).to.be.true;\n\n    });\n\n    it(\"should throw error when invalid value is specified\", function() {\n\n      // Arrange\n      let values = [\n        -65,\n        64\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendMasterTuning(value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should send correct MIDI messages\", function(done) {\n\n      // Arrange\n      let coarse = 25;\n      let fine = 0.13;\n      let expected = [\n\n        // Master coarse tuning 0,2\n        [ 176, 101, 0 ],\n        [ 176, 100, 2 ],\n        [ 176, 6, coarse + 64 ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ],\n\n        // Master fine tuning 0,1\n        [ 176, 101, 0 ],\n        [ 176, 100, 1 ],\n        [ 176, 6, 72 ],  // msb\n        [ 176, 38, 40 ], // lsb\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendMasterTuning(coarse + fine);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendMasterTuning(0)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendModulationRange()\", function () {\n\n    it(\"should properly call 'sendRpnValue()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendRpnValue\");\n      let semitones = 8;\n      let cents = 123;\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendModulationRange(semitones, cents, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(\"modulationrange\", [semitones, cents], options)\n      ).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI messages\", function(done) {\n\n      // Arrange\n      let semitones = 8;\n      let cents = 123;\n\n      let expected = [\n        [ 176, 101, 0 ],\n        [ 176, 100, 5 ],\n        [ 176, 6, semitones ],\n        [ 176, 38, cents ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendModulationRange(semitones, cents);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid semitones value is specified\", function() {\n\n      // Arrange\n      let semitones = [\n        -1,\n        128,\n        undefined,\n        NaN,\n        null\n      ];\n\n      // Act\n      semitones.forEach(assert);\n\n      // Assert\n      function assert(semitone) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendModulationRange(semitone);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should throw error if cents value is specified but invalid\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        NaN\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendModulationRange(64, value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendModulationRange(8, 9)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendNrpnValue()\", function () {\n\n    it(\"should properly call '_selectNonRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let parameter = [8, 123];\n      let data = 123;\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_selectNonRegisteredParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendNrpnValue(parameter, data, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(parameter, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call '_setCurrentParameter()' method\", function () {\n\n      // Arrange\n      let parameter = [8, 123];\n      let data = [47];\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_setCurrentParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendNrpnValue(parameter, data, options);\n\n      // Assert\n      expect(\n        spy.calledWithExactly(data, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call '_deselectNonRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let parameter = [8, 123];\n      let data = 123;\n      let options = {time: 0};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_deselectNonRegisteredParameter\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendNrpnValue(parameter, data, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(options)\n      ).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI messages when using data array\", function(done) {\n\n      // Arrange\n      let parameter = [8, 123];\n      let data = [45, 67];\n\n      let expected = [\n        [ 176, 99, parameter[0] ],\n        [ 176, 98, parameter[1] ],\n        [ 176, 6, data[0] ],\n        [ 176, 38, data[1] ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendNrpnValue(parameter, data);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct MIDI messages when single data byte\", function(done) {\n\n      // Arrange\n      let parameter = [8, 123];\n      let data = 45;\n\n      let expected = [\n        [ 176, 99, parameter[0] ],\n        [ 176, 98, parameter[1] ],\n        [ 176, 6, data ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendNrpnValue(parameter, data);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid NRPN value is specified\", function() {\n\n      // Arrange\n      let values = [\n        1,\n        [-1, 64],\n        [64, 128],\n        undefined,\n        NaN,\n        null\n      ];\n      let data = 45;\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendNrpnValue(value, data);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw error when invalid data is specified\", function() {\n\n      // Arrange\n      let values = [\n        1,\n        [-1, 64],\n        [64, 128],\n        undefined,\n        NaN,\n        null\n      ];\n      let nrpn = 45;\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendNrpnValue(nrpn, value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n\n      // Arrange\n      let nrpn = [12, 34];\n\n      // Assert\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendNrpnValue(nrpn, 56)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n\n    });\n\n  });\n\n  describe(\"sendOmniMode()\", function () {\n\n    it(\"should call 'sendChannelMode()' method with correct parameter for 'true'\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendOmniMode(true, options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"omnimodeon\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should call 'sendChannelMode()' method with correct parameter for 'false'\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendOmniMode(false, options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"omnimodeoff\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI message\", function(done) {\n\n      // Arrange\n      let expected = [176, 125, 0];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendOmniMode();\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendOmniMode(true)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendPitchBend()\", function () {\n\n    it(\"should send correct MIDI message when using float (-1 to 1)\", function(done) {\n\n      // Arrange\n      let index = 0;\n      let values = [-1, 0, 1];\n      let expected = [\n        [224, 0, 0],\n        [224, 0, 64],\n        [224, 127, 127]\n      ];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      values.forEach(value => WEBMIDI_OUTPUT.channels[1].sendPitchBend(value));\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      }\n\n    });\n\n    it(\"should send correct MIDI message when using integers\", function(done) {\n\n      // Arrange\n      let values = [\n        [0, 0],\n        [64, 0],\n        [127, 0],\n        [0, 0],\n        [0, 64],\n        [0, 127]\n      ];\n      let options = {rawValue: true};\n      let expected = [\n        [224, 0, 0],\n        [224, 0, 64],\n        [224, 0, 127],\n        [224, 0, 0],\n        [224, 64, 0],\n        [224, 127, 0]\n      ];\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      values.forEach(value => WEBMIDI_OUTPUT.channels[1].sendPitchBend(value, options));\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      }\n\n    });\n\n    it(\"should throw error when invalid value is specified\", function() {\n\n      // Arrange\n      let values = [\n        undefined,\n        NaN,\n        null,\n        -2,\n        2\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendPitchBend(value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw error when invalid raw value is specified\", function() {\n\n      // Arrange\n      let values = [\n        undefined,\n        NaN,\n        null,\n        -1,\n        128,\n        [-1, 64],\n        [128, 64],\n        [64, -1],\n        [64, 128],\n        []\n      ];\n      let options = {rawValue: true};\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendPitchBend(value, options);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendPitchBend(0)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendPitchBendRange()\", function () {\n\n    it(\"should call 'sendRpnValue()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendRpnValue\");\n      let semitones = 8;\n      let cents = 123;\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendPitchBendRange(semitones, cents, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(\"pitchbendrange\", [semitones, cents], options)\n      ).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI messages\", function(done) {\n\n      // Arrange\n      let semitones = 8;\n      let cents = 123;\n\n      let expected = [\n        [ 176, 101, 0 ],\n        [ 176, 100, 0 ],\n        [ 176, 6, semitones ],\n        [ 176, 38, cents ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendPitchBendRange(semitones, cents);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid semitones value is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        undefined,\n        NaN,\n        null\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendPitchBendRange(64, value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should throw error when invalid cents value is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        128,\n        undefined,\n        NaN,\n        null\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendPitchBendRange(64, value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendPitchBendRange(8, 9)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendPolyphonicMode()\", function () {\n\n    it(\"should call 'sendChannelMode' method with correct parameter for 'mono'\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendPolyphonicMode(\"mono\", options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"monomodeon\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should call 'sendChannelMode' method with correct parameter for 'poly'\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendPolyphonicMode(\"poly\", options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"polymodeon\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI message for 'mono'\", function(done) {\n\n      // Arrange\n      let expected = [176, 126, 0];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendPolyphonicMode(\"mono\");\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should send correct MIDI message for 'poly'\", function(done) {\n\n      // Arrange\n      let expected = [176, 127, 0];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendPolyphonicMode(\"poly\");\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendPolyphonicMode(\"mono\")\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendProgramChange()\", function () {\n\n    it(\"should send correct MIDI message\", function(done) {\n\n      // Arrange\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      for (let i = 0; i <= 127; i++) {\n        WEBMIDI_OUTPUT.channels[1].sendProgramChange(i);\n      }\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message[0]).to.equal(192);\n        expect(message[1]).to.equal(index);\n        index++;\n        if (index > 127) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n      }\n\n    });\n\n    it(\"should throw error when invalid value is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        129\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendProgramChange(value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendProgramChange(1)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendRpnValue()\", function () {\n\n    it(\"should properly call '_selectRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let rpn = [0x3D, 0x00];\n      let data = 123;\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_selectRegisteredParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnValue(rpn, data, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(rpn, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call '_setCurrentParameter()' method\", function () {\n\n      // Arrange\n      let rpn = [0x00, 0x00];\n      let data = 47;\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_setCurrentParameter\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnValue(rpn, data, options);\n\n      // Assert\n      expect(\n        spy.calledWithExactly(data, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should properly call '_deselectRegisteredParameter()' method\", function () {\n\n      // Arrange\n      let rpn = [0x00, 0x01];\n      let data = 123;\n      let options = {time: 0};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"_deselectRegisteredParameter\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnValue(rpn, data, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(options)\n      ).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI messages when using RPN name\", function(done) {\n\n      // Arrange\n      let rpn = \"modulationrange\";\n      let rpnAsNumbers = [0x00, 0x05];\n      let data = [45, 67];\n\n      let expected = [\n        [ 176, 101, rpnAsNumbers[0] ],\n        [ 176, 100, rpnAsNumbers[1] ],\n        [ 176, 6, data[0] ],\n        [ 176, 38, data[1] ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnValue(rpn, data);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct MIDI messages when using data array\", function(done) {\n\n      // Arrange\n      let rpn = [0x00, 0x02];\n      let data = [45, 67];\n\n      let expected = [\n        [ 176, 101, rpn[0] ],\n        [ 176, 100, rpn[1] ],\n        [ 176, 6, data[0] ],\n        [ 176, 38, data[1] ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnValue(rpn, data);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should send correct MIDI messages when using single data byte\", function(done) {\n\n      // Arrange\n      let rpn = [8, 123];\n      let data = 45;\n\n      let expected = [\n        [ 176, 101, rpn[0] ],\n        [ 176, 100, rpn[1] ],\n        [ 176, 6, data ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendRpnValue(rpn, data);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid RPN value is specified\", function() {\n\n      // Arrange\n      let values = [\n        1,\n        [-1, 64],\n        [64, 128],\n        undefined,\n        NaN,\n        null\n      ];\n      let data = 45;\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendRpnValue(value, data);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw error when invalid data is specified\", function() {\n\n      // Arrange\n      let values = [\n        1,\n        [-1, 64],\n        [64, 128],\n        undefined,\n        NaN,\n        null\n      ];\n      let rpn = 45;\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendRpnValue(rpn, value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n\n      // Arrange\n      let nrpn = [12, 34];\n\n      // Assert\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendRpnValue(nrpn, 56)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n\n    });\n\n  });\n\n  describe(\"sendTuningBank()\", function () {\n\n    it(\"should call 'sendRpnValue()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendRpnValue\");\n      let value = 8;\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendTuningBank(value, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWith(\"tuningbank\", value, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI messages\", function(done) {\n\n      // Arrange\n      let value = 8;\n\n      let expected = [\n        [ 176, 101, 0 ],\n        [ 176, 100, 4 ],\n        [ 176, 6, value ],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendTuningBank(value);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid value is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        129,\n        undefined,\n        NaN,\n        null\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendTuningBank(value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendTuningBank(8)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendTuningProgram()\", function () {\n\n    it(\"should call 'sendRpnValue()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendRpnValue\");\n      let value = 8;\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendTuningProgram(value, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWith(\"tuningprogram\", value, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI messages\", function(done) {\n\n      // Arrange\n      let value = 8;\n      let expected = [\n        [ 176, 101, 0 ],\n        [ 176, 100, 3 ],\n        [ 176, 6, value],\n        [ 176, 101, 127 ],\n        [ 176, 100, 127 ]\n      ];\n      let index = 0;\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendTuningProgram(value);\n\n      // Assert\n      function assert(deltaTime, message) {\n\n        expect(message).to.have.ordered.members(expected[index]);\n        index++;\n\n        if (index >= expected.length) {\n          VIRTUAL_OUTPUT.removeAllListeners();\n          done();\n        }\n\n      }\n\n    });\n\n    it(\"should throw error when invalid value is specified\", function() {\n\n      // Arrange\n      let values = [\n        -1,\n        129,\n        undefined,\n        NaN,\n        null\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          WEBMIDI_OUTPUT.channels[1].sendTuningProgram(value);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendTuningProgram(8)\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"stopNote()\", function () {\n\n    it(\"should properly call 'sendNoteOff()' method\", function () {\n\n      // Arrange\n      let channel = 1;\n      let note = \"G5\";\n      let options = {time: 0};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendNoteOff\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[channel].stopNote(note, options);\n\n      // Assert\n      expect(\n        spy.calledOnceWithExactly(note, options)\n      ).to.be.true;\n\n    });\n\n    it(\"should call the 'sendNoteOff()' method with correct parameters\", function () {\n\n      // Arrange\n      let note = \"G5\";\n      let options = {time: 10, release: 0.5, rawRelease: 127};\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendNoteOff\");\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].stopNote(note, options);\n\n      // Assert\n      expect(spy.calledOnceWith(note)).to.be.true;\n      expect(spy.args[0][0]).to.equal(note);\n      expect(spy.args[0][1].time).to.equal(options.time);\n      expect(spy.args[0][1].release).to.equal(options.release);\n      expect(spy.args[0][1].rawRelease).to.equal(options.rawRelease);\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].playNote(\"C3\")\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendAllNotesOff()\", function () {\n\n    it(\"should properly call 'sendChannelMode()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendAllNotesOff(options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"allnotesoff\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should send correct MIDI message\", function(done) {\n\n      // Arrange\n      let expected = [176, 123, 0];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendAllNotesOff();\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendAllNotesOff()\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n  describe(\"sendAllSoundOff()\", function () {\n\n    it(\"should properly call 'sendChannelMode()' method\", function () {\n\n      // Arrange\n      let spy = sinon.spy(WEBMIDI_OUTPUT.channels[1], \"sendChannelMode\");\n      let options = {time: 0};\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendAllSoundOff(options);\n\n      // Assert\n      expect(spy.calledOnceWithExactly(\"allsoundoff\", 0, options)).to.be.true;\n\n    });\n\n    it(\"should send the correct MIDI message\", function(done) {\n\n      // Arrange\n      let expected = [176, 120, 0];\n      VIRTUAL_OUTPUT.on(\"message\", assert);\n\n      // Act\n      WEBMIDI_OUTPUT.channels[1].sendAllSoundOff();\n\n      // Assert\n      function assert(deltaTime, message) {\n        expect(message).to.have.ordered.members(expected);\n        VIRTUAL_OUTPUT.removeAllListeners();\n        done();\n      }\n\n    });\n\n    it(\"should return 'OutputChannel' object for method chaining\", function () {\n      expect(\n        WEBMIDI_OUTPUT.channels[1].sendAllSoundOff()\n      ).to.equal(WEBMIDI_OUTPUT.channels[1]);\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/Utilities.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst {Utilities, WebMidi, Note, Enumerations} = require(\"../dist/cjs/webmidi.cjs.js\");\n\n// VERIFIED\ndescribe(\"Utilities Object\", function() {\n\n  describe(\"toNoteNumber()\", function() {\n\n    it(\"should return the correct MIDI note number\", function() {\n\n      // Arrange\n      const pairs = [\n        {identifier: \"C-1\", number: 0},\n        {identifier: \"C4\", number: 60},\n        {identifier: \"G9\", number: 127}\n      ];\n\n      // Act\n      pairs.forEach(assert);\n\n      // Assert\n      function assert(pair) {\n        expect(Utilities.toNoteNumber(pair.identifier)).to.equal(pair.number);\n      }\n\n    });\n\n    it(\"should throw error if invalid identifier is provided\", function() {\n\n      // Arrange\n      const values = [\n        \"\",\n        \"abc\",\n        null,\n        undefined,\n        \"G#9\",\n        \"Cb-1\",\n        \"X2\",\n        function () {},\n        {},\n        \"555\",\n        \"C-3\",\n        \"Cbb-1\",\n        \"H3\",\n        \"G##9\"\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n\n      function assert(value) {\n        expect(function() {\n          Utilities.toNoteNumber(value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should return the correct MIDI note number when using octaveOffset\", function() {\n\n      // Arrange\n      const items = [\n        {identifier: \"C-1\", octaveOffset: 1, number: 12},\n        {identifier: \"C4\", octaveOffset: -1, number: 48},\n        {identifier: \"C4\", octaveOffset: 1, number: 72},\n        {identifier: \"G9\", octaveOffset: -1, number: 115},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.toNoteNumber(item.identifier, item.octaveOffset)\n        ).to.equal(item.number);\n      }\n\n    });\n\n    it(\"should throw when using invalid octaveOffset\", function() {\n\n      // Arrange\n      const items = [\n        NaN,\n        Infinity,\n        -Infinity,\n        [],\n        {},\n        -1,\n        20\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(function () {\n          Utilities.toNoteNumber(\"C-1\", item);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n  });\n\n  describe(\"getNoteDetails()\", function() {\n\n    it(\"should return the correct fragments when using identifier\", function () {\n\n      // Arrange\n      const items = [\n        {identifier: \"C-1\", name: \"C\", accidental: undefined, octave: -1},\n        {identifier: \"D##0\", name: \"D\", accidental: \"##\", octave: 0},\n        {identifier: \"E#1\", name: \"E\", accidental: \"#\", octave: 1},\n        {identifier: \"Fb8\", name: \"F\", accidental: \"b\", octave: 8},\n        {identifier: \"Fbb9\", name: \"F\", accidental: \"bb\", octave: 9},\n        {identifier: \"G9\", name: \"G\", accidental: undefined, octave: 9},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        const fragments = Utilities.getNoteDetails(item.identifier);\n        expect(fragments.identifier).to.equal(item.identifier);\n        expect(fragments.name).to.equal(item.name);\n        expect(fragments.accidental).to.equal(item.accidental);\n        expect(fragments.octave).to.equal(item.octave);\n      }\n\n    });\n\n    it(\"should return the correct fragments when using number\", function () {\n\n      // Arrange\n      const items = [\n        {number: 0, name: \"C\", accidental: undefined, octave: -1},\n        {number: 59, name: \"B\", accidental: undefined, octave: 3},\n        {number: 60, name: \"C\", accidental: undefined, octave: 4},\n        {number: 61, name: \"C\", accidental: \"#\", octave: 4},\n        {number: 127, name: \"G\", accidental: undefined, octave: 9},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        const fragments = Utilities.getNoteDetails(item.number);\n        expect(fragments.name).to.equal(item.name);\n        expect(fragments.accidental).to.equal(item.accidental);\n        expect(fragments.octave).to.equal(item.octave);\n      }\n\n    });\n\n    it(\"should should throw when passing invalid note identifier\", function () {\n\n      // Arrange\n      const items = [\n        NaN,\n        Infinity,\n        -Infinity,\n        [],\n        {},\n        -1,\n        20,\n        \"C###3\",\n        \"Bbbb6\",\n        \"test\"\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(function () {\n          Utilities.toNoteNumber(item);\n        }).to.throw(TypeError);\n      }\n\n    });\n\n  });\n\n  describe(\"getCcNameByNumber()\", function() {\n\n    it(\"should return the correct name\", function () {\n\n      // Arrange\n      const items = [-1, 128, null, undefined, \"test\"];\n\n      // Act\n\n      // Assert\n      for (let i = 0; i <= 127; i++) {\n        expect(Utilities.getCcNameByNumber(i))\n          .to.equal(Enumerations.CONTROL_CHANGE_MESSAGES[i].name);\n      }\n\n      items.forEach(item => {\n        expect(Utilities.getCcNameByNumber(item)).to.equal(undefined);\n      });\n\n    });\n\n  });\n\n  describe(\"getCcNumberByName()\", function() {\n\n    it(\"should return the correct number\", function () {\n\n      // Arrange\n      const items = [\n        {name: \"bankselectcoarse\", number: 0},\n        {name: \"controller31\", number: 31},\n        {name: \"attacktime\", number: 73},\n        {name: \"polymodeon\", number: 127},\n      ];\n\n      // Act\n\n      // Assert\n      items.forEach(item => {\n        expect(Utilities.getCcNumberByName(item.name)).to.equal(item.number);\n      });\n\n    });\n\n    it(\"should return undefined for invalid names\", function () {\n\n      // Arrange\n      const items = [\"\", undefined, null, \"test\"];\n\n      // Act\n\n      // Assert\n      items.forEach(item => {\n        expect(Utilities.getCcNumberByName(item)).to.equal(undefined);\n      });\n\n    });\n\n  });\n\n  describe(\"sanitizeChannels()\", function() {\n\n    it(\"should return only valid MIDI channel numbers\", function() {\n\n      // Valid values are 1, 8 and 16\n      let channels = [\n        1, -1, -2.3, 0, 17, \"x\", NaN, null, Infinity, 8, -Infinity, {}, true, false, [undefined],\n        4e2, 16\n      ];\n      expect(Utilities.sanitizeChannels(channels).length).to.equal(3);\n      expect(Utilities.sanitizeChannels([]).length).to.equal(0);\n      expect(Utilities.sanitizeChannels([9.7])[0]).to.equal(9);\n      expect(Utilities.sanitizeChannels([1e1])[0]).to.equal(10);\n      expect(Utilities.sanitizeChannels(\"none\").length).to.equal(0);\n\n    });\n\n    it('should return all channels when \"all\" is specified', function() {\n      expect(Utilities.sanitizeChannels(\"all\").length).to.equal(16);\n    });\n\n    it(\"should return an empty array when 'none' is passed\", function() {\n      expect(Utilities.sanitizeChannels(\"none\").length).to.equal(0);\n    });\n\n  });\n\n  describe(\"convertToTimestamp()\", function() {\n\n    beforeEach(\"Enable WebMidi\", async function() {\n      await WebMidi.enable({sysex: true});\n    });\n\n    it(\"should return timestamp when passed string starting with '+'\", function() {\n      // Assert\n      expect(Utilities.toTimestamp(\"+1000\")).to.be.a(\"number\");\n    });\n\n    it(\"should return false for invalid input\", function() {\n\n      // Arrange\n      let values = [undefined, null, false, [], {}, \"\", \"-1\", \"+\", -1, -Infinity];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(Utilities.toTimestamp(value)).to.be.false;\n      }\n\n    });\n\n    it(\"should return a positive number as is\", function() {\n\n      // Arrange\n      let values = [0, 1, Infinity];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(Utilities.toTimestamp(value)).to.equal(value);\n      }\n\n    });\n\n  });\n\n  describe(\"toNoteIdentifier()\", function() {\n\n    it(\"should return the correct note identifier\", function() {\n\n      // Arrange\n      const items = [\n        {number: 0, identifier: \"C-1\"},\n        {number: 60, identifier: \"C4\"},\n        {number: 127, identifier: \"G9\"},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(Utilities.toNoteIdentifier(item.number)).to.equal(item.identifier);\n      }\n\n    });\n\n    it(\"should return the correct identifier when using 'octaveOffset'\", function() {\n\n      // Arrange\n      const items = [\n        {identifier: \"C-1\", octaveOffset: 1, number: 12},\n        {identifier: \"C4\", octaveOffset: -1, number: 48},\n        {identifier: \"C4\", octaveOffset: 1, number: 72},\n        {identifier: \"G9\", octaveOffset: -1, number: 115},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.toNoteNumber(item.identifier, item.octaveOffset)\n        ).to.equal(item.number);\n      }\n\n    });\n\n    it(\"should throw error if invalid number is provided\", function() {\n\n      // Arrange\n      const values = [\n        -1,\n        128,\n        \"\",\n        \"abc\",\n        null,\n        undefined,\n        {},\n        \"555\"\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n\n      function assert(value) {\n        expect(function() {\n          Utilities.toNoteIdentifier(value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should throw when using invalid octaveOffset\", function() {\n\n      // Arrange\n      const items = [\n        NaN,\n        Infinity,\n        -Infinity,\n        [],\n        {},\n        \"abc\"\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(function () {\n          Utilities.toNoteIdentifier(60, item);\n        }).to.throw(RangeError);\n      }\n\n    });\n\n  });\n\n  describe(\"guessNoteNumber()\", function() {\n\n    it(\"should return the correct number for ints and floats\", function() {\n\n      // Arrange\n      const items = [\n        0,\n        0.0,\n        0.1,\n        0.5,\n        0.9\n      ];\n\n      // Act\n      for (let i = 0; i <= 127; i++) {\n        assert(i);\n      }\n\n      // Assert\n      function assert(value) {\n\n        items.forEach(item => {\n          expect(\n            Utilities.guessNoteNumber(value + item)\n          ).to.equal(value);\n        });\n\n      }\n\n    });\n\n    it(\"should return the correct number for numbers in strings\", function() {\n\n      // Arrange\n      const items = [\n        \"abc\",\n        \".\",\n        undefined,\n        null\n      ];\n\n      // Act\n      for (let i = 0; i <= 127; i++) {\n        assert(i);\n      }\n\n      // Assert\n      function assert(value) {\n\n        items.forEach(item => {\n          expect(\n            Utilities.guessNoteNumber(value.toString() + item)\n          ).to.equal(value);\n        });\n\n      }\n\n    });\n\n    it(\"should return the correct number for note identifiers\", function() {\n\n      // Arrange\n      const items = [\n        {identifier: \"C-1\", number: 0},\n        {identifier: \"D#0\", number: 15},\n        {identifier: \"D##0\", number: 16},\n        {identifier: \"C4\", number: 60},\n        {identifier: \"Eb4\", number: 63},\n        {identifier: \"Ebb4\", number: 62},\n        {identifier: \"G9\", number: 127},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.guessNoteNumber(item.identifier)\n        ).to.equal(item.number);\n      }\n\n    });\n\n    it(\"should strip whitespace for note identifiers\", function() {\n\n      // Arrange\n      const items = [\n        {identifier: \" C-1 \", number: 0},\n        {identifier: \"\\nD#0\", number: 15},\n        {identifier: \"D##0\\t\", number: 16},\n        {identifier: \"\\rC4\", number: 60},\n        {identifier: \"\\rEb4\\r\", number: 63},\n        {identifier: \"\\n\\r\\t Ebb4\", number: 62},\n        {identifier: \"G9\\n\\r\\t \", number: 127},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.guessNoteNumber(item.identifier)\n        ).to.equal(item.number);\n      }\n\n    });\n\n    it(\"should return the correct number for note identifiers when using octaveOffset\", function() {\n\n      // Arrange\n      const items = [\n        {identifier: \"C-1\", number: 0, offset: 0},\n        {identifier: \"C-1\", number: 12, offset: 1},\n        {identifier: \"C4\", number: 60, offset: 0},\n        {identifier: \"C4\", number: 48, offset: -1},\n        {identifier: \"C4\", number: 72, offset: 1},\n        {identifier: \"G9\", number: 127, offset: 0},\n        {identifier: \"G9\", number: 115, offset: -1},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.guessNoteNumber(item.identifier, item.offset)\n        ).to.equal(item.number);\n      }\n\n    });\n\n    it(\"should disregard octaveOffset for note numbers\", function() {\n\n      // Arrange\n      const items = [\n        {number: 0, offset: -3},\n        {number: 64, offset: -2},\n        {number: 127, offset: -1},\n        {number: \"0\", offset: 1},\n        {number: \"64.0\", offset: 2},\n        {number: \"127x\", offset: 3},\n        {number: 0.1, offset: 4},\n        {number: 64.5, offset: 5},\n        {number: 127.9, offset: 6},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.guessNoteNumber(item.number, item.offset)\n        ).to.equal(parseInt(item.number));\n      }\n\n    });\n\n    it(\"should return false for identifiers when using out-of-bounds octaveOffset\", function() {\n\n      // Arrange\n      const items = [\n        {identifier: \"C-1\", offset: -10},\n        {identifier: \"C-1\", offset: -1},\n        {identifier: \"G9\", offset: 1},\n        {identifier: \"G9\", offset: 10},\n        {identifier: \"C4\", offset: 100},\n        {identifier: \"C4\", offset: -100}\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.guessNoteNumber(item.identifier, item.offset)\n        ).to.be.false;\n      }\n\n    });\n\n    it(\"should return false if invalid input is provided\", function() {\n\n      // Arrange\n      const items = [\n        \"abc\",\n        null,\n        undefined,\n        -1,\n        -1.2,\n        128,\n        128.1,\n        function () {},\n        {},\n        \"555\",\n        \"H3\",\n        \"Z#8\",\n        Infinity,\n        -Infinity,\n        -2.3,\n        false,\n        true\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(Utilities.guessNoteNumber(item)).to.be.false;\n      };\n\n    });\n\n  });\n\n  describe(\"offsetNumber()\", function () {\n\n    it(\"should use 0 if octaveOffset is invalid\", function() {\n\n      // Arrange\n      let number = 60;\n      let values = [\n        Infinity,\n        -Infinity,\n        \"abc\",\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(Utilities.offsetNumber(number, value)).to.equal(number);\n      }\n\n    });\n\n    it(\"should use 0 if semitoneOffset is invalid\", function() {\n\n      // Arrange\n      let number = 60;\n      let values = [\n        Infinity,\n        -Infinity,\n        \"abc\",\n        NaN,\n        null,\n        undefined\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(Utilities.offsetNumber(number, 0, value)).to.equal(number);\n      }\n\n    });\n\n    it(\"should cap returned value at 127\", function() {\n\n      // Arrange\n      let number = 60;\n      let pairs = [\n        {octaveOffset: 0, semitoneOffset: 1000},\n        {octaveOffset: 100, semitoneOffset: 0},\n        {octaveOffset: 20, semitoneOffset: 20},\n      ];\n\n      // Act\n      pairs.forEach(assert);\n\n      // Assert\n      function assert(pair) {\n        expect(\n          Utilities.offsetNumber(number, pair.octaveOffset, pair.semitoneOffset)\n        ).to.equal(127);\n      }\n\n    });\n\n    it(\"should return 0 for values smaller than 0\", function() {\n\n      // Arrange\n      let number = 60;\n      let pairs = [\n        {octaveOffset: 0, semitoneOffset: -200},\n        {octaveOffset: -20, semitoneOffset: 0},\n        {octaveOffset: -20, semitoneOffset: -20},\n      ];\n\n      // Act\n      pairs.forEach(assert);\n\n      // Assert\n      function assert(pair) {\n        expect(\n          Utilities.offsetNumber(number, pair.octaveOffset, pair.semitoneOffset)\n        ).to.equal(0);\n      }\n\n    });\n\n  });\n\n  describe(\"buildNote()\", function() {\n\n    it(\"should return the same note if a note is passed in\", function () {\n\n      // Arrange\n      const note = new Note(\"C4\", {attack: 0.12, release: 0.34, duration: 56});\n\n      // Act\n      const result = Utilities.buildNote(note);\n\n      // Assert\n      expect(result).to.equal(note);\n      expect(result.identifier).to.equal(note.identifier);\n      expect(result.attack).to.equal(note.attack);\n      expect(result.release).to.equal(note.release);\n      expect(result.duration).to.equal(note.duration);\n\n    });\n\n    it(\"should return the right note when passing identifier\", function () {\n\n      // Arrange\n      const identifier = \"C4\";\n      const options = {attack: 0.12, release: 0.34, duration: 56};\n\n      // Act\n      const result = Utilities.buildNote(identifier, options);\n\n      // Assert\n      expect(result.identifier).to.equal(identifier);\n      expect(result.attack).to.equal(options.attack);\n      expect(result.release).to.equal(options.release);\n      expect(result.duration).to.equal(options.duration);\n\n    });\n\n    it(\"should return the right note when passing identifier and octaveOffset\", function () {\n\n      // Arrange\n      const items = [\n        {target: \"C0\", offset: 1, identifier: \"C-1\"},\n        {target: \"C2\", offset: -2, identifier: \"C4\"},\n        {target: \"C6\", offset: 2, identifier: \"C4\"},\n        {target: \"G8\", offset: -1, identifier: \"G9\"}\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        const n = Utilities.buildNote(item.identifier, {octaveOffset: item.offset});\n        expect(n.identifier).to.equal(item.target);\n      }\n\n    });\n\n    it(\"should return the right note when passing number\", function () {\n\n      // Arrange\n      const number = 60;\n      const identifier = \"C4\";\n      const options = {attack: 0.98, release: 0.76, duration: 56};\n\n      // Act\n      const result = Utilities.buildNote(number, options);\n\n      // Assert\n      expect(result.identifier).to.equal(identifier);\n      expect(result.attack).to.equal(options.attack);\n      expect(result.release).to.equal(options.release);\n      expect(result.duration).to.equal(options.duration);\n\n    });\n\n    it(\"should disregard octaveOffset when passing number\", function () {\n\n      // Arrange\n      const items = [\n        {number: 0, offset: -3, identifier: \"C-1\"},\n        {number: 64, offset: -2, identifier: \"E4\"},\n        {number: 127, offset: -1, identifier: \"G9\"},\n        {number: \"0\", offset: 1, identifier: \"C-1\"},\n        {number: \"64.0\", offset: 2, identifier: \"E4\"},\n        {number: \"127x\", offset: 3, identifier: \"G9\"},\n        {number: 0.1, offset: 4, identifier: \"C-1\"},\n        {number: 64.5, offset: 5, identifier: \"E4\"},\n        {number: 127.9, offset: -6, identifier: \"G9\"}\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        const n = Utilities.buildNote(item.number, {octaveOffset: item.offset});\n        expect(n.identifier).to.equal(item.identifier);\n      }\n\n    });\n\n    it(\"should throw if invalid input is provided\", function() {\n\n      // Arrange\n      const items = [\n        \"abc\",\n        null,\n        undefined,\n        -1,\n        -1.2,\n        128,\n        128.1,\n        function () {},\n        {},\n        \"555\",\n        \"H3\",\n        \"Z#8\",\n        Infinity,\n        -Infinity,\n        -2.3,\n        false,\n        true\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(function () {\n          Utilities.buildNote(item);\n        }).to.throw(TypeError);\n      };\n\n    });\n\n  });\n\n  describe(\"buildNoteArray()\", function() {\n\n    it(\"should return an array of valid notes when passing number array\", function () {\n\n      // Arrange\n      const items = [\n        0,\n        60,\n        127\n      ];\n      const targets = [\n        \"C-1\",\n        \"C4\",\n        \"G9\"\n      ];\n\n      // Act\n      const results = Utilities.buildNoteArray(items);\n\n      // Assert\n      results.forEach((result, index) => {\n        expect(result.identifier).to.equal(targets[index]);\n      });\n\n    });\n\n    it(\"should return an array of valid notes when passing identifier array\", function () {\n\n      // Arrange\n      const items = [\n        \"C-1\",\n        \"C4\",\n        \"G9\"\n      ];\n\n      // Act\n      const results = Utilities.buildNoteArray(items);\n\n      // Assert\n      results.forEach((result, index) => {\n        expect(result.identifier).to.equal(items[index]);\n      });\n\n    });\n\n    it(\"should return an array of valid notes when passing notes array\", function () {\n\n      // Arrange\n      const items = [\n        new Note(\"C-1\"),\n        new Note(\"C4\"),\n        new Note(\"G9\")\n      ];\n\n      // Act\n      const results = Utilities.buildNoteArray(items);\n\n      // Assert\n      results.forEach((result, index) => {\n        expect(result).to.equal(items[index]);\n      });\n\n    });\n\n    it(\"should return an array of valid notes when passing single number\", function () {\n\n      // Arrange\n      const items = [\n        0,\n        60,\n        127\n      ];\n      const targets = [\n        \"C-1\",\n        \"C4\",\n        \"G9\"\n      ];\n      const results = [];\n\n      // Act\n      items.forEach(item => {\n        results.push(Utilities.buildNoteArray(item));\n      });\n\n      // Assert\n      results.forEach((result, index) => {\n        expect(result[0].identifier).to.equal(targets[index]);\n      });\n\n    });\n\n    it(\"should return an array of valid notes when passing single identifier\", function () {\n\n      // Arrange\n      const items = [\n        \"C-1\",\n        \"C4\",\n        \"G9\"\n      ];\n      const results = [];\n\n      // Act\n      items.forEach(item => {\n        results.push(Utilities.buildNoteArray(item));\n      });\n\n      // Assert\n      results.forEach((result, index) => {\n        expect(result[0].identifier).to.equal(items[index]);\n      });\n\n    });\n\n    it(\"should return an array of valid notes when passing single note\", function () {\n\n      // Arrange\n      const items = [\n        new Note(\"C-1\"),\n        new Note(\"C4\"),\n        new Note(\"G9\")\n      ];\n      const results = [];\n\n      // Act\n      items.forEach(item => {\n        results.push(Utilities.buildNoteArray(item));\n      });\n\n      // Assert\n      results.forEach((result, index) => {\n        expect(result[0].identifier).to.equal(items[index].identifier);\n      });\n\n    });\n\n    it(\"should throw error for invalid values\", function() {\n\n      // Arrange\n      let values = [\n        \"abc\",\n        null,\n        undefined,\n        -1,\n        128,\n        {},\n        \"555\",\n        \"H3\",\n        \"Z#8\",\n        Infinity,\n        -Infinity,\n        \"G10\",\n        \"C-2\"\n      ];\n\n      // Act\n      values.forEach(assert);\n\n      // Assert\n      function assert(value) {\n        expect(() => {\n          Utilities.buildNoteArray(value);\n        }).to.throw();\n      }\n\n    });\n\n    it(\"should pass on options to values that aren't 'Note' objects\", function() {\n\n      let options = {\n        duration: 1000,\n        attack: 0.56,\n        release: 0.78\n      };\n\n      let notes1 = Utilities.buildNoteArray([60], options);\n      expect(notes1[0].duration).to.equal(1000);\n      expect(notes1[0].attack).to.equal(options.attack);\n      expect(notes1[0].release).to.equal(options.release);\n\n    });\n\n  });\n\n  describe(\"from7bitToFloat()\", function() {\n\n    it(\"should return the correct value for normal input\", function () {\n\n      // Arrange\n      const items = [\n        {input: 0, output: 0},\n        {input: 64, output: 64/127},\n        {input: 127, output: 1},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.from7bitToFloat(item.input)\n        ).to.equal(item.output);\n      }\n\n    });\n\n    it(\"should return the correct value for out of bounds input\", function () {\n\n      // Arrange\n      const items = [\n        {input: -1, output: 0},\n        {input: -10, output: 0},\n        {input: 128, output: 1},\n        {input: 1280, output: 1},\n        {input: Infinity, output: 1},\n        {input: -Infinity, output: 0},\n        {input: 127.1, output: 1},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.from7bitToFloat(item.input)\n        ).to.equal(item.output);\n      }\n\n    });\n\n  });\n\n  describe(\"fromFloatTo7Bit()\", function() {\n\n    it(\"should return the correct value for normal input\", function () {\n\n      // Arrange\n      const items = [\n        {input: 0, output: 0},\n        {input: 0.5, output: 64},\n        {input: 1, output: 127},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.fromFloatTo7Bit(item.input)\n        ).to.equal(item.output);\n      }\n\n    });\n\n    it(\"should return the correct value for out of bounds input\", function () {\n\n      // Arrange\n      const items = [\n        {input: -1, output: 0},\n        {input: -10, output: 0},\n        {input: 2, output: 127},\n        {input: 20, output: 127},\n        {input: Infinity, output: 127},\n        {input: -Infinity, output: 0},\n        {input: 1.1, output: 127},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.fromFloatTo7Bit(item.input)\n        ).to.equal(item.output);\n      }\n\n    });\n\n  });\n\n  describe(\"fromMsbLsbToFloat()\", function() {\n\n    it(\"should return the correct value for normal input\", function () {\n\n      // Arrange\n      const items = [\n        {msb: 0, lsb: 0, output: 0/16383},\n        {msb: 0, lsb: 64, output: 64/16383},\n        {msb: 0, lsb: 127, output: 127/16383},\n        {msb: 64, lsb: 0, output: 8192/16383},\n        {msb: 64, lsb: 64, output: 8256/16383},\n        {msb: 64, lsb: 127, output: 8319/16383},\n        {msb: 127, lsb: 0, output: 16256/16383},\n        {msb: 127, lsb: 64, output: 16320/16383},\n        {msb: 127, lsb: 127, output: 16383/16383}\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.fromMsbLsbToFloat(item.msb, item.lsb)\n        ).to.equal(item.output);\n      }\n\n    });\n\n    it(\"should return the correct value for out of bounds input\", function () {\n\n      // Arrange\n      const items = [\n        {msb: -1, lsb: 0, output: 0},\n        {msb: 0, lsb: -1, output: 0},\n        {msb: 128, lsb: 0, output: 16256/16383},\n        {msb: 0, lsb: 128, output: 127/16383}\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        expect(\n          Utilities.fromMsbLsbToFloat(item.msb, item.lsb)\n        ).to.equal(item.output);\n      }\n\n    });\n\n  });\n\n  describe(\"fromFloatToMsbLsb()\", function() {\n\n    it(\"should return the correct value for normal input\", function () {\n\n      // Arrange\n      const items = [\n        {input: 0, msb: 0, lsb: 0},\n        {input: 0.25, msb: 32, lsb: 0},\n        {input: 0.5, msb: 64, lsb: 0},\n        {input: 0.75, msb: 95, lsb: 127},\n        {input: 1, msb: 127, lsb: 127}\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        let {msb, lsb} = Utilities.fromFloatToMsbLsb(item.input);\n        expect(msb).to.equal(item.msb);\n        expect(lsb).to.equal(item.lsb);\n      }\n\n    });\n\n    it(\"should return the correct value for out of bounds input\", function () {\n\n      // Arrange\n      const items = [\n        {input: -10, msb: 0, lsb: 0},\n        {input: -1, msb: 0, lsb: 0},\n        {input: 1.2, msb: 127, lsb: 127},\n        {input: 12, msb: 127, lsb: 127},\n      ];\n\n      // Act\n      items.forEach(assert);\n\n      // Assert\n      function assert(item) {\n        let {msb, lsb} = Utilities.fromFloatToMsbLsb(item.input);\n        expect(msb).to.equal(item.msb);\n        expect(lsb).to.equal(item.lsb);\n      }\n\n    });\n\n  });\n\n  describe(\"isNode\", function() {\n\n    it(\"should return the correct value depending on environment\", function () {\n      // Assert\n      expect(Utilities.isNode).to.be.true;\n    });\n\n  });\n\n  describe(\"isBrowser\", function() {\n\n    it(\"should return the correct value depending on environment\", function () {\n      // Assert\n      expect(Utilities.isBrowser).to.be.false;\n    });\n\n  });\n\n  describe(\"getPropertyByValue()\", function() {\n\n    it(\"should return the correct property\");\n\n  });\n\n});\n"
  },
  {
    "path": "test/WebMidi.test.js",
    "content": "const expect = require(\"chai\").expect;\nconst {WebMidi, Utilities} = require(\"../dist/cjs/webmidi.cjs.js\");\nconst midi = require(\"@julusian/midi\");\nconst semver = require(\"semver\");\n\n// The virtual port from the 'midi' library is an \"external\" device so an output is seen as an input\n// by WebMidi. To avoid confusion, the naming scheme adopts WebMidi's perspective.\nconst VIRTUAL_INPUT_NAME = \"Virtual Input\";\nconst VIRTUAL_INPUT = new midi.Output(VIRTUAL_INPUT_NAME);\nconst VIRTUAL_OUTPUT_NAME = \"Virtual Output\";\nconst VIRTUAL_OUTPUT = new midi.Input(VIRTUAL_OUTPUT_NAME);\n\ndescribe(\"WebMidi Object\", function() {\n\n  afterEach(\"Disable WebMidi.js\", async function () {\n    await WebMidi.disable();\n  });\n\n  it(\"should not allow itself to be instantiated\", function() {\n\n    // Assert\n    expect(function() {\n      new WebMidi();\n    }).to.throw(TypeError);\n\n  });\n\n  it(\"should not allow itself to be instantiated through 'constructor' property\", function() {\n\n    // Assert\n    expect(function() {\n      new WebMidi.constructor();\n    }).to.throw(TypeError);\n\n  });\n\n  it(\"should remain extensible\", function() {\n\n    // Assert\n    expect(Object.isExtensible(WebMidi)).to.be.true;\n\n  });\n\n  it(\"should have empty 'inputs' and 'outputs' arrays\", function() {\n\n    // Assert\n    expect(WebMidi.inputs.length).to.equal(0);\n    expect(WebMidi.outputs.length).to.equal(0);\n\n  });\n\n  it(\"should report valid version\", function() {\n    expect(semver.valid(WebMidi.version)).to.not.be.null;\n  });\n\n  it(\"should report valid flavour\", function() {\n    expect(WebMidi.flavour).to.equal(\"cjs\");\n  });\n\n  // THIS WORKS BY ITSELF BUT STOPS WORKING WHEN THE OTHER TESTS ARE RUN!\n  it(\"should trigger 'connected' events for new inputs\");\n  // it(\"should trigger 'connected' events for new inputs\", function(done) {\n  //\n  //   WebMidi.addListener(\"connected\", e => {\n  //     if (e.port.name === VIRTUAL_INPUT_NAME) {\n  //       WebMidi.removeListener();\n  //       VIRTUAL_INPUT.closePort();\n  //       done();\n  //     }\n  //   });\n  //\n  //   // Assert\n  //   WebMidi.enable().then(() => {\n  //     setTimeout(function () {\n  //       VIRTUAL_INPUT.openVirtualPort(VIRTUAL_INPUT_NAME);\n  //     }, 100);\n  //   });\n  //\n  // });\n\n  // THIS WORKS BY ITSELF BUT STOPS WORKING WHEN THE OTHER TESTS ARE RUN!\n  it(\"should trigger 'connected' events for new outputs\");\n  // it(\"should trigger 'connected' events for new outputs\", function(done) {\n  //\n  //   WebMidi.addListener(\"connected\", e => {\n  //     if (e.port.name === VIRTUAL_OUTPUT_NAME) {\n  //       WebMidi.removeListener();\n  //       VIRTUAL_OUTPUT.closePort();\n  //       done();\n  //     }\n  //   });\n  //\n  //   // Assert\n  //   WebMidi.enable().then(() => {\n  //     setTimeout(()=>{\n  //       VIRTUAL_OUTPUT.openVirtualPort(VIRTUAL_OUTPUT_NAME);\n  //     }, 250);\n  //   });\n  //\n  // });\n\n  it(\"should trigger 'disconnected' events for disconnected outputs\");\n  // it(\"should trigger 'disconnected' events for disconnected outputs\", function(done) {\n  //\n  //   VIRTUAL_OUTPUT.openVirtualPort(VIRTUAL_OUTPUT_NAME);\n  //\n  //   WebMidi.addListener(\"disconnected\", e => {\n  //     if (e.port.name === VIRTUAL_OUTPUT_NAME) {\n  //       WebMidi.removeListener();\n  //       done();\n  //     }\n  //   });\n  //\n  //   // Assert\n  //   WebMidi.enable().then(() => {\n  //     setTimeout(() => {\n  //       VIRTUAL_OUTPUT.closePort();\n  //     }, 250);\n  //   });\n  //\n  // });\n\n  it(\"should trigger 'disconnected' events for disconnected inputs\");\n  // it(\"should trigger 'disconnected' events for disconnected inputs\", function(done) {\n  //\n  //   VIRTUAL_INPUT.openVirtualPort(VIRTUAL_INPUT_NAME);\n  //\n  //   WebMidi.addListener(\"disconnected\", e => {\n  //     if (e.port.name === VIRTUAL_INPUT_NAME) {\n  //       WebMidi.removeListener();\n  //       done();\n  //     }\n  //   });\n  //\n  //   // Assert\n  //   WebMidi.enable().then(() => {\n  //     setTimeout(() => {\n  //       VIRTUAL_INPUT.closePort();\n  //     }, 250);\n  //   });\n  //\n  // });\n\n  // VERIFIED\n  describe(\"constructor()\", function() {\n\n    beforeEach(\"Enable WebMidi\", async function() {\n      await WebMidi.enable({sysex: true});\n    });\n\n    it(\"should adjust to Node.js environment\", function() {\n\n      // Assert\n      expect(typeof navigator.requestMIDIAccess).to.equal(\"function\");\n      expect(typeof performance.now).to.equal(\"function\");\n\n    });\n\n  });\n\n  // VERIFIED\n  describe(\"disable()\", function() {\n\n    beforeEach(\"Enable WebMidi\", async function() {\n      await WebMidi.enable({sysex: true});\n    });\n\n    it(\"should set 'enabled' property to false\", function(done) {\n\n      // Assert\n      WebMidi.disable().then(() => {\n        expect(WebMidi.enabled).to.equal(false);\n        done();\n      });\n\n    });\n\n    it(\"should set the 'sysexEnabled' property to false\", function(done) {\n\n      // Assert\n      WebMidi.disable().then(() => {\n        expect(WebMidi.sysexEnabled).to.equal(false);\n        done();\n      });\n\n    });\n\n    it(\"should trim 'input' and 'output' array lengths to 0\", function(done) {\n\n      // Assert\n      WebMidi.disable().then(() => {\n        expect(WebMidi.inputs.length).to.equal(0);\n        expect(WebMidi.outputs.length).to.equal(0);\n        done();\n      });\n\n    });\n\n    it(\"should remove all user handlers\", async function() {\n\n      // Arrange\n      WebMidi.addListener(\"connected\", () => {});\n      WebMidi.addListener(\"disconnected\", () => {});\n      WebMidi.addListener(\"enabled\", () => {});\n      WebMidi.addListener(\"disabled\", () => {});\n\n      // Act\n      await WebMidi.disable();\n\n      // Assert\n      expect(WebMidi.hasListener()).to.be.false;\n\n    });\n\n    it(\"should set 'interface' to 'null'\", async function() {\n\n      // Act\n      await WebMidi.disable();\n\n      // Assert\n      expect(WebMidi.interface).to.be.null;\n\n    });\n\n    it(\"should dispatch 'disabled' event\", function (done) {\n      WebMidi.addListener(\"disabled\", () => done());\n      WebMidi.disable();\n    });\n\n  });\n\n  // VERIFIED\n  describe(\"enable()\", function() {\n\n    it(\"should return a resolved promise if already enabled\", function(done) {\n\n      WebMidi.enable().then(() => {\n        WebMidi.enable().then(() => {\n          done();\n        });\n      });\n\n    });\n\n    it(\"should execute the callback if already enabled (legacy)\", function(done) {\n\n      WebMidi.enable(function() {\n        WebMidi.enable(function() {\n          done();\n        });\n      });\n\n    });\n\n    it(\"should pass error to callback upon failure to create interface (legacy)\", function(done) {\n\n      // Arrange\n      let backup;\n\n      if (navigator && navigator.requestMIDIAccess) {\n        backup = navigator.requestMIDIAccess;\n        navigator.requestMIDIAccess = () => Promise.reject(new Error(\"Simulated failure!\"));\n      }\n\n      // Assert\n      WebMidi.enable(function (err) {\n        expect(err).to.be.an(\"error\");\n        navigator.requestMIDIAccess = backup;\n        done();\n      });\n\n    });\n\n    it(\"should pass error to callback upon failure to create interface\", function(done) {\n\n      // Arrange\n      let backup;\n\n      if (navigator && navigator.requestMIDIAccess) {\n        backup = navigator.requestMIDIAccess;\n        navigator.requestMIDIAccess = () => Promise.reject(new Error(\"Simulated failure!\"));\n      }\n\n      // Assert\n      WebMidi.enable({callback: err => {\n        expect(err).to.be.an(\"error\");\n        navigator.requestMIDIAccess = backup;\n        done();\n      }}).catch(() => {});\n\n\n    });\n\n    it(\"should trigger error event upon failure to create interface\", function(done) {\n\n      // Arrange\n      let backup;\n\n      if (navigator && navigator.requestMIDIAccess) {\n        backup = navigator.requestMIDIAccess;\n        navigator.requestMIDIAccess = () => Promise.reject(new Error(\"Simulated failure!\"));\n      }\n\n      // Assert\n      WebMidi.addListener(\"error\", e => {\n        expect(e.error).to.be.an(\"error\");\n        navigator.requestMIDIAccess = backup;\n        done();\n      });\n\n      // Act\n      WebMidi.enable();\n\n    });\n\n    it(\"should return a rejected promise upon failure to create interface\", function(done) {\n\n      // Arrange\n      let backup;\n\n      if (navigator && navigator.requestMIDIAccess) {\n        backup = navigator.requestMIDIAccess;\n        navigator.requestMIDIAccess = () => Promise.reject(new Error(\"Simulated failure!\"));\n      }\n\n      // Assert\n      WebMidi.enable().catch(err => {\n        navigator.requestMIDIAccess = backup;\n        expect(err).to.be.an(\"error\");\n        done();\n      });\n\n    });\n\n    it(\"should set 'enabled' property to false if enable() method fails\", function(done) {\n\n      // Arrange\n      let backup;\n\n      if (navigator && navigator.requestMIDIAccess) {\n        backup = navigator.requestMIDIAccess;\n        navigator.requestMIDIAccess = () => Promise.reject(new Error(\"Simulated failure!\"));\n      }\n\n      // Assert\n      WebMidi.enable().catch(() => {\n        expect(WebMidi.enabled).to.equal(false);\n        navigator.requestMIDIAccess = backup;\n        done();\n      });\n\n    });\n\n    it(\"should set 'enabled' property to false if enable() method fails (legacy)\", function(done) {\n\n      // Arrange\n      let backup;\n\n      if (navigator && navigator.requestMIDIAccess) {\n        backup = navigator.requestMIDIAccess;\n        navigator.requestMIDIAccess = () => Promise.reject(new Error(\"Simulated failure!\"));\n      }\n\n      WebMidi.enable(function () {\n        expect(WebMidi.enabled).to.equal(false);\n        navigator.requestMIDIAccess = backup;\n        done();\n      });\n\n    });\n\n    it(\"should set 'enabled' property to true if successful\", function(done) {\n\n      WebMidi.enable({callback: function (err) {\n\n        if (err) { // This could happen if WebMIDIAPIShim is there but not the Jazz-Plugin\n          expect(WebMidi.enabled).to.equal(false);\n        } else {\n          expect(WebMidi.enabled).to.equal(true);\n        }\n\n        done();\n\n      }});\n\n    });\n\n    it(\"should set 'enabled' property to true if successful (legacy)\", function(done) {\n\n      WebMidi.enable(function (err) {\n\n        if (err) { // This could happen if WebMIDIAPIShim is there but not the Jazz-Plugin\n          expect(WebMidi.enabled).to.equal(false);\n        } else {\n          expect(WebMidi.enabled).to.equal(true);\n        }\n\n        done();\n\n      });\n\n    });\n\n    it(\"should execute the callback if successful\", function(done) {\n      WebMidi.enable({callback: function () {\n        done();\n      }});\n    });\n\n    it(\"should execute the callback if successful (legacy)\", function(done) {\n      WebMidi.enable(function() {\n        done();\n      });\n    });\n\n    it(\"should return a promise fulfilled with the WebMidi objectif successful\", function(done) {\n      WebMidi.enable().then(wm => {\n        expect(wm).to.equal(WebMidi);\n        done();\n      });\n    });\n\n    it(\"should trigger 'connected' events for already connected inputs\", function(done) {\n\n      // Arrange\n      VIRTUAL_INPUT.openVirtualPort(VIRTUAL_INPUT_NAME);\n\n      WebMidi.addListener(\"connected\", e => {\n        if (e.port.name === VIRTUAL_INPUT_NAME) {\n          WebMidi.removeListener();\n          VIRTUAL_INPUT.closePort();\n          done();\n        }\n      });\n\n      // Assert\n      WebMidi.enable();\n\n    });\n\n    it(\"should trigger 'connected' events for already connected outputs\", function(done) {\n\n      VIRTUAL_OUTPUT.openVirtualPort(VIRTUAL_OUTPUT_NAME);\n\n      WebMidi.addListener(\"connected\", e => {\n        if (e.port.name === VIRTUAL_OUTPUT_NAME) {\n          WebMidi.removeListener();\n          VIRTUAL_OUTPUT.closePort();\n          done();\n        }\n      });\n\n      // Assert\n      WebMidi.enable();\n\n    });\n\n    it(\"should enable sysex only if requested\", function(done) {\n      WebMidi.enable({sysex: true}).then(() => {\n        expect(WebMidi.sysexEnabled).to.equal(true);\n        done();\n      });\n    });\n\n    it(\"should enable sysex only if requested (legacy)\", function(done) {\n      WebMidi.enable(function () {\n        expect(WebMidi.sysexEnabled).to.equal(true);\n        done();\n      }, true);\n    });\n\n    it(\"should not enable sysex unless requested\", function(done) {\n\n      WebMidi.enable().then(() => {\n        expect(WebMidi.sysexEnabled).to.equal(false);\n        done();\n      });\n\n    });\n\n    it(\"should not enable sysex unless requested (legacy)\", function(done) {\n      WebMidi.enable(function () {\n        expect(WebMidi.sysexEnabled).to.equal(false);\n        done();\n      });\n    });\n\n    it(\"should continue to support the legacy properties\", function(done) {\n\n      WebMidi.enable(function () {\n\n        if (WebMidi.supported) {\n          expect(WebMidi.enabled).to.equal(true);\n          expect(WebMidi.sysexEnabled).to.equal(true);\n        } else {\n          expect(WebMidi.enabled).to.equal(false);\n        }\n\n        done();\n\n      }, true);\n\n    });\n\n    it(\"should dispatch 'enabled' event\", function (done) {\n\n      // Arrange\n      this.timeout(10000);\n\n      // Assert\n      WebMidi.addListener(\"enabled\", () => done());\n      WebMidi.enable();\n\n    });\n\n    it(\"should dispatch 'midiaccessgranted' event\", function(done) {\n\n      // Arrange\n      WebMidi.addListener(\"midiaccessgranted\", () => done());\n\n      // Act\n      WebMidi.enable();\n\n    });\n\n  });\n\n  describe(\"getInputById()\", function() {\n\n    beforeEach(\"Enable WebMidi\", async function() {\n      await WebMidi.enable({sysex: true});\n    });\n\n    it(\"should throw error if WebMidi is disabled\", async function() {\n\n      await WebMidi.disable();\n\n      try {\n        WebMidi.getInputById(\"test\");\n        return Promise.reject();\n      } catch (err) {\n        return Promise.resolve();\n      }\n\n    });\n\n    it(\"should return undefined if no device is found\", function() {\n      expect(WebMidi.getInputById(\"0000000\")).to.equal(undefined);\n    });\n\n    it(\"should return the right input\", function() {\n\n      if (WebMidi.inputs.length > 0) {\n        let id = WebMidi.inputs[0].id;\n        expect(WebMidi.getInputById(id)).to.equal(WebMidi.inputs[0]);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return an instance of 'Input' class\", function() {\n\n      if (WebMidi.inputs.length > 0) {\n        expect(WebMidi.getInputById(WebMidi.inputs[0].id))\n          .to.be.instanceOf(WebMidi.inputs[0].constructor);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return 'undefined' when invalid id is provided\", function() {\n\n      if (WebMidi.inputs.length > 0) {\n        [null, undefined, \"\", [], {}].forEach(id => {\n          expect(WebMidi.getInputById(id)).to.equal(undefined);\n        });\n      } else {\n        this.skip();\n      }\n\n    });\n\n  });\n\n  describe(\"getInputByName()\", function() {\n\n    beforeEach(\"Enable WebMidi\", async function() {\n      await WebMidi.enable({sysex: true});\n    });\n\n    it(\"should throw error if WebMidi is disabled\", async function() {\n\n      await WebMidi.disable();\n\n      try {\n        WebMidi.getInputByName(\"test\");\n        return Promise.reject();\n      } catch (err) {\n        return Promise.resolve();\n      }\n\n    });\n\n    it(\"should return undefined if no device is found\", function() {\n      expect(WebMidi.getInputByName(\"0000000\")).to.equal(undefined);\n    });\n\n    it(\"should return the right input\", function() {\n\n      if (WebMidi.inputs.length > 0) {\n        let name = WebMidi.inputs[0].name;\n        expect(WebMidi.getInputByName(name).name).to.equal(name);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return instance of 'Input' class\", function() {\n\n      if (WebMidi.inputs.length > 0) {\n        expect(WebMidi.getInputByName(WebMidi.inputs[WebMidi.inputs.length - 1].name))\n          .to.be.instanceOf(WebMidi.inputs[WebMidi.inputs.length - 1].constructor);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return 'undefined' when an invalid name is provided\", function() {\n\n      if (WebMidi.inputs.length > 0) {\n        [null, undefined, \"\"].forEach(name => {\n          expect(WebMidi.getInputByName(name)).to.equal(undefined);\n        });\n      } else {\n        this.skip();\n      }\n\n    });\n\n  });\n\n  describe(\"getOutputById()\", function() {\n\n    beforeEach(\"Enable WebMidi\", async function() {\n      await WebMidi.enable({sysex: true});\n    });\n\n    it(\"should throw error if WebMidi is disabled\", async function() {\n\n      await WebMidi.disable();\n\n      try {\n        WebMidi.getOutputById(\"test\");\n        return Promise.reject();\n      } catch (err) {\n        return Promise.resolve();\n      }\n\n    });\n\n    it(\"should return undefined if no device is found\", function() {\n      expect(WebMidi.getOutputById(\"0000000\")).to.equal(undefined);\n    });\n\n    it(\"should return the right output\", function() {\n\n      if (WebMidi.outputs.length > 0) {\n        var id = WebMidi.outputs[0].id;\n        expect(WebMidi.getOutputById(id)).to.equal(WebMidi.outputs[0]);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return an instance of the Output class\", function() {\n\n      if (WebMidi.outputs.length > 0) {\n        expect(WebMidi.getOutputById(WebMidi.outputs[0].id))\n          .to.be.instanceOf(WebMidi.outputs[0].constructor);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return 'undefined' when a weird id is provided\", function() {\n\n      if (WebMidi.inputs.length > 0) {\n        [null, undefined, \"\", [], {}].forEach(id => {\n          expect(WebMidi.getOutputById(id)).to.equal(undefined);\n        });\n      } else {\n        this.skip();\n      }\n\n    });\n\n  });\n\n  describe(\"getOutputByName()\", function() {\n\n    beforeEach(\"Enable WebMidi\", async function() {\n      await WebMidi.enable({sysex: true});\n    });\n\n    it(\"should throw error if WebMidi is disabled\", async function() {\n\n      await WebMidi.disable();\n\n      try {\n        WebMidi.getOutputByName(\"test\");\n        return Promise.reject();\n      } catch (err) {\n        return Promise.resolve();\n      }\n\n    });\n\n    it(\"should return undefined if no device is found\", function() {\n      expect(WebMidi.getOutputByName(\"0000000\")).to.equal(undefined);\n    });\n\n    it(\"should return the right output\", function() {\n\n      if (WebMidi.outputs.length > 0) {\n        var name = WebMidi.outputs[0].name;\n        expect(WebMidi.getOutputByName(name).name).to.equal(WebMidi.outputs[0].name);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return instance of Output class\", function() {\n\n      if (WebMidi.outputs.length > 0) {\n        expect(WebMidi.getOutputByName(WebMidi.outputs[WebMidi.outputs.length - 1].name))\n          .to.be.instanceOf(WebMidi.outputs[WebMidi.outputs.length - 1].constructor);\n      } else {\n        this.skip();\n      }\n\n    });\n\n    it(\"should return 'undefined' when an invalid name is provided\", function() {\n\n      if (WebMidi.outputs.length > 0) {\n        [null, undefined, \"\"].forEach(name => {\n          expect(WebMidi.getOutputByName(name)).to.equal(undefined);\n        });\n      } else {\n        this.skip();\n      }\n\n    });\n\n  });\n\n  describe(\"getOctave()\", function() {\n\n    it(\"should return false if number is invalid\", function() {\n      [\"abc\", null, undefined, -1, 128, () => {}, {}, \"555\", Infinity, -Infinity]\n        .forEach(function (param) {\n          expect(WebMidi.getOctave(param)).to.be.false;\n        });\n    });\n\n    it(\"should return a signed integer\", function() {\n      for (let i = 0; i <= 127; i++) {\n        let result = WebMidi.getOctave(i);\n        expect(result % 1).to.be.equal(0);\n      }\n    });\n\n    it(\"should place MIDI note number 60 (C4/middle C) at octave 4\", function() {\n      expect(WebMidi.getOctave(60)).to.be.equal(4);\n    });\n\n    it(\"should take into account desired 'octaveOffset' value\", function() {\n      WebMidi.octaveOffset = 2;\n      expect(WebMidi.getOctave(60)).to.be.equal(6);\n      WebMidi.octaveOffset = -4;\n      expect(WebMidi.getOctave(60)).to.be.equal(0);\n      WebMidi.octaveOffset = 0;\n    });\n\n  });\n\n  describe(\"noteNameToNumber()\", function() {\n\n    it(\"should return the same as Utilities.toNoteNumber()\", function() {\n\n      // Arrange\n      const items = [\n        \"C-1\",\n        \"Cb4\",\n        \"C4\",\n        \"C#4\",\n        \"G9\"\n      ];\n\n      // Act\n      items.forEach(param => {\n        expect(WebMidi.noteNameToNumber(param)).to.equal(Utilities.toNoteNumber(param));\n      });\n\n    });\n\n    it(\"should return the correct number given the current octave\", function() {\n\n      expect(Utilities.toNoteNumber(\"C-1\", 0)).to.equal(0);\n      expect(Utilities.toNoteNumber(\"C-1\", 1)).to.equal(12);\n      expect(Utilities.toNoteNumber(\"C4\", 0)).to.equal(60);\n      expect(Utilities.toNoteNumber(\"C4\", -1)).to.equal(48);\n      expect(Utilities.toNoteNumber(\"C4\", 1)).to.equal(72);\n      expect(Utilities.toNoteNumber(\"G8\", 1)).to.equal(127);\n\n    });\n\n  });\n\n  describe(\"set octaveOffset()\", function() {\n\n    it(\"should be tested!\");\n\n  });\n\n  describe(\"toMIDIChannels()\", function() {\n\n    it(\"should return the same as sanitizeChannels()\", function() {\n\n      let values = [\n        1, -1, -2.3, 0, 17, \"x\", NaN, null, Infinity, 8, -Infinity, true, false, [undefined],\n        4e2, 16, 1, 16\n      ];\n\n      let a = WebMidi.toMIDIChannels(values);\n      let b = Utilities.sanitizeChannels(values);\n      for (let i = 0; i <a.length; i++) expect(a[i]).to.equal(b[i]);\n\n    });\n\n  });\n\n  describe(\"set octaveOffset()\", function() {\n\n    afterEach(\"Reset octave\", function () {\n      WebMidi.octaveOffset = 0;\n    });\n\n    it(\"should throw error for invalid values\", function() {\n      [\"abc\", null, undefined, () => {}, {}, Infinity, -Infinity].forEach(value => {\n        expect(() => WebMidi.octaveOffset = value).to.throw(Error);\n      });\n    });\n\n    it(\"should properly set the octave when the value is valid\", function() {\n      [-1, 9, \"-1\", \"9\"].forEach(value => {\n        WebMidi.octaveOffset = value;\n        expect(WebMidi._octaveOffset).to.equal(WebMidi.octaveOffset);\n      });\n    });\n\n  });\n\n  describe(\"get supported()\", function() {\n\n    it(\"should return true if midi available\", function(done) {\n\n      WebMidi.enable().then(() => {\n        expect(WebMidi.supported).to.be.true;\n        done();\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/support/JZZ.js",
    "content": "!function(t,e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof define&&define.amd)define(\"JZZ\",[],e);else{if((t=t||window).JZZ&&t.JZZ.MIDI)return;t.JZZ=e()}}(this,function(){var o,r,n,t,i,e=\"undefined\"==typeof window?global:window,s=\"1.0.3\",u=Date.now||function(){return(new Date).getTime()},a=u(),h=\"undefined\"!=typeof performance&&performance.now?function(){return performance.now()}:function(){return u()-a},f=function(t){setTimeout(t,0)};function c(){(this._orig=this)._ready=!1,this._queue=[],this._log=[]}function p(t,e){this._bad?e instanceof Function&&e.apply(this,[new Error(this._err())]):t instanceof Function&&t.apply(this,[this])}function l(t,e){this._bad?t._crash(this._err()):setTimeout(function(){t._resume()},e)}function d(t){this._bad&&t._break(this._err()),t._resume()}function _(t,n,r){t[r]=function(){var t=arguments,e=n._image();return this._push(d,[e]),e[r].apply(e,t)}}function m(t){this._bad||(t instanceof Function?t.apply(this):console.log(t))}function g(t){this._bad&&(t instanceof Function?t.apply(this):console.log(t))}function v(t){this._bad?t._crash(this._err()):(this._break(\"Closed\"),t._resume())}function y(t){if(t.length){var e=t.shift();if(t.length){var n=this;this._slip(g,[function(){y.apply(n,[t])}])}try{this._repair(),e.apply(this)}catch(t){this._break(t.toString())}}else this._break()}function C(t,e){for(var n=0;n<t.length;n++)if(t[n]===e)return;t.push(e)}function b(t,e){for(var n=0;n<t.length;n++)if(t[n]===e)return void t.splice(n,1)}function w(){c.apply(this)}function S(t,e,n){if(void 0===e)return S(t,[],[]);if(t instanceof Object){for(var r=0;r<e.length;r++)if(e[r]===t)return n[r];var i;for(var o in i=t instanceof Array?[]:{},e.push(t),n.push(i),t)t.hasOwnProperty(o)&&(i[o]=S(t[o],e,n));return i}return t}c.prototype._exec=function(){for(;this._ready&&this._queue.length;){var t=this._queue.shift();t[0].apply(this,t[1])}},c.prototype._push=function(t,e){this._queue.push([t,e]),c.prototype._exec.apply(this)},c.prototype._slip=function(t,e){this._queue.unshift([t,e])},c.prototype._pause=function(){this._ready=!1},c.prototype._resume=function(){this._ready=!0,c.prototype._exec.apply(this)},c.prototype._break=function(t){this._orig._bad=!0,this._orig._log.push(t||\"Unknown JZZ error\")},c.prototype._repair=function(){this._orig._bad=!1},c.prototype._crash=function(t){this._break(t),this._resume()},c.prototype._err=function(){return this._log[this._log.length-1]},c.prototype.log=function(){return S(this._log)},c.prototype._image=function(){function t(){}t.prototype=this._orig;var e=new t;return e._ready=!1,e._queue=[],e},c.prototype._thenable=function(){function t(){}var n=this;t.prototype=n;var e=new t;return e.then=function(t,e){return n._push(p,[t,e]),this},e},c.prototype.wait=function(t){if(!t)return this;var e=this._image();return this._push(l,[e,t]),e._thenable()},c.prototype.and=function(t){return this._push(m,[t]),this._thenable()},c.prototype.or=function(t){return this._push(g,[t]),this._thenable()},c.prototype._info={},c.prototype.info=function(){var t=S(this._orig._info);return void 0===t.engine&&(t.engine=\"none\"),void 0===t.sysex&&(t.sysex=!0),t},c.prototype.name=function(){return this.info().name},c.prototype.close=function(){var t=new c;return this._close&&this._push(this._close,[]),this._push(v,[t]),t._thenable()},(w.prototype=new c)._info={name:\"JZZ.js\",ver:s,version:s,inputs:[],outputs:[]};var M,O=[],I=[],A=[],E=[];function x(){var t,e;for(M._info.engine=K._type,M._info.version=K._version,M._info.sysex=K._sysex,M._info.inputs=[],M._info.outputs=[],O=[],I=[],K._allOuts={},K._allIns={},t=0;t<K._outs.length;t++)((e=K._outs[t]).engine=K)._allOuts[e.name]=e,M._info.outputs.push({id:e.name,name:e.name,manufacturer:e.manufacturer,version:e.version,engine:K._type}),O.push(e);for(t=0;t<$._outs.length;t++)e=$._outs[t],M._info.outputs.push({id:e.name,name:e.name,manufacturer:e.manufacturer,version:e.version,engine:e.type}),O.push(e);for(t=0;t<K._ins.length;t++)((e=K._ins[t]).engine=K)._allIns[e.name]=e,M._info.inputs.push({id:e.name,name:e.name,manufacturer:e.manufacturer,version:e.version,engine:K._type}),I.push(e);for(t=0;t<$._ins.length;t++)e=$._ins[t],M._info.inputs.push({id:e.name,name:e.name,manufacturer:e.manufacturer,version:e.version,engine:e.type}),I.push(e);if(M._watcher&&M._watcher._handles.length){var n=function(t,e,n,r){if(!function(t,e,n,r){var i;if(t.length!=n.length||e.length!=r.length)return!0;for(i=0;i<t.length;i++)if(t[i].name!=n[i].name)return!0;for(i=0;i<e.length;i++)if(e[i].name!=r[i].name)return!0;return!1}(t,e,n,r))return;var i,o=[],s=[],u=[],a=[],h={};for(i=0;i<t.length;i++)h[t[i].name]=!0;for(i=0;i<n.length;i++)h[n[i].name]||o.push(n[i]);for(h={},i=0;i<n.length;i++)h[n[i].name]=!0;for(i=0;i<t.length;i++)h[t[i].name]||u.push(t[i]);for(h={},i=0;i<e.length;i++)h[e[i].name]=!0;for(i=0;i<r.length;i++)h[r[i].name]||s.push(r[i]);for(h={},i=0;i<r.length;i++)h[r[i].name]=!0;for(i=0;i<e.length;i++)h[e[i].name]||a.push(e[i]);return{inputs:{added:o,removed:u},outputs:{added:s,removed:a}}}(E,A,M._info.inputs,M._info.outputs);if(n){for(r=0;r<n.inputs.removed.length;r++)(e=K._inMap[n.inputs.removed[r].name])&&e._closeAll();for(r=0;r<n.outputs.removed.length;r++)(e=K._outMap[n.outputs.removed[r].name])&&e._closeAll();!function(t){for(o=0;o<M._watcher._handles.length;o++)M._watcher._handles[o].apply(M,[t])}(n)}}E=M._info.inputs,A=M._info.outputs}function P(){this._bad||K._refresh(this)}function B(t,e){var n,r;t instanceof Function&&(t=t(e)),t instanceof Array||(t=[t]);var i=[],o=[],s=e.slice(),u=i;for(n=0;n<t.length;n++)if(void 0===t[n])u=o;else if(t[n]instanceof RegExp)for(r=0;r<s.length;r++)t[n].test(s[r].name)&&(u.push(s[r]),s.splice(r,1),r--);else for(r=0;r<s.length;r++)(t[n]+\"\"==r+\"\"||t[n]===s[r].name||t[n]instanceof Object&&t[n].name===s[r].name)&&(u.push(s[r]),s.splice(r,1),r--);return u==i?i:i.concat(s).concat(o)}function T(t,e){var n;n=e instanceof RegExp?\"Port matching \"+e+\" not found\":e instanceof Object||void 0===e?\"Port not found\":'Port \"'+e+'\" not found',t._crash(n)}function F(t,e){if(this._bad)t._crash(this._err());else{var n=B(e,O);if(!n.length)return void T(t,e);function r(t){return function(){t.engine._openOut(this,t.name)}}for(var i=0;i<n.length;i++)n[i]=r(n[i]);t._slip(y,[n]),t._resume()}}function q(t,e){if(this._bad)t._crash(this._err());else{var n=B(e,I);if(!n.length)return void T(t,e);function r(t){return function(){t.engine._openIn(this,t.name)}}for(var i=0;i<n.length;i++)n[i]=r(n[i]);t._slip(y,[n]),t._resume()}}function D(t,e){this._bad?t._crash():(t._slip(J,[e]),t._resume())}function L(){c.apply(this),this._handles=[],this._outs=[]}function z(t){this._bad||this._receive(t)}function j(t){this._emit(t)}function k(t){t instanceof Function?C(this._orig._handles,t):C(this._orig._outs,t)}function N(t){void 0===t?(this._orig._handles=[],this._orig._outs=[]):t instanceof Function?b(this._orig._handles,t):b(this._orig._outs,t)}function R(t,e){this._orig._mpe||(this._orig._mpe=new Nt),this._orig._mpe.setup(t,e)}function U(t){if(t!=parseInt(t)||t<0||15<t)throw RangeError(\"Bad channel value (must not be less than 0 or more than 15): \"+t)}function G(t,e){L.apply(this),this._port=t._orig,this._chan=e,_(this,this._port,\"ch\"),_(this,this._port,\"mpe\"),_(this,this._port,\"connect\"),_(this,this._port,\"disconnect\"),_(this,this._port,\"close\")}function Z(t,e,n){L.apply(this),this._port=t._orig,this._master=e,this._band=n,_(this,this._port,\"ch\"),_(this,this._port,\"mpe\"),_(this,this._port,\"connect\"),_(this,this._port,\"disconnect\"),_(this,this._port,\"close\")}function V(){c.apply(this),this._handles=[],_(this,M,\"refresh\"),_(this,M,\"openMidiOut\"),_(this,M,\"openMidiIn\"),_(this,M,\"onChange\"),_(this,M,\"close\")}function J(t){t instanceof Function&&(this._orig._handles.length||K._watch(),C(this._orig._handles,t))}function W(t){void 0===t?this._orig._handles=[]:b(this._orig._handles,t),this._orig._handles.length||K._unwatch()}w.prototype.refresh=function(){return this._push(P,[]),this._thenable()},w.prototype.openMidiOut=function(t){var e=new L;return this._push(P,[]),this._push(F,[e,t]),e._thenable()},w.prototype._openMidiOutNR=function(t){var e=new L;return this._push(F,[e,t]),e._thenable()},w.prototype.openMidiIn=function(t){var e=new L;return this._push(P,[]),this._push(q,[e,t]),e._thenable()},w.prototype._openMidiInNR=function(t){var e=new L;return this._push(q,[e,t]),e._thenable()},w.prototype.onChange=function(t){this._orig._watcher||(this._orig._watcher=new V);var e=this._orig._watcher._image();return this._push(D,[e,t]),e._thenable()},w.prototype._close=function(){K._close()},(L.prototype=new c)._filter=function(t){if(this._orig._mpe){var e,n=0;this._handles&&this._handles.length&&(n=this._handles.length,e=this._handles[0]),this._outs&&this._outs.length&&(n=this._outs.length,e=this._outs[0]),1!=n||e._mpe||(t=this._orig._mpe.filter(t))}return t},L.prototype._receive=function(t){this._emit(this._filter(t))},L.prototype.send=function(){return this._push(z,[lt.apply(null,arguments)]),this._thenable()},L.prototype.note=function(t,e,n,r){return this.noteOn(t,e,n),0<r&&this.wait(r).noteOff(t,e),this._thenable()},L.prototype._emit=function(t){var e;for(e=0;e<this._handles.length;e++)this._handles[e].apply(this,[lt(t)._stamp(this)]);for(e=0;e<this._outs.length;e++){var n=lt(t);n._stamped(this._outs[e])||this._outs[e].send(n._stamp(this))}},L.prototype.emit=function(t){return this._push(j,[t]),this._thenable()},L.prototype.connect=function(t){return this._push(k,[t]),this._thenable()},L.prototype.disconnect=function(t){return this._push(N,[t]),this._thenable()},L.prototype.connected=function(){return this._orig._handles.length+this._orig._outs.length},L.prototype.ch=function(t){if(void 0===t)return this;U(t);var e=new G(this,t);return this._push(d,[e]),e._thenable()},L.prototype.mpe=function(t,e){if(void 0===t&&void 0===e)return this;Nt.validate(t,e);var n=e?new Z(this,t,e):new G(this,t);return this._push(R,[t,e]),this._push(d,[n]),n._thenable()},(G.prototype=new L).channel=function(){return this._chan},G.prototype._receive=function(t){this._port._receive(t)},G.prototype.note=function(t,e,n){return this.noteOn(t,e),n&&this.wait(n).noteOff(t),this._thenable()},(Z.prototype=new L).channel=function(){return this._master},Z.prototype._receive=function(t){this._port._receive(t)},Z.prototype.note=function(t,e,n){return this.noteOn(t,e),n&&this.wait(n).noteOff(t),this._thenable()},(V.prototype=new c).connect=function(t){return this._push(J,[t]),this._thenable()},V.prototype.disconnect=function(t){return this._push(W,[t]),this._thenable()};var H,Q,K={},$={_outs:[],_ins:[]};function X(){\"undefined\"!=typeof module&&module.exports?function(t){K._type=\"node\",K._main=t,K._pool=[],K._newPlugin=function(){return new t.MIDI},st()}(require(\"jazz-midi\")):this._break()}function Y(){var t=document.createElement(\"div\");t.style.visibility=\"hidden\",document.body.appendChild(t);var e=document.createElement(\"object\");e.style.visibility=\"hidden\",e.style.width=\"0px\",e.style.height=\"0px\",e.classid=\"CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90\",e.type=\"audio/x-jazz\",document.body.appendChild(e),e.isJazz?function(e){K._type=\"plugin\",K._main=e,K._pool=[e],K._newPlugin=function(){var t=document.createElement(\"object\");return t.style.visibility=\"hidden\",t.style.width=\"0px\",e.style.height=\"0px\",t.classid=\"CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90\",t.type=\"audio/x-jazz\",document.body.appendChild(t),t.isJazz?t:void 0},st()}(e):this._break()}if(\"undefined\"!=typeof navigator&&navigator.requestMIDIAccess){H=navigator,Q=navigator.requestMIDIAccess;try{-1!=Q.toString().indexOf(\"JZZ(\")&&(Q=void 0)}catch(t){}}function tt(){if(Q){var e=this;return Q.call(H,{}).then(function(t){ut(t),e._resume()},function(t){e._crash(t)}),void this._pause()}this._break()}function et(){if(Q){var e=this;return Q.call(H,{sysex:!0}).then(function(t){ut(t,!0),e._resume()},function(t){e._crash(t)}),void this._pause()}this._break()}function nt(){var n,r,i=this;this._pause(),document.addEventListener(\"jazz-midi-msg\",function t(){if(n=!0,r=r||document.getElementById(\"jazz-midi-msg\")){var e=[];try{e=JSON.parse(r.innerText)}catch(t){}r.innerText=\"\",document.removeEventListener(\"jazz-midi-msg\",t),\"version\"===e[0]?(function(t,e){function i(){for(var t=0;t<this.clients.length;t++)this._close(this.clients[t])}var n;K._type=\"extension\",K._version=e,K._sysex=!0,K._pool=[],K._outs=[],K._ins=[],K._inArr=[],K._outArr=[],K._inMap={},K._outMap={},K._outsW=[],K._insW=[],K.refreshClients=[],K._msg=t,K._newPlugin=function(){var t={id:K._pool.length};K._pool.push(t),t.id?document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:[\"new\"]})):t.ready=!0},K._newPlugin(),K._refresh=function(t){K.refreshClients.push(t),t._pause(),f(function(){document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:[\"refresh\"]}))})},K._openOut=function(t,e){var n=K._outMap[e];if(!n){K._pool.length<=K._outArr.length&&K._newPlugin();var r=K._pool[K._outArr.length];((n={name:e,clients:[],info:{name:e,manufacturer:K._allOuts[e].manufacturer,version:K._allOuts[e].version,type:\"MIDI-out\",sysex:K._sysex,engine:K._type},_start:function(){document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:[\"openout\",r.id,e]}))},_close:function(t){K._closeOut(t)},_closeAll:i,_receive:function(t){if(t.length){var e=t.slice();e.splice(0,0,\"play\",r.id),document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:e}))}}}).plugin=r).output=n,K._outArr.push(n),K._outMap[e]=n}C((t._orig._impl=n).clients,t._orig),t._info=n.info,t._receive=function(t){n._receive(t)},t._close=function(){n._close(this)},n.open||(t._pause(),n.plugin.ready&&n._start())},K._openIn=function(t,e){var n=K._inMap[e];if(!n){K._pool.length<=K._inArr.length&&K._newPlugin();var r=K._pool[K._inArr.length];((n={name:e,clients:[],info:{name:e,manufacturer:K._allIns[e].manufacturer,version:K._allIns[e].version,type:\"MIDI-in\",sysex:K._sysex,engine:K._type},_start:function(){document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:[\"openin\",r.id,e]}))},_close:function(t){K._closeIn(t)},_closeAll:i}).plugin=r).input=n,K._inArr.push(n),K._inMap[e]=n}C((t._orig._impl=n).clients,t._orig),t._info=n.info,t._close=function(){n._close(this)},n.open||(t._pause(),n.plugin.ready&&n._start())},K._closeOut=function(t){var e=t._impl;b(e.clients,t._orig),!e.clients.length&&e.open&&(e.open=!1,document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:[\"closeout\",e.plugin.id]})))},K._closeIn=function(t){var e=t._impl;b(e.clients,t._orig),!e.clients.length&&e.open&&(e.open=!1,document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:[\"closein\",e.plugin.id]})))},K._close=function(){K._unwatch()},K._watch=function(){K._insW=K._ins,K._outsW=K._outs,n=setInterval(function(){document.dispatchEvent(new CustomEvent(\"jazz-midi\",{detail:[\"refresh\"]}))},250)},K._unwatch=function(){clearInterval(n),n=void 0},document.addEventListener(\"jazz-midi-msg\",function(){var t,e,n,r=K._msg.innerText.split(\"\\n\");for(K._msg.innerText=\"\",e=0;e<r.length;e++){var i=[];try{i=JSON.parse(r[e])}catch(t){}if(i.length)if(\"refresh\"===i[0]){if(i[1].ins){for(n=0;n<i[1].ins.length;n++)i[1].ins[n].type=K._type;K._ins=i[1].ins}if(i[1].outs){for(n=0;n<i[1].outs.length;n++)i[1].outs[n].type=K._type;K._outs=i[1].outs}for(x(),n=0;n<K.refreshClients.length;n++)K.refreshClients[n]._resume();K.refreshClients=[]}else if(\"version\"===i[0]){var o=K._pool[i[1]];o&&(o.ready=!0,o.input&&o.input._start(),o.output&&o.output._start())}else if(\"openout\"===i[0]){if(t=K._pool[i[1]].output)if(i[2]==t.name){if(t.open=!0,t.clients)for(n=0;n<t.clients.length;n++)t.clients[n]._resume()}else if(t.clients)for(n=0;n<t.clients.length;n++)t.clients[n]._crash()}else if(\"openin\"===i[0]){if(t=K._pool[i[1]].input)if(i[2]==t.name){if(t.open=!0,t.clients)for(n=0;n<t.clients.length;n++)t.clients[n]._resume()}else if(t.clients)for(n=0;n<t.clients.length;n++)t.clients[n]._crash()}else if(\"midi\"===i[0]&&(t=K._pool[i[1]].input)&&t.clients)for(n=0;n<t.clients.length;n++){var s=lt(i.slice(3));t.clients[n]._emit(s)}}})}(r,e[2]),i._resume()):i._crash()}});try{document.dispatchEvent(new Event(\"jazz-midi\"))}catch(t){}f(function(){n||i._crash()})}function rt(){this._pause();var t=this;f(function(){t._crash()})}function it(t){for(var e=[],n=function(t){var e=[\"node\",\"extension\",\"plugin\",\"webmidi\"];if(!t||!t.engine)return e;var n,r,i,o=t.engine instanceof Array?t.engine:[t.engine],s={},u=[],a=[];for(i=0;i<o.length;i++){var h=o[i].toString().toLowerCase();s[h]||(s[h]=!0,\"none\"===h&&(n=!0),\"etc\"!==h&&void 0!==h||(r=!0),r?a.push(h):u.push(h),b(e,h))}(r||u.length||a.length)&&(n=!1);return n?[]:u.concat(r?e:a)}(t),r=0;r<n.length;r++)\"webmidi\"==n[r]?(t&&!0===t.sysex&&e.push(et),t&&!0===t.sysex&&!0!==t.degrade||e.push(tt)):\"node\"==n[r]?(e.push(X),e.push(rt)):\"extension\"==n[r]?e.push(nt):\"plugin\"==n[r]&&e.push(Y);return e.push(ot),e}function ot(){K._type=\"none\",K._version=s,K._sysex=!0,K._outs=[],K._ins=[],K._refresh=function(){x()},K._watch=function(){},K._unwatch=function(){},K._close=function(){}}function st(){var t;function s(){for(var t=0;t<this.clients.length;t++)this._close(this.clients[t])}K._inArr=[],K._outArr=[],K._inMap={},K._outMap={},K._outsW=[],K._insW=[],K._version=K._main.version,K._sysex=!0,K._refresh=function(){var t,e;for(K._outs=[],K._ins=[],t=0;(e=K._main.MidiOutInfo(t)).length;t++)K._outs.push({type:K._type,name:e[0],manufacturer:e[1],version:e[2]});for(t=0;(e=K._main.MidiInInfo(t)).length;t++)K._ins.push({type:K._type,name:e[0],manufacturer:e[1],version:e[2]});x()},K._openOut=function(t,e){var n=K._outMap[e];if(!n){K._pool.length<=K._outArr.length&&K._pool.push(K._newPlugin()),n={name:e,clients:[],info:{name:e,manufacturer:K._allOuts[e].manufacturer,version:K._allOuts[e].version,type:\"MIDI-out\",sysex:K._sysex,engine:K._type},_close:function(t){K._closeOut(t)},_closeAll:s,_receive:function(t){t.length&&this.plugin.MidiOutRaw(t.slice())}};var r=K._pool[K._outArr.length];n.plugin=r,K._outArr.push(n),K._outMap[e]=n}if(!n.open){var i=n.plugin.MidiOutOpen(e);if(i!==e)return i&&n.plugin.MidiOutClose(),void t._break();n.open=!0}C((t._orig._impl=n).clients,t._orig),t._info=n.info,t._receive=function(t){n._receive(t)},t._close=function(){n._close(this)}},K._openIn=function(t,e){var n,r=K._inMap[e];if(!r){K._pool.length<=K._inArr.length&&K._pool.push(K._newPlugin());(r={name:e,clients:[],info:{name:e,manufacturer:K._allIns[e].manufacturer,version:K._allIns[e].version,type:\"MIDI-in\",sysex:K._sysex,engine:K._type},_close:function(t){K._closeIn(t)},_closeAll:s,handle:function(t,e){for(var n=0;n<this.clients.length;n++){var r=lt(e);this.clients[n]._emit(r)}}}).onmidi=(n=r,function(t,e){n.handle(t,e)});var i=K._pool[K._inArr.length];r.plugin=i,K._inArr.push(r),K._inMap[e]=r}if(!r.open){var o=r.plugin.MidiInOpen(e,r.onmidi);if(o!==e)return o&&r.plugin.MidiInClose(),void t._break();r.open=!0}C((t._orig._impl=r).clients,t._orig),t._info=r.info,t._close=function(){r._close(this)}},K._closeOut=function(t){var e=t._impl;b(e.clients,t._orig),!e.clients.length&&e.open&&(e.open=!1,e.plugin.MidiOutClose())},K._closeIn=function(t){var e=t._impl;b(e.clients,t._orig),!e.clients.length&&e.open&&(e.open=!1,e.plugin.MidiInClose())},K._close=function(){for(var t=0;t<K._inArr.length;t++)K._inArr[t].open&&K._inArr[t].plugin.MidiInClose();K._unwatch()},K._watch=function(){t=t||setInterval(function(){K._refresh()},250)},K._unwatch=function(){t&&clearInterval(t),t=void 0}}function ut(t,e){var n;function o(){for(var t=0;t<this.clients.length;t++)this._close(this.clients[t])}K._type=\"webmidi\",K._version=43,K._sysex=!!e,K._access=t,K._inMap={},K._outMap={},K._outsW=[],K._insW=[],K._refresh=function(){K._outs=[],K._ins=[],K._access.outputs.forEach(function(t){K._outs.push({type:K._type,name:t.name,manufacturer:t.manufacturer,version:t.version})}),K._access.inputs.forEach(function(t){K._ins.push({type:K._type,name:t.name,manufacturer:t.manufacturer,version:t.version})}),x()},K._openOut=function(t,e){var n,r=K._outMap[e];r=r||{name:e,clients:[],info:{name:e,manufacturer:K._allOuts[e].manufacturer,version:K._allOuts[e].version,type:\"MIDI-out\",sysex:K._sysex,engine:K._type},_close:function(t){K._closeOut(t)},_closeAll:o,_receive:function(t){r.dev&&t.length&&this.dev.send(t.slice())}},K._access.outputs.forEach(function(t){t.name===e&&(n=t)}),n?(r.dev=n,K._outMap[e]=r,C((t._orig._impl=r).clients,t._orig),t._info=r.info,t._receive=function(t){r._receive(t)},t._close=function(){r._close(this)},r.dev.open&&(t._pause(),r.dev.open().then(function(){t._resume()},function(){t._crash()}))):t._break()},K._openIn=function(t,e){var n,r,i=K._inMap[e];if(i=i||{name:e,clients:[],info:{name:e,manufacturer:K._allIns[e].manufacturer,version:K._allIns[e].version,type:\"MIDI-in\",sysex:K._sysex,engine:K._type},_close:function(t){K._closeIn(t)},_closeAll:o,handle:function(t){for(var e=0;e<this.clients.length;e++){var n=lt([].slice.call(t.data));this.clients[e]._emit(n)}}},K._access.inputs.forEach(function(t){t.name===e&&(n=t)}),n){i.dev=n;i.dev.onmidimessage=(r=i,function(t){r.handle(t)}),K._inMap[e]=i,C((t._orig._impl=i).clients,t._orig),t._info=i.info,t._close=function(){i._close(this)},i.dev.open&&(t._pause(),i.dev.open().then(function(){t._resume()},function(){t._crash()}))}else t._break()},K._closeOut=function(t){var e=t._impl;b(e.clients,t._orig),e.clients.length||(e.dev&&e.dev.close&&e.dev.close(),e.dev=void 0)},K._closeIn=function(t){var e=t._impl;b(e.clients,t._orig),e.clients.length||(e.dev&&(e.dev.onmidimessage=null,e.dev.close&&e.dev.close()),e.dev=void 0)},K._close=function(){K._unwatch()},K._watch=function(){K._access.onstatechange=function(){n=!0,f(function(){n&&(K._refresh(),n=!1)})}},K._unwatch=function(){K._access.onstatechange=void 0}}function at(t){return M||function(t){Rt(),(M=new w)._options=t,M._push(y,[it(t)]),M.refresh(),M._resume()}(t),M._thenable()}function ht(){var t=this instanceof ht?this:t=new ht;return ht.prototype.reset.apply(t,arguments),t}function ft(){29.97==this.type&&!this.second&&this.frame<2&&this.minute%10&&(this.frame=2)}function ct(t){return[[24,25,29.97,30][t[7]>>1&3],(1&t[7])<<4|t[6],t[5]<<4|t[4],t[3]<<4|t[2],t[1]<<4|t[0]]}function pt(t){for(var e,n=[],r=0;r<t.length;r++)n[r]=(e=t[r])<10?\"0\"+e:e;return n.join(\":\")}function lt(t){var e,n=this instanceof lt?this:n=new lt;if(t instanceof lt){for(e in n._from=t._from.slice(),t)t.hasOwnProperty(e)&&\"_from\"!=e&&(n[e]=t[e]);return n}if(n._from=[],void 0===t)return n;var r=t instanceof Array?t:arguments;for(e=0;e<r.length;e++)i=r[e],1==e&&(128<=n[0]&&n[0]<=175&&(i=lt.noteValue(i)),192<=n[0]&&n[0]<=207&&(i=lt.programValue(i))),(i!=parseInt(i)||i<0||255<i)&&mt(r[e]),n.push(i);return n}(at.JZZ=at).info=function(){return w.prototype.info()},w.prototype.Widget=at.Widget=function(t){var e=new L;if(t instanceof Object)for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e._resume(),e},ht.prototype.reset=function(t){if(t instanceof ht)return this.setType(t.getType()),this.setHour(t.getHour()),this.setMinute(t.getMinute()),this.setSecond(t.getSecond()),this.setFrame(t.getFrame()),this.setQuarter(t.getQuarter()),this;var e=t instanceof Array?t:arguments;return this.setType(e[0]),this.setHour(e[1]),this.setMinute(e[2]),this.setSecond(e[3]),this.setFrame(e[4]),this.setQuarter(e[5]),this},ht.prototype.isFullFrame=function(){return 0==this.quarter||4==this.quarter},ht.prototype.getType=function(){return this.type},ht.prototype.getHour=function(){return this.hour},ht.prototype.getMinute=function(){return this.minute},ht.prototype.getSecond=function(){return this.second},ht.prototype.getFrame=function(){return this.frame},ht.prototype.getQuarter=function(){return this.quarter},ht.prototype.setType=function(t){if(void 0===t||24==t)this.type=24;else if(25==t)this.type=25;else if(29.97==t)this.type=29.97,ft.apply(this);else{if(30!=t)throw RangeError(\"Bad SMPTE frame rate: \"+t);this.type=30}return this.frame>=this.type&&(this.frame=this.type-1),this},ht.prototype.setHour=function(t){if(void 0===t&&(t=0),t!=parseInt(t)||t<0||24<=t)throw RangeError(\"Bad SMPTE hours value: \"+t);return this.hour=t,this},ht.prototype.setMinute=function(t){if(void 0===t&&(t=0),t!=parseInt(t)||t<0||60<=t)throw RangeError(\"Bad SMPTE minutes value: \"+t);return this.minute=t,ft.apply(this),this},ht.prototype.setSecond=function(t){if(void 0===t&&(t=0),t!=parseInt(t)||t<0||60<=t)throw RangeError(\"Bad SMPTE seconds value: \"+t);return this.second=t,ft.apply(this),this},ht.prototype.setFrame=function(t){if(void 0===t&&(t=0),t!=parseInt(t)||t<0||t>=this.type)throw RangeError(\"Bad SMPTE frame number: \"+t);return this.frame=t,ft.apply(this),this},ht.prototype.setQuarter=function(t){if(void 0===t&&(t=0),t!=parseInt(t)||t<0||8<=t)throw RangeError(\"Bad SMPTE quarter frame: \"+t);return this.quarter=t,this},ht.prototype.incrFrame=function(){return this.frame++,this.frame>=this.type&&(this.frame=0,this.second++,60<=this.second&&(this.second=0,this.minute++,60<=this.minute&&(this.minute=0,this.hour=23<=this.hour?0:this.hour+1))),ft.apply(this),this},ht.prototype.decrFrame=function(){return!this.second&&2==this.frame&&29.97==this.type&&this.minute%10&&(this.frame=0),this.frame--,this.frame<0&&(this.frame=29.97==this.type?29:this.type-1,this.second--,this.second<0&&(this.second=59,this.minute--,this.minute<0&&(this.minute=59,this.hour=this.hour?this.hour-1:23))),this},ht.prototype.incrQF=function(){return this.backwards=!1,this.quarter=this.quarter+1&7,0!=this.quarter&&4!=this.quarter||this.incrFrame(),this},ht.prototype.decrQF=function(){return this.backwards=!0,this.quarter=this.quarter+7&7,3!=this.quarter&&7!=this.quarter||this.decrFrame(),this},ht.prototype.read=function(t){if(t instanceof lt||(t=lt.apply(null,arguments)),240==t[0]&&127==t[1]&&1==t[3]&&1==t[4]&&247==t[9])return this.type=[24,25,29.97,30][t[5]>>5&3],this.hour=31&t[5],this.minute=t[6],this.second=t[7],this.frame=t[8],this.quarter=0,this._=void 0,this._b=void 0,!(this._f=void 0);if(241!=t[0]||void 0===t[1])return!1;var e=t[1]>>4,n=15&t[1];return 0==e?7==this._&&(7==this._f&&(this.reset(ct(this._a)),this.incrFrame()),this.incrFrame()):3==e?4==this._&&this.decrFrame():4==e?3==this._&&this.incrFrame():7==e&&0===this._&&(0===this._b&&(this.reset(ct(this._a)),this.decrFrame()),this.decrFrame()),this._a||(this._a=[]),this._a[e]=n,this._f=this._f===e-1||0==e?e:void 0,this._b=this._b===1+e||7==e?e:void 0,this._=e,this.quarter=e,!0},ht.prototype.toString=function(){return pt([this.hour,this.minute,this.second,this.frame])},at.SMPTE=ht;var dt={};((lt.prototype=[]).constructor=lt).noteValue=function(t){return void 0===t?void 0:dt[t.toString().toLowerCase()]},lt.programValue=function(t){return t},lt.freq=function(t,e){return void 0===e&&(e=440),e*Math.pow(2,(vt(lt.noteValue(t),t)-69)/12)};var _t={c:0,d:2,e:4,f:5,g:7,a:9,b:11,h:11};for(n in _t)if(_t.hasOwnProperty(n))for(i=0;i<12&&!(127<(t=_t[n]+12*i));i++)0<(dt[n+i]=t)&&(dt[n+\"b\"+i]=t-1,dt[n+\"bb\"+i]=t-2),t<127&&(dt[n+\"#\"+i]=t+1,dt[n+\"##\"+i]=t+2);for(i=0;i<128;i++)dt[i]=i;function mt(t){throw RangeError(\"Bad MIDI value: \"+t)}function gt(t){return U(t),parseInt(t)}function vt(t,e){return(t!=parseInt(t)||t<0||127<t)&&mt(void 0===e?t:e),parseInt(t)}function yt(t,e){return(t!=parseInt(t)||t<0||255<t)&&mt(void 0===e?t:e),parseInt(t)}function Ct(t){return(t!=parseInt(t)||t<0||16383<t)&&mt(t),127&parseInt(t)}function bt(t){return(t!=parseInt(t)||t<0||16383<t)&&mt(t),parseInt(t)>>7}var wt={noteOff:function(t,e,n){return void 0===n&&(n=64),[128+gt(t),vt(lt.noteValue(e),e),vt(n)]},noteOn:function(t,e,n){return void 0===n&&(n=127),[144+gt(t),vt(lt.noteValue(e),e),vt(n)]},aftertouch:function(t,e,n){return[160+gt(t),vt(lt.noteValue(e),e),vt(n)]},control:function(t,e,n){return[176+gt(t),vt(e),vt(n)]},program:function(t,e){return[192+gt(t),vt(lt.programValue(e),e)]},pressure:function(t,e){return[208+gt(t),vt(e)]},pitchBend:function(t,e){return[224+gt(t),Ct(e),bt(e)]},bankMSB:function(t,e){return[176+gt(t),0,vt(e)]},bankLSB:function(t,e){return[176+gt(t),32,vt(e)]},modMSB:function(t,e){return[176+gt(t),1,vt(e)]},modLSB:function(t,e){return[176+gt(t),33,vt(e)]},breathMSB:function(t,e){return[176+gt(t),2,vt(e)]},breathLSB:function(t,e){return[176+gt(t),34,vt(e)]},footMSB:function(t,e){return[176+gt(t),4,vt(e)]},footLSB:function(t,e){return[176+gt(t),36,vt(e)]},portamentoMSB:function(t,e){return[176+gt(t),5,vt(e)]},portamentoLSB:function(t,e){return[176+gt(t),37,vt(e)]},volumeMSB:function(t,e){return[176+gt(t),7,vt(e)]},volumeLSB:function(t,e){return[176+gt(t),39,vt(e)]},balanceMSB:function(t,e){return[176+gt(t),8,vt(e)]},balanceLSB:function(t,e){return[176+gt(t),40,vt(e)]},panMSB:function(t,e){return[176+gt(t),10,vt(e)]},panLSB:function(t,e){return[176+gt(t),42,vt(e)]},expressionMSB:function(t,e){return[176+gt(t),11,vt(e)]},expressionLSB:function(t,e){return[176+gt(t),43,vt(e)]},damper:function(t,e){return[176+gt(t),64,e?127:0]},portamento:function(t,e){return[176+gt(t),65,e?127:0]},sostenuto:function(t,e){return[176+gt(t),66,e?127:0]},soft:function(t,e){return[176+gt(t),67,e?127:0]},allSoundOff:function(t){return[176+gt(t),120,0]},allNotesOff:function(t){return[176+gt(t),123,0]}},St={mtc:function(t){return[241,function(t){var e;switch(!t.backwards&&4<=t.quarter?t.decrFrame():t.backwards&&t.quarter<4&&t.incrFrame(),t.quarter>>1){case 0:e=t.frame;break;case 1:e=t.second;break;case 2:e=t.minute;break;default:e=t.hour}return 1&t.quarter?e>>=4:e&=15,7==t.quarter&&(25==t.type?e|=2:29.97==t.type?e|=4:30==t.type&&(e|=6)),!t.backwards&&4<=t.quarter?t.incrFrame():t.backwards&&t.quarter<4&&t.decrFrame(),e|t.quarter<<4}(t)]},songPosition:function(t){return[242,Ct(t),bt(t)]},songSelect:function(t){return[243,vt(t)]},tune:function(){return[246]},clock:function(){return[248]},start:function(){return[250]},continue:function(){return[251]},stop:function(){return[252]},active:function(){return[254]},sxIdRequest:function(){return[240,126,127,6,1,247]},sxFullFrame:function(t){return[240,127,127,1,1,function(t){return 25==t.type?32|t.hour:29.97==t.type?64|t.hour:30==t.type?96|t.hour:t.hour}(t),t.getMinute(),t.getSecond(),t.getFrame(),247]},reset:function(){return[255]}};function Mt(t,e){var n=new lt;return n.ff=yt(t),n.dd=void 0===e?\"\":function(t){t=\"\"+t;for(var e=0;e<t.length;e++)255<t.charCodeAt(e)&&mt(t[e]);return t}(e),n}var Ot={smf:function(t){if(t instanceof lt)return new lt(t);var e=t instanceof Array?t:arguments,n=yt(e[0]),r=\"\";return 2==e.length?r=Dt(e[1]):2<e.length&&(r=Dt(Array.prototype.slice.call(e,1))),Mt(n,r)},smfSeqNumber:function(t){if(t==parseInt(t)){if(t<0||65535<t)throw RangeError(\"Sequence number out of range: \"+t);t=String.fromCharCode(t>>8)+String.fromCharCode(255&t)}else if(0==(t=\"\"+t).length)t=\"\\0\\0\";else if(1==t.length)t=\"\\0\"+t;else if(2<t.length)throw RangeError(\"Sequence number out of range: \"+kt(t));return Mt(0,t)},smfText:function(t){return Mt(1,at.lib.toUTF8(t))},smfCopyright:function(t){return Mt(2,at.lib.toUTF8(t))},smfSeqName:function(t){return Mt(3,at.lib.toUTF8(t))},smfInstrName:function(t){return Mt(4,at.lib.toUTF8(t))},smfLyric:function(t){return Mt(5,at.lib.toUTF8(t))},smfMarker:function(t){return Mt(6,at.lib.toUTF8(t))},smfCuePoint:function(t){return Mt(7,at.lib.toUTF8(t))},smfProgName:function(t){return Mt(8,at.lib.toUTF8(t))},smfDevName:function(t){return Mt(9,at.lib.toUTF8(t))},smfChannelPrefix:function(t){if(t==parseInt(t))U(t),t=String.fromCharCode(t);else if(0==(t=\"\"+t).length)t=\"\\0\";else if(1<t.length||15<t.charCodeAt(0))throw RangeError(\"Channel number out of range: \"+kt(t));return Mt(32,t)},smfMidiPort:function(t){if(t==parseInt(t)){if(t<0||127<t)throw RangeError(\"Port number out of range: \"+t);t=String.fromCharCode(t)}else if(0==(t=\"\"+t).length)t=\"\\0\";else if(1<t.length||127<t.charCodeAt(0))throw RangeError(\"Port number out of range: \"+kt(t));return Mt(33,t)},smfEndOfTrack:function(t){if(\"\"!=Dt(t))throw RangeError(\"Unexpected data: \"+kt(Dt(t)));return Mt(47)},smfTempo:function(t){if(3==(\"\"+t).length)return Mt(81,t);if(t==parseInt(t)&&0<t&&t<=16777215)return Mt(81,String.fromCharCode(t>>16)+String.fromCharCode(t>>8&255)+String.fromCharCode(255&t));throw RangeError(\"Out of range: \"+kt(Dt(t)))},smfBPM:function(t){return Ot.smfTempo(Math.round(6e7/t))},smfSMPTE:function(t){if(t instanceof ht)return Mt(84,String.fromCharCode(t.hour)+String.fromCharCode(t.minute)+String.fromCharCode(t.second)+String.fromCharCode(t.frame)+String.fromCharCode(t.quarter%4*25));if(5==(\"\"+t).length)return Mt(84,t);var e=t instanceof Array?t:Array.prototype.slice.call(arguments);return e.splice(0,0,30),Ot.smfSMPTE(new ht(e))},smfTimeSignature:function(t,e,n,r){var i,o,s,u,a=(\"\"+t).match(/^\\s*(\\d+)\\s*\\/\\s*(\\d+)\\s*$/);if(a){if(i=parseInt(a[1]),o=parseInt(a[2]),0<i&&i<=255&&o&&!(o&o-1)){for(s=o,o=0,s>>=1;s;s>>=1)o++;return s=e==parseInt(e)?e:24,u=n==parseInt(n)?n:8,Mt(88,String.fromCharCode(i)+String.fromCharCode(o)+String.fromCharCode(s)+String.fromCharCode(u))}if(4==(\"\"+t).length)return Mt(88,t)}else{if(t==parseInt(t)&&e==parseInt(e)&&e&&!(e&e-1)){for(i=t,o=0,s=e,s>>=1;s;s>>=1)o++;return s=n==parseInt(n)?n:24,u=r==parseInt(r)?r:8,Mt(88,String.fromCharCode(i)+String.fromCharCode(o)+String.fromCharCode(s)+String.fromCharCode(u))}if(4==(\"\"+t).length)return Mt(88,t)}throw RangeError(\"Wrong time signature: \"+kt(Dt(t)))},smfKeySignature:function(t){var e=(t=\"\"+t).match(/^\\s*([A-H][b#]?)\\s*(|maj|major|dur|m|min|minor|moll)\\s*$/i);if(e){var n={CB:0,GB:1,DB:2,AB:3,EB:4,BB:5,F:6,C:7,G:8,D:9,A:10,E:11,B:12,H:12,\"F#\":13,\"C#\":14,\"G#\":15,\"D#\":16,\"A#\":17}[e[1].toUpperCase()],r={\"\":0,MAJ:0,MAJOR:0,DUR:0,M:1,MIN:1,MINOR:1,MOLL:1}[e[2].toUpperCase()];void 0!==n&&void 0!==r&&(r&&(n-=3),-7<=(n-=7)&&n<0?t=String.fromCharCode(256+n)+String.fromCharCode(r):0<=n&&n<=7&&(t=String.fromCharCode(n)+String.fromCharCode(r)))}if(2==t.length&&t.charCodeAt(1)<=1&&(t.charCodeAt(0)<=7||t.charCodeAt(0)<=255&&249<=t.charCodeAt(0)))return Mt(89,t);throw RangeError(\"Incorrect key signature: \"+kt(t))},smfSequencer:function(t){return Mt(127,Dt(t))}};function It(t,e,n){t.prototype[e]=function(){return this.send(n.apply(0,arguments)),this}}function At(t,e,n){t.prototype[e]=function(){return this.send(n.apply(0,[this._chan].concat(Array.prototype.slice.call(arguments)))),this}}function Et(t,e){lt[t]=function(){return new lt(e.apply(0,arguments))}}function xt(t,e){lt[t]=function(){return e.apply(0,arguments)}}function Pt(t,r){Et(t,r),Z.prototype[t]=function(){var t,e=Array.prototype.slice.call(arguments);e.length<r.length?e=[this._master].concat(e):(t=vt(lt.noteValue(e[0],e[0])),e[0]=this._master);var n=r.apply(0,e);return n.mpe=t,this.send(n),this}}for(n in St)St.hasOwnProperty(n)&&Et(n,St[n]);for(n in Ot)Ot.hasOwnProperty(n)&&xt(n,Ot[n]);for(n in wt)wt.hasOwnProperty(n)&&Pt(n,wt[n]);function Bt(t,e){for(n in St)St.hasOwnProperty(n)&&It(t,n,St[n]);for(n in Ot)Ot.hasOwnProperty(n)&&It(t,n,Ot[n]);for(n in wt)wt.hasOwnProperty(n)&&It(t,n,wt[n]);if(e)for(n in wt)wt.hasOwnProperty(n)&&At(e,n,wt[n])}Bt(L,G),Z.prototype.noteOn=function(t,e){var n=lt.noteOn(this._master,t,e);return n._mpe=n[1],this.send(n),this},Z.prototype.noteOff=function(t,e){var n=lt.noteOff(this._master,t,e);return n._mpe=n[1],this.send(n),this},Z.prototype.aftertouch=function(t,e){var n=lt.aftertouch(this._master,t,e);return n._mpe=n[1],this.send(n),this};var Tt,Ft={a:10,b:11,c:12,d:13,e:14,f:15,A:10,B:11,C:12,D:13,E:14,F:15};for(n=0;n<16;n++)Ft[n]=n;function qt(t){for(var e=[],n=0;n<t.length;n++)e[n]=t.charCodeAt(n);return e}function Dt(t){return t instanceof Array?function(t){for(var e=\"\",n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return e}(t):void 0===t?\"\":\"\"+t}function Lt(t){return(t<16?\"0\":\"\")+t.toString(16)}function zt(t){for(var e=[],n=0;n<t.length;n++)e[n]=Lt(t[n]);return e.join(\" \")}function jt(t){return t.length?\": \"+zt(qt(t)):\"\"}function kt(t){return t.length?\": \"+function(t){for(var e=\"\",n=0;n<t.length;n++)\"\\n\"==t[n]?e+=\"\\\\n\":\"\\r\"==t[n]?e+=\"\\\\r\":\"\\t\"==t[n]?e+=\"\\\\t\":t.charCodeAt(n)<32?e+=\"\\\\x\"+Lt(t.charCodeAt(n)):e+=t[n];return e}(at.lib.fromUTF8(t)):\"\"}function Nt(){var t=this instanceof Nt?this:t=new Nt;return t.reset(),arguments.length&&Nt.prototype.setup.apply(t,arguments),t}function Rt(){if(!Tt&&\"undefined\"!=typeof window){var t=window.AudioContext||window.webkitAudioContext;if(t){(Tt=new t)&&!Tt.createGain&&(Tt.createGain=Tt.createGainNode);var n=function(){if(\"running\"!=Tt.state){Tt.resume();var t=Tt.createOscillator(),e=Tt.createGain();try{e.gain.value=0}catch(t){}e.gain.setTargetAtTime(0,Tt.currentTime,.01),t.connect(e),e.connect(Tt.destination),t.start||(t.start=t.noteOn),t.stop||(t.stop=t.noteOff),t.start(.1),t.stop(.11)}else document.removeEventListener(\"touchstart\",n),document.removeEventListener(\"touchend\",n),document.removeEventListener(\"mousedown\",n),document.removeEventListener(\"keydown\",n)};document.addEventListener(\"touchstart\",n),document.addEventListener(\"touchend\",n),document.addEventListener(\"mousedown\",n),document.addEventListener(\"keydown\",n),n()}}}lt.prototype.getChannel=function(){if(32==this.ff&&1==this.dd.length&&this.dd.charCodeAt(0)<16)return this.dd.charCodeAt(0);var t=this[0];return void 0===t||t<128||239<t?void 0:15&t},lt.prototype.setChannel=function(t){if(void 0===(t=Ft[t]))return this;if(32==this.ff)this.dd=String.fromCharCode(t);else{var e=this[0];void 0!==e&&128<=e&&e<=239&&(this[0]=240&e|t)}return this},lt.prototype.getNote=function(){var t=this[0];if(!(void 0===t||t<128||175<t))return this[1]},lt.prototype.setNote=function(t){var e=this[0];return void 0===e||e<128||175<e||void 0!==(t=lt.noteValue(t))&&(this[1]=t),this},lt.prototype.getVelocity=function(){var t=this[0];if(!(void 0===t||t<128||159<t))return this[2]},lt.prototype.setVelocity=function(t){var e=this[0];return void 0===e||e<128||159<e||0<=(t=parseInt(t))&&t<128&&(this[2]=t),this},lt.prototype.getSysExChannel=function(){if(240==this[0])return this[2]},lt.prototype.setSysExChannel=function(t){return 240==this[0]&&2<this.length&&0<=(t=parseInt(t))&&t<128&&(this[2]=t),this},lt.prototype.getData=function(){if(void 0!==this.dd)return this.dd.toString()},lt.prototype.setData=function(t){return this.dd=Dt(t),this},lt.prototype.getText=function(){if(void 0!==this.dd)return at.lib.fromUTF8(this.dd)},lt.prototype.setText=function(t){return this.dd=at.lib.toUTF8(t),this},lt.prototype.getTempo=function(){if(81==this.ff&&void 0!==this.dd)return 65536*this.dd.charCodeAt(0)+256*this.dd.charCodeAt(1)+this.dd.charCodeAt(2)},lt.prototype.getBPM=function(){var t=this.getTempo();if(t)return 6e7/t},lt.prototype.getTimeSignature=function(){if(88==this.ff&&void 0!==this.dd)return[this.dd.charCodeAt(0),1<<this.dd.charCodeAt(1)]},lt.prototype.getKeySignature=function(){if(89==this.ff&&void 0!==this.dd){var t=this.dd.charCodeAt(0),e=this.dd.charCodeAt(1);if(128&t&&(t-=256),-7<=t&&t<=7&&0<=e&&e<=1)return[t,[\"Cb\",\"Gb\",\"Db\",\"Ab\",\"Eb\",\"Bb\",\"F\",\"C\",\"G\",\"D\",\"A\",\"E\",\"B\",\"F#\",\"C#\",\"G#\",\"D#\",\"A#\"][e?t+10:t+7],!!e]}},lt.prototype.isNoteOn=function(){var t=this[0];return!(void 0===t||t<144||159<t)&&0<this[2]},lt.prototype.isNoteOff=function(){var t=this[0];return!(void 0===t||t<128||159<t)&&(t<144||0==this[2])},lt.prototype.isSysEx=function(){return 240==this[0]},lt.prototype.isFullSysEx=function(){return 240==this[0]&&247==this[this.length-1]},lt.prototype.isSMF=function(){return 0<=this.ff&&this.ff<=127},lt.prototype.isEOT=function(){return 47==this.ff},lt.prototype.isTempo=function(){return 81==this.ff},lt.prototype.isTimeSignature=function(){return 88==this.ff},lt.prototype.isKeySignature=function(){return 89==this.ff},lt.prototype.toString=function(){var t,e;if(!this.length){if(void 0===this.ff)return\"empty\";if(t=\"ff\"+Lt(this.ff)+\" -- \",0==this.ff)t+=\"Sequence Number: \"+function(t){for(var e=0,n=0;n<t.length;n++)e=(e<<8)+t.charCodeAt(n);return e}(this.dd);else if(0<this.ff&&this.ff<10)t+=[\"\",\"Text\",\"Copyright\",\"Sequence Name\",\"Instrument Name\",\"Lyric\",\"Marker\",\"Cue Point\",\"Program Name\",\"Device Name\"][this.ff]+kt(this.dd);else if(32==this.ff)t+=\"Channel Prefix\"+jt(this.dd);else if(33==this.ff)t+=\"MIDI Port\"+jt(this.dd);else if(47==this.ff)t+=\"End of Track\"+jt(this.dd);else if(81==this.ff){t+=\"Tempo: \"+Math.round(100*this.getBPM())/100+\" bpm\"}else if(84==this.ff)t+=\"SMPTE Offset: \"+pt(qt(this.dd));else if(88==this.ff){var n=1<<this.dd.charCodeAt(1);t+=\"Time Signature: \"+this.dd.charCodeAt(0)+\"/\"+n,t+=\" \"+this.dd.charCodeAt(2)+\" \"+this.dd.charCodeAt(3)}else if(89==this.ff){t+=\"Key Signature: \";var r=this.getKeySignature();r?(t+=r[1],r[2]&&(t+=\" min\")):t+=\"invalid\"}else 127==this.ff?t+=\"Sequencer Specific\"+jt(this.dd):t+=\"SMF\"+jt(this.dd);return t}if(t=zt(this),this[0]<128)return t;if(e={241:\"MIDI Time Code\",242:\"Song Position\",243:\"Song Select\",244:\"Undefined\",245:\"Undefined\",246:\"Tune request\",248:\"Timing clock\",249:\"Undefined\",250:\"Start\",251:\"Continue\",252:\"Stop\",253:\"Undefined\",254:\"Active Sensing\",255:\"Reset\"}[this[0]])return t+\" -- \"+e;var i=this[0]>>4;return(e={8:\"Note Off\",10:\"Aftertouch\",12:\"Program Change\",13:\"Channel Aftertouch\",14:\"Pitch Wheel\"}[i])?t+\" -- \"+e:9==i?t+\" -- \"+(this[2]?\"Note On\":\"Note Off\"):11!=i?t:t+\" -- \"+(e=(e={0:\"Bank Select MSB\",1:\"Modulation Wheel MSB\",2:\"Breath Controller MSB\",4:\"Foot Controller MSB\",5:\"Portamento Time MSB\",6:\"Data Entry MSB\",7:\"Channel Volume MSB\",8:\"Balance MSB\",10:\"Pan MSB\",11:\"Expression Controller MSB\",12:\"Effect Control 1 MSB\",13:\"Effect Control 2 MSB\",16:\"General Purpose Controller 1 MSB\",17:\"General Purpose Controller 2 MSB\",18:\"General Purpose Controller 3 MSB\",19:\"General Purpose Controller 4 MSB\",32:\"Bank Select LSB\",33:\"Modulation Wheel LSB\",34:\"Breath Controller LSB\",36:\"Foot Controller LSB\",37:\"Portamento Time LSB\",38:\"Data Entry LSB\",39:\"Channel Volume LSB\",40:\"Balance LSB\",42:\"Pan LSB\",43:\"Expression Controller LSB\",44:\"Effect control 1 LSB\",45:\"Effect control 2 LSB\",48:\"General Purpose Controller 1 LSB\",49:\"General Purpose Controller 2 LSB\",50:\"General Purpose Controller 3 LSB\",51:\"General Purpose Controller 4 LSB\",64:\"Damper Pedal On/Off\",65:\"Portamento On/Off\",66:\"Sostenuto On/Off\",67:\"Soft Pedal On/Off\",68:\"Legato Footswitch\",69:\"Hold 2\",70:\"Sound Controller 1\",71:\"Sound Controller 2\",72:\"Sound Controller 3\",73:\"Sound Controller 4\",74:\"Sound Controller 5\",75:\"Sound Controller 6\",76:\"Sound Controller 7\",77:\"Sound Controller 8\",78:\"Sound Controller 9\",79:\"Sound Controller 10\",80:\"General Purpose Controller 5\",81:\"General Purpose Controller 6\",82:\"General Purpose Controller 7\",83:\"General Purpose Controller 8\",84:\"Portamento Control\",88:\"High Resolution Velocity Prefix\",91:\"Effects 1 Depth\",92:\"Effects 2 Depth\",93:\"Effects 3 Depth\",94:\"Effects 4 Depth\",95:\"Effects 5 Depth\",96:\"Data Increment\",97:\"Data Decrement\",98:\"Non-Registered Parameter Number LSB\",99:\"Non-Registered Parameter Number MSB\",100:\"Registered Parameter Number LSB\",101:\"Registered Parameter Number MSB\",120:\"All Sound Off\",121:\"Reset All Controllers\",122:\"Local Control On/Off\",123:\"All Notes Off\",124:\"Omni Mode Off\",125:\"Omni Mode On\",126:\"Mono Mode On\",127:\"Poly Mode On\"}[this[1]])||\"Undefined\")},lt.prototype._stamp=function(t){return this._from.push(t._orig?t._orig:t),this},lt.prototype._unstamp=function(t){if(void 0===t)this._from=[];else{t._orig&&(t=t._orig);var e=this._from.indexOf(t);-1<e&&this._from.splice(e,1)}return this},lt.prototype._stamped=function(t){t._orig&&(t=t._orig);for(var e=0;e<this._from.length;e++)if(this._from[e]==t)return!0;return!1},at.MIDI=lt,Nt.validate=function(t){var e=t instanceof Array?t:arguments;if(e[0]!=parseInt(e[0])||e[0]<0||14<e[0])throw RangeError(\"Bad master channel value: \"+e[0]);if(e[1]!=parseInt(e[1])||e[1]<0||15<e[0]+e[1])throw RangeError(\"Bad zone size value: \"+e[1])},Nt.prototype.reset=function(){for(var t=0;t<16;t++)this[t]={band:0,master:t}},Nt.prototype.setup=function(t,e){var n;Nt.validate(t,e);var r=t+e;if((this[t].master!=t||this[t].band!=e)&&(e||this[t].band)){for(this[t].band?r<(n=t+this[t].band)&&(r=n):this[t].master==t-1?(n=t-1,r<(n+=this[n].band)&&(r=n),this[t-1]={band:0,master:t-1}):this[t].master!=t&&(n=this[t].master,r<(n+=this[n].band)&&(r=n),this[this[t].master].band=t-this[t].master-1),this[t].master=t,this[t].band=e,n=t+1;n<=t+e;n++)this[n].band&&r<n+this[n].band&&(r=n+this[n].band),this[n]={band:0,master:t};for(;n<=r;n++)this[n]={band:0,master:n}}},Nt.prototype.filter=function(t){var e=t.getChannel();if(!this[e]||!this[this[e].master].band)return t;var n,r,i,o=this[e].master,s=this[o].band;if(void 0!==t._mpe){for(i=256,n=o+1;n<=o+s;n++)if(this[n].notes){for(i>this[n].notes.length&&(i=this[e=n].notes.length),r=0;r<this[n].notes.length;r++)if(this[n].notes[r]==t._mpe){e=n,i=-1;break}}else 0<i&&(e=n,i=0);t.setChannel(e),t._mpe=void 0}return e==o||(t.isNoteOn()?(this[e].notes||(this[e].notes=[]),C(this[e].notes,t.getNote())):t.isNoteOff()&&this[e].notes&&b(this[e].notes,t.getNote())),t},at.MPE=Nt,(at.lib={}).now=h,at.lib.schedule=f,at.lib.openMidiOut=function(t,e){var n=new L;return e._openOut(n),n._info=e._info(t),n},at.lib.openMidiIn=function(t,e){var n=new L;return e._openIn(n),n._info=e._info(t),n},at.lib.registerMidiOut=function(t,e){for(var n=e._info(t),r=0;r<$._outs.length;r++)if($._outs[r].name==n.name)return!1;return n.engine=e,$._outs.push(n),M&&(x(),M._bad&&(M._repair(),M._resume())),!0},at.lib.registerMidiIn=function(t,e){for(var n=e._info(t),r=0;r<$._ins.length;r++)if($._ins[r].name==n.name)return!1;return n.engine=e,$._ins.push(n),M&&(x(),M._bad&&(M._repair(),M._resume())),!0},at.lib.copyMidiHelpers=Bt,at.lib.getAudioContext=function(){return Rt(),Tt};var Ut=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";at.lib.fromBase64=function(t){var e,n,r,i,o,s,u=\"\",a=0;for(t=t.replace(/[^A-Za-z0-9+/=]/g,\"\");a<t.length;)e=Ut.indexOf(t.charAt(a++))<<2|(i=Ut.indexOf(t.charAt(a++)))>>4,n=(15&i)<<4|(o=Ut.indexOf(t.charAt(a++)))>>2,r=(3&o)<<6|(s=Ut.indexOf(t.charAt(a++))),u+=String.fromCharCode(e),64!=o&&(u+=String.fromCharCode(n)),64!=s&&(u+=String.fromCharCode(r));return u},at.lib.toBase64=function(t){var e,n,r,i,o,s=0,u=0,a=\"\",h=[];if(!t)return t;for(;e=(o=t.charCodeAt(s++)<<16|t.charCodeAt(s++)<<8|t.charCodeAt(s++))>>18&63,n=o>>12&63,r=o>>6&63,i=63&o,h[u++]=Ut.charAt(e)+Ut.charAt(n)+Ut.charAt(r)+Ut.charAt(i),s<t.length;);a=h.join(\"\");var f=t.length%3;return f?a.slice(0,f-3)+\"===\".slice(f):a},at.lib.fromUTF8=function(t){t=void 0===t?\"\":\"\"+t;var e,n,r,i=\"\";for(e=0;e<t.length;e++){if(255<(n=t.charCodeAt(e)))return t;if(n<128)i+=t[e];else if(192==(224&n)){if(n=(31&n)<<6,++e>=t.length)return t;if(128!=(192&(r=t.charCodeAt(e))))return t;n+=63&r,i+=String.fromCharCode(n)}else if(224==(240&n)){if(n=(15&n)<<12,++e>=t.length)return t;if(128!=(192&(r=t.charCodeAt(e))))return t;if(n+=(63&r)<<6,++e>=t.length)return t;if(128!=(192&(r=t.charCodeAt(e))))return t;n+=63&r,i+=String.fromCharCode(n)}else if(240==(248&n)){if(n=(7&n)<<18,++e>=t.length)return t;if(128!=(192&(r=t.charCodeAt(e))))return t;if(n+=(63&r)<<12,++e>=t.length)return t;if(128!=(192&(r=t.charCodeAt(e))))return t;if(n+=(63&r)<<6,++e>=t.length)return t;if(128!=(192&(r=t.charCodeAt(e))))return t;if(1114111<(n+=63&r))return t;n-=65536,i+=String.fromCharCode(55296+(n>>10)),i+=String.fromCharCode(56320+(1023&n))}}return i},at.lib.toUTF8=function(t){t=void 0===t?\"\":\"\"+t;var e,n,r=\"\";for(e=0;e<t.length;e++)(n=t.charCodeAt(e))<128?r+=t[e]:(n<2048?r+=String.fromCharCode(192+(n>>6)):(n<65536?r+=String.fromCharCode(224+(n>>12)):(r+=String.fromCharCode(240+(n>>18)),r+=String.fromCharCode(128+(n>>12&63))),r+=String.fromCharCode(128+(n>>6&63))),r+=String.fromCharCode(128+(63&n)));return r};var Gt=[],Zt={},Vt={},Jt=e.Promise;function Wt(t,e,n){this.name=t,this.message=e,this.code=n}function Ht(t,e){this.bubbles=!1,this.cancelBubble=!1,this.cancelable=!1,this.currentTarget=e,this.defaultPrevented=!1,this.eventPhase=0,this.path=[],this.port=t,this.returnValue=!0,this.srcElement=e,this.target=e,this.timeStamp=h(),this.type=\"statechange\"}function Qt(t,e){this.bubbles=!1,this.cancelBubble=!1,this.cancelable=!1,this.currentTarget=t,this.data=e,this.defaultPrevented=!1,this.eventPhase=0,this.path=[],this.receivedTime=h(),this.returnValue=!0,this.srcElement=t,this.target=t,this.timeStamp=this.receivedTime,this.type=\"midimessage\"}function Kt(t,e){t&&(t.onstatechange&&t.onstatechange(new Ht(t,t)),e.onstatechange&&e.onstatechange(new Ht(t,e)))}function $t(n,r){var i=this,o=!1,e=null,s=null;this.type=\"input\",this.id=r.id,this.name=r.name,this.manufacturer=r.man,this.version=r.ver,Object.defineProperty(this,\"state\",{get:function(){return r.connected?\"connected\":\"disconnected\"},enumerable:!0}),Object.defineProperty(this,\"connection\",{get:function(){return o?r.proxy?\"open\":\"pending\":\"closed\"},enumerable:!0}),Object.defineProperty(this,\"onmidimessage\",{get:function(){return s},set:function(t){t instanceof Function?(s=t,o||i.open()):s=null},enumerable:!0}),Object.defineProperty(this,\"onstatechange\",{get:function(){return e},set:function(t){e=t instanceof Function?t:null},enumerable:!0}),this.open=function(){return new Jt(function(t,e){o?t(i):r.open().then(function(){o||(o=!0,Kt(i,n)),t(i)},function(){e(new Wt(\"InvalidAccessError\",\"Port is not available\",15))})})},this.close=function(){return new Jt(function(t){o&&(o=!1,r.close(),Kt(i,n)),t(i)})},Object.freeze(this)}function Xt(t){for(var e,n;t.length;){for(e=0;e<t.length&&!(t[e]==parseInt(t[e])&&128<=t[e]&&t[e]<=255&&247!=t[e]);e++);if(t.splice(0,e),!t.length)return;if(240==t[0]){for(e=1;e<t.length&&247!=t[e];e++);if(e==t.length)return;return t.splice(0,e+1)}if((n=te(t[0])+1)>t.length)return;for(e=1;e<n&&!(t[e]!=parseInt(t[e])||t[e]<0||128<=t[e]);e++);if(e==n)return t.splice(0,e);t.splice(0,e)}}function Yt(t,e,n,r){var i=this;this.id=t,this.name=e,this.man=n,this.ver=r,this.connected=!0,this.ports=[],this.pending=[],this.proxy=void 0,this.queue=[],this.onmidi=function(t){var e;for(i.queue=i.queue.concat(t.slice()),e=Xt(i.queue);e;e=Xt(i.queue))for(o=0;o<i.ports.length;o++)i.ports[o][0].onmidimessage&&(240!=e[0]||i.ports[o][1])&&i.ports[o][0].onmidimessage(new Qt(i,new Uint8Array(e)))}}function te(t){return 128<=t&&t<=191||224<=t&&t<=239||242==t?2:192<=t&&t<=223||241==t||243==t?1:0}\"function\"!=typeof Jt&&((Jt=function(t){this.executor=t}).prototype.then=function(t,e){\"function\"!=typeof t&&(t=function(){}),\"function\"!=typeof e&&(e=function(){}),this.executor(t,e)}),Yt.prototype.open=function(){var r=this;return new Jt(function(t,e){var n;r.proxy||!r.connected?t():(r.pending.push([t,e]),1==r.pending.length&&at().openMidiIn(r.name).or(function(){for(n=0;n<r.pending.length;n++)r.pending[n][1]();r.pending=[]}).and(function(){for(r.proxy=this,r.proxy.connect(r.onmidi),n=0;n<r.pending.length;n++)r.pending[n][0]();r.pending=[]}))})},Yt.prototype.close=function(){var t;if(this.proxy){for(t=0;t<this.ports.length;t++)if(\"open\"==this.ports[t].connection)return;this.proxy.close(),this.proxy=void 0}},Yt.prototype.disconnect=function(){this.connected=!1,this.proxy&&(this.proxy.close(),this.proxy=void 0)},Yt.prototype.reconnect=function(){var t,e,n=this,r=[];for(this.connected=!0,t=0;t<Gt.length;t++)\"closed\"==(e=Gt[t].inputs.get(this.id)).connection?Kt(e,Gt[t]):r.push([e,Gt[t]]);r.length&&at()._openMidiInNR(n.name).or(function(){for(t=0;t<r.length;t++)r[t][0].close()}).and(function(){for(n.proxy=this,n.proxy.connect(n.onmidi),t=0;t<r.length;t++)Kt(r[t][0],r[t][1])})};var ee=\"Failed to execute 'send' on 'MIDIOutput': \";function ne(r,i){var o=this,s=!1,e=null;this.type=\"output\",this.id=i.id,this.name=i.name,this.manufacturer=i.man,this.version=i.ver,Object.defineProperty(this,\"state\",{get:function(){return i.connected?\"connected\":\"disconnected\"},enumerable:!0}),Object.defineProperty(this,\"connection\",{get:function(){return s?i.proxy?\"open\":\"pending\":\"closed\"},enumerable:!0}),Object.defineProperty(this,\"onstatechange\",{get:function(){return e},set:function(t){e=t instanceof Function?t:null},enumerable:!0}),this.open=function(){return new Jt(function(t,e){s?t(o):i.open().then(function(){s||(s=!0,Kt(o,r)),t(o)},function(){e(new Wt(\"InvalidAccessError\",\"Port is not available\",15))})})},this.close=function(){return new Jt(function(t){s&&(s=!1,o.clear(),i.close(),Kt(o,r)),t(o)})},this.clear=function(){},this.send=function(t,e){if(function(t,e){var n,r,i,o=[];for(n=0;n<t.length;n++)if(t[n]!=parseInt(t[n])||t[n]<0||255<t[n])throw TypeError(ee+t[n]+\" is not a UInt8 value.\");for(n=r=0;n<t.length;n++)if(r){if(127<t[n])throw TypeError(ee+\"Unexpected status byte at index \"+n+\" (\"+t[n]+\").\");i.push(t[n]),r--}else{if(t[n]<128)throw TypeError(ee+\"Running status is not allowed at index \"+n+\" (\"+t[n]+\").\");if(247==t[n])throw TypeError(ee+\"Unexpected end of system exclusive message at index \"+n+\" (\"+t[n]+\").\");if(i=[t[n]],o.push(i),240==t[n]){if(!e)throw new Wt(\"InvalidAccessError\",ee+\"System exclusive messag is not allowed at index \"+n+\" (\"+t[n]+\").\",15);for(r=-1;n<t.length;n++)if(i.push(t[n]),247==t[n]){r=0;break}}else r=te(t[n])}if(r)throw TypeError(ee+\"Message is incomplete\")}(t,r.sysexEnabled),!i.connected)throw new Wt(\"InvalidStateError\",\"Port is not connected\",11);if(s){var n=h();n<e?setTimeout(function(){i.proxy.send(t)},e-n):i.proxy.send(t)}else this.open().then(function(){o.send(t,e)})},Object.freeze(this)}function re(t,e,n,r){this.id=t,this.name=e,this.man=n,this.ver=r,this.connected=!0,this.ports=[],this.pending=[],this.proxy=void 0}function ie(r){this.has=function(t){return r.hasOwnProperty(t)&&r[t].connected},this.keys=function(){try{var t=new Map;for(var e in r)this.has(e)&&t.set(e,this.get(e));return t.keys()}catch(t){}},this.values=function(){try{var t=new Map;for(var e in r)this.has(e)&&t.set(e,this.get(e));return t.values()}catch(t){}},this.entries=function(){try{var t=new Map;for(var e in r)this.has(e)&&t.set(e,this.get(e));return t.entries()}catch(t){}},this.forEach=function(t,e){for(var n in void 0===e&&(e=this),r)this.has(n)&&t.call(e,this.get(n),n,this)},Object.defineProperty(this,\"size\",{get:function(){var t=0;for(var e in r)this.has(e)&&t++;return t},enumerable:!0})}function oe(e,n){this.get=function(t){if(Vt.hasOwnProperty(t)&&Vt[t].connected)return n[t]||(n[t]=new $t(e,Vt[t]),Vt[t].ports.push([n[t],e.sysexEnabled])),n[t]},Object.freeze(this)}function se(e,n){this.get=function(t){if(Zt.hasOwnProperty(t)&&Zt[t].connected)return n[t]||(n[t]=new ne(e,Zt[t]),Zt[t].ports.push([n[t],e.sysexEnabled])),n[t]},Object.freeze(this)}function ue(t){var e,n,r,i;for(e=0;e<t.inputs.added.length;e++)r=t.inputs.added[e],Vt.hasOwnProperty(r.id)||(Vt[r.id]=new Yt(r.id,r.name,r.manufacturer,r.version)),Vt[r.id].reconnect();for(e=0;e<t.outputs.added.length;e++)r=t.outputs.added[e],Zt.hasOwnProperty(r.id)||(Zt[r.id]=new re(r.id,r.name,r.manufacturer,r.version)),Zt[r.id].reconnect();for(e=0;e<t.inputs.removed.length;e++)if(r=t.inputs.removed[e],Vt.hasOwnProperty(r.id)){for(i=[],n=0;n<Gt.length;n++)i.push([Gt[n].inputs.get(r.id),Gt[n]]);for(Vt[r.id].disconnect(),n=0;n<i.length;n++)Kt(i[n][0],i[n][1])}for(e=0;e<t.outputs.removed.length;e++)if(r=t.outputs.removed[e],Zt.hasOwnProperty(r.id)){for(i=[],n=0;n<Gt.length;n++)i.push([Gt[n].outputs.get(r.id),Gt[n]]);for(Zt[r.id].disconnect(),n=0;n<i.length;n++)Kt(i[n][0],i[n][1])}}function ae(t){var e,n,r=null;this.sysexEnabled=t,this.inputs=new oe(this,{}),this.outputs=new se(this,{}),Object.defineProperty(this,\"onstatechange\",{get:function(){return r},set:function(t){r=t instanceof Function?t:null},enumerable:!0}),Object.freeze(this);var i=M._info;for(e=0;e<i.inputs.length;e++)n=i.inputs[e],Vt.hasOwnProperty(n.id)||(Vt[n.id]=new Yt(n.id,n.name,n.manufacturer,n.version));for(e=0;e<i.outputs.length;e++)n=i.outputs[e],Zt.hasOwnProperty(n.id)||(Zt[n.id]=new re(n.id,n.name,n.manufacturer,n.version));Gt.length||at().onChange(ue),Gt.push(this)}return re.prototype.open=function(){var r=this;return new Jt(function(t,e){var n;r.proxy||!r.connected?t():(r.pending.push([t,e]),1==r.pending.length&&at().openMidiOut(r.name).or(function(){for(n=0;n<r.pending.length;n++)r.pending[n][1]();r.pending=[]}).and(function(){for(r.proxy=this,n=0;n<r.pending.length;n++)r.pending[n][0]();r.pending=[]}))})},re.prototype.close=function(){var t;if(this.proxy){for(t=0;t<this.ports.length;t++)if(\"open\"==this.ports[t].connection)return;this.proxy.close(),this.proxy=void 0}},re.prototype.disconnect=function(){this.connected=!1,this.proxy&&(this.proxy.close(),this.proxy=void 0)},re.prototype.reconnect=function(){var t,e,n=this,r=[];for(this.connected=!0,t=0;t<Gt.length;t++)\"closed\"==(e=Gt[t].outputs.get(this.id)).connection?Kt(e,Gt[t]):r.push([e,Gt[t]]);r.length&&at()._openMidiOutNR(n.name).or(function(){for(t=0;t<r.length;t++)r[t][0].close()}).and(function(){for(n.proxy=this,t=0;t<r.length;t++)Kt(r[t][0],r[t][1])})},(oe.prototype=new ie(Vt)).constructor=oe,(se.prototype=new ie(Zt)).constructor=se,at.requestMIDIAccess=function(i){return new Jt(function(n,r){at.JZZ(i).or(function(){}).and(function(){var t=!(!i||!i.sysex);if(t&&!this.info().sysex)r(new Wt(\"SecurityError\",\"Sysex is not allowed\",18));else{var e=new ae(t);n(e)}})})},\"undefined\"==typeof navigator||navigator.requestMIDIAccess||(navigator.requestMIDIAccess=at.requestMIDIAccess),at.close=function(){K._close&&K._close()},at});"
  },
  {
    "path": "test/support/Utils.cjs.js",
    "content": "const UtilsCjs = {\n\n  isNative: function(fn) {\n    return (/\\{\\s*\\[native code\\]\\s*\\}/).test(\"\" + fn);\n  }\n\n};\n\nmodule.exports = UtilsCjs;\n"
  },
  {
    "path": "test/support/Utils.iife.js",
    "content": "function isNative(fn) {\n  return (/\\{\\s*\\[native code\\]\\s*\\}/).test(\"\" + fn);\n}\n\n"
  },
  {
    "path": "typescript/webmidi.d.ts",
    "content": "// Type definitions for {{LIBRARY}} v{{VERSION}}\n// Project: {{HOMEPAGE}}\n// Definitions by: {{AUTHOR_NAME}} <{{AUTHOR_URL}}>\n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n\n// The type definitions for the native 'Navigator' interface and 'WebMidiApi' namespace have been\n// created by Toshiya Nakakura <https://github.com/nakakura>. I am copying them here until I figure\n// out how to rename a TypeScript namespace upon import. The problem is that the original 'WebMidi'\n// namespace collides with the 'WebMidi' object. So, I simply renamed the namespace to 'WebMidiApi'\n// and adjusted the references accordingly.\ninterface Navigator {\n  /**\n   * When invoked, returns a Promise object representing a request for access to MIDI devices on the\n   * user's system.\n   */\n  requestMIDIAccess(options?: WebMidiApi.MIDIOptions): Promise<WebMidiApi.MIDIAccess>;\n}\n\ndeclare namespace WebMidiApi {\n\n  interface MIDIOptions {\n    /**\n     * This member informs the system whether the ability to send and receive system\n     * exclusive messages is requested or allowed on a given MIDIAccess object.\n     */\n    sysex: boolean;\n  }\n\n  /**\n   * This is a maplike interface whose value is a MIDIInput instance and key is its\n   * ID.\n   */\n  type MIDIInputMap = Map<string, MIDIInput>;\n\n  /**\n   * This is a maplike interface whose value is a MIDIOutput instance and key is its\n   * ID.\n   */\n  type MIDIOutputMap = Map<string, MIDIOutput>;\n\n  interface MIDIAccess extends EventTarget {\n    /**\n     * The MIDI input ports available to the system.\n     */\n    inputs: MIDIInputMap;\n\n    /**\n     * The MIDI output ports available to the system.\n     */\n    outputs: MIDIOutputMap;\n\n    /**\n     * The handler called when a new port is connected or an existing port changes the\n     * state attribute.\n     */\n    onstatechange(e: MIDIConnectionEvent): void;\n\n    addEventListener(\n      type: 'statechange',\n      listener: (this: this, e: MIDIConnectionEvent) => any,\n      options?: boolean | AddEventListenerOptions,\n    ): void;\n    addEventListener(\n      type: string,\n      listener: EventListenerOrEventListenerObject,\n      options?: boolean | AddEventListenerOptions,\n    ): void;\n\n    /**\n     * This attribute informs the user whether system exclusive support is enabled on\n     * this MIDIAccess.\n     */\n    sysexEnabled: boolean;\n  }\n\n  type MIDIPortType = 'input' | 'output';\n\n  type MIDIPortDeviceState = 'disconnected' | 'connected';\n\n  type MIDIPortConnectionState = 'open' | 'closed' | 'pending';\n\n  interface MIDIPort extends EventTarget {\n    /**\n     * A unique ID of the port. This can be used by developers to remember ports the\n     * user has chosen for their application.\n     */\n    id: string;\n\n    /**\n     * The manufacturer of the port.\n     */\n    manufacturer?: string | undefined;\n\n    /**\n     * The system name of the port.\n     */\n    name?: string | undefined;\n\n    /**\n     * A descriptor property to distinguish whether the port is an input or an output\n     * port.\n     */\n    type: MIDIPortType;\n\n    /**\n     * The version of the port.\n     */\n    version?: string | undefined;\n\n    /**\n     * The state of the device.\n     */\n    state: MIDIPortDeviceState;\n\n    /**\n     * The state of the connection to the device.\n     */\n    connection: MIDIPortConnectionState;\n\n    /**\n     * The handler called when an existing port changes its state or connection\n     * attributes.\n     */\n    onstatechange(e: MIDIConnectionEvent): void;\n\n    addEventListener(\n      type: 'statechange',\n      listener: (this: this, e: MIDIConnectionEvent) => any,\n      options?: boolean | AddEventListenerOptions,\n    ): void;\n    addEventListener(\n      type: string,\n      listener: EventListenerOrEventListenerObject,\n      options?: boolean | AddEventListenerOptions,\n    ): void;\n\n    /**\n     * Makes the MIDI device corresponding to the MIDIPort explicitly available. Note\n     * that this call is NOT required in order to use the MIDIPort - calling send() on\n     * a MIDIOutput or attaching a MIDIMessageEvent handler on a MIDIInputPort will\n     * cause an implicit open().\n     *\n     * When invoked, this method returns a Promise object representing a request for\n     * access to the given MIDI port on the user's system.\n     */\n    open(): Promise<MIDIPort>;\n\n    /**\n     * Makes the MIDI device corresponding to the MIDIPort\n     * explicitly unavailable (subsequently changing the state from \"open\" to\n     * \"connected\"). Note that successful invocation of this method will result in MIDI\n     * messages no longer being delivered to MIDIMessageEvent handlers on a\n     * MIDIInputPort (although setting a new handler will cause an implicit open()).\n     *\n     * When invoked, this method returns a Promise object representing a request for\n     * access to the given MIDI port on the user's system. When the port has been\n     * closed (and therefore, in exclusive access systems, the port is available to\n     * other applications), the vended Promise is resolved. If the port is\n     * disconnected, the Promise is rejected.\n     */\n    close(): Promise<MIDIPort>;\n  }\n\n  interface MIDIInput extends MIDIPort {\n    type: 'input';\n    onmidimessage(e: MIDIMessageEvent): void;\n\n    addEventListener(\n      type: 'midimessage',\n      listener: (this: this, e: MIDIMessageEvent) => any,\n      options?: boolean | AddEventListenerOptions,\n    ): void;\n    addEventListener(\n      type: 'statechange',\n      listener: (this: this, e: MIDIConnectionEvent) => any,\n      options?: boolean | AddEventListenerOptions,\n    ): void;\n    addEventListener(\n      type: string,\n      listener: EventListenerOrEventListenerObject,\n      options?: boolean | AddEventListenerOptions,\n    ): void;\n  }\n\n  interface MIDIOutput extends MIDIPort {\n    type: 'output';\n\n    /**\n     * Enqueues the message to be sent to the corresponding MIDI port.\n     * @param data The data to be enqueued, with each sequence entry representing a single byte of data.\n     * @param timestamp The time at which to begin sending the data to the port. If timestamp is set\n     * to zero (or another time in the past), the data is to be sent as soon as\n     * possible.\n     */\n    send(data: number[] | Uint8Array, timestamp?: number): void;\n\n    /**\n     * Clears any pending send data that has not yet been sent from the MIDIOutput 's\n     * queue. The implementation will need to ensure the MIDI stream is left in a good\n     * state, so if the output port is in the middle of a sysex message, a sysex\n     * termination byte (0xf7) should be sent.\n     */\n    clear(): void;\n  }\n\n  interface MIDIMessageEvent extends Event {\n    /**\n     * A timestamp specifying when the event occurred.\n     */\n    receivedTime: number;\n\n    /**\n     * A Uint8Array containing the MIDI data bytes of a single MIDI message.\n     */\n    data: Uint8Array;\n  }\n\n  interface MIDIMessageEventInit extends EventInit {\n    /**\n     * A timestamp specifying when the event occurred.\n     */\n    receivedTime: number;\n\n    /**\n     * A Uint8Array containing the MIDI data bytes of a single MIDI message.\n     */\n    data: Uint8Array;\n  }\n\n  interface MIDIConnectionEvent extends Event {\n    /**\n     * The port that has been connected or disconnected.\n     */\n    port: MIDIPort;\n  }\n\n  interface MIDIConnectionEventInit extends EventInit {\n    /**\n     * The port that has been connected or disconnected.\n     */\n    port: MIDIPort;\n  }\n\n}\n\n/**\n * The `EventEmitter` class provides methods to implement the _observable_ design pattern. This\n * pattern allows one to _register_ a function to execute when a specific event is _emitted_ by the\n * emitter.\n *\n * It is intended to be an abstract class meant to be extended by (or mixed into) other objects.\n */\nexport declare class EventEmitter {\n\n  /**\n   * Identifier (Symbol) to use when adding or removing a listener that should be triggered when any\n   * events occur.\n   *\n   * @type {Symbol}\n   */\n  static get ANY_EVENT(): Symbol;\n\n  /**\n   * Creates a new `EventEmitter`object.\n   *\n   * @param {boolean} [eventsSuspended=false] Whether the `EventEmitter` is initially in a suspended\n   * state (i.e. not executing callbacks).\n   */\n  constructor(eventsSuspended?: boolean);\n\n  /**\n   * An object containing a property for each event with at least one registered listener. Each\n   * event property contains an array of all the [`Listener`]{@link Listener} objects registered\n   * for the event.\n   *\n   * @type {Object}\n   * @readonly\n   */\n  eventMap: any;\n\n  /**\n   * Whether or not the execution of callbacks is currently suspended for this emitter.\n   *\n   * @type {boolean}\n   */\n  eventsSuspended: boolean;\n\n  /**\n   * Adds a listener for the specified event. It returns the [`Listener`]{@link Listener} object\n   * that was created and attached to the event.\n   *\n   * To attach a global listener that will be triggered for any events, use\n   * [`EventEmitter.ANY_EVENT`]{@link #ANY_EVENT} as the first parameter. Note that a global\n   * listener will also be triggered by non-registered events.\n   *\n   * @param {string|Symbol} event The event to listen to.\n   * @param {EventEmitterCallback} callback The callback function to execute when the event occurs.\n   * @param {Object} [options={}]\n   * @param {Object} [options.context=this] The value of `this` in the callback function.\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus executed first.\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   * @param {number} [options.remaining=Infinity] The number of times after which the callback\n   * should automatically be removed.\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`]{@link Listener#arguments}\n   * property of the [`Listener`]{@link Listener} object and can be retrieved or modified as\n   * desired.\n   *\n   * @returns {Listener} The newly created [`Listener`]{@link Listener} object (typical) or an array\n   * of [`Listener`]{@link Listener} objects.\n   *\n   * @throws {TypeError} The `event` parameter must be a string or\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}.\n   * @throws {TypeError} The `callback` parameter must be a function.\n   */\n  addListener(event: string | Symbol, callback: EventEmitterCallback, options?: {\n    context?: any;\n    prepend?: boolean;\n    duration?: number;\n    remaining?: number;\n    arguments?: any[];\n  }): Listener | Listener[];\n\n  /**\n   * Adds a one-time listener for the specified event. The listener will be executed once and then\n   * destroyed. It returns the [`Listener`]{@link Listener} object that was created and attached\n   * to the event.\n   *\n   * To attach a global listener that will be triggered for any events, use\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the first parameter. Note that a\n   * global listener will also be triggered by non-registered events.\n   *\n   * @param {string|Symbol} event The event to listen to\n   * @param {EventEmitterCallback} callback The callback function to execute when the event occurs\n   * @param {Object} [options={}]\n   * @param {Object} [options.context=this] The context to invoke the callback function in.\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus executed first.\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`]{@link Listener#arguments}\n   * property of the [`Listener`]{@link Listener} object and can be retrieved or modified as\n   * desired.\n   *\n   * @returns {Listener} The newly created [`Listener`]{@link Listener} object (typical) or an array\n   * of [`Listener`]{@link Listener} objects.\n   *\n   * @throws {TypeError} The `event` parameter must be a string or\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}.\n   * @throws {TypeError} The `callback` parameter must be a function.\n   */\n  addOneTimeListener(event: string | Symbol, callback: EventEmitterCallback, options?: {\n    context?: any;\n    prepend?: boolean;\n    duration?: number;\n    arguments?: any[];\n  }): Listener | Listener[];\n\n  /**\n   * Returns `true` if the specified event has at least one registered listener. If no event is\n   * specified, the method returns `true` if any event has at least one listener registered (this\n   * includes global listeners registered to\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}).\n   *\n   * Note: to specifically check for global listeners added with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}, use\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the parameter.\n   *\n   * @param {string|Symbol} [event=(any event)] The event to check\n   * @param {function|Listener} [callback=(any callback)] The actual function that was added to the\n   * event or the {@link Listener} object returned by `addListener()`.\n   * @returns {boolean}\n   */\n  hasListener(event?: string | Symbol, callback?: Function | Listener): boolean;\n\n  /**\n   * An array of all the unique event names for which the emitter has at least one registered\n   * listener.\n   *\n   * Note: this excludes global events registered with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} because they are not tied to a\n   * specific event.\n   *\n   * @type {string[]}\n   * @readonly\n   */\n  get eventNames(): string[];\n\n  /**\n   * Returns an array of all the [`Listener`]{@link Listener} objects that have been registered for\n   * a specific event.\n   *\n   * Please note that global events (those added with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}) are not returned for \"regular\"\n   * events. To get the list of global listeners, specifically use\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the parameter.\n   *\n   * @param {string|Symbol} event The event to get listeners for.\n   * @returns {Listener[]} An array of [`Listener`]{@link Listener} objects.\n   */\n  getListeners(event: string | Symbol): Listener[];\n\n  /**\n   * Suspends execution of all callbacks functions registered for the specified event type.\n   *\n   * You can suspend execution of callbacks registered with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} by passing\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} to `suspendEvent()`. Beware that this\n   * will not suspend all callbacks but only those registered with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}. While this may seem counter-intuitive\n   * at first glance, it allows the selective suspension of global listeners while leaving other\n   * listeners alone. If you truly want to suspends all callbacks for a specific\n   * [`EventEmitter`]{@link EventEmitter}, simply set its `eventsSuspended` property to `true`.\n   *\n   * @param {string|Symbol} event The event name (or `EventEmitter.ANY_EVENT`) for which to suspend\n   * execution of all callback functions.\n   */\n  suspendEvent(event: string | Symbol): void;\n\n  /**\n   * Resumes execution of all suspended callback functions registered for the specified event type.\n   *\n   * You can resume execution of callbacks registered with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} by passing\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} to `unsuspendEvent()`. Beware that\n   * this will not resume all callbacks but only those registered with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}. While this may seem\n   * counter-intuitive, it allows the selective unsuspension of global listeners while leaving other\n   * callbacks alone.\n   *\n   * @param {string|Symbol} event The event name (or `EventEmitter.ANY_EVENT`) for which to resume\n   * execution of all callback functions.\n   */\n  unsuspendEvent(event: string | Symbol): void;\n\n  /**\n   * Returns the number of listeners registered for a specific event.\n   *\n   * Please note that global events (those added with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}) do not count towards the remaining\n   * number for a \"regular\" event. To get the number of global listeners, specifically use\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the parameter.\n   *\n   * @param {string|Symbol} event The event which is usually a string but can also be the special\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} symbol.\n   * @returns {number} An integer representing the number of listeners registered for the specified\n   * event.\n   */\n  getListenerCount(event: string | Symbol): number;\n\n  /**\n   * Executes the callback function of all the [`Listener`]{@link Listener} objects registered for\n   * a given event. The callback functions are passed the additional arguments passed to `emit()`\n   * (if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object (if any).\n   *\n   * If the [`eventsSuspended`]{@link #eventsSuspended} property is `true` or the\n   * [`Listener.suspended`]{@link Listener#suspended} property is `true`, the callback functions\n   * will not be executed.\n   *\n   * This function returns an array containing the return values of each of the callbacks.\n   *\n   * It should be noted that the regular listeners are triggered first followed by the global\n   * listeners (those added with [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}).\n   *\n   * @param {string} event The event\n   * @param {...*} args Arbitrary number of arguments to pass along to the callback functions\n   *\n   * @returns {Array} An array containing the return value of each of the executed listener\n   * functions.\n   *\n   * @throws {TypeError} The `event` parameter must be a string.\n   */\n  emit(event: string, ...args: any[]): any[];\n\n  /**\n   * Removes all the listeners that match the specified criterias. If no parameters are passed, all\n   * listeners will be removed. If only the `event` parameter is passed, all listeners for that\n   * event will be removed. You can remove global listeners by using\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the first parameter.\n   *\n   * To use more granular options, you must at least define the `event`. Then, you can specify the\n   * callback to match or one or more of the additional options.\n   *\n   * @param {string | Symbol} [event] The event name.\n   * @param {EventEmitterCallback} [callback] Only remove the listeners that match this exact\n   * callback function.\n   * @param {Object} [options]\n   * @param {*} [options.context] Only remove the listeners that have this exact context.\n   * @param {number} [options.remaining] Only remove the listener if it has exactly that many\n   * remaining times to be executed.\n   */\n  removeListener(\n    event?: string | Symbol,\n    callback?: EventEmitterCallback,\n    options?: {\n      context?: any;\n      remaining?: number;\n    }\n  ): void;\n\n  /**\n   * The `waitFor()` method is an async function which returns a promise. The promise is fulfilled\n   * when the specified event occurs. The event can be a regular event or\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} (if you want to resolve as soon as any\n   * event is emitted).\n   *\n   * If the `duration` option is set, the promise will only be fulfilled if the event is emitted\n   * within the specified duration. If the event has not been fulfilled after the specified\n   * duration, the promise is rejected. This makes it super easy to wait for an event and timeout\n   * after a certain time if the event is not triggered.\n   *\n   * @param {string|Symbol} event The event to wait for\n   * @param {Object} [options={}]\n   * @param {number} [options.duration=Infinity] The number of milliseconds to wait before the\n   * promise is automatically rejected.\n   */\n  waitFor(event: string | Symbol, options?: {\n    duration?: number;\n  }): Promise<any>;\n\n  /**\n   * The number of unique events that have registered listeners.\n   *\n   * Note: this excludes global events registered with\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} because they are not tied to a\n   * specific event.\n   *\n   * @type {number}\n   * @readonly\n   */\n  get eventCount(): number;\n\n}\n\n/**\n * The `Listener` class represents a single event listener object. Such objects keep all relevant\n * contextual information such as the event being listened to, the object the listener was attached\n * to, the callback function and so on.\n *\n */\nexport declare class Listener {\n\n  /**\n   * Creates a new `Listener` object\n   *\n   * @param {string|Symbol} event The event being listened to\n   * @param {EventEmitter} target The [`EventEmitter`]{@link EventEmitter} object that the listener\n   * is attached to.\n   * @param {EventEmitterCallback} callback The function to call when the listener is triggered\n   * @param {Object} [options={}]\n   * @param {Object} [options.context=target] The context to invoke the listener in (a.k.a. the\n   * value of `this` inside the callback function).\n   * @param {number} [options.remaining=Infinity] The remaining number of times after which the\n   * callback should automatically be removed.\n   * @param {array} [options.arguments] An array of arguments that will be passed separately to the\n   * callback function upon execution. The array is stored in the [`arguments`]{@link #arguments}\n   * property and can be retrieved or modified as desired.\n   *\n   * @throws {TypeError} The `event` parameter must be a string or\n   * [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}.\n   * @throws {ReferenceError} The `target` parameter is mandatory.\n   * @throws {TypeError} The `callback` must be a function.\n   */\n  constructor(event: string | Symbol, target: EventEmitter, callback: EventEmitterCallback, options?: {\n    context?: any;\n    remaining?: number;\n    arguments?: any[];\n  }, ...args: any[]);\n\n  /**\n   * An array of arguments to pass to the callback function upon execution.\n   * @type {array}\n   */\n  arguments: any[];\n\n  /**\n   * The callback function to execute.\n   * @type {Function}\n   */\n  callback: Function;\n\n  /**\n   * The context to execute the callback function in (a.k.a. the value of `this` inside the\n   * callback function)\n   * @type {Object}\n   */\n  context: any;\n\n  /**\n   * The number of times the listener function was executed.\n   * @type {number}\n   */\n  count: number;\n\n  /**\n   * The event name.\n   * @type {string}\n   */\n  event: string;\n\n  /**\n   * The remaining number of times after which the callback should automatically be removed.\n   * @type {number}\n   */\n  remaining: number;\n\n  /**\n   * Whether this listener is currently suspended or not.\n   * @type {boolean}\n   */\n  suspended: boolean;\n\n  /**\n   * The object that the event is attached to (or that emitted the event).\n   * @type {EventEmitter}\n   */\n  target: EventEmitter;\n\n  /**\n   * Removes the listener from its target.\n   */\n  remove(): void;\n\n}\n\n/**\n * The `Enumerations` class contains enumerations and arrays of elements used throughout the\n * library. All properties are static and should be referenced using the class name. For example:\n * `Enumerations.CHANNEL_MESSAGES`.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Enumerations {\n\n  /**\n   * Enumeration of all MIDI channel message names and their associated 4-bit numerical value:\n   *\n   * | Message Name        | Hexadecimal | Decimal |\n   * |---------------------|-------------|---------|\n   * | `noteoff`           | 0x8         | 8       |\n   * | `noteon`            | 0x9         | 9       |\n   * | `keyaftertouch`     | 0xA         | 10      |\n   * | `controlchange`     | 0xB         | 11      |\n   * | `programchange`     | 0xC         | 12      |\n   * | `channelaftertouch` | 0xD         | 13      |\n   * | `pitchbend`         | 0xE         | 14      |\n   *\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @since 3.1\n   * @static\n   */\n  static get CHANNEL_MESSAGES(): {\n    noteoff: number;\n    noteon: number;\n    keyaftertouch: number;\n    controlchange: number;\n    programchange: number;\n    channelaftertouch: number;\n    pitchbend: number;\n  };\n\n  /**\n   * A simple array of the 16 valid MIDI channel numbers (`1` to `16`):\n   *\n   * @type {number[]}\n   * @readonly\n   * @static\n   */\n  static get CHANNEL_NUMBERS(): number[];\n\n  /**\n   * Enumeration of all MIDI channel mode message names and their associated numerical value:\n   *\n   *\n   * | Message Name          | Hexadecimal | Decimal |\n   * |-----------------------|-------------|---------|\n   * | `allsoundoff`         | 0x78        | 120     |\n   * | `resetallcontrollers` | 0x79        | 121     |\n   * | `localcontrol`        | 0x7A        | 122     |\n   * | `allnotesoff`         | 0x7B        | 123     |\n   * | `omnimodeoff`         | 0x7C        | 124     |\n   * | `omnimodeon`          | 0x7D        | 125     |\n   * | `monomodeon`          | 0x7E        | 126     |\n   * | `polymodeon`          | 0x7F        | 127     |\n   *\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @static\n   */\n  static get CHANNEL_MODE_MESSAGES(): {\n    allsoundoff: number;\n    resetallcontrollers: number;\n    localcontrol: number;\n    allnotesoff: number;\n    omnimodeoff: number;\n    omnimodeon: number;\n    monomodeon: number;\n    polymodeon: number;\n  };\n\n  /**\n   * An array of objects, ordered by control number, describing control change messages. Each object\n   * in the array can have up to 4 properties:\n   *\n   *  * `number`: MIDI control number (0-127);\n   *  * `event`: name of emitted event (eg: `bankselectcoarse`, `choruslevel`, etc) that can be\n   *  listened to;\n   *  * `description`: user-friendly description of the controller's purpose;\n   *  * `position`: whether this controller's value should be considered an `msb` or `lsb` (if\n   *  appropriate).\n   *\n   * Not all controllers have a predefined function. For those that don't, name is the word\n   * \"controller\" followed by the number (e.g. `controller112`).\n   *\n   * | Event name                     | Control Number |\n   * |--------------------------------|----------------|\n   * | `bankselectcoarse`             | 0              |\n   * | `modulationwheelcoarse`        | 1              |\n   * | `breathcontrollercoarse`       | 2              |\n   * | `controller3`                  | 3              |\n   * | `footcontrollercoarse`         | 4              |\n   * | `portamentotimecoarse`         | 5              |\n   * | `dataentrycoarse`              | 6              |\n   * | `volumecoarse`                 | 7              |\n   * | `balancecoarse`                | 8              |\n   * | `controller9`                  | 9              |\n   * | `pancoarse`                    | 10             |\n   * | `expressioncoarse`             | 11             |\n   * | `effectcontrol1coarse`         | 12             |\n   * | `effectcontrol2coarse`         | 13             |\n   * | `controller14`                 | 14             |\n   * | `controller15`                 | 15             |\n   * | `generalpurposecontroller1`    | 16             |\n   * | `generalpurposecontroller2`    | 17             |\n   * | `generalpurposecontroller3`    | 18             |\n   * | `generalpurposecontroller4`    | 19             |\n   * | `controller20`                 | 20             |\n   * | `controller21`                 | 21             |\n   * | `controller22`                 | 22             |\n   * | `controller23`                 | 23             |\n   * | `controller24`                 | 24             |\n   * | `controller25`                 | 25             |\n   * | `controller26`                 | 26             |\n   * | `controller27`                 | 27             |\n   * | `controller28`                 | 28             |\n   * | `controller29`                 | 29             |\n   * | `controller30`                 | 30             |\n   * | `controller31`                 | 31             |\n   * | `bankselectfine`               | 32             |\n   * | `modulationwheelfine`          | 33             |\n   * | `breathcontrollerfine`         | 34             |\n   * | `controller35`                 | 35             |\n   * | `footcontrollerfine`           | 36             |\n   * | `portamentotimefine`           | 37             |\n   * | `dataentryfine`                | 38             |\n   * | `channelvolumefine`            | 39             |\n   * | `balancefine`                  | 40             |\n   * | `controller41`                 | 41             |\n   * | `panfine`                      | 42             |\n   * | `expressionfine`               | 43             |\n   * | `effectcontrol1fine`           | 44             |\n   * | `effectcontrol2fine`           | 45             |\n   * | `controller46`                 | 46             |\n   * | `controller47`                 | 47             |\n   * | `controller48`                 | 48             |\n   * | `controller49`                 | 49             |\n   * | `controller50`                 | 50             |\n   * | `controller51`                 | 51             |\n   * | `controller52`                 | 52             |\n   * | `controller53`                 | 53             |\n   * | `controller54`                 | 54             |\n   * | `controller55`                 | 55             |\n   * | `controller56`                 | 56             |\n   * | `controller57`                 | 57             |\n   * | `controller58`                 | 58             |\n   * | `controller59`                 | 59             |\n   * | `controller60`                 | 60             |\n   * | `controller61`                 | 61             |\n   * | `controller62`                 | 62             |\n   * | `controller63`                 | 63             |\n   * | `damperpedal`                  | 64             |\n   * | `portamento`                   | 65             |\n   * | `sostenuto`                    | 66             |\n   * | `softpedal`                    | 67             |\n   * | `legatopedal`                  | 68             |\n   * | `hold2`                        | 69             |\n   * | `soundvariation`               | 70             |\n   * | `resonance`                    | 71             |\n   * | `releasetime`                  | 72             |\n   * | `attacktime`                   | 73             |\n   * | `brightness`                   | 74             |\n   * | `decaytime`                    | 75             |\n   * | `vibratorate`                  | 76             |\n   * | `vibratodepth`                 | 77             |\n   * | `vibratodelay`                 | 78             |\n   * | `controller79`                 | 79             |\n   * | `generalpurposecontroller5`    | 80             |\n   * | `generalpurposecontroller6`    | 81             |\n   * | `generalpurposecontroller7`    | 82             |\n   * | `generalpurposecontroller8`    | 83             |\n   * | `portamentocontrol`            | 84             |\n   * | `controller85`                 | 85             |\n   * | `controller86`                 | 86             |\n   * | `controller87`                 | 87             |\n   * | `highresolutionvelocityprefix` | 88             |\n   * | `controller89`                 | 89             |\n   * | `controller90`                 | 90             |\n   * | `effect1depth`                 | 91             |\n   * | `effect2depth`                 | 92             |\n   * | `effect3depth`                 | 93             |\n   * | `effect4depth`                 | 94             |\n   * | `effect5depth`                 | 95             |\n   * | `dataincrement`                | 96             |\n   * | `datadecrement`                | 97             |\n   * | `nonregisteredparameterfine`   | 98             |\n   * | `nonregisteredparametercoarse` | 99             |\n   * | `nonregisteredparameterfine`   | 100            |\n   * | `registeredparametercoarse`    | 101            |\n   * | `controller102`                | 102            |\n   * | `controller103`                | 103            |\n   * | `controller104`                | 104            |\n   * | `controller105`                | 105            |\n   * | `controller106`                | 106            |\n   * | `controller107`                | 107            |\n   * | `controller108`                | 108            |\n   * | `controller109`                | 109            |\n   * | `controller110`                | 110            |\n   * | `controller111`                | 111            |\n   * | `controller112`                | 112            |\n   * | `controller113`                | 113            |\n   * | `controller114`                | 114            |\n   * | `controller115`                | 115            |\n   * | `controller116`                | 116            |\n   * | `controller117`                | 117            |\n   * | `controller118`                | 118            |\n   * | `controller119`                | 119            |\n   * | `allsoundoff`                  | 120            |\n   * | `resetallcontrollers`          | 121            |\n   * | `localcontrol`                 | 122            |\n   * | `allnotesoff`                  | 123            |\n   * | `omnimodeoff`                  | 124            |\n   * | `omnimodeon`                   | 125            |\n   * | `monomodeon`                   | 126            |\n   * | `polymodeon`                   | 127            |\n   *\n   * @type {Object[]}\n   * @readonly\n   * @static\n   * @since 3.1\n   */\n  static get CONTROL_CHANGE_MESSAGES():object[]\n\n  /**\n   * Enumeration of all MIDI registered parameters and their associated pair of numerical values.\n   * MIDI registered parameters extend the original list of control change messages. Currently,\n   * there are only a limited number of them:\n   *\n   *\n   * | Control Function             | [LSB, MSB]   |\n   * |------------------------------|--------------|\n   * | `pitchbendrange`             | [0x00, 0x00] |\n   * | `channelfinetuning`          | [0x00, 0x01] |\n   * | `channelcoarsetuning`        | [0x00, 0x02] |\n   * | `tuningprogram`              | [0x00, 0x03] |\n   * | `tuningbank`                 | [0x00, 0x04] |\n   * | `modulationrange`            | [0x00, 0x05] |\n   * | `azimuthangle`               | [0x3D, 0x00] |\n   * | `elevationangle`             | [0x3D, 0x01] |\n   * | `gain`                       | [0x3D, 0x02] |\n   * | `distanceratio`              | [0x3D, 0x03] |\n   * | `maximumdistance`            | [0x3D, 0x04] |\n   * | `maximumdistancegain`        | [0x3D, 0x05] |\n   * | `referencedistanceratio`     | [0x3D, 0x06] |\n   * | `panspreadangle`             | [0x3D, 0x07] |\n   * | `rollangle`                  | [0x3D, 0x08] |\n   *\n   * @enum {Object.<string, number[]>}\n   * @readonly\n   * @static\n   */\n  static get REGISTERED_PARAMETERS(): {\n    pitchbendrange: number[];\n    channelfinetuning: number[];\n    channelcoarsetuning: number[];\n    tuningprogram: number[];\n    tuningbank: number[];\n    modulationrange: number[];\n    azimuthangle: number[];\n    elevationangle: number[];\n    gain: number[];\n    distanceratio: number[];\n    maximumdistance: number[];\n    maximumdistancegain: number[];\n    referencedistanceratio: number[];\n    panspreadangle: number[];\n    rollangle: number[];\n  };\n\n  /**\n   * Enumeration of all valid MIDI system messages and matching numerical values. WebMidi.js also\n   * uses two additional custom messages.\n   *\n   * **System Common Messages**\n   *\n   * | Function               | Hexadecimal | Decimal |\n   * |------------------------|-------------|---------|\n   * | `sysex`                | 0xF0        |  240    |\n   * | `timecode`             | 0xF1        |  241    |\n   * | `songposition`         | 0xF2        |  242    |\n   * | `songselect`           | 0xF3        |  243    |\n   * | `tunerequest`          | 0xF6        |  246    |\n   * | `sysexend`             | 0xF7        |  247    |\n   *\n   * The `sysexend` message is never actually received. It simply ends a sysex stream.\n   *\n   * **System Real-Time Messages**\n   *\n   * | Function               | Hexadecimal | Decimal |\n   * |------------------------|-------------|---------|\n   * | `clock`                | 0xF8        |  248    |\n   * | `start`                | 0xFA        |  250    |\n   * | `continue`             | 0xFB        |  251    |\n   * | `stop`                 | 0xFC        |  252    |\n   * | `activesensing`        | 0xFE        |  254    |\n   * | `reset`                | 0xFF        |  255    |\n   *\n   * Values 249 and 253 are relayed by the\n   * [Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API) but they do not\n   * serve any specific purpose. The\n   * [MIDI 1.0 spec](https://www.midi.org/specifications/item/table-1-summary-of-midi-message)\n   * simply states that they are undefined/reserved.\n   *\n   * **Custom WebMidi.js Messages**\n   *\n   * These two messages are mostly for internal use. They are not MIDI messages and cannot be sent\n   * or forwarded.\n   *\n   * | Function               | Hexadecimal | Decimal |\n   * |------------------------|-------------|---------|\n   * | `midimessage`          |             |  0      |\n   * | `unknownsystemmessage` |             |  -1     |\n   *\n   * @enum {Object.<string, number>}\n   * @readonly\n   * @static\n   */\n  static get SYSTEM_MESSAGES(): {\n    sysex: number;\n    timecode: number;\n    songposition: number;\n    songselect: number;\n    tunerequest: number;\n    tuningrequest: number;\n    sysexend: number;\n    clock: number;\n    start: number;\n    continue: number;\n    stop: number;\n    activesensing: number;\n    reset: number;\n    midimessage: number;\n    unknownsystemmessage: number;\n  };\n\n  /**\n   * Array of channel-specific event names that can be listened for. This includes channel mode\n   * events and RPN/NRPN events.\n   *\n   * @type {string[]}\n   * @readonly\n   */\n  static get CHANNEL_EVENTS(): string[];\n\n}\n\n/**\n * The `Forwarder` class allows the forwarding of MIDI messages to predetermined outputs. When you\n * call its [`forward()`](#forward) method, it will send the specified [`Message`](Message) object\n * to all the outputs listed in its [`destinations`](#destinations) property.\n *\n * If specific channels or message types have been defined in the [`channels`](#channels) or\n * [`types`](#types) properties, only messages matching the channels/types will be forwarded.\n *\n * While it can be manually instantiated, you are more likely to come across a `Forwarder` object as\n * the return value of the [`Input.addForwarder()`](Input#addForwarder) method.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Forwarder {\n\n  /**\n   * Creates a `Forwarder` object.\n   *\n   * @param {Output|Output[]} [destinations=\\[\\]] An [`Output`](Output) object, or an array of such\n   * objects, to forward the message to.\n   *\n   * @param {object} [options={}]\n   * @param {string|string[]} [options.types=(all messages)] A MIDI message type or an array of such\n   * types (`\"noteon\"`, `\"controlchange\"`, etc.), that the specified message must match in order to\n   * be forwarded. If this option is not specified, all types of messages will be forwarded. Valid\n   * messages are the ones found in either\n   * [`SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES)\n   * or [`CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * A MIDI channel number or an array of channel numbers that the message must match in order to be\n   * forwarded. By default all MIDI channels are included (`1` to `16`).\n   */\n  constructor(destinations?: Output | Output[], options?: {\n    types?: string | string[];\n    channels?: number | number[];\n  });\n\n  /**\n   * An array of [`Output`](Output) objects to forward the message to.\n   * @type {Output[]}\n   */\n  destinations: Output[];\n\n  /**\n   * An array of message types (`\"noteon\"`, `\"controlchange\"`, etc.) that must be matched in order\n   * for messages to be forwarded. By default, this array includes all\n   * [`Enumerations.SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) and\n   * [`Enumerations.CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).\n   * @type {string[]}\n   */\n  types: string[];\n\n  /**\n   * An array of MIDI channel numbers that the message must match in order to be forwarded. By\n   * default, this array includes all MIDI channels (`1` to `16`).\n   * @type {number[]}\n   */\n  channels: number[];\n\n  /**\n   * Indicates whether message forwarding is currently suspended or not in this forwarder.\n   * @type {boolean}\n   */\n  suspended: boolean;\n\n  /**\n   * Sends the specified message to the forwarder's destination(s) if it matches the specified\n   * type(s) and channel(s).\n   *\n   * @param {Message} message The [`Message`](Message) object to forward.\n   */\n  forward(message: Message): void;\n\n}\n\n/**\n * The `Input` class represents a single MIDI input port. This object is automatically instantiated\n * by the library according to the host's MIDI subsystem and does not need to be directly\n * instantiated. Instead, you can access all `Input` objects by referring to the\n * [`WebMidi.inputs`](WebMidi#inputs) array. You can also retrieve inputs by using methods such as\n * [`WebMidi.getInputByName()`](WebMidi#getInputByName) and\n * [`WebMidi.getInputById()`](WebMidi#getInputById).\n *\n * Note that a single MIDI device may expose several inputs and/or outputs.\n *\n * **Important**: the `Input` class does not directly fire channel-specific MIDI messages\n * (such as [`noteon`](InputChannel#event:noteon) or\n * [`controlchange`](InputChannel#event:controlchange), etc.). The [`InputChannel`](InputChannel)\n * object does that. However, you can still use the\n * [`Input.addListener()`](#addListener) method to listen to channel-specific events on multiple\n * [`InputChannel`](InputChannel) objects at once.\n *\n * @fires Input#opened\n * @fires Input#disconnected\n * @fires Input#closed\n * @fires Input#midimessage\n *\n * @fires Input#sysex\n * @fires Input#timecode\n * @fires Input#songposition\n * @fires Input#songselect\n * @fires Input#tunerequest\n * @fires Input#clock\n * @fires Input#start\n * @fires Input#continue\n * @fires Input#stop\n * @fires Input#activesensing\n * @fires Input#reset\n *\n * @fires Input#unknownmidimessage\n *\n * @extends EventEmitter\n * @license Apache-2.0\n */\nexport class Input extends EventEmitter {\n\n  /**\n   * Creates an `Input` object.\n   *\n   * @param {WebMidiApi.MIDIInput} midiInput [`MIDIInput`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIInput)\n   * object as provided by the MIDI subsystem (Web MIDI API).\n   */\n  constructor(midiInput: WebMidiApi.MIDIInput);\n\n  private _forwarders;\n  private _midiInput;\n  private _octaveOffset;\n  private _onMidiMessage;\n  private _onStateChange;\n  private _parseEvent;\n\n  /**\n   * Array containing the 16 [`InputChannel`](InputChannel) objects available for this `Input`. The\n   * channels are numbered 1 through 16.\n   *\n   * @type {InputChannel[]}\n   */\n  channels: InputChannel[];\n\n  /**\n   * Adds a forwarder that will forward all incoming MIDI messages matching the criteria to the\n   * specified [`Output`](Output) destination(s). This is akin to the hardware MIDI THRU port, with\n   * the added benefit of being able to filter which data is forwarded.\n   *\n   * @param {Output|Output[]|Forwarder} output An [`Output`](Output) object, a [`Forwarder`](Forwarder)\n   * object or an array of such objects, to forward messages to.\n   * @param {object} [options={}]\n   * @param {string|string[]} [options.types=(all messages)] A message type, or an array of such\n   * types (`noteon`, `controlchange`, etc.), that the message type must match in order to be\n   * forwarded. If this option is not specified, all types of messages will be forwarded. Valid\n   * messages are the ones found in either\n   * [`SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) or\n   * [`CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * A MIDI channel number or an array of channel numbers that the message must match in order to be\n   * forwarded. By default all MIDI channels are included (`1` to `16`).\n   *\n   * @returns {Forwarder} The [`Forwarder`](Forwarder) object created to handle the forwarding. This\n   * is useful if you wish to manipulate or remove the [`Forwarder`](Forwarder) later on.\n   */\n  addForwarder(output: Output | Output[] | Forwarder, options?: {\n    types?: string | string[];\n    channels?: number | number[];\n  }): Forwarder;\n\n  // @ts-ignore\n  /**\n   * Adds an event listener that will trigger a function callback when the specified event is\n   * dispatched. The event usually is **input-wide** but can also be **channel-specific**.\n   *\n   * Input-wide events do not target a specific MIDI channel so it makes sense to listen for them\n   * at the `Input` level and not at the [`InputChannel`](InputChannel) level. Channel-specific\n   * events target a specific channel. Usually, in this case, you would add the listener to the\n   * [`InputChannel`](InputChannel) object. However, as a convenience, you can also listen to\n   * channel-specific events directly on an `Input`. This allows you to react to a channel-specific\n   * event no matter which channel it actually came through.\n   *\n   * When listening for an event, you simply need to specify the event name and the function to\n   * execute:\n   *\n   * ```javascript\n   * const listener = WebMidi.inputs[0].addListener(\"midimessage\", e => {\n   *   console.log(e);\n   * });\n   * ```\n   *\n   * Calling the function with an input-wide event (such as\n   * [`\"midimessage\"`]{@link #event:midimessage}), will return the [`Listener`](Listener) object\n   * that was created.\n   *\n   * If you call the function with a channel-specific event (such as\n   * [`\"noteon\"`]{@link InputChannel#event:noteon}), it will return an array of all\n   * [`Listener`](Listener) objects that were created (one for each channel):\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addListener(\"noteon\", someFunction);\n   * ```\n   *\n   * You can also specify which channels you want to add the listener to:\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addListener(\"noteon\", someFunction, {channels: [1, 2, 3]});\n   * ```\n   *\n   * In this case, `listeners` is an array containing 3 [`Listener`](Listener) objects.\n   *\n   * Note that, when adding channel-specific listeners, it is the [`InputChannel`](InputChannel)\n   * instance that actually gets a listener added and not the `Input` instance. You can check that\n   * by calling [`InputChannel.hasListener()`](InputChannel#hasListener()).\n   *\n   * There are 8 families of events you can listen to:\n   *\n   * 1. **MIDI System Common** Events (input-wide)\n   *\n   *    * [`songposition`]{@link Input#event:songposition}\n   *    * [`songselect`]{@link Input#event:songselect}\n   *    * [`sysex`]{@link Input#event:sysex}\n   *    * [`timecode`]{@link Input#event:timecode}\n   *    * [`tunerequest`]{@link Input#event:tunerequest}\n   *\n   * 2. **MIDI System Real-Time** Events (input-wide)\n   *\n   *    * [`clock`]{@link Input#event:clock}\n   *    * [`start`]{@link Input#event:start}\n   *    * [`continue`]{@link Input#event:continue}\n   *    * [`stop`]{@link Input#event:stop}\n   *    * [`activesensing`]{@link Input#event:activesensing}\n   *    * [`reset`]{@link Input#event:reset}\n   *\n   * 3. **State Change** Events (input-wide)\n   *\n   *    * [`opened`]{@link Input#event:opened}\n   *    * [`closed`]{@link Input#event:closed}\n   *    * [`disconnected`]{@link Input#event:disconnected}\n   *\n   * 4. **Catch-All** Events (input-wide)\n   *\n   *    * [`midimessage`]{@link Input#event:midimessage}\n   *    * [`unknownmidimessage`]{@link Input#event:unknownmidimessage}\n   *\n   * 5. **Channel Voice** Events (channel-specific)\n   *\n   *    * [`channelaftertouch`]{@link InputChannel#event:channelaftertouch}\n   *    * [`controlchange`]{@link InputChannel#event:controlchange}\n   *      * [`controlchange-controller0`]{@link InputChannel#event:controlchange-controller0}\n   *      * [`controlchange-controller1`]{@link InputChannel#event:controlchange-controller1}\n   *      * [`controlchange-controller2`]{@link InputChannel#event:controlchange-controller2}\n   *      * (...)\n   *      * [`controlchange-controller127`]{@link InputChannel#event:controlchange-controller127}\n   *    * [`keyaftertouch`]{@link InputChannel#event:keyaftertouch}\n   *    * [`noteoff`]{@link InputChannel#event:noteoff}\n   *    * [`noteon`]{@link InputChannel#event:noteon}\n   *    * [`pitchbend`]{@link InputChannel#event:pitchbend}\n   *    * [`programchange`]{@link InputChannel#event:programchange}\n   *\n   *    Note: you can listen for a specific control change message by using an event name like this:\n   *    `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   *    etc.\n   *\n   * 6. **Channel Mode** Events (channel-specific)\n   *\n   *    * [`allnotesoff`]{@link InputChannel#event:allnotesoff}\n   *    * [`allsoundoff`]{@link InputChannel#event:allsoundoff}\n   *    * [`localcontrol`]{@link InputChannel#event:localcontrol}\n   *    * [`monomode`]{@link InputChannel#event:monomode}\n   *    * [`omnimode`]{@link InputChannel#event:omnimode}\n   *    * [`resetallcontrollers`]{@link InputChannel#event:resetallcontrollers}\n   *\n   * 7. **NRPN** Events (channel-specific)\n   *\n   *    * [`nrpn`]{@link InputChannel#event:nrpn}\n   *    * [`nrpn-dataentrycoarse`]{@link InputChannel#event:nrpn-dataentrycoarse}\n   *    * [`nrpn-dataentryfine`]{@link InputChannel#event:nrpn-dataentryfine}\n   *    * [`nrpn-dataincrement`]{@link InputChannel#event:nrpn-dataincrement}\n   *    * [`nrpn-datadecrement`]{@link InputChannel#event:nrpn-datadecrement}\n   *\n   * 8. **RPN** Events (channel-specific)\n   *\n   *    * [`rpn`]{@link InputChannel#event:rpn}\n   *    * [`rpn-dataentrycoarse`]{@link InputChannel#event:rpn-dataentrycoarse}\n   *    * [`rpn-dataentryfine`]{@link InputChannel#event:rpn-dataentryfine}\n   *    * [`rpn-dataincrement`]{@link InputChannel#event:rpn-dataincrement}\n   *    * [`rpn-datadecrement`]{@link InputChannel#event:rpn-datadecrement}\n   *\n   * @param event {string | Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event is detected.\n   * This function will receive an event parameter object. For details on this object's properties,\n   * check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * An integer between 1 and 16 or an array of such integers representing the MIDI channel(s) to\n   * listen on. If no channel is specified, all channels will be used. This parameter is ignored for\n   * input-wide events.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @param {number} [options.remaining=Infinity] The number of times after which the callback\n   * should automatically be removed.\n   *\n   * @returns {Listener|Listener[]} If the event is input-wide, a single [`Listener`](Listener)\n   * object is returned. If the event is channel-specific, an array of all the\n   * [`Listener`](Listener) objects is returned (one for each channel).\n   */\n  addListener<T extends keyof InputEventMap>(\n    e: Symbol | T,\n    listener: InputEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"channels\"?: number | number[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n      \"remaining\"?: number;\n    }\n  ): Listener | Listener[];\n\n  /**\n   * Adds a one-time event listener that will trigger a function callback when the specified event\n   * happens. The event can be **channel-bound** or **input-wide**. Channel-bound events are\n   * dispatched by [`InputChannel`]{@link InputChannel} objects and are tied to a specific MIDI\n   * channel while input-wide events are dispatched by the `Input` object itself and are not tied\n   * to a specific channel.\n   *\n   * Calling the function with an input-wide event (such as\n   * [`\"midimessage\"`]{@link #event:midimessage}), will return the [`Listener`](Listener) object\n   * that was created.\n   *\n   * If you call the function with a channel-specific event (such as\n   * [`\"noteon\"`]{@link InputChannel#event:noteon}), it will return an array of all\n   * [`Listener`](Listener) objects that were created (one for each channel):\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addOneTimeListener(\"noteon\", someFunction);\n   * ```\n   *\n   * You can also specify which channels you want to add the listener to:\n   *\n   * ```javascript\n   * const listeners = WebMidi.inputs[0].addOneTimeListener(\"noteon\", someFunction, {channels: [1, 2, 3]});\n   * ```\n   *\n   * In this case, the `listeners` variable contains an array of 3 [`Listener`](Listener) objects.\n   *\n   * The code above will add a listener for the `\"noteon\"` event and call `someFunction` when the\n   * event is triggered on MIDI channels `1`, `2` or `3`.\n   *\n   * Note that, when adding events to channels, it is the [`InputChannel`](InputChannel) instance\n   * that actually gets a listener added and not the `Input` instance.\n   *\n   * Note: if you want to add a listener to a single MIDI channel you should probably do so directly\n   * on the [`InputChannel`](InputChannel) object itself.\n   *\n   * There are 8 families of events you can listen to:\n   *\n   * 1. **MIDI System Common** Events (input-wide)\n   *\n   *    * [`songposition`]{@link Input#event:songposition}\n   *    * [`songselect`]{@link Input#event:songselect}\n   *    * [`sysex`]{@link Input#event:sysex}\n   *    * [`timecode`]{@link Input#event:timecode}\n   *    * [`tunerequest`]{@link Input#event:tunerequest}\n   *\n   * 2. **MIDI System Real-Time** Events (input-wide)\n   *\n   *    * [`clock`]{@link Input#event:clock}\n   *    * [`start`]{@link Input#event:start}\n   *    * [`continue`]{@link Input#event:continue}\n   *    * [`stop`]{@link Input#event:stop}\n   *    * [`activesensing`]{@link Input#event:activesensing}\n   *    * [`reset`]{@link Input#event:reset}\n   *\n   * 3. **State Change** Events (input-wide)\n   *\n   *    * [`opened`]{@link Input#event:opened}\n   *    * [`closed`]{@link Input#event:closed}\n   *    * [`disconnected`]{@link Input#event:disconnected}\n   *\n   * 4. **Catch-All** Events (input-wide)\n   *\n   *    * [`midimessage`]{@link Input#event:midimessage}\n   *    * [`unknownmidimessage`]{@link Input#event:unknownmidimessage}\n   *\n   * 5. **Channel Voice** Events (channel-specific)\n   *\n   *    * [`channelaftertouch`]{@link InputChannel#event:channelaftertouch}\n   *    * [`controlchange`]{@link InputChannel#event:controlchange}\n   *      * [`controlchange-controller0`]{@link InputChannel#event:controlchange-controller0}\n   *      * [`controlchange-controller1`]{@link InputChannel#event:controlchange-controller1}\n   *      * [`controlchange-controller2`]{@link InputChannel#event:controlchange-controller2}\n   *      * (...)\n   *      * [`controlchange-controller127`]{@link InputChannel#event:controlchange-controller127}\n   *    * [`keyaftertouch`]{@link InputChannel#event:keyaftertouch}\n   *    * [`noteoff`]{@link InputChannel#event:noteoff}\n   *    * [`noteon`]{@link InputChannel#event:noteon}\n   *    * [`pitchbend`]{@link InputChannel#event:pitchbend}\n   *    * [`programchange`]{@link InputChannel#event:programchange}\n   *\n   *    Note: you can listen for a specific control change message by using an event name like this:\n   *    `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   *    etc.\n   *\n   * 6. **Channel Mode** Events (channel-specific)\n   *\n   *    * [`allnotesoff`]{@link InputChannel#event:allnotesoff}\n   *    * [`allsoundoff`]{@link InputChannel#event:allsoundoff}\n   *    * [`localcontrol`]{@link InputChannel#event:localcontrol}\n   *    * [`monomode`]{@link InputChannel#event:monomode}\n   *    * [`omnimode`]{@link InputChannel#event:omnimode}\n   *    * [`resetallcontrollers`]{@link InputChannel#event:resetallcontrollers}\n   *\n   * 7. **NRPN** Events (channel-specific)\n   *\n   *    * [`nrpn`]{@link InputChannel#event:nrpn}\n   *    * [`nrpn-dataentrycoarse`]{@link InputChannel#event:nrpn-dataentrycoarse}\n   *    * [`nrpn-dataentryfine`]{@link InputChannel#event:nrpn-dataentryfine}\n   *    * [`nrpn-dataincrement`]{@link InputChannel#event:nrpn-dataincrement}\n   *    * [`nrpn-datadecrement`]{@link InputChannel#event:nrpn-datadecrement}\n   *\n   * 8. **RPN** Events (channel-specific)\n   *\n   *    * [`rpn`]{@link InputChannel#event:rpn}\n   *    * [`rpn-dataentrycoarse`]{@link InputChannel#event:rpn-dataentrycoarse}\n   *    * [`rpn-dataentryfine`]{@link InputChannel#event:rpn-dataentryfine}\n   *    * [`rpn-dataincrement`]{@link InputChannel#event:rpn-dataincrement}\n   *    * [`rpn-datadecrement`]{@link InputChannel#event:rpn-datadecrement}\n   *\n   * @param event {string} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event\n   * is detected. This function will receive an event parameter object. For details on this object's\n   * properties, check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {number|number[]} [options.channels]  An integer between 1 and 16 or an array of\n   * such integers representing the MIDI channel(s) to listen on. This parameter is ignored for\n   * input-wide events.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @returns {Listener|Listener[]} An array of all [`Listener`](Listener) objects that were\n   * created.\n   */\n  addOneTimeListener<T extends keyof InputEventMap>(\n    e: Symbol | T,\n    listener: InputEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"channels\"?: number | number[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n    }\n  ): Listener | Listener[];\n\n  /**\n   * Closes the input. When an input is closed, it cannot be used to listen to MIDI messages until\n   * the input is opened again by calling [`Input.open()`](Input#open).\n   *\n   * **Note**: if what you want to do is stop events from being dispatched, you should use\n   * [`eventsSuspended`](#eventsSuspended) instead.\n   *\n   * @returns {Promise<Input>} The promise is fulfilled with the `Input` object\n   */\n  close(): Promise<Input>;\n\n  /**\n   * Destroys the `Input` by removing all listeners, emptying the [`channels`](#channels) array and\n   * unlinking the MIDI subsystem. This is mostly for internal use.\n   *\n   * @returns {Promise<void>}\n   */\n  destroy(): Promise<void>;\n\n  /**\n   * Checks whether the specified [`Forwarder`](Forwarder) object has already been attached to this\n   * input.\n   *\n   * @param {Forwarder} forwarder The [`Forwarder`](Forwarder) to check for (the\n   * [`Forwarder`](Forwarder) object is returned when calling [`addForwarder()`](#addForwarder).\n   * @returns {boolean}\n   */\n  hasForwarder(forwarder: Forwarder): boolean;\n\n  /**\n   * Checks if the specified event type is already defined to trigger the specified callback\n   * function. For channel-specific events, the function will return `true` only if all channels\n   * have the listener defined.\n   *\n   * @param event {string|Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels]  An integer between 1 and 16 or an array of such\n   * integers representing the MIDI channel(s) to check. This parameter is ignored for input-wide\n   * events.\n   *\n   * @returns {boolean} Boolean value indicating whether or not the `Input` or `InputChannel`\n   * already has this listener defined.\n   */\n  hasListener<T extends keyof InputEventMap>(\n    e: Symbol | T,\n    listener: InputEventMap[T],\n    options?: {\n      \"channels\"?: number | number[];\n    }\n  ): boolean;\n\n  /**\n   * Opens the input for usage. This is usually unnecessary as the port is opened automatically when\n   * WebMidi is enabled.\n   *\n   * @returns {Promise<Input>} The promise is fulfilled with the `Input` object.\n   */\n  open(): Promise<Input>;\n\n  /**\n   * Removes the specified [`Forwarder`](Forwarder) object from the input.\n   *\n   * @param {Forwarder} forwarder The [`Forwarder`](Forwarder) to remove (the\n   * [`Forwarder`](Forwarder) object is returned when calling `addForwarder()`.\n   */\n  removeForwarder(forwarder: Forwarder): void;\n\n  /**\n   * Removes the specified listener for the specified event. If no listener is specified, all\n   * listeners for the specified event will be removed. If no event is specified, all listeners for\n   * the `Input` as well as all listeners for all [`InputChannel`]{@link InputChannel} objects will\n   * be removed.\n   *\n   * By default, channel-specific listeners will be removed from all channels unless the\n   * `options.channel` narrows it down.\n   *\n   * @param [type] {string} The type of the event.\n   *\n   * @param [listener] {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels]  An integer between 1 and 16 or an array of\n   * such integers representing the MIDI channel(s) to match. This parameter is ignored for\n   * input-wide events.\n   *\n   * @param {*} [options.context] Only remove the listeners that have this exact context.\n   *\n   * @param {number} [options.remaining] Only remove the listener if it has exactly that many\n   * remaining times to be executed.\n   */\n  removeListener<T extends keyof InputEventMap>(\n    type?: Symbol | T,\n    listener?: InputEventMap[T],\n    options?: {\n      \"channels\"?: number | number[];\n      \"context\"?: any;\n      \"remaining\"?: number;\n    }\n  ): void;\n\n  /**\n   * Input port's connection state: `pending`, `open` or `closed`.\n   *\n   * @type {WebMidiApi.MIDIPortConnectionState}\n   * @readonly\n   */\n  get connection(): WebMidiApi.MIDIPortConnectionState;\n\n  /**\n   * ID string of the MIDI port. The ID is host-specific. Do not expect the same ID on different\n   * platforms. For example, Google Chrome and the Jazz-Plugin report completely different IDs for\n   * the same port.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get id(): string;\n\n  /**\n   * Name of the manufacturer of the device that makes this input port available.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get manufacturer(): string;\n\n  /**\n   * Name of the MIDI input.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get name(): string;\n\n  /**\n   * An integer to offset the reported octave of incoming notes. By default, middle C (MIDI note\n   * number 60) is placed on the 4th octave (C4).\n   *\n   * If, for example, `octaveOffset` is set to 2, MIDI note number 60 will be reported as C6. If\n   * `octaveOffset` is set to -1, MIDI note number 60 will be reported as C3.\n   *\n   * Note that this value is combined with the global offset value defined in the\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) property (if any).\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  set octaveOffset(arg: number);\n  get octaveOffset(): number;\n\n  /**\n   * State of the input port: `connected` or `disconnected`.\n   *\n   * @type {WebMidiApi.MIDIPortDeviceState}\n   * @readonly\n   */\n  get state(): WebMidiApi.MIDIPortDeviceState;\n\n  /**\n   * The port type. In the case of the `Input` object, this is always: `input`.\n   *\n   * @type {WebMidiApi.MIDIPortType}\n   * @readonly\n   */\n  get type(): WebMidiApi.MIDIPortType;\n\n}\n\n/**\n * The `InputChannel` class represents a single MIDI input channel (1-16) from a single input\n * device. This object is derived from the host's MIDI subsystem and should not be instantiated\n * directly.\n *\n * All 16 `InputChannel` objects can be found inside the input's [`channels`](Input#channels)\n * property.\n *\n * @fires InputChannel#midimessage\n * @fires InputChannel#unknownmessage\n *\n * @fires InputChannel#noteoff\n * @fires InputChannel#noteon\n * @fires InputChannel#keyaftertouch\n * @fires InputChannel#programchange\n * @fires InputChannel#channelaftertouch\n * @fires InputChannel#pitchbend\n *\n * @fires InputChannel#allnotesoff\n * @fires InputChannel#allsoundoff\n * @fires InputChannel#localcontrol\n * @fires InputChannel#monomode\n * @fires InputChannel#omnimode\n * @fires InputChannel#resetallcontrollers\n *\n * @fires InputChannel#event:nrpn\n * @fires InputChannel#event:nrpn-dataentrycoarse\n * @fires InputChannel#event:nrpn-dataentryfine\n * @fires InputChannel#event:nrpn-dataincrement\n * @fires InputChannel#event:nrpn-datadecrement\n * @fires InputChannel#event:rpn\n * @fires InputChannel#event:rpn-dataentrycoarse\n * @fires InputChannel#event:rpn-dataentryfine\n * @fires InputChannel#event:rpn-dataincrement\n * @fires InputChannel#event:rpn-datadecrement\n *\n * @fires InputChannel#controlchange\n * @fires InputChannel#event:controlchange-controllerxxx\n * @fires InputChannel#event:controlchange-bankselectcoarse\n * @fires InputChannel#event:controlchange-modulationwheelcoarse\n * @fires InputChannel#event:controlchange-breathcontrollercoarse\n * @fires InputChannel#event:controlchange-footcontrollercoarse\n * @fires InputChannel#event:controlchange-portamentotimecoarse\n * @fires InputChannel#event:controlchange-dataentrycoarse\n * @fires InputChannel#event:controlchange-volumecoarse\n * @fires InputChannel#event:controlchange-balancecoarse\n * @fires InputChannel#event:controlchange-pancoarse\n * @fires InputChannel#event:controlchange-expressioncoarse\n * @fires InputChannel#event:controlchange-effectcontrol1coarse\n * @fires InputChannel#event:controlchange-effectcontrol2coarse\n * @fires InputChannel#event:controlchange-generalpurposecontroller1\n * @fires InputChannel#event:controlchange-generalpurposecontroller2\n * @fires InputChannel#event:controlchange-generalpurposecontroller3\n * @fires InputChannel#event:controlchange-generalpurposecontroller4\n * @fires InputChannel#event:controlchange-bankselectfine\n * @fires InputChannel#event:controlchange-modulationwheelfine\n * @fires InputChannel#event:controlchange-breathcontrollerfine\n * @fires InputChannel#event:controlchange-footcontrollerfine\n * @fires InputChannel#event:controlchange-portamentotimefine\n * @fires InputChannel#event:controlchange-dataentryfine\n * @fires InputChannel#event:controlchange-channelvolumefine\n * @fires InputChannel#event:controlchange-balancefine\n * @fires InputChannel#event:controlchange-panfine\n * @fires InputChannel#event:controlchange-expressionfine\n * @fires InputChannel#event:controlchange-effectcontrol1fine\n * @fires InputChannel#event:controlchange-effectcontrol2fine\n * @fires InputChannel#event:controlchange-damperpedal\n * @fires InputChannel#event:controlchange-portamento\n * @fires InputChannel#event:controlchange-sostenuto\n * @fires InputChannel#event:controlchange-softpedal\n * @fires InputChannel#event:controlchange-legatopedal\n * @fires InputChannel#event:controlchange-hold2\n * @fires InputChannel#event:controlchange-soundvariation\n * @fires InputChannel#event:controlchange-resonance\n * @fires InputChannel#event:controlchange-releasetime\n * @fires InputChannel#event:controlchange-attacktime\n * @fires InputChannel#event:controlchange-brightness\n * @fires InputChannel#event:controlchange-decaytime\n * @fires InputChannel#event:controlchange-vibratorate\n * @fires InputChannel#event:controlchange-vibratodepth\n * @fires InputChannel#event:controlchange-vibratodelay\n * @fires InputChannel#event:controlchange-generalpurposecontroller5\n * @fires InputChannel#event:controlchange-generalpurposecontroller6\n * @fires InputChannel#event:controlchange-generalpurposecontroller7\n * @fires InputChannel#event:controlchange-generalpurposecontroller8\n * @fires InputChannel#event:controlchange-portamentocontrol\n * @fires InputChannel#event:controlchange-highresolutionvelocityprefix\n * @fires InputChannel#event:controlchange-effect1depth\n * @fires InputChannel#event:controlchange-effect2depth\n * @fires InputChannel#event:controlchange-effect3depth\n * @fires InputChannel#event:controlchange-effect4depth\n * @fires InputChannel#event:controlchange-effect5depth\n * @fires InputChannel#event:controlchange-dataincrement\n * @fires InputChannel#event:controlchange-datadecrement\n * @fires InputChannel#event:controlchange-nonregisteredparameterfine\n * @fires InputChannel#event:controlchange-nonregisteredparametercoarse\n * @fires InputChannel#event:controlchange-registeredparameterfine\n * @fires InputChannel#event:controlchange-registeredparametercoarse\n * @fires InputChannel#event:controlchange-allsoundoff\n * @fires InputChannel#event:controlchange-resetallcontrollers\n * @fires InputChannel#event:controlchange-localcontrol\n * @fires InputChannel#event:controlchange-allnotesoff\n * @fires InputChannel#event:controlchange-omnimodeoff\n * @fires InputChannel#event:controlchange-omnimodeon\n * @fires InputChannel#event:controlchange-monomodeon\n * @fires InputChannel#event:controlchange-polymodeon\n * @fires InputChannel#event:\n *\n * @extends EventEmitter\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class InputChannel extends EventEmitter {\n\n  /**\n   * Creates an `InputChannel` object.\n   *\n   * @param {Input} input The [`Input`](Input) object this channel belongs to.\n   * @param {number} number The channel's MIDI number (1-16).\n   */\n  constructor(input: Input, number: number);\n\n  private _dispatchParameterNumberEvent;\n  private _input;\n  private _isRpnOrNrpnController;\n  private _number;\n  private _octaveOffset;\n  private _parseChannelModeMessage;\n  private _parseEventForParameterNumber;\n  private _parseEventForStandardMessages;\n  private _processMidiMessageEvent;\n  private _nrpnBuffer;\n  private _rpnBuffer;\n\n  /**\n   * Contains the current playing state of all MIDI notes of this channel (0-127). The state is\n   * `true` for a currently playing note and `false` otherwise.\n   * @type {boolean[]}\n   */\n  notesState: boolean[];\n\n  /**\n   * Indicates whether events for **Registered Parameter Number** and **Non-Registered Parameter\n   * Number** should be dispatched. RPNs and NRPNs are composed of a sequence of specific\n   * **control change** messages. When a valid sequence of such control change messages is\n   * received, an [`rpn`](#event-rpn) or [`nrpn`](#event-nrpn) event will fire.\n   *\n   * If an invalid or out-of-order **control change** message is received, it will fall through\n   * the collector logic and all buffered **control change** messages will be discarded as\n   * incomplete.\n   *\n   * @type {boolean}\n   */\n  parameterNumberEventsEnabled: boolean;\n\n  /**\n   * Adds an event listener that will trigger a function callback when the specified event is\n   * dispatched.\n   *\n   * Here are the events you can listen to:\n   *\n   * **Channel Voice** Events\n   *\n   *    * [`channelaftertouch`]{@link InputChannel#event:channelaftertouch}\n   *    * [`controlchange`]{@link InputChannel#event:controlchange}\n   *      * [`controlchange-controller0`]{@link InputChannel#event:controlchange-controller0}\n   *      * [`controlchange-controller1`]{@link InputChannel#event:controlchange-controller1}\n   *      * [`controlchange-controller2`]{@link InputChannel#event:controlchange-controller2}\n   *      * (...)\n   *      * [`controlchange-controller127`]{@link InputChannel#event:controlchange-controller127}\n   *    * [`keyaftertouch`]{@link InputChannel#event:keyaftertouch}\n   *    * [`noteoff`]{@link InputChannel#event:noteoff}\n   *    * [`noteon`]{@link InputChannel#event:noteon}\n   *    * [`pitchbend`]{@link InputChannel#event:pitchbend}\n   *    * [`programchange`]{@link InputChannel#event:programchange}\n   *\n   *    Note: you can listen for a specific control change message by using an event name like this:\n   *    `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   *    etc.\n   *\n   * **Channel Mode** Events\n   *\n   *    * [`allnotesoff`]{@link InputChannel#event:allnotesoff}\n   *    * [`allsoundoff`]{@link InputChannel#event:allsoundoff}\n   *    * [`localcontrol`]{@link InputChannel#event:localcontrol}\n   *    * [`monomode`]{@link InputChannel#event:monomode}\n   *    * [`omnimode`]{@link InputChannel#event:omnimode}\n   *    * [`resetallcontrollers`]{@link InputChannel#event:resetallcontrollers}\n   *\n   * **NRPN** Events\n   *\n   *    * [`nrpn`]{@link InputChannel#event:nrpn}\n   *    * [`nrpn-dataentrycoarse`]{@link InputChannel#event:nrpn-dataentrycoarse}\n   *    * [`nrpn-dataentryfine`]{@link InputChannel#event:nrpn-dataentryfine}\n   *    * [`nrpn-dataincrement`]{@link InputChannel#event:nrpn-dataincrement}\n   *    * [`nrpn-datadecrement`]{@link InputChannel#event:nrpn-datadecrement}\n   *\n   * **RPN** Events\n   *\n   *    * [`rpn`]{@link InputChannel#event:rpn}\n   *    * [`rpn-dataentrycoarse`]{@link InputChannel#event:rpn-dataentrycoarse}\n   *    * [`rpn-dataentryfine`]{@link InputChannel#event:rpn-dataentryfine}\n   *    * [`rpn-dataincrement`]{@link InputChannel#event:rpn-dataincrement}\n   *    * [`rpn-datadecrement`]{@link InputChannel#event:rpn-datadecrement}\n   *\n   * @param event {string | Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event\n   * is detected. This function will receive an event parameter object. For details on this object's\n   * properties, check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @param {number} [options.remaining=Infinity] The number of times after which the callback\n   * should automatically be removed.\n   *\n   * @returns {Listener} The listener object that was created\n   */\n  addListener<T extends keyof InputChannelEventMap>(\n    e: Symbol | T,\n    listener: InputChannelEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n      \"remaining\"?: number;\n    }\n  ): Listener;\n\n  /**\n   * Adds a one-time event listener that will trigger a function callback when the specified event\n   * is dispatched.\n   *\n   * Here are the events you can listen to:\n   *\n   * **Channel Voice** Events\n   *\n   *    * [`channelaftertouch`]{@link InputChannel#event:channelaftertouch}\n   *    * [`controlchange`]{@link InputChannel#event:controlchange}\n   *      * [`controlchange-controller0`]{@link InputChannel#event:controlchange-controller0}\n   *      * [`controlchange-controller1`]{@link InputChannel#event:controlchange-controller1}\n   *      * [`controlchange-controller2`]{@link InputChannel#event:controlchange-controller2}\n   *      * (...)\n   *      * [`controlchange-controller127`]{@link InputChannel#event:controlchange-controller127}\n   *    * [`keyaftertouch`]{@link InputChannel#event:keyaftertouch}\n   *    * [`noteoff`]{@link InputChannel#event:noteoff}\n   *    * [`noteon`]{@link InputChannel#event:noteon}\n   *    * [`pitchbend`]{@link InputChannel#event:pitchbend}\n   *    * [`programchange`]{@link InputChannel#event:programchange}\n   *\n   *    Note: you can listen for a specific control change message by using an event name like this:\n   *    `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   *    etc.\n   *\n   * **Channel Mode** Events\n   *\n   *    * [`allnotesoff`]{@link InputChannel#event:allnotesoff}\n   *    * [`allsoundoff`]{@link InputChannel#event:allsoundoff}\n   *    * [`localcontrol`]{@link InputChannel#event:localcontrol}\n   *    * [`monomode`]{@link InputChannel#event:monomode}\n   *    * [`omnimode`]{@link InputChannel#event:omnimode}\n   *    * [`resetallcontrollers`]{@link InputChannel#event:resetallcontrollers}\n   *\n   * **NRPN** Events\n   *\n   *    * [`nrpn`]{@link InputChannel#event:nrpn}\n   *    * [`nrpn-dataentrycoarse`]{@link InputChannel#event:nrpn-dataentrycoarse}\n   *    * [`nrpn-dataentryfine`]{@link InputChannel#event:nrpn-dataentryfine}\n   *    * [`nrpn-dataincrement`]{@link InputChannel#event:nrpn-dataincrement}\n   *    * [`nrpn-datadecrement`]{@link InputChannel#event:nrpn-datadecrement}\n   *\n   * **RPN** Events\n   *\n   *    * [`rpn`]{@link InputChannel#event:rpn}\n   *    * [`rpn-dataentrycoarse`]{@link InputChannel#event:rpn-dataentrycoarse}\n   *    * [`rpn-dataentryfine`]{@link InputChannel#event:rpn-dataentryfine}\n   *    * [`rpn-dataincrement`]{@link InputChannel#event:rpn-dataincrement}\n   *    * [`rpn-datadecrement`]{@link InputChannel#event:rpn-datadecrement}\n   *\n   * @param event {string | Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event\n   * is detected. This function will receive an event parameter object. For details on this object's\n   * properties, check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @param {number} [options.remaining=Infinity] The number of times after which the callback\n   * should automatically be removed.\n   *\n   * @returns {Listener} The listener object that was created\n   */\n   addOneTimeListener<T extends keyof InputChannelEventMap>(\n    e: Symbol | T,\n    listener: InputChannelEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n    }\n  ): Listener;\n\n  /**\n   * Destroys the `InputChannel` by removing all listeners and severing the link with the MIDI\n   * subsystem's input.\n   */\n  destroy(): void;\n\n  /**\n   * Returns the playing status of the specified note (`true` if the note is currently playing,\n   * `false` if it is not). The `note` parameter can be an unsigned integer (0-127), a note\n   * identifier (`\"C4\"`, `\"G#5\"`, etc.) or a [`Note`]{@link Note} object.\n   *\n   * IF the note is specified using an integer (0-127), no octave offset will be applied.\n   *\n   * @param {number|string|Note} note The note to get the state for. The\n   * [`octaveOffset`](#octaveOffset) (channel, input and global) will be factored in for note\n   * identifiers and [`Note`]{@link Note} objects.\n   * @returns {boolean}\n   * @since version 3.0.0\n   */\n  getNoteState(note: number | string | Note): boolean;\n\n  /**\n   * Checks if the specified event type is already defined to trigger the specified callback\n   * function.\n   *\n   * @param event {string|Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @returns {boolean} Boolean value indicating whether or not the `Input` or `InputChannel`\n   * already has this listener defined.\n   */\n  hasListener<T extends keyof InputChannelEventMap>(\n    e: Symbol | T,\n    listener: InputChannelEventMap[T]\n  ): boolean;\n\n  /**\n   * Removes the specified listener for the specified event. If no listener is specified, all\n   * listeners for the specified event will be removed. If no event is specified, all listeners\n   * will be removed.\n   *\n   * @param [type] {string} The type of the event.\n   *\n   * @param [listener] {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {*} [options.context] Only remove the listeners that have this exact context.\n   *\n   * @param {number} [options.remaining] Only remove the listener if it has exactly that many\n   * remaining times to be executed.\n   */\n  removeListener<T extends keyof InputChannelEventMap>(\n    type?: Symbol | T,\n    listener?: InputChannelEventMap[T],\n    options?: {\n      \"channels\"?: number | number[];\n      \"context\"?: any;\n      \"remaining\"?: number;\n    }\n  ): void;\n\n  /**\n   * The [`Input`](Input) this channel belongs to.\n   * @type {Input}\n   * @since 3.0\n   */\n  get input(): Input;\n\n  /**\n   * This channel's MIDI number (1-16).\n   * @type {number}\n   * @since 3.0\n   */\n  get number(): number;\n\n  /**\n   * An integer to offset the reported octave of incoming note-specific messages (`noteon`,\n   * `noteoff` and `keyaftertouch`). By default, middle C (MIDI note number 60) is placed on the 4th\n   * octave (C4).\n   *\n   * If, for example, `octaveOffset` is set to 2, MIDI note number 60 will be reported as C6. If\n   * `octaveOffset` is set to -1, MIDI note number 60 will be reported as C3.\n   *\n   * Note that this value is combined with the global offset value defined by\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) object and with the value defined on the parent\n   * input object with [`Input.octaveOffset`](Input#octaveOffset).\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  set octaveOffset(arg: number);\n  get octaveOffset(): number;\n\n}\n\n/**\n * The `Message` class represents a single MIDI message. It has several properties that make it\n * easy to make sense of the binary data it contains.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Message {\n\n  /**\n   * Creates a new `Message` object from raw MIDI data.\n   *\n   * @param {Uint8Array} data The raw data of the MIDI message as a\n   * [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\n   * of integers between `0` and `255`.\n   */\n  constructor(data: Uint8Array);\n\n  /**\n   * The MIDI channel number (`1` - `16`) that the message is targeting. This is only for\n   * channel-specific messages. For system messages, this will be left `undefined`.\n   *\n   * @type {number}\n   * @readonly\n   */\n  channel: number;\n\n  /**\n   * An integer identifying the MIDI command. For channel-specific messages, the value is 4-bit\n   * and will be between `8` and `14`. For system messages, the value will be between `240` and\n   * `255`.\n   *\n   * @type {number}\n   * @readonly\n   */\n  command: number;\n\n  /**\n   * An array containing all the bytes of the MIDI message. Each byte is an integer between `0`\n   * and `255`.\n   *\n   * @type {number[]}\n   * @readonly\n   */\n  data: number[];\n\n  /**\n   * An array of the the data byte(s) of the MIDI message (as opposed to the status byte). When\n   * the message is a system exclusive message (sysex), `dataBytes` explicitly excludes the\n   * manufacturer ID and the sysex end byte so only the actual data is included.\n   *\n   * @type {number[]}\n   * @readonly\n   */\n  dataBytes: number[];\n\n  /**\n   * A boolean indicating whether the MIDI message is a channel-specific message.\n   *\n   * @type {boolean}\n   * @readonly\n   */\n  isChannelMessage: boolean;\n\n  /**\n   * A boolean indicating whether the MIDI message is a system message (not specific to a\n   * channel).\n   *\n   * @type {boolean}\n   * @readonly\n   */\n  isSystemMessage: boolean;\n\n  /**\n   * When the message is a system exclusive message (sysex), this property contains an array with\n   * either 1 or 3 entries that identify the manufacturer targeted by the message.\n   *\n   * To know how to translate these entries into manufacturer names, check out the official list:\n   * https://www.midi.org/specifications-old/item/manufacturer-id-numbers\n   *\n   * @type {number[]}\n   * @readonly\n   */\n  manufacturerId: number[];\n\n  /**\n   * A\n   * [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\n   * containing the bytes of the MIDI message. Each byte is an integer between `0` and `255`.\n   *\n   * @type {Uint8Array}\n   * @readonly\n   */\n  rawData: Uint8Array;\n\n  /**\n   * A\n   * [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\n   * of the data byte(s) of the MIDI message. When the message is a system exclusive message\n   * (sysex), `rawDataBytes` explicitly excludes the manufacturer ID and the sysex end byte so\n   * only the actual data is included.\n   *\n   * @type {Uint8Array}\n   * @readonly\n   */\n  rawDataBytes: Uint8Array;\n\n  /**\n   * The MIDI status byte of the message as an integer between `0` and `255`.\n   *\n   * @type {number}\n   * @readonly\n   */\n  statusByte: number;\n\n  /**\n   * The type of message as a string (`\"noteon\"`, `\"controlchange\"`, `\"sysex\"`, etc.)\n   *\n   * @type {string}\n   * @readonly\n   */\n  type: string;\n\n}\n\n/**\n * The `Note` class represents a single musical note such as `\"D3\"`, `\"G#4\"`, `\"F-1\"`, `\"Gb7\"`, etc.\n *\n * `Note` objects can be played back on a single channel by calling\n * [`OutputChannel.playNote()`]{@link OutputChannel#playNote} or, on multiple channels of the same\n * output, by calling [`Output.playNote()`]{@link Output#playNote}.\n *\n * The note has [`attack`](#attack) and [`release`](#release) velocities set at `0.5` by default.\n * These can be changed by passing in the appropriate option. It is also possible to set a\n * system-wide default for attack and release velocities by using the\n * [`WebMidi.defaults`](WebMidi#defaults) property.\n *\n * If you prefer to work with raw MIDI values (`0` to `127`), you can use [`rawAttack`](#rawAttack) and\n * [`rawRelease`](#rawRelease) to both get and set the values.\n *\n * The note may have a [`duration`](#duration). If it does, playback will be automatically stopped\n * when the duration has elapsed by sending a `\"noteoff\"` event. By default, the duration is set to\n * `Infinity`. In this case, it will never stop playing unless explicitly stopped by calling a\n * method such as [`OutputChannel.stopNote()`]{@link OutputChannel#stopNote},\n * [`Output.stopNote()`]{@link Output#stopNote} or similar.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Note {\n\n  private _accidental: string;\n  private _attack: number;\n  private _duration: number;\n  private _name: string;\n  private _octave: number;\n  private _release: number;\n\n  /**\n   * Creates a `Note` object.\n   *\n   * @param value {string|number} The value used to create the note. If an identifier string is used,\n   * it must start with the note letter, optionally followed by an accidental and followed by the\n   * octave number (`\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`, etc.). If a number is used, it must be an\n   * integer between 0 and 127. In this case, middle C is considered to be C4 (note number 60).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the note should be\n   * explicitly stopped.\n   *\n   * @param {number} [options.attack=0.5] The note's attack velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.release=0.5] The note's release velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawAttack=64] The note's attack velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawRelease=64] The note's release velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @throws {Error} Invalid note identifier\n   * @throws {RangeError} Invalid name value\n   * @throws {RangeError} Invalid accidental value\n   * @throws {RangeError} Invalid octave value\n   * @throws {RangeError} Invalid duration value\n   * @throws {RangeError} Invalid attack value\n   * @throws {RangeError} Invalid release value\n   */\n  constructor(value: string | number, options?: {\n    duration?: number;\n    attack?: number;\n    release?: number;\n    rawAttack?: number;\n    rawRelease?: number;\n  });\n\n  /**\n   * Returns a MIDI note number offset by octave and/or semitone. If the calculated value is less\n   * than 0, 0 will be returned. If the calculated value is more than 127, 127 will be returned. If\n   * an invalid value is supplied, 0 will be used.\n   *\n   * @param [octaveOffset] {number} An integer to offset the note number by octave.\n   * @param [semitoneOffset] {number} An integer to offset the note number by semitone.\n   * @returns {number} An integer between 0 and 127\n   */\n  getOffsetNumber(octaveOffset?: number, semitoneOffset?: number): number;\n\n  /**\n   * The accidental (#, ##, b or bb) of the note.\n   * @type {string}\n   * @since 3.0.0\n   */\n  get accidental(): string;\n  set accidental(arg: string);\n\n  /**\n   * The attack velocity of the note as a float between 0 and 1.\n   * @type {number}\n   * @since 3.0.0\n   */\n  set attack(arg: number);\n  get attack(): number;\n\n  /**\n   * The duration of the note as a positive decimal number representing the number of milliseconds\n   * that the note should play for.\n   *\n   * @type {number}\n   * @since 3.0.0\n   */\n  set duration(arg: number);\n  get duration(): number;\n\n  /**\n   * The name, optional accidental and octave of the note, as a string.\n   * @type {string}\n   * @since 3.0.0\n   */\n  set identifier(arg: string);\n  get identifier(): string;\n\n  /**\n   * The name (letter) of the note. If you need the full name with octave and accidental, you can\n   * use the [`identifier`]{@link Note#identifier} property instead.\n   * @type {string}\n   * @since 3.0.0\n   */\n  get name(): string;\n  set name(arg: string);\n\n  /**\n   * The MIDI number of the note (`0` - `127`). This number is derived from the note identifier\n   * using C4 as a reference for middle C.\n   *\n   * @type {number}\n   * @readonly\n   * @since 3.0.0\n   */\n  get number(): number;\n\n  /**\n   * The octave of the note.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get octave(): number;\n  set octave(arg: number);\n\n  /**\n   * The attack velocity of the note as a positive integer between 0 and 127.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get rawAttack(): number;\n  set rawAttack(arg: number);\n\n  /**\n   * The release velocity of the note as a positive integer between 0 and 127.\n   * @type {number}\n   * @since 3.0.0\n   */\n  get rawRelease(): number;\n  set rawRelease(arg: number);\n\n  /**\n   * The release velocity of the note as an integer between 0 and 1.\n   * @type {number}\n   * @since 3.0.0\n   */\n  set release(arg: number);\n  get release(): number;\n\n}\n\n/**\n * The `Output` class represents a single MIDI output port (not to be confused with a MIDI channel).\n * A port is made available by a MIDI device. A MIDI device can advertise several input and output\n * ports. Each port has 16 MIDI channels which can be accessed via the [`channels`](#channels)\n * property.\n *\n * The `Output` object is automatically instantiated by the library according to the host's MIDI\n * subsystem and should not be directly instantiated.\n *\n * You can access all available `Output` objects by referring to the\n * [`WebMidi.outputs`](WebMidi#outputs) array or by using methods such as\n * [`WebMidi.getOutputByName()`](WebMidi#getOutputByName) or\n * [`WebMidi.getOutputById()`](WebMidi#getOutputById).\n *\n * @fires Output#opened\n * @fires Output#disconnected\n * @fires Output#closed\n *\n * @extends EventEmitter\n * @license Apache-2.0\n */\nexport class Output extends EventEmitter {\n\n  /**\n   * Creates an `Output` object.\n   *\n   * @param {WebMidiApi.MIDIOutput} midiOutput [`MIDIOutput`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIOutput)\n   * object as provided by the MIDI subsystem.\n   */\n  constructor(midiOutput: WebMidiApi.MIDIOutput);\n\n  private _midiOutput;\n  private _octaveOffset;\n  private _onStateChange;\n\n  /**\n   * Array containing the 16 [`OutputChannel`]{@link OutputChannel} objects available provided by\n   * this `Output`. The channels are numbered 1 through 16.\n   *\n   * @type {OutputChannel[]}\n   */\n  channels: OutputChannel[];\n\n  /**\n   * Adds an event listener that will trigger a function callback when the specified event is\n   * dispatched.\n   *\n   * Here are the events you can listen to: closed, disconnected, open.\n   *\n   * @param event {string | Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event\n   * is detected. This function will receive an event parameter object. For details on this object's\n   * properties, check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @param {number} [options.remaining=Infinity] The number of times after which the callback\n   * should automatically be removed.\n   *\n   * @returns {Listener} The listener object that was created\n   */\n  addListener<T extends keyof PortEventMap>(\n    e: Symbol | T,\n    listener: PortEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n      \"remaining\"?: number;\n    }\n  ): Listener;\n\n  /**\n   * Adds a one-time event listener that will trigger a function callback when the specified event\n   * is dispatched.\n   *\n   * Here are the events you can listen to: closed, disconnected, open.\n   *\n   * @param event {string | Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event\n   * is detected. This function will receive an event parameter object. For details on this object's\n   * properties, check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @returns {Listener} The listener object that was created\n   */\n  addOneTimeListener<T extends keyof PortEventMap>(\n    e: Symbol | T,\n    listener: PortEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n    }\n  ): Listener;\n\n  /**\n   * Clears all messages that have been queued but not yet delivered.\n   *\n   * **Warning**: this method has been defined in the specification but has not been implemented\n   * yet. As soon as browsers implement it, it will work.\n   *\n   * You can check out the current status of this feature for Chromium (Chrome) here:\n   * https://bugs.chromium.org/p/chromium/issues/detail?id=471798\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  clear(): Output;\n\n  /**\n   * Closes the output connection. When an output is closed, it cannot be used to send MIDI messages\n   * until the output is opened again by calling [`open()`]{@link #open}. You can check\n   * the connection status by looking at the [`connection`]{@link #connection} property.\n   *\n   * @returns {Promise<void>}\n   */\n  close(): Promise<void>;\n\n  /**\n   * Destroys the `Output`. All listeners are removed, all channels are destroyed and the MIDI\n   * subsystem is unlinked.\n   * @returns {Promise<void>}\n   */\n  destroy(): Promise<void>;\n\n  /**\n   * Checks if the specified event type is already defined to trigger the specified callback\n   * function.\n   *\n   * @param event {string|Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @returns {boolean} Boolean value indicating whether or not the `Input` or `InputChannel`\n   * already has this listener defined.\n   */\n  hasListener<T extends keyof PortEventMap>(\n    e: Symbol | T,\n    listener: PortEventMap[T]\n  ): boolean;\n\n  /**\n   * Opens the output for usage. When the library is enabled, all ports are automatically opened.\n   * This method is only useful for ports that have been manually closed.\n   *\n   * @returns {Promise<Output>} The promise is fulfilled with the `Output` object.\n   */\n  open(): Promise<Output>;\n\n  /**\n   * Plays a note or an array of notes on one or more channels of this output. If you intend to play\n   * notes on a single channel, you should probably use\n   * [`OutputChannel.playNote()`](OutputChannel#playNote) instead.\n   *\n   * The first parameter is the note to play. It can be a single value or an array of the following\n   * valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`]{@link Note} object\n   *\n   * The `playNote()` method sends a **note on** MIDI message for all specified notes on all\n   * specified channels. If no channel is specified, it will send to all channels. If a `duration`\n   * is set in the `options` parameter or in the [`Note`]{@link Note} object's\n   * [`duration`]{@link Note#duration} property, it will also schedule a **note off** message to end\n   * the note after said duration. If no `duration` is set, the note will simply play until a\n   * matching **note off** message is sent with [`stopNote()`]{@link #stopNote}.\n   *\n   * The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the durations and velocities defined in the\n   * [`Note`]{@link Note} objects have precedence over the ones specified via the method's `options`\n   * parameter.\n   *\n   * **Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to play. The notes can be\n   * specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a\n   * [`Note`]{@link Note} object or an array of the previous types. When using a note identifier,\n   * octave range must be between -1 and 9. The lowest note is C-1 (MIDI note number `0`) and the\n   * highest note is G9 (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.duration=undefined] The number of milliseconds after which a\n   * **note off** message will be scheduled. If left undefined, only a **note on** message is sent.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`). If the `rawAttack` option is also defined, it will have priority. An invalid velocity\n   * value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The attack velocity at which to play the note (between\n   * `0` and `127`). This has priority over the `attack` property. An invalid velocity value will\n   * silently trigger the default of 64.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note (between `0`\n   * and `1`). If the `rawRelease` option is also defined, it will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`. This is only used with the\n   * **note off** event triggered when `options.duration` is set.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note (between `0`\n   * and `127`). This has priority over the `release` property. An invalid velocity value will\n   * silently trigger the default of 64. This is only used with the **note off** event triggered\n   * when `options.duration` is set.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  playNote(note: number | string | Note | number[] | string[] | Note[], options?: {\n    channels?: number | number[];\n    duration?: number;\n    attack?: number;\n    rawAttack?: number;\n    release?: number;\n    rawRelease?: number;\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Removes the specified listener for the specified event. If no listener is specified, all\n   * listeners for the specified event will be removed.\n   *\n   * @param [type] {string} The type of the event.\n   *\n   * @param [listener] {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {*} [options.context] Only remove the listeners that have this exact context.\n   *\n   * @param {number} [options.remaining] Only remove the listener if it has exactly that many\n   * remaining times to be executed.\n   */\n  removeListener<T extends keyof PortEventMap>(\n    type?: Symbol | T,\n    listener?: PortEventMap[T],\n    options?: {\n      \"context\"?: any;\n      \"remaining\"?: number;\n    }\n  ): void;\n\n  /**\n   * Sends a MIDI message on the MIDI output port. If no time is specified, the message will be\n   * sent immediately. The message should be an array of 8 bit unsigned integers (0-225), a\n   * [`Uint8Array`]{@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array}\n   * object or a [`Message`](Message) object.\n   *\n   * It is usually not necessary to use this method directly as you can use one of the simpler\n   * helper methods such as [`playNote()`](#playNote), [`stopNote()`](#stopNote),\n   * [`sendControlChange()`](#sendControlChange), etc.\n   *\n   * Details on the format of MIDI messages are available in the summary of\n   * [MIDI messages]{@link https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message}\n   * from the MIDI Manufacturers Association.\n   *\n   * @param message {number[]|Uint8Array|Message} An array of 8bit unsigned integers, a `Uint8Array`\n   * object (not available in Node.js) containing the message bytes or a `Message` object.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The first byte (status) must be an integer between 128 and 255.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @license Apache-2.0\n   */\n  send(message: number[] | Uint8Array | Message, options?: {\n    time?: number | string;\n  }, legacy?: number): Output;\n\n  /**\n   * Sends a MIDI [**system exclusive**]{@link\n    * https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages}\n   * (*sysex*) message. There are two categories of system exclusive messages: manufacturer-specific\n   * messages and universal messages. Universal messages are further divided into three subtypes:\n   *\n   *   * Universal non-commercial (for research and testing): `0x7D`\n   *   * Universal non-realtime: `0x7E`\n   *   * Universal realtime: `0x7F`\n   *\n   * The method's first parameter (`identification`) identifies the type of message. If the value of\n   * `identification` is `0x7D` (125), `0x7E` (126) or `0x7F` (127), the message will be identified\n   * as a **universal non-commercial**, **universal non-realtime** or **universal realtime** message\n   * (respectively).\n   *\n   * If the `identification` value is an array or an integer between 0 and 124, it will be used to\n   * identify the manufacturer targeted by the message. The *MIDI Manufacturers Association*\n   * maintains a full list of\n   * [Manufacturer ID Numbers](https://www.midi.org/specifications-old/item/manufacturer-id-numbers).\n   *\n   * The `data` parameter should only contain the data of the message. When sending out the actual\n   * MIDI message, WEBMIDI.js will automatically prepend the data with the **sysex byte** (`0xF0`)\n   * and the identification byte(s). It will also automatically terminate the message with the\n   * **sysex end byte** (`0xF7`).\n   *\n   * To use the `sendSysex()` method, system exclusive message support must have been enabled. To\n   * do so, you must set the `sysex` option to `true` when calling\n   * [`WebMidi.enable()`]{@link WebMidi#enable}:\n   *\n   * ```js\n   * WebMidi.enable({sysex: true})\n   *   .then(() => console.log(\"System exclusive messages are enabled\");\n   * ```\n   *\n   * ##### Examples of manufacturer-specific system exclusive messages\n   *\n   * If you want to send a sysex message to a Korg device connected to the first output, you would\n   * use the following code:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex(0x42, [0x1, 0x2, 0x3, 0x4, 0x5]);\n   * ```\n   * In this case `0x42` is the ID of the manufacturer (Korg) and `[0x1, 0x2, 0x3, 0x4, 0x5]` is the\n   * data being sent.\n   *\n   * The parameters can be specified using any number notation (decimal, hex, binary, etc.).\n   * Therefore, the code above is equivalent to this code:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex(66, [1, 2, 3, 4, 5]);\n   * ```\n   *\n   * Some manufacturers are identified using 3 bytes. In this case, you would use a 3-position array\n   * as the first parameter. For example, to send the same sysex message to a\n   * *Native Instruments* device:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex([0x00, 0x21, 0x09], [0x1, 0x2, 0x3, 0x4, 0x5]);\n   * ```\n   *\n   * There is no limit for the length of the data array. However, it is generally suggested to keep\n   * system exclusive messages to 64Kb or less.\n   *\n   * ##### Example of universal system exclusive message\n   *\n   * If you want to send a universal sysex message, simply assign the correct identification number\n   * in the first parameter. Number `0x7D` (125) is for non-commercial, `0x7E` (126) is for\n   * non-realtime and `0x7F` (127) is for realtime.\n   *\n   * So, for example, if you wanted to send an identity request non-realtime message (`0x7E`), you\n   * could use the following:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendSysex(0x7E, [0x7F, 0x06, 0x01]);\n   * ```\n   *\n   * For more details on the format of universal messages, consult the list of\n   * [universal sysex messages](https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages).\n   *\n   * @param {number|number[]} identification An unsigned integer or an array of three unsigned\n   * integers between `0` and `127` that either identify the manufacturer or sets the message to be\n   * a **universal non-commercial message** (`0x7D`), a **universal non-realtime message** (`0x7E`)\n   * or a **universal realtime message** (`0x7F`). The *MIDI Manufacturers Association* maintains a\n   * full list of\n   * [Manufacturer ID Numbers](https://www.midi.org/specifications-old/item/manufacturer-id-numbers).\n   *\n   * @param {number[]|Uint8Array} [data] A `Uint8Array` or an array of unsigned integers between `0`\n   * and `127`. This is the data you wish to transfer.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {DOMException} Failed to execute 'send' on 'MIDIOutput': System exclusive message is\n   * not allowed.\n   *\n   * @throws {TypeError} Failed to execute 'send' on 'MIDIOutput': The value at index x is greater\n   * than 0xFF.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendSysex(identification: number | number[], data?: number[] | Uint8Array, options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **timecode quarter frame** message. Please note that no processing is being done\n   * on the data. It is up to the developer to format the data according to the\n   * [MIDI Timecode](https://en.wikipedia.org/wiki/MIDI_timecode) format.\n   *\n   * @param value {number} The quarter frame message content (integer between 0 and 127).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendTimecodeQuarterFrame(value: number, options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **song position** MIDI message. The value is expressed in MIDI beats (between `0` and\n   * `16383`) which are 16th note. Position `0` is always the start of the song.\n   *\n   * @param {number} [value=0] The MIDI beat to cue to (integer between `0` and `16383`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendSongPosition(value?: number, options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **song select** MIDI message.\n   *\n   * @param {number} [value=0] The number of the song to select (integer between `0` and `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws The song number must be between 0 and 127.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendSongSelect(value?: number, options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **tune request** real-time message.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendTuneRequest(options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **clock** real-time message. According to the standard, there are 24 MIDI clocks\n   * for every quarter note.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendClock(options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **start** real-time message. A MIDI Start message starts the playback of the current\n   * song at beat 0. To start playback elsewhere in the song, use the\n   * [`sendContinue()`]{@link #sendContinue} method.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendStart(options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **continue** real-time message. This resumes song playback where it was previously\n   * stopped or where it was last cued with a song position message. To start playback from the\n   * start, use the [`sendStart()`]{@link Output#sendStart}` method.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendContinue(options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **stop** real-time message. This tells the device connected to this output to stop\n   * playback immediately (or at the scheduled time, if specified).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendStop(options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends an **active sensing** real-time message. This tells the device connected to this port\n   * that the connection is still good. Active sensing messages are often sent every 300 ms if there\n   * was no other activity on the MIDI port.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendActiveSensing(options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **reset** real-time message. This tells the device connected to this output that it\n   * should reset itself to a default state.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendReset(options?: {\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **key aftertouch** message to the specified channel(s) at the scheduled time. This\n   * is a key-specific aftertouch. For a channel-wide aftertouch message, use\n   * [`setChannelAftertouch()`]{@link #setChannelAftertouch}.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) for which you are sending\n   * an aftertouch value. The notes can be specified by using a MIDI note number (`0` - `127`), a\n   * [`Note`](Note) object, a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the\n   * previous types. When using a note identifier, octave range must be between `-1` and `9`. The\n   * lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number\n   * `127`).\n   *\n   * @param [pressure=0.5] {number} The pressure level (between 0 and 1). An invalid pressure value\n   * will silently trigger the default behaviour. If the `rawValue` option is set to `true`, the\n   * pressure can be defined by using an integer between 0 and 127.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendKeyAftertouch(note: number | Note | string | number[] | Note[] | string[], pressure?: number, options?: {\n    channels?: number | number[];\n    rawValue?: boolean;\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **control change** message to the specified channel(s) at the scheduled time. The\n   * control change message to send can be specified numerically (0-127) or by using one of the\n   * following common names:\n   *\n   * | Number | Name                          |\n   * |--------|-------------------------------|\n   * | 0      |`bankselectcoarse`             |\n   * | 1      |`modulationwheelcoarse`        |\n   * | 2      |`breathcontrollercoarse`       |\n   * | 4      |`footcontrollercoarse`         |\n   * | 5      |`portamentotimecoarse`         |\n   * | 6      |`dataentrycoarse`              |\n   * | 7      |`volumecoarse`                 |\n   * | 8      |`balancecoarse`                |\n   * | 10     |`pancoarse`                    |\n   * | 11     |`expressioncoarse`             |\n   * | 12     |`effectcontrol1coarse`         |\n   * | 13     |`effectcontrol2coarse`         |\n   * | 18     |`generalpurposeslider3`        |\n   * | 19     |`generalpurposeslider4`        |\n   * | 32     |`bankselectfine`               |\n   * | 33     |`modulationwheelfine`          |\n   * | 34     |`breathcontrollerfine`         |\n   * | 36     |`footcontrollerfine`           |\n   * | 37     |`portamentotimefine`           |\n   * | 38     |`dataentryfine`                |\n   * | 39     |`volumefine`                   |\n   * | 40     |`balancefine`                  |\n   * | 42     |`panfine`                      |\n   * | 43     |`expressionfine`               |\n   * | 44     |`effectcontrol1fine`           |\n   * | 45     |`effectcontrol2fine`           |\n   * | 64     |`holdpedal`                    |\n   * | 65     |`portamento`                   |\n   * | 66     |`sustenutopedal`               |\n   * | 67     |`softpedal`                    |\n   * | 68     |`legatopedal`                  |\n   * | 69     |`hold2pedal`                   |\n   * | 70     |`soundvariation`               |\n   * | 71     |`resonance`                    |\n   * | 72     |`soundreleasetime`             |\n   * | 73     |`soundattacktime`              |\n   * | 74     |`brightness`                   |\n   * | 75     |`soundcontrol6`                |\n   * | 76     |`soundcontrol7`                |\n   * | 77     |`soundcontrol8`                |\n   * | 78     |`soundcontrol9`                |\n   * | 79     |`soundcontrol10`               |\n   * | 80     |`generalpurposebutton1`        |\n   * | 81     |`generalpurposebutton2`        |\n   * | 82     |`generalpurposebutton3`        |\n   * | 83     |`generalpurposebutton4`        |\n   * | 91     |`reverblevel`                  |\n   * | 92     |`tremololevel`                 |\n   * | 93     |`choruslevel`                  |\n   * | 94     |`celestelevel`                 |\n   * | 95     |`phaserlevel`                  |\n   * | 96     |`dataincrement`          |\n   * | 97     |`datadecrement`          |\n   * | 98     |`nonregisteredparametercoarse` |\n   * | 99     |`nonregisteredparameterfine`   |\n   * | 100    |`registeredparametercoarse`    |\n   * | 101    |`registeredparameterfine`      |\n   * | 120    |`allsoundoff`                  |\n   * | 121    |`resetallcontrollers`          |\n   * | 122    |`localcontrol`                 |\n   * | 123    |`allnotesoff`                  |\n   * | 124    |`omnimodeoff`                  |\n   * | 125    |`omnimodeon`                   |\n   * | 126    |`monomodeon`                   |\n   * | 127    |`polymodeon`                   |\n   *\n   * Note: as you can see above, not all control change message have a matching name. This does not\n   * mean you cannot use the others. It simply means you will need to use their number (`0` - `127`)\n   * instead of their name. While you can still use them, numbers `120` to `127` are usually\n   * reserved for *channel mode* messages. See [`sendChannelMode()`]{@link #sendChannelMode} method\n   * for more info.\n   *\n   * To view a list of all available **control change** messages, please consult [Table 3 - Control\n   * Change Messages](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\n   * from the MIDI specification.\n   *\n   * @param controller {number|string} The MIDI controller name or number (0-127).\n   *\n   * @param [value=0] {number} The value to send (0-127).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} Controller numbers must be between 0 and 127.\n   * @throws {RangeError} Invalid controller name.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendControlChange(controller: number | string, value?: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **pitch bend range** message to the specified channel(s) at the scheduled time so that\n   * they adjust the range used by their pitch bend lever. The range is specified by using the\n   * `semitones` and `cents` parameters. For example, setting the `semitones` parameter to `12`\n   * means that the pitch bend range will be 12 semitones above and below the nominal pitch.\n   *\n   * @param {number} [semitones=0] The desired adjustment value in semitones (between `0` and `127`).\n   * While nothing imposes that in the specification, it is very common for manufacturers to limit\n   * the range to 2 octaves (-12 semitones to 12 semitones).\n   *\n   * @param {number} [cents=0] The desired adjustment value in cents (integer between `0` and\n   * `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The msb value must be between 0 and 127.\n   * @throws {RangeError} The lsb value must be between 0 and 127.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendPitchBendRange(semitones?: number, cents?: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sets the specified MIDI registered parameter to the desired value. The value is defined with\n   * up to two bytes of data (msb, lsb) that each can go from `0` to `127`.\n   *\n   * MIDI\n   * [registered parameters](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\n   * extend the original list of control change messages. The MIDI 1.0 specification lists only a\n   * limited number of them:\n   *\n   * | Numbers      | Function                 |\n   * |--------------|--------------------------|\n   * | (0x00, 0x00) | `pitchbendrange`         |\n   * | (0x00, 0x01) | `channelfinetuning`      |\n   * | (0x00, 0x02) | `channelcoarsetuning`    |\n   * | (0x00, 0x03) | `tuningprogram`          |\n   * | (0x00, 0x04) | `tuningbank`             |\n   * | (0x00, 0x05) | `modulationrange`        |\n   * | (0x3D, 0x00) | `azimuthangle`           |\n   * | (0x3D, 0x01) | `elevationangle`         |\n   * | (0x3D, 0x02) | `gain`                   |\n   * | (0x3D, 0x03) | `distanceratio`          |\n   * | (0x3D, 0x04) | `maximumdistance`        |\n   * | (0x3D, 0x05) | `maximumdistancegain`    |\n   * | (0x3D, 0x06) | `referencedistanceratio` |\n   * | (0x3D, 0x07) | `panspreadangle`         |\n   * | (0x3D, 0x08) | `rollangle`              |\n   *\n   * Note that the `tuningprogram` and `tuningbank` parameters are part of the *MIDI Tuning\n   * Standard*, which is not widely implemented.\n   *\n   * @param parameter {string|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (e.g. `[0x65, 0x64]`) that identify the\n   * registered parameter.\n   *\n   * @param [data=[]] {number|number[]} A single integer or an array of integers with a maximum\n   * length of 2 specifying the desired data.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendRpnValue(parameter: string | number[], data?: number | number[], options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **channel aftertouch** message to the specified channel(s). For key-specific\n   * aftertouch, you should instead use [`setKeyAftertouch()`]{@link #setKeyAftertouch}.\n   *\n   * @param [pressure=0.5] {number} The pressure level (between `0` and `1`). An invalid pressure\n   * value will silently trigger the default behaviour. If the `rawValue` option is set to `true`,\n   * the pressure can be defined by using an integer between `0` and `127`.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   * @since 3.0.0\n   */\n  sendChannelAftertouch(pressure?: number, options?: {\n    channels?: number | number[];\n    rawValue?: boolean;\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **pitch bend** message to the specified channel(s) at the scheduled time.\n   *\n   * The resulting bend is relative to the pitch bend range that has been defined. The range can be\n   * set with [`sendPitchBendRange()`]{@link #sendPitchBendRange}. So, for example, if the pitch\n   * bend range has been set to 12 semitones, using a bend value of `-1` will bend the note 1 octave\n   * below its nominal value.\n   *\n   * @param {number|number[]} value The intensity of the bend (between `-1.0` and `1.0`). A value of\n   * `0` means no bend. If an invalid value is specified, the nearest valid value will be used\n   * instead. If the `rawValue` option is set to `true`, the intensity of the bend can be defined by\n   * either using a single integer between `0` and `127` (MSB) or an array of two integers between\n   * `0` and `127` representing, respectively, the MSB (most significant byte) and the LSB (least\n   * significant byte). The MSB is expressed in semitones with `64` meaning no bend. A value lower\n   * than `64` bends downwards while a value higher than `64` bends upwards. The LSB is expressed\n   * in cents (1/100 of a semitone). An LSB of `64` also means no bend.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered as a float between `-1.0` and `1.0` (default) or as raw integer between `0` and\n   * 127` (or an array of 2 integers if using both MSB and LSB).\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendPitchBend(value: number | number[], options?: {\n    channels?: number | number[];\n    rawValue?: boolean;\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **program change** message to the specified channel(s) at the scheduled time.\n   *\n   * @param {number} [program=0] The MIDI patch (program) number (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Failed to execute 'send' on 'MIDIOutput': The value at index 1 is greater\n   * than 0xFF.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendProgramChange(program?: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **modulation depth range** message to the specified channel(s) so that they adjust the\n   * depth of their modulation wheel's range. The range can be specified with the `semitones`\n   * parameter, the `cents` parameter or by specifying both parameters at the same time.\n   *\n   * @param [semitones=0] {number} The desired adjustment value in semitones (integer between\n   * 0 and 127).\n   *\n   * @param [cents=0] {number} The desired adjustment value in cents (integer between 0 and 127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The msb value must be between 0 and 127\n   * @throws {RangeError} The lsb value must be between 0 and 127\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendModulationRange(semitones?: number, cents?: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a master tuning message to the specified channel(s). The value is decimal and must be\n   * larger than `-65` semitones and smaller than `64` semitones.\n   *\n   * Because of the way the MIDI specification works, the decimal portion of the value will be\n   * encoded with a resolution of 14bit. The integer portion must be between -64 and 63\n   * inclusively. This function actually generates two MIDI messages: a **Master Coarse Tuning** and\n   * a **Master Fine Tuning** RPN messages.\n   *\n   * @param [value=0.0] {number} The desired decimal adjustment value in semitones (-65 < x < 64)\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The value must be a decimal number between larger than -65 and smaller\n   * than 64.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendMasterTuning(value?: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sets the MIDI tuning program to use. Note that the **Tuning Program** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param value {number} The desired tuning program (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The program value must be between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendTuningProgram(value: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sets the MIDI tuning bank to use. Note that the **Tuning Bank** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param {number} [value=0] The desired tuning bank (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The bank value must be between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendTuningBank(value?: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a MIDI **channel mode** message to the specified channel(s). The channel mode message to\n   * send can be specified numerically or by using one of the following common names:\n   *\n   * |  Type                |Number| Shortcut Method                                               |\n   * | ---------------------|------|-------------------------------------------------------------- |\n   * | `allsoundoff`        | 120  | [`sendAllSoundOff()`]{@link #sendAllSoundOff}                 |\n   * | `resetallcontrollers`| 121  | [`sendResetAllControllers()`]{@link #sendResetAllControllers} |\n   * | `localcontrol`       | 122  | [`sendLocalControl()`]{@link #sendLocalControl}               |\n   * | `allnotesoff`        | 123  | [`sendAllNotesOff()`]{@link #sendAllNotesOff}                 |\n   * | `omnimodeoff`        | 124  | [`sendOmniMode(false)`]{@link #sendOmniMode}                  |\n   * | `omnimodeon`         | 125  | [`sendOmniMode(true)`]{@link #sendOmniMode}                   |\n   * | `monomodeon`         | 126  | [`sendPolyphonicMode(\"mono\")`]{@link #sendPolyphonicMode}     |\n   * | `polymodeon`         | 127  | [`sendPolyphonicMode(\"poly\")`]{@link #sendPolyphonicMode}     |\n   *\n   * Note: as you can see above, to make it easier, all channel mode messages also have a matching\n   * helper method.\n   *\n   * It should also be noted that, per the MIDI specification, only `localcontrol` and `monomodeon`\n   * may require a value that's not zero. For that reason, the `value` parameter is optional and\n   * defaults to 0.\n   *\n   * @param {number|string} command The numerical identifier of the channel mode message (integer\n   * between 120-127) or its name as a string.\n   *\n   * @param {number} [value=0] The value to send (integer between 0-127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Invalid channel mode message name.\n   * @throws {RangeError} Channel mode controller numbers must be between 120 and 127.\n   * @throws {RangeError} Value must be an integer between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   */\n  sendChannelMode(command: number | string, value?: number, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends an **all sound off** channel mode message. This will silence all sounds playing on that\n   * channel but will not prevent new sounds from being triggered.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output}\n   *\n   * @since 3.0.0\n   */\n  sendAllSoundOff(options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends an **all notes off** channel mode message. This will make all currently playing notes\n   * fade out just as if their key had been released. This is different from the\n   * [`turnSoundOff()`]{@link #turnSoundOff} method which mutes all sounds immediately.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output}\n   *\n   * @since 3.0.0\n   */\n  sendAllNotesOff(options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **reset all controllers** channel mode message. This resets all controllers, such as\n   * the pitch bend, to their default value.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output}\n   */\n  sendResetAllControllers(options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sets the polyphonic mode. In `poly` mode (usually the default), multiple notes can be played\n   * and heard at the same time. In `mono` mode, only one note will be heard at once even if\n   * multiple notes are being played.\n   *\n   * @param mode {string} The mode to use: `mono` or `poly`.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendPolyphonicMode(mode: string, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Turns local control on or off. Local control is usually enabled by default. If you disable it,\n   * the instrument will no longer trigger its own sounds. It will only send the MIDI messages to\n   * its out port.\n   *\n   * @param [state=false] {boolean} Whether to activate local control (`true`) or disable it\n   * (`false`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendLocalControl(state?: boolean, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sets OMNI mode to **on** or **off** for the specified channel(s). MIDI's OMNI mode causes the\n   * instrument to respond to messages from all channels.\n   *\n   * It should be noted that support for OMNI mode is not as common as it used to be.\n   *\n   * @param [state] {boolean} Whether to activate OMNI mode (`true`) or not (`false`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Invalid channel mode message name.\n   * @throws {RangeError} Channel mode controller numbers must be between 120 and 127.\n   * @throws {RangeError} Value must be an integer between 0 and 127.\n   *\n   * @return {Output} Returns the `Output` object so methods can be chained.\n   *\n   * @since 3.0.0\n   */\n  sendOmniMode(state?: boolean, options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sets a non-registered parameter to the specified value. The NRPN is selected by passing a\n   * two-position array specifying the values of the two control bytes. The value is specified by\n   * passing a single integer (most cases) or an array of two integers.\n   *\n   * NRPNs are not standardized in any way. Each manufacturer is free to implement them any way\n   * they see fit. For example, according to the Roland GS specification, you can control the\n   * **vibrato rate** using NRPN (`1`, `8`). Therefore, to set the **vibrato rate** value to `123`\n   * you would use:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendNrpnValue([1, 8], 123);\n   * ```\n   *\n   * You probably want to should select a channel so the message is not sent to all channels. For\n   * instance, to send to channel `1` of the first output port, you would use:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendNrpnValue([1, 8], 123, 1);\n   * ```\n   *\n   * In some rarer cases, you need to send two values with your NRPN messages. In such cases, you\n   * would use a 2-position array. For example, for its **ClockBPM** parameter (`2`, `63`), Novation\n   * uses a 14-bit value that combines an MSB and an LSB (7-bit values). So, for example, if the\n   * value to send was `10`, you could use:\n   *\n   * ```js\n   * WebMidi.outputs[0].sendNrpnValue([2, 63], [0, 10], 1);\n   * ```\n   *\n   * For further implementation details, refer to the manufacturer's documentation.\n   *\n   * @param parameter {number[]} A two-position array specifying the two control bytes (`0x63`,\n   * `0x62`) that identify the non-registered parameter.\n   *\n   * @param [data=[]] {number|number[]} An integer or an array of integers with a length of 1 or 2\n   * specifying the desired data.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The control value must be between 0 and 127.\n   * @throws {RangeError} The msb value must be between 0 and 127\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendNrpnValue(parameter: number[], data?: number | number[], options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Increments the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this method:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendRpnIncrement(parameter: string | number[], options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Decrements the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this method:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws TypeError The specified parameter is not available.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendRpnDecrement(parameter: string | number[], options?: {\n    channels?: number | number[];\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **note off** message for the specified MIDI note number on the specified channel(s).\n   * The first parameter is the note to stop. It can be a single value or an array of the following\n   * valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`,\n   * `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range\n   * must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest\n   * note is `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendNoteOff(note: number | Note | string | number[] | Note[] | string[], options?: {\n    channels?: number | number[];\n    release?: number;\n    rawRelease?: number;\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Sends a **note off** message for the specified MIDI note number on the specified channel(s).\n   * The first parameter is the note to stop. It can be a single value or an array of the following\n   * valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`,\n   * `Db7`) or an array of the previous types. When using a note identifier, octave range must be\n   * between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is\n   * `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  stopNote(note: number | Note | string | number[] | Note[] | string[], options?: {\n    channels?: number | number[];\n    release?: number;\n    rawRelease?: number;\n    time?: number | string;\n  }): Output;\n\n\n\n  /**\n   * Sends a **note on** message for the specified MIDI note number on the specified channel(s). The\n   * first parameter is the number. It can be a single value or an array of the following valid\n   * values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   *  The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * **Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`,\n   * `Db7`) or an array of the previous types. When using a note identifier, octave range must be\n   * between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is\n   * `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]\n   * The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no\n   * channel is specified, all channels will be used.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`).  If the `rawAttack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The velocity at which to release the note (between `0`\n   * and `127`). If the `attack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  sendNoteOn(note: number | Note | string | number[] | Note[] | string[], options?: {\n    channels?: number | number[];\n    attack?: number;\n    rawAttack?: number;\n    time?: number | string;\n  }): Output;\n\n  /**\n   * Output port's connection state: `pending`, `open` or `closed`.\n   *\n   * @type {WebMidiApi.MIDIPortConnectionState}\n   * @readonly\n   */\n  get connection(): WebMidiApi.MIDIPortConnectionState;\n\n  /**\n   * ID string of the MIDI output. The ID is host-specific. Do not expect the same ID on different\n   * platforms. For example, Google Chrome and the Jazz-Plugin report completely different IDs for\n   * the same port.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get id(): string;\n\n  /**\n   * Name of the manufacturer of the device that makes this output port available.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get manufacturer(): string;\n\n  /**\n   * Name of the MIDI output.\n   *\n   * @type {string}\n   * @readonly\n   */\n  get name(): string;\n\n  /**\n   * An integer to offset the octave of outgoing notes. By default, middle C (MIDI note number 60)\n   * is placed on the 4th octave (C4).\n   *\n   * Note that this value is combined with the global offset value defined in\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) (if any).\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  set octaveOffset(arg: number);\n  get octaveOffset(): number;\n\n  /**\n   * State of the output port: `connected` or `disconnected`.\n   *\n   * @type {WebMidiApi.MIDIPortDeviceState}\n   * @readonly\n   */\n  get state(): WebMidiApi.MIDIPortDeviceState;\n\n  /**\n   * Type of the output port (it will always be: `output`).\n   *\n   * @type {WebMidiApi.MIDIPortType}\n   * @readonly\n   */\n  get type(): WebMidiApi.MIDIPortType;\n\n}\n\n/**\n * The `OutputChannel` class represents a single output MIDI channel. `OutputChannel` objects are\n * provided by an [`Output`](Output) port which, itself, is made available by a device. The\n * `OutputChannel` object is derived from the host's MIDI subsystem and should not be instantiated\n * directly.\n *\n * All 16 `OutputChannel` objects can be found inside the parent output's\n * [`channels`]{@link Output#channels} property.\n *\n * @param {Output} output The [`Output`](Output) this channel belongs to.\n * @param {number} number The MIDI channel number (`1` - `16`).\n *\n * @extends EventEmitter\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class OutputChannel extends EventEmitter {\n\n  /**\n   * Creates an `OutputChannel` object.\n   *\n   * @param {Output} output The [`Output`](Output) this channel belongs to.\n   * @param {number} number The MIDI channel number (`1` - `16`).\n   */\n  constructor(output: Output, number: number);\n\n  private _output;\n  private _number;\n  private _octaveOffset;\n  private destroy;\n\n  /**\n   * Sends a MIDI message on the MIDI output port. If no time is specified, the message will be\n   * sent immediately. The message should be an array of 8-bit unsigned integers (`0` - `225`),\n   * a\n   * [`Uint8Array`]{@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array}\n   * object or a [`Message`](Message) object.\n   *\n   * It is usually not necessary to use this method directly as you can use one of the simpler\n   * helper methods such as [`playNote()`](#playNote), [`stopNote()`](#stopNote),\n   * [`sendControlChange()`](#sendControlChange), etc.\n   *\n   * Details on the format of MIDI messages are available in the summary of\n   * [MIDI messages]{@link https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message}\n   * from the MIDI Manufacturers Association.\n   *\n   * @param message {number[]|Uint8Array|Message} A `Message` object, an array of 8-bit unsigned\n   * integers or a `Uint8Array` object (not available in Node.js) containing the message bytes.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The first byte (status) must be an integer between 128 and 255.\n   *\n   * @throws {RangeError} Data bytes must be integers between 0 and 255.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  send(message: number[] | Uint8Array | Message, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a MIDI **key aftertouch** message at the scheduled time. This is a key-specific\n   * aftertouch. For a channel-wide aftertouch message, use\n   * [`sendChannelAftertouch()`]{@link #sendChannelAftertouch}.\n   *\n   * @param target {number|Note|string|number[]|Note[]|string[]} The note(s) for which you are sending\n   * an aftertouch value. The notes can be specified by using a MIDI note number (`0` - `127`), a\n   * [`Note`](Note) object, a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the\n   * previous types. When using a note identifier, octave range must be between `-1` and `9`. The\n   * lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number\n   * `127`).\n   *\n   * When using a note identifier, the octave value will be offset by the local\n   * [`octaveOffset`](#octaveOffset) and by\n   * [`Output.octaveOffset`](Output#octaveOffset) and [`WebMidi.octaveOffset`](WebMidi#octaveOffset)\n   * (if those values are not `0`). When using a key number, `octaveOffset` values are ignored.\n   *\n   * @param [pressure=0.5] {number} The pressure level (between `0` and `1`). An invalid pressure\n   * value will silently trigger the default behaviour. If the `rawValue` option is set to `true`,\n   * the pressure is defined by using an integer between `0` and `127`.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @return {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   * @throws RangeError Invalid key aftertouch value.\n   */\n  sendKeyAftertouch(target: number | Note | string | number[] | Note[] | string[], pressure?: number, options?: {\n    rawValue?: boolean;\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a MIDI **control change** message to the channel at the scheduled time. The control\n   * change message to send can be specified numerically (`0` to `127`) or by using one of the\n   * following common names:\n   *\n   * | Number | Name                          |\n   * |--------|-------------------------------|\n   * | 0      |`bankselectcoarse`             |\n   * | 1      |`modulationwheelcoarse`        |\n   * | 2      |`breathcontrollercoarse`       |\n   * | 4      |`footcontrollercoarse`         |\n   * | 5      |`portamentotimecoarse`         |\n   * | 6      |`dataentrycoarse`              |\n   * | 7      |`volumecoarse`                 |\n   * | 8      |`balancecoarse`                |\n   * | 10     |`pancoarse`                    |\n   * | 11     |`expressioncoarse`             |\n   * | 12     |`effectcontrol1coarse`         |\n   * | 13     |`effectcontrol2coarse`         |\n   * | 18     |`generalpurposeslider3`        |\n   * | 19     |`generalpurposeslider4`        |\n   * | 32     |`bankselectfine`               |\n   * | 33     |`modulationwheelfine`          |\n   * | 34     |`breathcontrollerfine`         |\n   * | 36     |`footcontrollerfine`           |\n   * | 37     |`portamentotimefine`           |\n   * | 38     |`dataentryfine`                |\n   * | 39     |`volumefine`                   |\n   * | 40     |`balancefine`                  |\n   * | 42     |`panfine`                      |\n   * | 43     |`expressionfine`               |\n   * | 44     |`effectcontrol1fine`           |\n   * | 45     |`effectcontrol2fine`           |\n   * | 64     |`holdpedal`                    |\n   * | 65     |`portamento`                   |\n   * | 66     |`sustenutopedal`               |\n   * | 67     |`softpedal`                    |\n   * | 68     |`legatopedal`                  |\n   * | 69     |`hold2pedal`                   |\n   * | 70     |`soundvariation`               |\n   * | 71     |`resonance`                    |\n   * | 72     |`soundreleasetime`             |\n   * | 73     |`soundattacktime`              |\n   * | 74     |`brightness`                   |\n   * | 75     |`soundcontrol6`                |\n   * | 76     |`soundcontrol7`                |\n   * | 77     |`soundcontrol8`                |\n   * | 78     |`soundcontrol9`                |\n   * | 79     |`soundcontrol10`               |\n   * | 80     |`generalpurposebutton1`        |\n   * | 81     |`generalpurposebutton2`        |\n   * | 82     |`generalpurposebutton3`        |\n   * | 83     |`generalpurposebutton4`        |\n   * | 91     |`reverblevel`                  |\n   * | 92     |`tremololevel`                 |\n   * | 93     |`choruslevel`                  |\n   * | 94     |`celestelevel`                 |\n   * | 95     |`phaserlevel`                  |\n   * | 96     |`dataincrement`          |\n   * | 97     |`datadecrement`          |\n   * | 98     |`nonregisteredparametercoarse` |\n   * | 99     |`nonregisteredparameterfine`   |\n   * | 100    |`registeredparametercoarse`    |\n   * | 101    |`registeredparameterfine`      |\n   * | 120    |`allsoundoff`                  |\n   * | 121    |`resetallcontrollers`          |\n   * | 122    |`localcontrol`                 |\n   * | 123    |`allnotesoff`                  |\n   * | 124    |`omnimodeoff`                  |\n   * | 125    |`omnimodeon`                   |\n   * | 126    |`monomodeon`                   |\n   * | 127    |`polymodeon`                   |\n   *\n   * As you can see above, not all control change message have a matching name. This does not mean\n   * you cannot use the others. It simply means you will need to use their number\n   * (`0` to `127`) instead of their name. While you can still use them, numbers `120` to `127` are\n   * usually reserved for *channel mode* messages. See\n   * [`sendChannelMode()`]{@link OutputChannel#sendChannelMode} method for more info.\n   *\n   * To view a detailed list of all available **control change** messages, please consult \"Table 3 -\n   * Control Change Messages\" from the [MIDI Messages](\n   * https://www.midi.org/specifications/item/table-3-control-change-messages-data-bytes-2)\n   * specification.\n   *\n   * **Note**: messages #0-31 (MSB) are paired with messages #32-63 (LSB). For example, message #1\n   * (`modulationwheelcoarse`) can be accompanied by a second control change message for\n   * `modulationwheelfine` to achieve a greater level of precision. if you want to specify both MSB\n   * and LSB for messages between `0` and `31`, you can do so by passing a 2-value array as the\n   * second parameter.\n   *\n   * @param {number|string} controller The MIDI controller name or number (`0` - `127`).\n   *\n   * @param {number|number[]} value The value to send (0-127). You can also use a two-position array\n   * for controllers 0 to 31. In this scenario, the first value will be sent as usual and the second\n   * value will be sent to the matching LSB controller (which is obtained by adding 32 to the first\n   * controller)\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} Controller numbers must be between 0 and 127.\n   * @throws {RangeError} Invalid controller name.\n   * @throws {TypeError} The value array must have a length of 2.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   * @license Apache-2.0\n   * @since 3.0.0\n   */\n  sendControlChange(controller: number | string, value: number | number[], options?: {\n    time?: number | string;\n  }): OutputChannel;\n\n  private _selectNonRegisteredParameter;\n  private _deselectRegisteredParameter;\n  private _deselectNonRegisteredParameter;\n  private _selectRegisteredParameter;\n  private _setCurrentParameter;\n\n  /**\n   * Decrements the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this function:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws TypeError The specified registered parameter is invalid.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendRpnDecrement(parameter: string | number[], options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Increments the specified MIDI registered parameter by 1. Here is the full list of parameter\n   * names that can be used with this function:\n   *\n   *  * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n   *  * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n   *  * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n   *  * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n   *  * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n   *  * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n   *  * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n   *  * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n   *  * Gain (0x3D, 0x02): `\"gain\"`\n   *  * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n   *  * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n   *  * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n   *  * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n   *  * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n   *  * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n   *\n   * @param parameter {String|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (0x65, 0x64) that identify the registered\n   * parameter.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws TypeError The specified registered parameter is invalid.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendRpnIncrement(parameter: string | number[], options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Plays a note or an array of notes on the channel. The first parameter is the note to play. It\n   * can be a single value or an array of the following valid values:\n   *\n   *  - A [`Note`]{@link Note} object\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note name, followed by the octave (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *\n   * The `playNote()` method sends a **note on** MIDI message for all specified notes. If a\n   * `duration` is set in the `options` parameter or in the [`Note`]{@link Note} object's\n   * [`duration`]{@link Note#duration} property, it will also schedule a **note off** message\n   * to end the note after said duration. If no `duration` is set, the note will simply play until\n   * a matching **note off** message is sent with [`stopNote()`]{@link OutputChannel#stopNote} or\n   * [`sendNoteOff()`]{@link OutputChannel#sendNoteOff}.\n   *\n   *  The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the durations and velocities defined in the\n   * [`Note`]{@link Note} objects have precedence over the ones specified via the method's `options`\n   * parameter.\n   *\n   * **Note**: per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to play. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`,\n   * `F-1`, `Db7`), a [`Note`]{@link Note} object or an array of the previous types. When using a\n   * note identifier, the octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI\n   * note number `0`) and the highest note is `G9` (MIDI note number `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration] A positive decimal number larger than `0` representing the\n   * number of milliseconds to wait before sending a **note off** message. If invalid or left\n   * undefined, only a **note on** message will be sent.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`). If the `rawAttack` option is also defined, it will have priority. An invalid velocity\n   * value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The attack velocity at which to play the note (between\n   * `0` and `127`). This has priority over the `attack` property. An invalid velocity value will\n   * silently trigger the default of 64.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note (between `0`\n   * and `1`). If the `rawRelease` option is also defined, it will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`. This is only used with the\n   * **note off** event triggered when `options.duration` is set.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note (between `0`\n   * and `127`). This has priority over the `release` property. An invalid velocity value will\n   * silently trigger the default of 64. This is only used with the **note off** event triggered\n   * when `options.duration` is set.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  playNote(note: number | string | Note | number[] | string[] | Note[], options?: {\n    duration?: number;\n    attack?: number;\n    rawAttack?: number;\n    release?: number;\n    rawRelease?: number;\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a **note off** message for the specified notes on the channel. The first parameter is the\n   * note. It can be a single value or an array of the following valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note name, followed by the octave (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`]{@link Note} object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the release velocity defined in the\n   * [`Note`]{@link Note} objects has precedence over the one specified via the method's `options`\n   * parameter.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a\n   * [`Note`]{@link Note} object or an array of the previous types. When using a note name, octave\n   * range must be between -1 and 9. The lowest note is C-1 (MIDI note number 0) and the highest\n   * note is G9 (MIDI note number 127).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendNoteOff(note: number | string | Note | number[] | string[] | Note[], options?: {\n    time?: number | string;\n    release?: number;\n    rawRelease?: number;\n  }): OutputChannel;\n  /**\n   * Sends a **note off** message for the specified MIDI note number. The first parameter is the\n   * note to stop. It can be a single value or an array of the following valid values:\n   *\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *  - A [`Note`](Note) object\n   *\n   * The execution of the **note off** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * @param note {number|Note|string|number[]|Note[]|string[]} The note(s) to stop. The notes can be\n   * specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`,\n   * `Db7`) or an array of the previous types. When using a note identifier, octave range must be\n   * between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is\n   * `G9` (MIDI note number `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number} [options.release=0.5] The velocity at which to release the note\n   * (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawRelease=64] The velocity at which to release the note\n   * (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have\n   * priority. An invalid velocity value will silently trigger the default of `64`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {Output} Returns the `Output` object so methods can be chained.\n   */\n  stopNote(note: number | Note | string | number[] | Note[] | string[], options?: {\n    release?: number;\n    rawRelease?: number;\n    time?: number | string;\n  }): Output;\n  /**\n   * Sends a **note on** message for the specified note(s) on the channel. The first parameter is\n   * the note. It can be a single value or an array of the following valid values:\n   *\n   *  - A [`Note`]{@link Note} object\n   *  - A MIDI note number (integer between `0` and `127`)\n   *  - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n   *\n   *  When passing a [`Note`]{@link Note}object or a note name, the `octaveOffset` will be applied.\n   *  This is not the case when using a note number. In this case, we assume you know exactly which\n   *  MIDI note number should be sent out.\n   *\n   * The execution of the **note on** command can be delayed by using the `time` property of the\n   * `options` parameter.\n   *\n   * When using [`Note`]{@link Note} objects, the attack velocity defined in the\n   * [`Note`]{@link Note} objects has precedence over the one specified via the method's `options`\n   * parameter. Also, the `duration` is ignored. If you want to also send a **note off** message,\n   * use the [`playNote()`]{@link #playNote} method instead.\n   *\n   * **Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\n   * functionally equivalent to a **note off** message.\n   *\n   * @param note {number|string|Note|number[]|string[]|Note[]} The note(s) to play. The notes can be\n   * specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a\n   * [`Note`]{@link Note} object or an array of the previous types.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @param {number} [options.attack=0.5] The velocity at which to play the note (between `0` and\n   * `1`).  If the `rawAttack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `0.5`.\n   *\n   * @param {number} [options.rawAttack=64] The velocity at which to release the note (between `0`\n   * and `127`). If the `attack` option is also defined, `rawAttack` will have priority. An invalid\n   * velocity value will silently trigger the default of `64`.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendNoteOn(note: number | string | Note | number[] | string[] | Note[], options?: {\n    time?: number | string;\n    attack?: number;\n    rawAttack?: number;\n  }): OutputChannel;\n  /**\n   * Sends a MIDI **channel mode** message. The channel mode message to send can be specified\n   * numerically or by using one of the following common names:\n   *\n   * |  Type                |Number| Shortcut Method                                               |\n   * | ---------------------|------|-------------------------------------------------------------- |\n   * | `allsoundoff`        | 120  | [`sendAllSoundOff()`]{@link #sendAllSoundOff}                 |\n   * | `resetallcontrollers`| 121  | [`sendResetAllControllers()`]{@link #sendResetAllControllers} |\n   * | `localcontrol`       | 122  | [`sendLocalControl()`]{@link #sendLocalControl}               |\n   * | `allnotesoff`        | 123  | [`sendAllNotesOff()`]{@link #sendAllNotesOff}                 |\n   * | `omnimodeoff`        | 124  | [`sendOmniMode(false)`]{@link #sendOmniMode}                  |\n   * | `omnimodeon`         | 125  | [`sendOmniMode(true)`]{@link #sendOmniMode}                   |\n   * | `monomodeon`         | 126  | [`sendPolyphonicMode(\"mono\")`]{@link #sendPolyphonicMode}     |\n   * | `polymodeon`         | 127  | [`sendPolyphonicMode(\"poly\")`]{@link #sendPolyphonicMode}     |\n   *\n   * **Note**: as you can see above, to make it easier, all channel mode messages also have a matching\n   * helper method.\n   *\n   * It should be noted that, per the MIDI specification, only `localcontrol` and `monomodeon` may\n   * require a value that's not zero. For that reason, the `value` parameter is optional and\n   * defaults to 0.\n   *\n   * @param {number|string} command The numerical identifier of the channel mode message (integer\n   * between `120` and `127`) or its name as a string.\n   *\n   * @param {number} [value=0] The value to send (integer between `0` - `127`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendChannelMode(command: number | string, value?: number, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sets OMNI mode to `\"on\"` or `\"off\"`. MIDI's OMNI mode causes the instrument to respond to\n   * messages from all channels.\n   *\n   * It should be noted that support for OMNI mode is not as common as it used to be.\n   *\n   * @param [state=true] {boolean} Whether to activate OMNI mode (`true`) or not (`false`).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Invalid channel mode message name.\n   * @throws {RangeError} Channel mode controller numbers must be between 120 and 127.\n   * @throws {RangeError} Value must be an integer between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendOmniMode(state?: boolean, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a MIDI **channel aftertouch** message. For key-specific aftertouch, you should instead\n   * use [`sendKeyAftertouch()`]{@link #sendKeyAftertouch}.\n   *\n   * @param [pressure] {number} The pressure level (between `0` and `1`). If the `rawValue` option\n   * is set to `true`, the pressure can be defined by using an integer between `0` and `127`.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   * @throws RangeError Invalid channel aftertouch value.\n   */\n  sendChannelAftertouch(pressure?: number, options?: {\n    rawValue?: boolean;\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a **master tuning** message. The value is decimal and must be larger than -65 semitones\n   * and smaller than 64 semitones.\n   *\n   * Because of the way the MIDI specification works, the decimal portion of the value will be\n   * encoded with a resolution of 14bit. The integer portion must be between -64 and 63\n   * inclusively. This function actually generates two MIDI messages: a **Master Coarse Tuning** and\n   * a **Master Fine Tuning** RPN messages.\n   *\n   * @param [value=0.0] {number} The desired decimal adjustment value in semitones (-65 < x < 64)\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The value must be a decimal number between larger than -65 and smaller\n   * than 64.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendMasterTuning(value?: number, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a **modulation depth range** message to adjust the depth of the modulation wheel's range.\n   * The range can be specified with the `semitones` parameter, the `cents` parameter or by\n   * specifying both parameters at the same time.\n   *\n   * @param {number} semitones The desired adjustment value in semitones (integer between 0 and\n   * 127).\n   *\n   * @param {number} [cents=0] The desired adjustment value in cents (integer between 0 and 127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendModulationRange(semitones: number, cents?: number, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sets a non-registered parameter (NRPN) to the specified value. The NRPN is selected by passing\n   * in a two-position array specifying the values of the two control bytes. The value is specified\n   * by passing in a single integer (most cases) or an array of two integers.\n   *\n   * NRPNs are not standardized in any way. Each manufacturer is free to implement them any way\n   * they see fit. For example, according to the Roland GS specification, you can control the\n   * **vibrato rate** using NRPN (1, 8). Therefore, to set the **vibrato rate** value to **123** you\n   * would use:\n   *\n   * ```js\n   * WebMidi.outputs[0].channels[0].sendNrpnValue([1, 8], 123);\n   * ```\n   *\n   * In some rarer cases, you need to send two values with your NRPN messages. In such cases, you\n   * would use a 2-position array. For example, for its **ClockBPM** parameter (2, 63), Novation\n   * uses a 14-bit value that combines an MSB and an LSB (7-bit values). So, for example, if the\n   * value to send was 10, you could use:\n   *\n   * ```js\n   * WebMidi.outputs[0].channels[0].sendNrpnValue([2, 63], [0, 10]);\n   * ```\n   *\n   * For further implementation details, refer to the manufacturer's documentation.\n   *\n   * @param nrpn {number[]} A two-position array specifying the two control bytes (0x63,\n   * 0x62) that identify the non-registered parameter.\n   *\n   * @param [data=[]] {number|number[]} An integer or an array of integers with a length of 1 or 2\n   * specifying the desired data.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The control value must be between 0 and 127.\n   * @throws {RangeError} The msb value must be between 0 and 127\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendNrpnValue(nrpn: number[], data?: number | number[], options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a MIDI **pitch bend** message at the scheduled time. The resulting bend is relative to\n   * the pitch bend range that has been defined. The range can be set with\n   * [`sendPitchBendRange()`]{@link #sendPitchBendRange}. So, for example, if the pitch\n   * bend range has been set to 12 semitones, using a bend value of -1 will bend the note 1 octave\n   * below its nominal value.\n   *\n   * @param {number|number[]} [value] The intensity of the bend (between -1.0 and 1.0). A value of\n   * zero means no bend. If the `rawValue` option is set to `true`, the intensity of the bend can be\n   * defined by either using a single integer between 0 and 127 (MSB) or an array of two integers\n   * between 0 and 127 representing, respectively, the MSB (most significant byte) and the LSB\n   * (least significant byte). The MSB is expressed in semitones with `64` meaning no bend. A value\n   * lower than `64` bends downwards while a value higher than `64` bends upwards. The LSB is\n   * expressed in cents (1/100 of a semitone). An LSB of `64` also means no bend.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {boolean} [options.rawValue=false] A boolean indicating whether the value should be\n   * considered as a float between -1.0 and 1.0 (default) or as raw integer between 0 and 127 (or\n   * an array of 2 integers if using both MSB and LSB).\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendPitchBend(value?: number | number[], options?: {\n    rawValue?: boolean;\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a **pitch bend range** message at the scheduled time to adjust the range used by the\n   * pitch bend lever. The range is specified by using the `semitones` and `cents` parameters. For\n   * example, setting the `semitones` parameter to `12` means that the pitch bend range will be 12\n   * semitones above and below the nominal pitch.\n   *\n   * @param semitones {number} The desired adjustment value in semitones (between 0 and 127). While\n   * nothing imposes that in the specification, it is very common for manufacturers to limit the\n   * range to 2 octaves (-12 semitones to 12 semitones).\n   *\n   * @param [cents=0] {number} The desired adjustment value in cents (integer between 0-127).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The semitones value must be an integer between 0 and 127.\n   * @throws {RangeError} The cents value must be an integer between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendPitchBendRange(semitones: number, cents?: number, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a MIDI **program change** message at the scheduled time.\n   *\n   * @param [program=1] {number} The MIDI patch (program) number (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {TypeError} Failed to execute 'send' on 'MIDIOutput': The value at index 1 is greater\n   * than 0xFF.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   *\n   */\n  sendProgramChange(program?: number, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sets the specified MIDI registered parameter to the desired value. The value is defined with\n   * up to two bytes of data (msb, lsb) that each can go from 0 to 127.\n   *\n   * MIDI\n   * [registered parameters](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\n   * extend the original list of control change messages. The MIDI 1.0 specification lists only a\n   * limited number of them:\n   *\n   * | Numbers      | Function                 |\n   * |--------------|--------------------------|\n   * | (0x00, 0x00) | `pitchbendrange`         |\n   * | (0x00, 0x01) | `channelfinetuning`      |\n   * | (0x00, 0x02) | `channelcoarsetuning`    |\n   * | (0x00, 0x03) | `tuningprogram`          |\n   * | (0x00, 0x04) | `tuningbank`             |\n   * | (0x00, 0x05) | `modulationrange`        |\n   * | (0x3D, 0x00) | `azimuthangle`           |\n   * | (0x3D, 0x01) | `elevationangle`         |\n   * | (0x3D, 0x02) | `gain`                   |\n   * | (0x3D, 0x03) | `distanceratio`          |\n   * | (0x3D, 0x04) | `maximumdistance`        |\n   * | (0x3D, 0x05) | `maximumdistancegain`    |\n   * | (0x3D, 0x06) | `referencedistanceratio` |\n   * | (0x3D, 0x07) | `panspreadangle`         |\n   * | (0x3D, 0x08) | `rollangle`              |\n   *\n   * Note that the **Tuning Program** and **Tuning Bank** parameters are part of the *MIDI Tuning\n   * Standard*, which is not widely implemented.\n   *\n   * @param rpn {string|number[]} A string identifying the parameter's name (see above) or a\n   * two-position array specifying the two control bytes (e.g. `[0x65, 0x64]`) that identify the\n   * registered parameter.\n   *\n   * @param [data=[]] {number|number[]} An single integer or an array of integers with a maximum\n   * length of 2 specifying the desired data.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendRpnValue(rpn: string | number[], data?: number | number[], options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sets the MIDI tuning bank to use. Note that the **Tuning Bank** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param value {number} The desired tuning bank (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The bank value must be between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendTuningBank(value: number, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sets the MIDI tuning program to use. Note that the **Tuning Program** parameter is part of the\n   * *MIDI Tuning Standard*, which is not widely implemented.\n   *\n   * @param value {number} The desired tuning program (integer between `0` and `127`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @throws {RangeError} The program value must be between 0 and 127.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendTuningProgram(value: number, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Turns local control on or off. Local control is usually enabled by default. If you disable it,\n   * the instrument will no longer trigger its own sounds. It will only send the MIDI messages to\n   * its out port.\n   *\n   * @param [state=false] {boolean} Whether to activate local control (`true`) or disable it\n   * (`false`).\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendLocalControl(state?: boolean, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends an **all notes off** channel mode message. This will make all currently playing notes\n   * fade out just as if their key had been released. This is different from the\n   * [`sendAllSoundOff()`]{@link #sendAllSoundOff} method which mutes all sounds immediately.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendAllNotesOff(options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends an **all sound off** channel mode message. This will silence all sounds playing on that\n   * channel but will not prevent new sounds from being triggered.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendAllSoundOff(options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sends a **reset all controllers** channel mode message. This resets all controllers, such as\n   * the pitch bend, to their default value.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendResetAllControllers(options?: {\n    time?: number | string;\n  }): OutputChannel;\n  /**\n   * Sets the polyphonic mode. In `\"poly\"` mode (usually the default), multiple notes can be played\n   * and heard at the same time. In `\"mono\"` mode, only one note will be heard at once even if\n   * multiple notes are being played.\n   *\n   * @param {string} [mode=poly] The mode to use: `\"mono\"` or `\"poly\"`.\n   *\n   * @param {Object} [options={}]\n   *\n   * @param {number|string} [options.time=(now)] If `time` is a string prefixed with `\"+\"` and\n   * followed by a number, the message will be delayed by that many milliseconds. If the value is a\n   * positive number\n   * ([`DOMHighResTimeStamp`]{@link https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp}),\n   * the operation will be scheduled for that time. The current time can be retrieved with\n   * [`WebMidi.time`]{@link WebMidi#time}. If `options.time` is omitted, or in the past, the\n   * operation will be carried out as soon as possible.\n   *\n   * @returns {OutputChannel} Returns the `OutputChannel` object so methods can be chained.\n   */\n  sendPolyphonicMode(mode?: string, options?: {\n    time?: number | string;\n  }): OutputChannel;\n  set octaveOffset(arg: number);\n  /**\n   * An integer to offset the reported octave of outgoing note-specific messages (`noteon`,\n   * `noteoff` and `keyaftertouch`). By default, middle C (MIDI note number 60) is placed on the 4th\n   * octave (C4).\n   *\n   * Note that this value is combined with the global offset value defined in\n   * [`WebMidi.octaveOffset`](WebMidi#octaveOffset) and with the parent value defined in\n   * [`Output.octaveOffset`]{@link Output#octaveOffset}.\n   *\n   * @type {number}\n   *\n   * @since 3.0\n   */\n  get octaveOffset(): number;\n  /**\n   * The parent [`Output`]{@link Output} this channel belongs to.\n   * @type {Output}\n   * @since 3.0\n   */\n  get output(): Output;\n  /**\n   * This channel's MIDI number (`1` - `16`).\n   * @type {number}\n   * @since 3.0\n   */\n  get number(): number;\n}\n\n/**\n * The `Utilities` class contains general-purpose utility methods. All methods are static and\n * should be called using the class name. For example: `Utilities.getNoteDetails(\"C4\")`.\n *\n * @license Apache-2.0\n * @since 3.0.0\n */\nexport class Utilities {\n\n  /**\n   * Converts the `input` parameter to a valid [`Note`]{@link Note} object. The input usually is an\n   * unsigned integer (0-127) or a note identifier (`\"C4\"`, `\"G#5\"`, etc.). If the input is a\n   * [`Note`]{@link Note} object, it will be returned as is.\n   *\n   * If the input is a note number or identifier, it is possible to specify options by providing the\n   * `options` parameter.\n   *\n   * @param [input] {number|string|Note}\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the note should\n   * be explicitly stopped.\n   *\n   * @param {number} [options.attack=0.5] The note's attack velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.release=0.5] The note's release velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawAttack=64] The note's attack velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawRelease=64] The note's release velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.octaveOffset=0] An integer to offset the octave by. **This is only\n   * used when the input value is a note identifier.**\n   *\n   * @returns {Note}\n   *\n   * @throws TypeError The input could not be parsed to a note\n   *\n   * @since version 3.0.0\n   * @static\n   */\n  static buildNote(input?: number | string | Note, options?: {\n    duration?: number;\n    attack?: number;\n    release?: number;\n    rawAttack?: number;\n    rawRelease?: number;\n    octaveOffset?: number;\n  }): Note;\n\n  /**\n   * Converts an input value, which can be an unsigned integer (0-127), a note identifier, a\n   * [`Note`]{@link Note}  object or an array of the previous types, to an array of\n   * [`Note`]{@link Note}  objects.\n   *\n   * [`Note`]{@link Note}  objects are returned as is. For note numbers and identifiers, a\n   * [`Note`]{@link Note} object is created with the options specified. An error will be thrown when\n   * encountering invalid input.\n   *\n   * Note: if both the `attack` and `rawAttack` options are specified, the later has priority. The\n   * same goes for `release` and `rawRelease`.\n   *\n   * @param [notes] {number|string|Note|number[]|string[]|Note[]}\n   *\n   * @param {object} [options={}]\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the note should\n   * be explicitly stopped.\n   *\n   * @param {number} [options.attack=0.5] The note's attack velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.release=0.5] The note's release velocity as a float between 0 and 1. If\n   * you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawAttack=64] The note's attack velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `attack` and `rawAttack` are specified, the latter has precedence.\n   *\n   * @param {number} [options.rawRelease=64] The note's release velocity as an integer between 0 and\n   * 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both\n   * `release` and `rawRelease` are specified, the latter has precedence.\n   *\n   * @param {number} [options.octaveOffset=0] An integer to offset the octave by. **This is only\n   * used when the input value is a note identifier.**\n   *\n   * @returns {Note[]}\n   *\n   * @throws TypeError An element could not be parsed as a note.\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static buildNoteArray(notes?: number | string | Note | number[] | string[] | Note[], options?: {\n    duration?: number;\n    attack?: number;\n    release?: number;\n    rawAttack?: number;\n    rawRelease?: number;\n    octaveOffset?: number;\n  }): Note[];\n\n  /**\n   * Returns a number between 0 and 1 representing the ratio of the input value divided by 127 (7\n   * bit). The returned value is restricted between 0 and 1 even if the input is greater than 127 or\n   * smaller than 0.\n   *\n   * Passing `Infinity` will return `1` and passing `-Infinity` will return `0`. Otherwise, when the\n   * input value cannot be converted to an integer, the method returns 0.\n   *\n   * @param value {number} A positive integer between 0 and 127 (inclusive)\n   * @returns {number} A number between 0 and 1 (inclusive)\n   * @static\n   */\n  static from7bitToFloat(value: number): number;\n\n  /**\n   * Returns an integer between 0 and 127 which is the result of multiplying the input value by\n   * 127. The input value should be a number between 0 and 1 (inclusively). The returned value is\n   * restricted between 0 and 127 even if the input is greater than 1 or smaller than 0.\n   *\n   * Passing `Infinity` will return `127` and passing `-Infinity` will return `0`. Otherwise, when\n   * the input value cannot be converted to a number, the method returns 0.\n   *\n   * @param value {number} A positive float between 0 and 1 (inclusive)\n   * @returns {number} A number between 0 and 127 (inclusive)\n   * @static\n   */\n  static fromFloatTo7Bit(value: number): number;\n\n  /**\n   * Extracts 7bit MSB and LSB values from the supplied float.\n   *\n   * @param value {number} A float between 0 and 1\n   * @returns {{lsb: number, msb: number}}\n   */\n  static fromFloatToMsbLsb(value: number): {\n    lsb: number;\n    msb: number;\n  };\n\n  /**\n   * Combines and converts MSB and LSB values (0-127) to a float between 0 and 1. The returned value\n   * is within between 0 and 1 even if the result is greater than 1 or smaller than 0.\n   *\n   * @param msb {number} The most significant byte as a integer between 0 and 127.\n   * @param [lsb=0] {number} The least significant byte as a integer between 0 and 127.\n   * @returns {number} A float between 0 and 1.\n   */\n  static fromMsbLsbToFloat(msb: number, lsb?: number): number;\n\n  /**\n   * Returns the name of a control change message matching the specified number (0-127). Some valid\n   * control change numbers do not have a specific name or purpose assigned in the MIDI\n   * [spec](https://midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2).\n   * In these cases, the method returns `controllerXXX` (where XXX is the number).\n   *\n   * @param {number} number An integer (0-127) representing the control change message\n   * @returns {string|undefined} The matching control change name or `undefined` if no match was\n   * found.\n   *\n   * @static\n   */\n  static getCcNameByNumber(number: number): string | undefined;\n\n  /**\n   * Returns the number of a control change message matching the specified name.\n   *\n   * @param {string} name A string representing the control change message\n   * @returns {string|undefined} The matching control change number or `undefined` if no match was\n   * found.\n   *\n   * @since 3.1\n   * @static\n   */\n  static getCcNumberByName(name: string): number | undefined;\n\n  /**\n   * Returns the channel mode name matching the specified number. If no match is found, the function\n   * returns `false`.\n   *\n   * @param {number} number An integer representing the channel mode message (120-127)\n   * @returns {string|false} The name of the matching channel mode or `false` if no match could be\n   * found.\n   *\n   * @since 2.0.0\n   */\n  static getChannelModeByNumber(number: number): string | false;\n\n  /**\n   * Given a proper note identifier (`C#4`, `Gb-1`, etc.) or a valid MIDI note number (0-127), this\n   * method returns an object containing broken down details about the specified note (uppercase\n   * letter, accidental and octave).\n   *\n   * When a number is specified, the translation to note is done using a value of 60 for middle C\n   * (C4 = middle C).\n   *\n   * @param value {string|number} A note identifier A  atring (\"C#4\", \"Gb-1\", etc.) or a MIDI note\n   * number (0-127).\n   *\n   * @returns {{accidental: string, identifier: string, name: string, octave: number }}\n   *\n   * @throws TypeError Invalid note identifier\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static getNoteDetails(value: string | number): {\n    accidental: string;\n    identifier: string;\n    name: string;\n    octave: number;\n  };\n\n  /**\n   * Returns the name of the first property of the supplied object whose value is equal to the one\n   * supplied. If nothing is found, `undefined` is returned.\n   *\n   * @param object {object} The object to look for the property in.\n   * @param value {*} Any value that can be expected to be found in the object's properties.\n   * @returns {string|undefined} The name of the matching property or `undefined` if nothing is\n   * found.\n   * @static\n   */\n  static getPropertyByValue(object: object, value: any): string | undefined;\n\n  /**\n   * Returns a valid MIDI note number (0-127) given the specified input. The input usually is a\n   * string containing a note identifier (`\"C3\"`, `\"F#4\"`, `\"D-2\"`, `\"G8\"`, etc.). If an integer\n   * between 0 and 127 is passed, it will simply be returned as is (for convenience). Other strings\n   * will be parsed for integer value, if possible.\n   *\n   * If the input is an identifier, the resulting note number is offset by the `octaveOffset`\n   * parameter. For example, if you pass in \"C4\" (note number 60) and the `octaveOffset` value is\n   * -2, the resulting MIDI note number will be 36.\n   *\n   * @param input {string|number} A string or number to extract the MIDI note number from.\n   * @param octaveOffset {number} An integer to offset the octave by\n   *\n   * @returns {number|false} A valid MIDI note number (0-127) or `false` if the input could not\n   * successfully be parsed to a note number.\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static guessNoteNumber(input: string | number, octaveOffset: number): number | false;\n\n  /**\n   * Indicates whether the execution environment is Node.js (`true`) or not (`false`)\n   * @type {boolean}\n   */\n  static get isNode(): boolean;\n\n  /**\n   * Indicates whether the execution environment is a browser (`true`) or not (`false`)\n   * @type {boolean}\n   */\n  static get isBrowser(): boolean;\n\n  /**\n   * Returns the supplied MIDI note number offset by the requested octave and semitone values. If\n   * the calculated value is less than 0, 0 will be returned. If the calculated value is more than\n   * 127, 127 will be returned. If an invalid offset value is supplied, 0 will be used.\n   *\n   * @param number {number} The MIDI note to offset as an integer between 0 and 127.\n   * @param octaveOffset {number} An integer to offset the note by (in octave)\n   * @param octaveOffset {number} An integer to offset the note by (in semitones)\n   * @returns {number} An integer between 0 and 127\n   *\n   * @throws {Error} Invalid note number\n   * @static\n   */\n  static offsetNumber(number: number, octaveOffset?: number, semitoneOffset?: number): number;\n\n  /**\n   * Returns a sanitized array of valid MIDI channel numbers (1-16). The parameter should be a\n   * single integer or an array of integers.\n   *\n   * For backwards-compatibility, passing `undefined` as a parameter to this method results in all\n   * channels being returned (1-16). Otherwise, parameters that cannot successfully be parsed to\n   * integers between 1 and 16 are silently ignored.\n   *\n   * @param [channel] {number|number[]} An integer or an array of integers to parse as channel\n   * numbers.\n   *\n   * @returns {number[]} An array of 0 or more valid MIDI channel numbers.\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static sanitizeChannels(channel?: number | number[]): number[];\n\n  /**\n   * Returns an identifier string representing a note name (with optional accidental) followed by an\n   * octave number. The octave can be offset by using the `octaveOffset` parameter.\n   *\n   * @param {number} number The MIDI note number to convert to a note identifier\n   * @param {number} octaveOffset An offset to apply to the resulting octave\n   *\n   * @returns {string}\n   *\n   * @throws RangeError Invalid note number\n   * @throws RangeError Invalid octaveOffset value\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static toNoteIdentifier(number: number, octaveOffset: number): string;\n\n  /**\n   * Returns a MIDI note number matching the identifier passed in the form of a string. The\n   * identifier must include the octave number. The identifier also optionally include a sharp (#),\n   * a double sharp (##), a flat (b) or a double flat (bb) symbol. For example, these are all valid\n   * identifiers: C5, G4, D#-1, F0, Gb7, Eb-1, Abb4, B##6, etc.\n   *\n   * When converting note identifiers to numbers, C4 is considered to be middle C (MIDI note number\n   * 60) as per the scientific pitch notation standard.\n   *\n   * The resulting note number can be offset by using the `octaveOffset` parameter.\n   *\n   * @param identifier {string} The identifier in the form of a letter, followed by an optional \"#\",\n   * \"##\", \"b\" or \"bb\" followed by the octave number. For exemple: C5, G4, D#-1, F0, Gb7, Eb-1,\n   * Abb4, B##6, etc.\n   *\n   * @param {number} [octaveOffset=0] A integer to offset the octave by.\n   *\n   * @returns {number} The MIDI note number (an integer between 0 and 127).\n   *\n   * @throws RangeError Invalid 'octaveOffset' value\n   *\n   * @throws TypeError Invalid note identifier\n   *\n   * @license Apache-2.0\n   * @since 3.0.0\n   * @static\n   */\n  static toNoteNumber(identifier: string, octaveOffset?: number): number;\n\n  /**\n   * Returns a valid timestamp, relative to the navigation start of the document, derived from the\n   * `time` parameter. If the parameter is a string starting with the \"+\" sign and followed by a\n   * number, the resulting timestamp will be the sum of the current timestamp plus that number. If\n   * the parameter is a positive number, it will be returned as is. Otherwise, false will be\n   * returned.\n   *\n   * @param [time] {number|string} The time string (e.g. `\"+2000\"`) or number to parse\n   * @return {number|false} A positive number or `false` (if the time cannot be converted)\n   *\n   * @since 3.0.0\n   * @static\n   */\n  static toTimestamp(time?: number | string): number | false;\n\n}\n\n/**\n * The `WebMidi` object makes it easier to work with the low-level Web MIDI API. Basically, it\n * simplifies sending outgoing MIDI messages and reacting to incoming MIDI messages.\n *\n * When using the WebMidi.js library, you should know that the `WebMidi` class has already been\n * instantiated. You cannot instantiate it yourself. If you use the **IIFE** version, you should\n * simply use the global object called `WebMidi`. If you use the **CJS** (CommonJS) or **ESM** (ES6\n * module) version, you get an already-instantiated object when you import the module.\n *\n * @fires WebMidi#connected\n * @fires WebMidi#disabled\n * @fires WebMidi#disconnected\n * @fires WebMidi#enabled\n * @fires WebMidi#error\n * @fires WebMidi#midiaccessgranted\n * @fires WebMidi#portschanged\n *\n * @extends EventEmitter\n * @license Apache-2.0\n */\ndeclare class WebMidi extends EventEmitter {\n\n  /**\n   * The WebMidi class is a singleton and you cannot instantiate it directly. It has already been\n   * instantiated for you.\n   */\n  constructor();\n\n  private _destroyInputsAndOutputs;\n  private _disconnectedInputs;\n  private _disconnectedOutputs;\n  private _inputs;\n  private _octaveOffset;\n  private _onInterfaceStateChange;\n  private _outputs;\n  private _updateInputs;\n  private _updateInputsAndOutputs;\n  private _updateOutputs;\n  private _stateChangeQueue;\n\n  /**\n   * Object containing system-wide default values that can be changed to customize how the library\n   * works.\n   *\n   * @type {object}\n   *\n   * @property {object}  defaults.note - Default values relating to note\n   * @property {number}  defaults.note.attack - A number between 0 and 127 representing the\n   * default attack velocity of notes. Initial value is 64.\n   * @property {number}  defaults.note.release - A number between 0 and 127 representing the\n   * default release velocity of notes. Initial value is 64.\n   * @property {number}  defaults.note.duration - A number representing the default duration of\n   * notes (in seconds). Initial value is Infinity.\n   */\n  defaults: object;\n\n  /**\n   * The [`MIDIAccess`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess)\n   * instance used to talk to the lower-level Web MIDI API. This should not be used directly\n   * unless you know what you are doing.\n   *\n   * @type {WebMidiApi.MIDIAccess}\n   * @readonly\n   */\n  interface: WebMidiApi.MIDIAccess;\n\n  /**\n   * Indicates whether argument validation and backwards-compatibility checks are performed\n   * throughout the WebMidi.js library for object methods and property setters.\n   *\n   * This is an advanced setting that should be used carefully. Setting `validation` to `false`\n   * improves performance but should only be done once the project has been thoroughly tested with\n   * `validation` turned on.\n   *\n   * @type {boolean}\n   */\n  validation: boolean;\n\n  /**\n   * Adds an event listener that will trigger a function callback when the specified event is\n   * dispatched.\n   *\n   * Here are the events you can listen to:   connected, disabled, disconnected, enabled,\n   * midiaccessgranted, portschanged, error\n   *\n   * @param event {string | Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event\n   * is detected. This function will receive an event parameter object. For details on this object's\n   * properties, check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @param {number} [options.remaining=Infinity] The number of times after which the callback\n   * should automatically be removed.\n   *\n   * @returns {Listener} The listener object that was created\n   */\n  addListener<T extends keyof WebMidiEventMap>(\n    e: Symbol | T,\n    listener: WebMidiEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n      \"remaining\"?: number;\n    }\n  ): Listener;\n\n  /**\n   * Adds a one-time event listener that will trigger a function callback when the specified event\n   * is dispatched.\n   *\n   * Here are the events you can listen to:   connected, disabled, disconnected, enabled,\n   * midiaccessgranted, portschanged, error\n   *\n   * @param event {string | Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} A callback function to execute when the specified event\n   * is detected. This function will receive an event parameter object. For details on this object's\n   * properties, check out the documentation for the various events (links above).\n   *\n   * @param {object} [options={}]\n   *\n   * @param {array} [options.arguments] An array of arguments which will be passed separately to the\n   * callback function. This array is stored in the [`arguments`](Listener#arguments) property of\n   * the [`Listener`](Listener) object and can be retrieved or modified as desired.\n   *\n   * @param {object} [options.context=this] The value of `this` in the callback function.\n   *\n   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener\n   * automatically expires.\n   *\n   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning\n   * of the listeners array and thus be triggered before others.\n   *\n   * @returns {Listener} The listener object that was created\n   */\n  addOneTimeListener<T extends keyof WebMidiEventMap>(\n    e: Symbol | T,\n    listener: WebMidiEventMap[T],\n    options?: {\n      \"arguments\"?: any[];\n      \"context\"?: any;\n      \"duration\"?: number;\n      \"prepend\"?: boolean;\n    }\n  ): Listener;\n\n  /**\n   * Completely disables **WebMidi.js** by unlinking the MIDI subsystem's interface and closing all\n   * [`Input`](Input) and [`Output`](Output) objects that may have been opened. This also means that\n   * listeners added to [`Input`](Input) objects, [`Output`](Output) objects or to `WebMidi` itself\n   * are also destroyed.\n   *\n   * @async\n   * @returns {Promise<Array>}\n   *\n   * @throws {Error} The Web MIDI API is not supported by your environment.\n   *\n   * @since 2.0.0\n   */\n  disable(): Promise<any[]>;\n\n  /**\n   * Checks if the Web MIDI API is available in the current environment and then tries to connect to\n   * the host's MIDI subsystem. This is an asynchronous operation and it causes a security prompt to\n   * be displayed to the user.\n   *\n   * To enable the use of MIDI system exclusive messages, the `sysex` option should be set to\n   * `true`. However, under some environments (e.g. Jazz-Plugin), the `sysex` option is ignored\n   * and system exclusive messages are always enabled. You can check the\n   * [`sysexEnabled`](#sysexEnabled) property to confirm.\n   *\n   * To enable access to software synthesizers available on the host, you would set the `software`\n   * option to `true`. However, this option is only there to future-proof the library as support for\n   * software synths has not yet been implemented in any browser (as of September 2021).\n   *\n   * By the way, if you call the [`enable()`](#enable) method while WebMidi.js is already enabled,\n   * the callback function will be executed (if any), the promise will resolve but the events\n   * ([`\"midiaccessgranted\"`](#event:midiaccessgranted), [`\"connected\"`](#event:connected) and\n   * [`\"enabled\"`](#event:enabled)) will not be fired.\n   *\n   * There are 3 ways to execute code after `WebMidi` has been enabled:\n   *\n   * - Pass a callback function in the `options`\n   * - Listen to the [`\"enabled\"`](#event:enabled) event\n   * - Wait for the promise to resolve\n   *\n   * In order, this is what happens towards the end of the enabling process:\n   *\n   * 1. [`\"midiaccessgranted\"`](#event:midiaccessgranted) event is triggered once the user has\n   * granted access to use MIDI.\n   * 2. [`\"connected\"`](#event:connected) events are triggered (for each available input and output)\n   * 3. [`\"enabled\"`](#event:enabled) event is triggered when WebMidi.js is fully ready\n   * 4. specified callback (if any) is executed\n   * 5. promise is resolved and fulfilled with the `WebMidi` object.\n   *\n   * **Important note**: starting with Chrome v77, a page using Web MIDI API must be hosted on a\n   * secure origin (`https://`, `localhost` or `file:///`) and the user will always be prompted to\n   * authorize the operation (no matter if the `sysex` option is `true` or not).\n   *\n   * ##### Example\n   * ```js\n   * // Enabling WebMidi and using the promise\n   * WebMidi.enable().then(() => {\n   *   console.log(\"WebMidi.js has been enabled!\");\n   * })\n   * ```\n   *\n   * @param [options] {object}\n   *\n   * @param [options.callback] {function} A function to execute once the operation completes. This\n   * function will receive an `Error` object if enabling the Web MIDI API failed.\n   *\n   * @param [options.sysex=false] {boolean} Whether to enable MIDI system exclusive messages or not.\n   *\n   * @param [options.validation=true] {boolean} Whether to enable library-wide validation of method\n   * arguments and setter values. This is an advanced setting that should be used carefully. Setting\n   * [`validation`](#validation) to `false` improves performance but should only be done once the\n   * project has been thoroughly tested with [`validation`](#validation)  turned on.\n   *\n   * @param [options.software=false] {boolean} Whether to request access to software synthesizers on\n   * the host system. This is part of the spec but has not yet been implemented by most browsers as\n   * of April 2020.\n   *\n   * @async\n   *\n   * @returns {Promise.<WebMidi>} The promise is fulfilled with the `WebMidi` object for\n   * chainability\n   *\n   * @throws {Error} The Web MIDI API is not supported in your environment.\n   * @throws {Error} Jazz-Plugin must be installed to use WebMIDIAPIShim.\n   */\n  enable(options?: {\n    callback?: Function;\n    sysex?: boolean;\n    validation?: boolean;\n    software?: boolean;\n    requestMIDIAccessFunction?: Function;\n  }): Promise<WebMidi>;\n\n  /**\n   * Returns the [`Input`](Input) object that matches the specified ID string or `false` if no\n   * matching input is found. As per the Web MIDI API specification, IDs are strings (not integers).\n   *\n   * Please note that IDs change from one host to another. For example, Chrome does not use the same\n   * kind of IDs as Jazz-Plugin.\n   *\n   * @param id {string} The ID string of the input. IDs can be viewed by looking at the\n   * [`WebMidi.inputs`](WebMidi#inputs) array. Even though they sometimes look like integers, IDs\n   * are strings.\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected input\n   *\n   * @returns {Input} An [`Input`](Input) object matching the specified ID string or `undefined`\n   * if no matching input can be found.\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getInputById(id: string, options?: {\n    disconnected?: boolean;\n  }): Input;\n\n  /**\n   * Returns the first [`Input`](Input) object whose name **contains** the specified string. Note\n   * that the port names change from one environment to another. For example, Chrome does not report\n   * input names in the same way as the Jazz-Plugin does.\n   *\n   * @param name {string} The non-empty string to look for within the name of MIDI inputs (such as\n   * those visible in the [inputs](WebMidi#inputs) array).\n   *\n   * @returns {Input | undefined} The [`Input`](Input) that was found or `undefined` if no input contained the\n   * specified name.\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected input\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getInputByName(name: string, options?: {\n    disconnected?: boolean;\n  }): Input | undefined;\n\n  /**\n   * Returns the [`Output`](Output) object that matches the specified ID string or `false` if no\n   * matching output is found. As per the Web MIDI API specification, IDs are strings (not\n   * integers).\n   *\n   * Please note that IDs change from one host to another. For example, Chrome does not use the same\n   * kind of IDs as Jazz-Plugin.\n   *\n   * @param id {string} The ID string of the port. IDs can be viewed by looking at the\n   * [`WebMidi.outputs`](WebMidi#outputs) array.\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected output\n   *\n   * @returns {Output | undefined} An [`Output`](Output) object matching the specified ID string. If no\n   * matching output can be found, the method returns `undefined`.\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getOutputById(id: string, options?: {\n    disconnected?: boolean;\n  }): Output | undefined;\n\n  /**\n   * Returns the first [`Output`](Output) object whose name **contains** the specified string. Note\n   * that the port names change from one environment to another. For example, Chrome does not report\n   * input names in the same way as the Jazz-Plugin does.\n   *\n   * @param name {string} The non-empty string to look for within the name of MIDI inputs (such as\n   * those visible in the [`outputs`](#outputs) array).\n   * @param [options] {object}\n   * @param [options.disconnected] {boolean} Whether to retrieve a disconnected output\n   *\n   * @returns {Output} The [`Output`](Output) that was found or `undefined` if no output matched\n   * the specified name.\n   *\n   * @throws {Error} WebMidi is not enabled.\n   *\n   * @since 2.0.0\n   */\n  getOutputByName(name: string, options?: {\n    disconnected?: boolean;\n  }): Output;\n\n  /**\n   * Checks if the specified event type is already defined to trigger the specified callback\n   * function.\n   *\n   * @param event {string|Symbol} The type of the event.\n   *\n   * @param listener {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @returns {boolean} Boolean value indicating whether or not the `Input` or `InputChannel`\n   * already has this listener defined.\n   */\n  hasListener<T extends keyof WebMidiEventMap>(\n    e: Symbol | T,\n    listener: WebMidiEventMap[T]\n  ): boolean;\n\n  /**\n   * Removes the specified listener for the specified event. If no listener is specified, all\n   * listeners for the specified event will be removed.\n   *\n   * @param [type] {string} The type of the event.\n   *\n   * @param [listener] {EventEmitterCallback} The callback function to check for.\n   *\n   * @param {object} [options={}]\n   *\n   * @param {*} [options.context] Only remove the listeners that have this exact context.\n   *\n   * @param {number} [options.remaining] Only remove the listener if it has exactly that many\n   * remaining times to be executed.\n   */\n  removeListener<T extends keyof WebMidiEventMap>(\n    type?: Symbol | T,\n    listener?: WebMidiEventMap[T],\n    options?: {\n      \"context\"?: any;\n      \"remaining\"?: number;\n    }\n  ): void;\n\n  /**\n   * Indicates whether access to the host's MIDI subsystem is active or not.\n   *\n   * @readonly\n   * @type {boolean}\n   */\n  get enabled(): boolean;\n\n  /**\n   * An array of all currently available MIDI inputs.\n   *\n   * @readonly\n   * @type {Input[]}\n   */\n  get inputs(): Input[];\n\n  /**\n   * An integer to offset the octave of notes received from external devices or sent to external\n   * devices.\n   *\n   * When a MIDI message comes in on an input channel the reported note name will be offset. For\n   * example, if the `octaveOffset` is set to `-1` and a [`\"noteon\"`](InputChannel#event:noteon)\n   * message with MIDI number 60 comes in, the note will be reported as C3 (instead of C4).\n   *\n   * By the same token, when [`OutputChannel.playNote()`](OutputChannel#playNote) is called, the\n   * MIDI note number being sent will be offset. If `octaveOffset` is set to `-1`, the MIDI note\n   * number sent will be 72 (instead of 60).\n   *\n   * @type {number}\n   *\n   * @since 2.1\n   */\n  set octaveOffset(arg: number);\n  get octaveOffset(): number;\n\n  /**\n   * An array of all currently available MIDI outputs as [`Output`](Output) objects.\n   *\n   * @readonly\n   * @type {Output[]}\n   */\n  get outputs(): Output[];\n\n  /**\n   * Indicates whether the environment provides support for the Web MIDI API or not.\n   *\n   * **Note**: in environments that do not offer built-in MIDI support, this will report `true` if\n   * the\n   * [`navigator.requestMIDIAccess`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess)\n   * function is available. For example, if you have installed WebMIDIAPIShim.js but no plugin, this\n   * property will be `true` even though actual support might not be there.\n   *\n   * @readonly\n   * @type {boolean}\n   */\n  get supported(): boolean;\n\n  /**\n   * Indicates whether MIDI system exclusive messages have been activated when WebMidi.js was\n   * enabled via the [`enable()`](#enable) method.\n   *\n   * @readonly\n   * @type boolean\n   */\n  get sysexEnabled(): boolean;\n\n  /**\n   * The elapsed time, in milliseconds, since the time\n   * [origin](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin).\n   * Said simply, it is the number of milliseconds that passed since the page was loaded. Being a\n   * floating-point number, it has sub-millisecond accuracy. According to the\n   * [documentation](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp), the\n   * time should be accurate to 5 µs (microseconds). However, due to various constraints, the\n   * browser might only be accurate to one millisecond.\n   *\n   * Note: `WebMidi.time` is simply an alias to `performance.now()`.\n   *\n   * @type {DOMHighResTimeStamp}\n   * @readonly\n   */\n  get time(): number;\n\n  /**\n   * The version of the library as a [semver](https://semver.org/) string.\n   *\n   * @readonly\n   * @type string\n   */\n  get version(): string;\n\n  /**\n   * The flavour of the library. Can be one of:\n   *\n   * * `esm`: ECMAScript Module\n   * * `cjs`: CommonJS Module\n   * * `iife`: Immediately-Invoked Function Expression\n   *\n   * @readonly\n   * @type string\n   * @since 3.0.25\n   */\n  get flavour(): string;\n\n}\n\ndeclare const wm: WebMidi;\nexport { wm as WebMidi };\n\n/**\n * The callback function is executed when the associated event is triggered via [`emit()`](#emit).\n * The [`emit()`](#emit) method relays all additional arguments it received to the callback\n * functions. Since [`emit()`](#emit) can be passed a variable number of arguments, it is up to\n * the developer to make sure the arguments match those of the associated callback. In addition,\n * the callback also separately receives all the arguments present in the listener's\n * [`arguments`](Listener#arguments) property. This makes it easy to pass data from where the\n * listener is added to where the listener is executed.\n *\n * @callback EventEmitterCallback\n * @param {...*} [args] A variable number of arguments matching the ones (if any) that were passed\n * to the [`emit()`](#emit) method (except, the first one) followed by the arguments found in the\n * listener's [`arguments`](Listener#arguments) array.\n */\nexport type EventEmitterCallback = (...args: any[]) => void;\n\n/**\n * The `Event` object is transmitted when state change events occur.\n *\n * WebMidi\n *  * disabled\n *  * enabled\n *  * midiaccessgranted\n *\n * @property {WebMidi} target The object that dispatched the event.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type The type of the event\n *\n */\nexport interface Event {\n  target: Input | InputChannel | Output | WebMidi;\n  timestamp: DOMHighResTimeStamp;\n  type: string;\n}\n\n/**\n * The `ErrorEvent` object is transmitted when an error occurs. Only the `WebMidi` object dispatches\n * this type of event.\n *\n * @property {Input} target The object that dispatched the event.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type The type of the event\n *\n * @property {*} error The type of the event\n *\n */\nexport interface ErrorEvent extends Event {\n  error: any;\n  target: WebMidi;\n}\n\n/**\n * The `PortEvent` object is transmitted when an event occurs on an `Input` or `Output` port.\n *\n * Input\n *  * closed\n *  * disconnected\n *  * opened\n *\n * Output\n *  * closed\n *  * disconnected\n *  * opened\n *\n * WebMidi\n *  * connected\n *  * disconnected\n *  * portschanged\n *\n * @property {Input|Output} port The `Input` or `Output` that triggered the event\n * @property {Input | InputChannel | Output | WebMidi} target The object that dispatched the event.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type The type of the event\n *\n */\nexport interface PortEvent extends Event {\n  // port: Input | Output;\n  port: any; // temporary fix, see issue #229\n}\n\n/**\n * The `MessageEvent` object is transmitted when a MIDI message has been received. These events\n * are dispatched by `Input` and `InputChannel` classes:\n *\n * `Input`: activesensing, clock, continue, midimessage, reset, songposition, songselect, start,\n * stop, sysex, timecode, tunerequest, unknownmessage\n *\n * `InputChannel`: allnotesoff, allsoundoff, midimessage, resetallcontrollers, channelaftertouch,\n * localcontrol, monomode, omnimode, pitchbend, programchange\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input | InputChannel} target The object that dispatched the event.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type The type of the event\n *\n * @property {Message} message An object containing details about the actual MIDI message content.\n * @property {number} [rawValue] The raw value of the message (if any) between 0-127.\n * @property {number | boolean} [value] The value of the message (if any)\n */\nexport interface MessageEvent extends PortEvent {\n  message: Message;\n  port: Input;\n  rawValue?: number;\n  target: Input | InputChannel;\n  value?: number | boolean;\n  data: Uint8Array;\n  rawData: Uint8Array;\n  statusByte: Number;\n  dataBytes: Uint8Array;\n}\n\n/**\n * The `ControlChangeMessageEvent` object is transmitted when a control change MIDI message has been\n * received. There is a general `controlchange` event and a specific `controlchange-controllerxxx`\n * for each controller.\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input | InputChannel} target The object that dispatched the event.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type The type of the event\n *\n * @property {Message} message An object containing details about the actual MIDI message content.\n * @property {number} [rawValue] The raw value of the message (if any) between 0-127.\n * @property {number | boolean} [value] The value of the message (if any)\n *\n * @property {object} controller\n * @property {string} controller.name The name of the controller\n * @property {number} controller.number The number of the controller (between 0-127)\n * @property {string} controller.description Uesr-friendly representation of the controller's\n * default function.\n * @property {string} controller.position Whether the controller is meant to be an `msb` or `lsb`\n * @property {string} [subtype] The actual controller event type\n */\nexport interface ControlChangeMessageEvent extends MessageEvent {\n  controller: {\n    name: string;\n    number: number;\n    description: string;\n    position: string;\n  };\n  port: Input;\n  subtype?: string;\n  target: Input | InputChannel;\n}\n\n/**\n * The `NoteMessageEvent` object is transmitted when a note-related MIDI message (`noteoff`,\n * `noteon` or `keyaftertouch`) is received on an input channel\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input | InputChannel} target The object that dispatched the event.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type The type of the event\n *\n * @property {Message} message An object containing details about the actual MIDI message content.\n * @property {number} [rawValue] The raw value of the message (if any) between 0-127.\n * @property {number | boolean} [value] The value of the message (if any)\n *\n * @property {Note} note A Note object with details about the triggered note.\n */\nexport interface NoteMessageEvent extends MessageEvent {\n  note: Note;\n  port: Input;\n  target: Input | InputChannel;\n}\n\n/**\n * The `ParameterNumberMessageEvent` object is transmitted when an RPN or NRPN message is received\n * on an input channel.\n *\n *  * nrpn\n *  * nrpn-datadecrement\n *  * nrpn-dataincrement\n *  * nrpn-dataentrycoarse\n *  * nrpn-dataentryfine\n *\n *  * rpn\n *  * rpn-datadecrement\n *  * rpn-dataincrement\n *  * rpn-dataentrycoarse\n *  * rpn-dataentryfine\n *\n * @property {Input} port The `Input` that triggered the event.\n * @property {Input | InputChannel} target The object that dispatched the event.\n * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in\n * milliseconds since the navigation start of the document).\n * @property {string} type The type of the event\n *\n * @property {Message} message An object containing details about the actual MIDI message content.\n * @property {number} [rawValue] The raw value of the message (if any) between 0-127.\n * @property {number | boolean} [value] The value of the message (if any)\n *\n * @property {string} parameter The parameter\n * @property {number} parameterMsb The parameter' MSB value (0-127)\n * @property {number} parameterLsb The parameter' LSB value (0-127)\n *\n */\nexport interface ParameterNumberMessageEvent extends MessageEvent {\n  parameter: string;\n  parameterMsb: number;\n  parameterLsb: number;\n  port: Input;\n  target: Input | InputChannel;\n}\n\n/**\n * A map of all the events that can be passed to `InputChannel.addListener()`.\n */\nexport interface InputChannelEventMap {\n\n  // Catch-ALl\n  \"midimessage\": (e: MessageEvent) => void;\n\n  // Channel Mode\n  \"channelaftertouch\": (e: MessageEvent) => void;\n  \"keyaftertouch\": (e: NoteMessageEvent) => void;\n  \"noteoff\": (e: NoteMessageEvent) => void;\n  \"noteon\": (e: NoteMessageEvent) => void;\n  \"pitchbend\": (e: MessageEvent) => void;\n  \"programchange\": (e: MessageEvent) => void;\n\n  // Control Change\n  \"controlchange\": (e: ControlChangeMessageEvent) => void;\n\n  \"controlchange-controller0\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller1\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller2\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller3\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller4\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller5\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller6\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller7\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller8\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller9\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller10\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller11\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller12\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller13\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller14\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller15\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller16\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller17\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller18\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller19\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller20\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller21\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller22\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller23\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller24\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller25\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller26\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller27\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller28\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller29\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller30\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller31\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller32\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller33\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller34\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller35\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller36\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller37\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller38\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller39\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller40\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller41\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller42\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller43\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller44\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller45\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller46\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller47\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller48\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller49\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller50\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller51\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller52\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller53\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller54\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller55\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller56\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller57\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller58\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller59\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller60\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller61\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller62\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller63\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller64\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller65\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller66\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller67\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller68\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller69\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller70\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller71\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller72\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller73\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller74\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller75\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller76\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller77\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller78\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller79\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller80\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller81\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller82\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller83\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller84\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller85\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller86\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller87\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller88\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller89\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller90\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller91\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller92\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller93\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller94\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller95\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller96\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller97\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller98\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller99\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller100\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller101\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller102\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller103\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller104\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller105\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller106\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller107\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller108\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller109\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller110\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller111\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller112\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller113\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller114\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller115\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller116\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller117\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller118\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller119\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller120\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller121\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller122\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller123\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller124\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller125\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller126\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller127\": (e: ControlChangeMessageEvent) => void;\n\n  // Channel Voice\n  \"allnotesoff\": (e: MessageEvent) => void;\n  \"allsoundoff\": (e: MessageEvent) => void;\n  \"localcontrol\": (e: MessageEvent) => void;\n  \"monomode\": (e: MessageEvent) => void;\n  \"omnimode\": (e: MessageEvent) => void;\n  \"resetallcontrollers\": (e: MessageEvent) => void;\n\n  // NRPN\n  \"nrpn\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-datadecrement\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-dataincrement\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-dataentrycoarse\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-dataentryfine\": (e: ParameterNumberMessageEvent) => void;\n\n  // RPN\n  \"rpn\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-datadecrement\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-dataincrement\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-dataentrycoarse\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-dataentryfine\": (e: ParameterNumberMessageEvent) => void;\n\n}\n\n/**\n * A map of all the events that can be passed to `Output.addListener()`.\n */\nexport interface PortEventMap {\n  \"closed\": (e: PortEvent) => void;\n  \"disconnected\": (e: PortEvent) => void;\n  \"opened\": (e: PortEvent) => void;\n}\n\n/**\n * A map of all the events that can be passed to `Input.addListener()`.\n */\nexport interface InputEventMap extends PortEventMap {\n\n  // System Common and System Real-Time\n  \"activesensing\": (e: MessageEvent) => void;\n  \"clock\": (e: MessageEvent) => void;\n  \"continue\": (e: MessageEvent) => void;\n  \"reset\": (e: MessageEvent) => void;\n  \"songposition\": (e: MessageEvent) => void;\n  \"songselect\": (e: MessageEvent) => void;\n  \"start\": (e: MessageEvent) => void;\n  \"stop\": (e: MessageEvent) => void;\n  \"sysex\": (e: MessageEvent) => void;\n  \"timecode\": (e: MessageEvent) => void;\n  \"tunerequest\": (e: MessageEvent) => void;\n\n  // Catch-ALl\n  \"midimessage\": (e: MessageEvent) => void;\n  \"unknownmessage\": (e: MessageEvent) => void;\n\n  // Channel Mode\n  \"channelaftertouch\": (e: MessageEvent) => void;\n  \"keyaftertouch\": (e: NoteMessageEvent) => void;\n  \"noteoff\": (e: NoteMessageEvent) => void;\n  \"noteon\": (e: NoteMessageEvent) => void;\n  \"pitchbend\": (e: MessageEvent) => void;\n  \"programchange\": (e: MessageEvent) => void;\n\n  // Control Change\n  \"controlchange\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller0\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller1\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller2\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller3\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller4\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller5\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller6\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller7\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller8\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller9\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller10\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller11\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller12\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller13\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller14\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller15\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller16\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller17\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller18\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller19\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller20\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller21\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller22\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller23\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller24\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller25\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller26\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller27\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller28\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller29\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller30\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller31\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller32\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller33\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller34\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller35\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller36\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller37\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller38\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller39\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller40\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller41\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller42\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller43\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller44\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller45\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller46\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller47\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller48\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller49\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller50\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller51\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller52\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller53\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller54\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller55\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller56\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller57\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller58\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller59\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller60\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller61\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller62\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller63\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller64\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller65\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller66\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller67\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller68\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller69\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller70\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller71\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller72\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller73\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller74\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller75\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller76\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller77\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller78\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller79\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller80\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller81\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller82\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller83\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller84\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller85\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller86\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller87\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller88\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller89\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller90\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller91\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller92\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller93\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller94\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller95\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller96\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller97\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller98\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller99\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller100\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller101\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller102\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller103\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller104\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller105\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller106\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller107\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller108\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller109\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller110\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller111\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller112\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller113\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller114\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller115\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller116\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller117\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller118\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller119\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller120\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller121\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller122\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller123\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller124\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller125\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller126\": (e: ControlChangeMessageEvent) => void;\n  \"controlchange-controller127\": (e: ControlChangeMessageEvent) => void;\n\n  // Channel Voice\n  \"allnotesoff\": (e: MessageEvent) => void;\n  \"allsoundoff\": (e: MessageEvent) => void;\n  \"localcontrol\": (e: MessageEvent) => void;\n  \"monomode\": (e: MessageEvent) => void;\n  \"omnimode\": (e: MessageEvent) => void;\n  \"resetallcontrollers\": (e: MessageEvent) => void;\n\n  // NRPN\n  \"nrpn\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-datadecrement\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-dataincrement\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-dataentrycoarse\": (e: ParameterNumberMessageEvent) => void;\n  \"nrpn-dataentryfine\": (e: ParameterNumberMessageEvent) => void;\n\n  // RPN\n  \"rpn\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-datadecrement\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-dataincrement\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-dataentrycoarse\": (e: ParameterNumberMessageEvent) => void;\n  \"rpn-dataentryfine\": (e: ParameterNumberMessageEvent) => void;\n\n}\n\n/**\n * A map of all the events that can be passed to `Output.addListener()`.\n */\nexport interface WebMidiEventMap {\n  \"connected\": (e: PortEvent) => void;\n  \"disabled\": (e: Event) => void;\n  \"disconnected\": (e: PortEvent) => void;\n  \"enabled\": (e: Event) => void;\n  \"midiaccessgranted\": (e: Event) => void;\n  \"portschanged\": (e: PortEvent) => void;\n  \"error\": (e: ErrorEvent) => void;\n}\n"
  },
  {
    "path": "website/.gitignore",
    "content": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "website/README.md",
    "content": "# Website\n\nThis website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.\n\n## Installation\n\n```console\nyarn install\n```\n\n## Local Development\n\n```console\nyarn start\n```\n\nThis command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.\n\n## Build\n\n```console\nyarn build\n```\n\nThis command generates static content into the `build` directory and can be served using any static contents hosting service.\n\n## Deployment\n\n```console\nGIT_USER=<Your GitHub username> USE_SSH=true yarn deploy\n```\n\nIf you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.\n"
  },
  {
    "path": "website/api/classes/Enumerations.md",
    "content": "\n# Enumerations\n\nThe `Enumerations` class contains enumerations and arrays of elements used throughout the\nlibrary. All its properties are static and should be referenced using the class name. For\nexample: `Enumerations.CHANNEL_MESSAGES`.\n\n**Since**: 3.0.0\n\n\n***\n\n## Properties\n\n### `.CHANNEL_EVENTS` {#CHANNEL_EVENTS}\n**Type**: Array.&lt;string&gt;<br />\n**Attributes**: read-only, static<br />\n\n\nArray of channel-specific event names that can be listened for. This includes channel mode\nevents and RPN/NRPN events.\n\n\n### `.CHANNEL_NUMBERS` {#CHANNEL_NUMBERS}\n**Since**: 3.1<br />\n**Type**: Array.&lt;number&gt;<br />\n**Attributes**: read-only, static<br />\n\n\nA simple array of the 16 valid MIDI channel numbers (`1` to `16`):\n\n\n### `.CONTROL_CHANGE_MESSAGES` {#CONTROL_CHANGE_MESSAGES}\n**Since**: 3.1<br />\n**Type**: Array.&lt;object&gt;<br />\n**Attributes**: read-only, static<br />\n\n\nAn array of objects, ordered by control number, describing control change messages. Each object\nin the array has 3 properties with some objects having a fourth one (`position`) :\n\n * `number`: MIDI control number (0-127);\n * `name`: name of emitted event (eg: `bankselectcoarse`, `choruslevel`, etc) that can be\n listened to;\n * `description`: user-friendly description of the controller's purpose;\n * `position` (optional): whether this controller's value should be considered an `msb` or\n `lsb`\n\nNot all controllers have a predefined function. For those that don't, `name` is the word\n\"controller\" followed by the number (e.g. `controller112`).\n\n| Event name                     | Control Number |\n|--------------------------------|----------------|\n| `bankselectcoarse`             | 0              |\n| `modulationwheelcoarse`        | 1              |\n| `breathcontrollercoarse`       | 2              |\n| `controller3`                  | 3              |\n| `footcontrollercoarse`         | 4              |\n| `portamentotimecoarse`         | 5              |\n| `dataentrycoarse`              | 6              |\n| `volumecoarse`                 | 7              |\n| `balancecoarse`                | 8              |\n| `controller9`                  | 9              |\n| `pancoarse`                    | 10             |\n| `expressioncoarse`             | 11             |\n| `effectcontrol1coarse`         | 12             |\n| `effectcontrol2coarse`         | 13             |\n| `controller14`                 | 14             |\n| `controller15`                 | 15             |\n| `generalpurposecontroller1`    | 16             |\n| `generalpurposecontroller2`    | 17             |\n| `generalpurposecontroller3`    | 18             |\n| `generalpurposecontroller4`    | 19             |\n| `controller20`                 | 20             |\n| `controller21`                 | 21             |\n| `controller22`                 | 22             |\n| `controller23`                 | 23             |\n| `controller24`                 | 24             |\n| `controller25`                 | 25             |\n| `controller26`                 | 26             |\n| `controller27`                 | 27             |\n| `controller28`                 | 28             |\n| `controller29`                 | 29             |\n| `controller30`                 | 30             |\n| `controller31`                 | 31             |\n| `bankselectfine`               | 32             |\n| `modulationwheelfine`          | 33             |\n| `breathcontrollerfine`         | 34             |\n| `controller35`                 | 35             |\n| `footcontrollerfine`           | 36             |\n| `portamentotimefine`           | 37             |\n| `dataentryfine`                | 38             |\n| `channelvolumefine`            | 39             |\n| `balancefine`                  | 40             |\n| `controller41`                 | 41             |\n| `panfine`                      | 42             |\n| `expressionfine`               | 43             |\n| `effectcontrol1fine`           | 44             |\n| `effectcontrol2fine`           | 45             |\n| `controller46`                 | 46             |\n| `controller47`                 | 47             |\n| `controller48`                 | 48             |\n| `controller49`                 | 49             |\n| `controller50`                 | 50             |\n| `controller51`                 | 51             |\n| `controller52`                 | 52             |\n| `controller53`                 | 53             |\n| `controller54`                 | 54             |\n| `controller55`                 | 55             |\n| `controller56`                 | 56             |\n| `controller57`                 | 57             |\n| `controller58`                 | 58             |\n| `controller59`                 | 59             |\n| `controller60`                 | 60             |\n| `controller61`                 | 61             |\n| `controller62`                 | 62             |\n| `controller63`                 | 63             |\n| `damperpedal`                  | 64             |\n| `portamento`                   | 65             |\n| `sostenuto`                    | 66             |\n| `softpedal`                    | 67             |\n| `legatopedal`                  | 68             |\n| `hold2`                        | 69             |\n| `soundvariation`               | 70             |\n| `resonance`                    | 71             |\n| `releasetime`                  | 72             |\n| `attacktime`                   | 73             |\n| `brightness`                   | 74             |\n| `decaytime`                    | 75             |\n| `vibratorate`                  | 76             |\n| `vibratodepth`                 | 77             |\n| `vibratodelay`                 | 78             |\n| `controller79`                 | 79             |\n| `generalpurposecontroller5`    | 80             |\n| `generalpurposecontroller6`    | 81             |\n| `generalpurposecontroller7`    | 82             |\n| `generalpurposecontroller8`    | 83             |\n| `portamentocontrol`            | 84             |\n| `controller85`                 | 85             |\n| `controller86`                 | 86             |\n| `controller87`                 | 87             |\n| `highresolutionvelocityprefix` | 88             |\n| `controller89`                 | 89             |\n| `controller90`                 | 90             |\n| `effect1depth`                 | 91             |\n| `effect2depth`                 | 92             |\n| `effect3depth`                 | 93             |\n| `effect4depth`                 | 94             |\n| `effect5depth`                 | 95             |\n| `dataincrement`                | 96             |\n| `datadecrement`                | 97             |\n| `nonregisteredparameterfine`   | 98             |\n| `nonregisteredparametercoarse` | 99             |\n| `nonregisteredparameterfine`   | 100            |\n| `registeredparametercoarse`    | 101            |\n| `controller102`                | 102            |\n| `controller103`                | 103            |\n| `controller104`                | 104            |\n| `controller105`                | 105            |\n| `controller106`                | 106            |\n| `controller107`                | 107            |\n| `controller108`                | 108            |\n| `controller109`                | 109            |\n| `controller110`                | 110            |\n| `controller111`                | 111            |\n| `controller112`                | 112            |\n| `controller113`                | 113            |\n| `controller114`                | 114            |\n| `controller115`                | 115            |\n| `controller116`                | 116            |\n| `controller117`                | 117            |\n| `controller118`                | 118            |\n| `controller119`                | 119            |\n| `allsoundoff`                  | 120            |\n| `resetallcontrollers`          | 121            |\n| `localcontrol`                 | 122            |\n| `allnotesoff`                  | 123            |\n| `omnimodeoff`                  | 124            |\n| `omnimodeon`                   | 125            |\n| `monomodeon`                   | 126            |\n| `polymodeon`                   | 127            |\n\n\n\n***\n\n## Enums\n\n### `.CHANNEL_MESSAGES` {#CHANNEL_MESSAGES}\n**Type**: Object.&lt;string, number&gt;<br />\n**Attributes**: static\n\nEnumeration of all MIDI channel message names and their associated 4-bit numerical value:\n\n| Message Name        | Hexadecimal | Decimal |\n|---------------------|-------------|---------|\n| `noteoff`           | 0x8         | 8       |\n| `noteon`            | 0x9         | 9       |\n| `keyaftertouch`     | 0xA         | 10      |\n| `controlchange`     | 0xB         | 11      |\n| `programchange`     | 0xC         | 12      |\n| `channelaftertouch` | 0xD         | 13      |\n| `pitchbend`         | 0xE         | 14      |\n### `.CHANNEL_MODE_MESSAGES` {#CHANNEL_MODE_MESSAGES}\n**Type**: Object.&lt;string, number&gt;<br />\n**Attributes**: static\n\nEnumeration of all MIDI channel mode message names and their associated numerical value:\n\n\n| Message Name          | Hexadecimal | Decimal |\n|-----------------------|-------------|---------|\n| `allsoundoff`         | 0x78        | 120     |\n| `resetallcontrollers` | 0x79        | 121     |\n| `localcontrol`        | 0x7A        | 122     |\n| `allnotesoff`         | 0x7B        | 123     |\n| `omnimodeoff`         | 0x7C        | 124     |\n| `omnimodeon`          | 0x7D        | 125     |\n| `monomodeon`          | 0x7E        | 126     |\n| `polymodeon`          | 0x7F        | 127     |\n### `.REGISTERED_PARAMETERS` {#REGISTERED_PARAMETERS}\n**Type**: Object.&lt;string, Array.&lt;number&gt;&gt;<br />\n**Attributes**: static\n\nEnumeration of all MIDI registered parameters and their associated pair of numerical values.\nMIDI registered parameters extend the original list of control change messages. Currently,\nthere are only a limited number of them:\n\n\n| Control Function             | [LSB, MSB]   |\n|------------------------------|--------------|\n| `pitchbendrange`             | [0x00, 0x00] |\n| `channelfinetuning`          | [0x00, 0x01] |\n| `channelcoarsetuning`        | [0x00, 0x02] |\n| `tuningprogram`              | [0x00, 0x03] |\n| `tuningbank`                 | [0x00, 0x04] |\n| `modulationrange`            | [0x00, 0x05] |\n| `azimuthangle`               | [0x3D, 0x00] |\n| `elevationangle`             | [0x3D, 0x01] |\n| `gain`                       | [0x3D, 0x02] |\n| `distanceratio`              | [0x3D, 0x03] |\n| `maximumdistance`            | [0x3D, 0x04] |\n| `maximumdistancegain`        | [0x3D, 0x05] |\n| `referencedistanceratio`     | [0x3D, 0x06] |\n| `panspreadangle`             | [0x3D, 0x07] |\n| `rollangle`                  | [0x3D, 0x08] |\n### `.SYSTEM_MESSAGES` {#SYSTEM_MESSAGES}\n**Type**: Object.&lt;string, number&gt;<br />\n**Attributes**: static\n\nEnumeration of all valid MIDI system messages and matching numerical values. This library also\nuses two additional custom messages.\n\n**System Common Messages**\n\n| Function               | Hexadecimal | Decimal |\n|------------------------|-------------|---------|\n| `sysex`                | 0xF0        |  240    |\n| `timecode`             | 0xF1        |  241    |\n| `songposition`         | 0xF2        |  242    |\n| `songselect`           | 0xF3        |  243    |\n| `tunerequest`          | 0xF6        |  246    |\n| `sysexend`             | 0xF7        |  247    |\n\nThe `sysexend` message is never actually received. It simply ends a sysex stream.\n\n**System Real-Time Messages**\n\n| Function               | Hexadecimal | Decimal |\n|------------------------|-------------|---------|\n| `clock`                | 0xF8        |  248    |\n| `start`                | 0xFA        |  250    |\n| `continue`             | 0xFB        |  251    |\n| `stop`                 | 0xFC        |  252    |\n| `activesensing`        | 0xFE        |  254    |\n| `reset`                | 0xFF        |  255    |\n\nValues 249 and 253 are relayed by the\n[Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API) but they do not\nserve any specific purpose. The\n[MIDI 1.0 spec](https://www.midi.org/specifications/item/table-1-summary-of-midi-message)\nsimply states that they are undefined/reserved.\n\n**Custom Messages**\n\nThese two messages are mostly for internal use. They are not MIDI messages and cannot be sent\nor forwarded.\n\n| Function               | Hexadecimal | Decimal |\n|------------------------|-------------|---------|\n| `midimessage`          |             |  0      |\n| `unknownsystemmessage` |             |  -1     |\n\n"
  },
  {
    "path": "website/api/classes/EventEmitter.md",
    "content": "\n# EventEmitter\n\nThe `EventEmitter` class provides methods to implement the _observable_ design pattern. This\npattern allows one to _register_ a function to execute when a specific event is _emitted_ by the\nemitter.\n\nIt is intended to be an abstract class meant to be extended by (or mixed into) other objects.\n\n\n\n\n### `Constructor`\n\nCreates a new `EventEmitter`object.\n\n\n  **Parameters**\n\n  > `new EventEmitter([eventsSuspended])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`eventsSuspended`**] | boolean<br /> |false|Whether the `EventEmitter` is initially in a suspended state (i.e. not executing callbacks).|\n\n  </div>\n\n\n\n***\n\n## Properties\n\n### `.ANY_EVENT` {#ANY_EVENT}\n**Type**: Symbol<br />\n\n\nIdentifier to use when adding or removing a listener that should be triggered when any events\noccur.\n\n\n### `.eventCount` {#eventCount}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe number of unique events that have registered listeners.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventMap` {#eventMap}\n**Type**: Object<br />\n**Attributes**: read-only<br />\n\n\nAn object containing a property for each event with at least one registered listener. Each\nevent property contains an array of all the [`Listener`](Listener) objects registered\nfor the event.\n\n\n### `.eventNames` {#eventNames}\n**Type**: Array.&lt;string&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all the unique event names for which the emitter has at least one registered\nlistener.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventsSuspended` {#eventsSuspended}\n**Type**: boolean<br />\n\n\nWhether or not the execution of callbacks is currently suspended for this emitter.\n\n\n\n***\n\n## Methods\n\n\n### `.addListener(...)` {#addListener}\n\n\nAdds a listener for the specified event. It returns the [`Listener`](Listener) object\nthat was created and attached to the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](#ANY_EVENT) as the first parameter. Note that a global\nlistener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to.|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The value of `this` in the callback function.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.remaining`**] | number<br /> |Infinity|The number of times after which the callback should automatically be removed.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.addOneTimeListener(...)` {#addOneTimeListener}\n\n\nAdds a one-time listener for the specified event. The listener will be executed once and then\ndestroyed. It returns the [`Listener`](Listener) object that was created and attached\nto the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter. Note that a\nglobal listener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addOneTimeListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The context to invoke the callback function in.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.emit(...)` {#emit}\n\n\nExecutes the callback function of all the [`Listener`](Listener) objects registered for\na given event. The callback functions are passed the additional arguments passed to `emit()`\n(if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of\nthe [`Listener`](Listener) object (if any).\n\nIf the [`eventsSuspended`](#eventsSuspended) property is `true` or the\n[`Listener.suspended`](Listener#suspended) property is `true`, the callback functions\nwill not be executed.\n\nThis function returns an array containing the return values of each of the callbacks.\n\nIt should be noted that the regular listeners are triggered first followed by the global\nlisteners (those added with [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\n\n  **Parameters**\n\n  > Signature: `emit(event, ...args)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br /> ||The event|\n    |**`args`** | *<br /> ||Arbitrary number of arguments to pass along to the callback functions|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array`<br />\n\nAn array containing the return value of each of the executed listener\nfunctions.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string.\n\n\n### `.getListenerCount(...)` {#getListenerCount}\n\n\nReturns the number of listeners registered for a specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) do not count towards the remaining\nnumber for a \"regular\" event. To get the number of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListenerCount(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event which is usually a string but can also be the special [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) symbol.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer representing the number of listeners registered for the specified\nevent.\n\n\n\n\n### `.getListeners(...)` {#getListeners}\n\n\nReturns an array of all the [`Listener`](Listener) objects that have been registered for\na specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) are not returned for \"regular\"\nevents. To get the list of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListeners(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to get listeners for.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Listener>`<br />\n\nAn array of [`Listener`](Listener) objects.\n\n\n\n\n### `.hasListener(...)` {#hasListener}\n\n\nReturns `true` if the specified event has at least one registered listener. If no event is\nspecified, the method returns `true` if any event has at least one listener registered (this\nincludes global listeners registered to\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\nNote: to specifically check for global listeners added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT), use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `hasListener([event], [callback])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br />Symbol<br /> |(any event)|The event to check|\n    |[**`callback`**] | function<br />Listener<br /> |(any callback)|The actual function that was added to the event or the [Listener](Listener) object returned by `addListener()`.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\n\n\n\n### `.removeListener(...)` {#removeListener}\n\n\nRemoves all the listeners that were added to the object upon which the method is called and\nthat match the specified criterias. If no parameters are passed, all listeners added to this\nobject will be removed. If only the `event` parameter is passed, all listeners for that event\nwill be removed from that object. You can remove global listeners by using\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter.\n\nTo use more granular options, you must at least define the `event`. Then, you can specify the\ncallback to match or one or more of the additional options.\n\n\n  **Parameters**\n\n  > Signature: `removeListener([event], [callback], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br /> ||The event name.|\n    |[**`callback`**] | EventEmitter~callback<br /> ||Only remove the listeners that match this exact callback function.|\n    |[**`options`**] | Object<br /> |||\n    |[**`options.context`**] | *<br /> ||Only remove the listeners that have this exact context.|\n    |[**`options.remaining`**] | number<br /> ||Only remove the listener if it has exactly that many remaining times to be executed.|\n\n  </div>\n\n\n\n\n\n\n### `.suspendEvent(...)` {#suspendEvent}\n\n\nSuspends execution of all callbacks functions registered for the specified event type.\n\nYou can suspend execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `suspendEvent()`. Beware that this\nwill not suspend all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem counter-intuitive\nat first glance, it allows the selective suspension of global listeners while leaving other\nlisteners alone. If you truly want to suspends all callbacks for a specific\n[`EventEmitter`](EventEmitter), simply set its `eventsSuspended` property to `true`.\n\n\n  **Parameters**\n\n  > Signature: `suspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to suspend execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.unsuspendEvent(...)` {#unsuspendEvent}\n\n\nResumes execution of all suspended callback functions registered for the specified event type.\n\nYou can resume execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `unsuspendEvent()`. Beware that\nthis will not resume all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem\ncounter-intuitive, it allows the selective unsuspension of global listeners while leaving other\ncallbacks alone.\n\n\n  **Parameters**\n\n  > Signature: `unsuspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to resume execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.waitFor(...)` {#waitFor}\n\n**Attributes**: async\n\nThe `waitFor()` method is an async function which returns a promise. The promise is fulfilled\nwhen the specified event occurs. The event can be a regular event or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) (if you want to resolve as soon as any\nevent is emitted).\n\nIf the `duration` option is set, the promise will only be fulfilled if the event is emitted\nwithin the specified duration. If the event has not been fulfilled after the specified\nduration, the promise is rejected. This makes it super easy to wait for an event and timeout\nafter a certain time if the event is not triggered.\n\n\n  **Parameters**\n\n  > Signature: `waitFor(event, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to wait for|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds to wait before the promise is automatically rejected.|\n\n  </div>\n\n\n\n\n\n\n"
  },
  {
    "path": "website/api/classes/Forwarder.md",
    "content": "\n# Forwarder\n\nThe `Forwarder` class allows the forwarding of MIDI messages to predetermined outputs. When you\ncall its [`forward()`](#forward) method, it will send the specified [`Message`](Message) object\nto all the outputs listed in its [`destinations`](#destinations) property.\n\nIf specific channels or message types have been defined in the [`channels`](#channels) or\n[`types`](#types) properties, only messages matching the channels/types will be forwarded.\n\nWhile it can be manually instantiated, you are more likely to come across a `Forwarder` object as\nthe return value of the [`Input.addForwarder()`](Input#addForwarder) method.\n\n**Since**: 3.0.0\n\n\n\n### `Constructor`\n\nCreates a `Forwarder` object.\n\n\n  **Parameters**\n\n  > `new Forwarder([destinations], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`destinations`**] | Output<br />Array.&lt;Output&gt;<br /> |\\[\\]|An [`Output`](Output) object, or an array of such objects, to forward the message to.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.types`**] | string<br />Array.&lt;string&gt;<br /> |(all messages)|A MIDI message type or an array of such types (`\"noteon\"`, `\"controlchange\"`, etc.), that the specified message must match in order to be forwarded. If this option is not specified, all types of messages will be forwarded. Valid messages are the ones found in either [`SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) or [`CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).|\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|A MIDI channel number or an array of channel numbers that the message must match in order to be forwarded. By default all MIDI channels are included (`1` to `16`).|\n\n  </div>\n\n\n\n***\n\n## Properties\n\n### `.channels` {#channels}\n**Type**: Array.&lt;number&gt;<br />\n\n\nAn array of MIDI channel numbers that the message must match in order to be forwarded. By\ndefault, this array includes all MIDI channels (`1` to `16`).\n\n\n### `.destinations` {#destinations}\n**Type**: Array.&lt;Output&gt;<br />\n\n\nAn array of [`Output`](Output) objects to forward the message to.\n\n\n### `.suspended` {#suspended}\n**Type**: boolean<br />\n\n\nIndicates whether message forwarding is currently suspended or not in this forwarder.\n\n\n### `.types` {#types}\n**Type**: Array.&lt;string&gt;<br />\n\n\nAn array of message types (`\"noteon\"`, `\"controlchange\"`, etc.) that must be matched in order\nfor messages to be forwarded. By default, this array includes all\n[`Enumerations.SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) and\n[`Enumerations.CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).\n\n\n\n***\n\n## Methods\n\n\n### `.forward(...)` {#forward}\n\n\nSends the specified message to the forwarder's destination(s) if it matches the specified\ntype(s) and channel(s).\n\n\n  **Parameters**\n\n  > Signature: `forward(message)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`message`** | Message<br /> ||The [`Message`](Message) object to forward.|\n\n  </div>\n\n\n\n\n\n\n"
  },
  {
    "path": "website/api/classes/Input.md",
    "content": "\n# Input\n\nThe `Input` class represents a single MIDI input port. This object is automatically instantiated\nby the library according to the host's MIDI subsystem and does not need to be directly\ninstantiated. Instead, you can access all `Input` objects by referring to the\n[`WebMidi.inputs`](WebMidi#inputs) array. You can also retrieve inputs by using methods such as\n[`WebMidi.getInputByName()`](WebMidi#getInputByName) and\n[`WebMidi.getInputById()`](WebMidi#getInputById).\n\nNote that a single MIDI device may expose several inputs and/or outputs.\n\n**Important**: the `Input` class does not directly fire channel-specific MIDI messages\n(such as [`noteon`](InputChannel#event:noteon) or\n[`controlchange`](InputChannel#event:controlchange), etc.). The [`InputChannel`](InputChannel)\nobject does that. However, you can still use the\n[`Input.addListener()`](#addListener) method to listen to channel-specific events on multiple\n[`InputChannel`](InputChannel) objects at once.\n\n\n**Extends**: [`EventEmitter`](EventEmitter)\n<!--**Extends**: EventEmitter-->\n\n**Fires**: [`activesensing`](#event:activesensing), [`clock`](#event:clock), [`closed`](#event:closed), [`continue`](#event:continue), [`disconnected`](#event:disconnected), [`midimessage`](#event:midimessage), [`opened`](#event:opened), [`reset`](#event:reset), [`songposition`](#event:songposition), [`songselect`](#event:songselect), [`start`](#event:start), [`stop`](#event:stop), [`sysex`](#event:sysex), [`timecode`](#event:timecode), [`tunerequest`](#event:tunerequest), [`unknownmidimessage`](#event:unknownmidimessage)\n\n### `Constructor`\n\nCreates an `Input` object.\n\n\n  **Parameters**\n\n  > `new Input(midiInput)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`midiInput`** | MIDIInput<br /> ||[`MIDIInput`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIInput) object as provided by the MIDI subsystem (Web MIDI API).|\n\n  </div>\n\n\n\n***\n\n## Properties\n\n### `.channels` {#channels}\n**Type**: Array.&lt;InputChannel&gt;<br />\n\n\nArray containing the 16 [`InputChannel`](InputChannel) objects available for this `Input`. The\nchannels are numbered 1 through 16.\n\n\n### `.connection` {#connection}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nInput port's connection state: `pending`, `open` or `closed`.\n\n\n### `.eventCount` {#eventCount}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe number of unique events that have registered listeners.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventMap` {#eventMap}\n**Type**: Object<br />\n**Attributes**: read-only<br />\n\n\nAn object containing a property for each event with at least one registered listener. Each\nevent property contains an array of all the [`Listener`](Listener) objects registered\nfor the event.\n\n\n### `.eventNames` {#eventNames}\n**Type**: Array.&lt;string&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all the unique event names for which the emitter has at least one registered\nlistener.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventsSuspended` {#eventsSuspended}\n**Type**: boolean<br />\n\n\nWhether or not the execution of callbacks is currently suspended for this emitter.\n\n\n### `.id` {#id}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nID string of the MIDI port. The ID is host-specific. Do not expect the same ID on different\nplatforms. For example, Google Chrome and the Jazz-Plugin report completely different IDs for\nthe same port.\n\n\n### `.manufacturer` {#manufacturer}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nName of the manufacturer of the device that makes this input port available.\n\n\n### `.name` {#name}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nName of the MIDI input.\n\n\n### `.octaveOffset` {#octaveOffset}\n**Since**: 3.0<br />\n**Type**: number<br />\n\n\nAn integer to offset the reported octave of incoming notes. By default, middle C (MIDI note\nnumber 60) is placed on the 4th octave (C4).\n\nIf, for example, `octaveOffset` is set to 2, MIDI note number 60 will be reported as C6. If\n`octaveOffset` is set to -1, MIDI note number 60 will be reported as C3.\n\nNote that this value is combined with the global offset value defined in the\n[`WebMidi.octaveOffset`](WebMidi#octaveOffset) property (if any).\n\n\n### `.state` {#state}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nState of the input port: `connected` or `disconnected`.\n\n\n### `.type` {#type}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nThe port type. In the case of the `Input` object, this is always: `input`.\n\n\n\n***\n\n## Methods\n\n\n### `.addForwarder(...)` {#addForwarder}\n\n\nAdds a forwarder that will forward all incoming MIDI messages matching the criteria to the\nspecified [`Output`](Output) destination(s). This is akin to the hardware MIDI THRU port, with\nthe added benefit of being able to filter which data is forwarded.\n\n\n  **Parameters**\n\n  > Signature: `addForwarder(output, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`output`** | Output<br />Array.&lt;Output&gt;<br />Forwarder<br /> ||An [`Output`](Output) object, a [`Forwarder`](Forwarder) object or an array of such objects, to forward messages to.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.types`**] | string<br />Array.&lt;string&gt;<br /> |(all messages)|A message type, or an array of such types (`noteon`, `controlchange`, etc.), that the message type must match in order to be forwarded. If this option is not specified, all types of messages will be forwarded. Valid messages are the ones found in either [`SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) or [`CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).|\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|A MIDI channel number or an array of channel numbers that the message must match in order to be forwarded. By default all MIDI channels are included (`1` to `16`).|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Forwarder`<br />\n\nThe [`Forwarder`](Forwarder) object created to handle the forwarding. This\nis useful if you wish to manipulate or remove the [`Forwarder`](Forwarder) later on.\n\n\n\n\n### `.addListener(...)` {#addListener}\n\n\nAdds an event listener that will trigger a function callback when the specified event is\ndispatched. The event usually is **input-wide** but can also be **channel-specific**.\n\nInput-wide events do not target a specific MIDI channel so it makes sense to listen for them\nat the `Input` level and not at the [`InputChannel`](InputChannel) level. Channel-specific\nevents target a specific channel. Usually, in this case, you would add the listener to the\n[`InputChannel`](InputChannel) object. However, as a convenience, you can also listen to\nchannel-specific events directly on an `Input`. This allows you to react to a channel-specific\nevent no matter which channel it actually came through.\n\nWhen listening for an event, you simply need to specify the event name and the function to\nexecute:\n\n```javascript\nconst listener = WebMidi.inputs[0].addListener(\"midimessage\", e => {\n  console.log(e);\n});\n```\n\nCalling the function with an input-wide event (such as\n[`\"midimessage\"`](#event:midimessage)), will return the [`Listener`](Listener) object\nthat was created.\n\nIf you call the function with a channel-specific event (such as\n[`\"noteon\"`](InputChannel#event:noteon)), it will return an array of all\n[`Listener`](Listener) objects that were created (one for each channel):\n\n```javascript\nconst listeners = WebMidi.inputs[0].addListener(\"noteon\", someFunction);\n```\n\nYou can also specify which channels you want to add the listener to:\n\n```javascript\nconst listeners = WebMidi.inputs[0].addListener(\"noteon\", someFunction, {channels: [1, 2, 3]});\n```\n\nIn this case, `listeners` is an array containing 3 [`Listener`](Listener) objects. The order of\nthe listeners in the array follows the order the channels were specified in.\n\nNote that, when adding channel-specific listeners, it is the [`InputChannel`](InputChannel)\ninstance that actually gets a listener added and not the `Input` instance. You can check that\nby calling [`InputChannel.hasListener()`](InputChannel#hasListener()).\n\nThere are 8 families of events you can listen to:\n\n1. **MIDI System Common** Events (input-wide)\n\n   * [`songposition`](Input#event:songposition)\n   * [`songselect`](Input#event:songselect)\n   * [`sysex`](Input#event:sysex)\n   * [`timecode`](Input#event:timecode)\n   * [`tunerequest`](Input#event:tunerequest)\n\n2. **MIDI System Real-Time** Events (input-wide)\n\n   * [`clock`](Input#event:clock)\n   * [`start`](Input#event:start)\n   * [`continue`](Input#event:continue)\n   * [`stop`](Input#event:stop)\n   * [`activesensing`](Input#event:activesensing)\n   * [`reset`](Input#event:reset)\n\n3. **State Change** Events (input-wide)\n\n   * [`opened`](Input#event:opened)\n   * [`closed`](Input#event:closed)\n   * [`disconnected`](Input#event:disconnected)\n\n4. **Catch-All** Events (input-wide)\n\n   * [`midimessage`](Input#event:midimessage)\n   * [`unknownmidimessage`](Input#event:unknownmidimessage)\n\n5. **Channel Voice** Events (channel-specific)\n\n   * [`channelaftertouch`](InputChannel#event:channelaftertouch)\n   * [`controlchange`](InputChannel#event:controlchange)\n     * [`controlchange-controller0`](InputChannel#event:controlchange-controller0)\n     * [`controlchange-controller1`](InputChannel#event:controlchange-controller1)\n     * [`controlchange-controller2`](InputChannel#event:controlchange-controller2)\n     * (...)\n     * [`controlchange-controller127`](InputChannel#event:controlchange-controller127)\n   * [`keyaftertouch`](InputChannel#event:keyaftertouch)\n   * [`noteoff`](InputChannel#event:noteoff)\n   * [`noteon`](InputChannel#event:noteon)\n   * [`pitchbend`](InputChannel#event:pitchbend)\n   * [`programchange`](InputChannel#event:programchange)\n\n   Note: you can listen for a specific control change message by using an event name like this:\n   `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   etc.\n\n6. **Channel Mode** Events (channel-specific)\n\n   * [`allnotesoff`](InputChannel#event:allnotesoff)\n   * [`allsoundoff`](InputChannel#event:allsoundoff)\n   * [`localcontrol`](InputChannel#event:localcontrol)\n   * [`monomode`](InputChannel#event:monomode)\n   * [`omnimode`](InputChannel#event:omnimode)\n   * [`resetallcontrollers`](InputChannel#event:resetallcontrollers)\n\n7. **NRPN** Events (channel-specific)\n\n   * [`nrpn`](InputChannel#event:nrpn)\n   * [`nrpn-dataentrycoarse`](InputChannel#event:nrpn-dataentrycoarse)\n   * [`nrpn-dataentryfine`](InputChannel#event:nrpn-dataentryfine)\n   * [`nrpn-dataincrement`](InputChannel#event:nrpn-dataincrement)\n   * [`nrpn-datadecrement`](InputChannel#event:nrpn-datadecrement)\n\n8. **RPN** Events (channel-specific)\n\n   * [`rpn`](InputChannel#event:rpn)\n   * [`rpn-dataentrycoarse`](InputChannel#event:rpn-dataentrycoarse)\n   * [`rpn-dataentryfine`](InputChannel#event:rpn-dataentryfine)\n   * [`rpn-dataincrement`](InputChannel#event:rpn-dataincrement)\n   * [`rpn-datadecrement`](InputChannel#event:rpn-datadecrement)\n\n\n  **Parameters**\n\n  > Signature: `addListener(event, listener, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />EventEmitter.ANY_EVENT<br /> ||The type of the event.|\n    |**`listener`** | function<br /> ||A callback function to execute when the specified event is detected. This function will receive an event parameter object. For details on this object's properties, check out the documentation for the various events (links above).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|An integer between 1 and 16 or an array of such integers representing the MIDI channel(s) to listen on. If no channel is specified, all channels will be used. This parameter is ignored for input-wide events.|\n    |[**`options.context`**] | object<br /> |this|The value of `this` in the callback function.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus be triggered before others.|\n    |[**`options.remaining`**] | number<br /> |Infinity|The number of times after which the callback should automatically be removed.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener` or `Array.<Listener>`<br />\n\nIf the event is input-wide, a single [`Listener`](Listener)\nobject is returned. If the event is channel-specific, an array of all the\n[`Listener`](Listener) objects is returned (one for each channel).\n\n\n\n\n### `.addOneTimeListener(...)` {#addOneTimeListener}\n\n\nAdds a one-time event listener that will trigger a function callback when the specified event\nhappens. The event can be **channel-bound** or **input-wide**. Channel-bound events are\ndispatched by [`InputChannel`](InputChannel) objects and are tied to a specific MIDI\nchannel while input-wide events are dispatched by the `Input` object itself and are not tied\nto a specific channel.\n\nCalling the function with an input-wide event (such as\n[`\"midimessage\"`](#event:midimessage)), will return the [`Listener`](Listener) object\nthat was created.\n\nIf you call the function with a channel-specific event (such as\n[`\"noteon\"`](InputChannel#event:noteon)), it will return an array of all\n[`Listener`](Listener) objects that were created (one for each channel):\n\n```javascript\nconst listeners = WebMidi.inputs[0].addOneTimeListener(\"noteon\", someFunction);\n```\n\nYou can also specify which channels you want to add the listener to:\n\n```javascript\nconst listeners = WebMidi.inputs[0].addOneTimeListener(\"noteon\", someFunction, {channels: [1, 2, 3]});\n```\n\nIn this case, the `listeners` variable contains an array of 3 [`Listener`](Listener) objects.\n\nThe code above will add a listener for the `\"noteon\"` event and call `someFunction` when the\nevent is triggered on MIDI channels `1`, `2` or `3`.\n\nNote that, when adding events to channels, it is the [`InputChannel`](InputChannel) instance\nthat actually gets a listener added and not the `Input` instance.\n\nNote: if you want to add a listener to a single MIDI channel you should probably do so directly\non the [`InputChannel`](InputChannel) object itself.\n\nThere are 8 families of events you can listen to:\n\n1. **MIDI System Common** Events (input-wide)\n\n   * [`songposition`](Input#event:songposition)\n   * [`songselect`](Input#event:songselect)\n   * [`sysex`](Input#event:sysex)\n   * [`timecode`](Input#event:timecode)\n   * [`tunerequest`](Input#event:tunerequest)\n\n2. **MIDI System Real-Time** Events (input-wide)\n\n   * [`clock`](Input#event:clock)\n   * [`start`](Input#event:start)\n   * [`continue`](Input#event:continue)\n   * [`stop`](Input#event:stop)\n   * [`activesensing`](Input#event:activesensing)\n   * [`reset`](Input#event:reset)\n\n3. **State Change** Events (input-wide)\n\n   * [`opened`](Input#event:opened)\n   * [`closed`](Input#event:closed)\n   * [`disconnected`](Input#event:disconnected)\n\n4. **Catch-All** Events (input-wide)\n\n   * [`midimessage`](Input#event:midimessage)\n   * [`unknownmidimessage`](Input#event:unknownmidimessage)\n\n5. **Channel Voice** Events (channel-specific)\n\n   * [`channelaftertouch`](InputChannel#event:channelaftertouch)\n   * [`controlchange`](InputChannel#event:controlchange)\n     * [`controlchange-controller0`](InputChannel#event:controlchange-controller0)\n     * [`controlchange-controller1`](InputChannel#event:controlchange-controller1)\n     * [`controlchange-controller2`](InputChannel#event:controlchange-controller2)\n     * (...)\n     * [`controlchange-controller127`](InputChannel#event:controlchange-controller127)\n   * [`keyaftertouch`](InputChannel#event:keyaftertouch)\n   * [`noteoff`](InputChannel#event:noteoff)\n   * [`noteon`](InputChannel#event:noteon)\n   * [`pitchbend`](InputChannel#event:pitchbend)\n   * [`programchange`](InputChannel#event:programchange)\n\n   Note: you can listen for a specific control change message by using an event name like this:\n   `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,\n   etc.\n\n6. **Channel Mode** Events (channel-specific)\n\n   * [`allnotesoff`](InputChannel#event:allnotesoff)\n   * [`allsoundoff`](InputChannel#event:allsoundoff)\n   * [`localcontrol`](InputChannel#event:localcontrol)\n   * [`monomode`](InputChannel#event:monomode)\n   * [`omnimode`](InputChannel#event:omnimode)\n   * [`resetallcontrollers`](InputChannel#event:resetallcontrollers)\n\n7. **NRPN** Events (channel-specific)\n\n   * [`nrpn`](InputChannel#event:nrpn)\n   * [`nrpn-dataentrycoarse`](InputChannel#event:nrpn-dataentrycoarse)\n   * [`nrpn-dataentryfine`](InputChannel#event:nrpn-dataentryfine)\n   * [`nrpn-dataincrement`](InputChannel#event:nrpn-dataincrement)\n   * [`nrpn-datadecrement`](InputChannel#event:nrpn-datadecrement)\n\n8. **RPN** Events (channel-specific)\n\n   * [`rpn`](InputChannel#event:rpn)\n   * [`rpn-dataentrycoarse`](InputChannel#event:rpn-dataentrycoarse)\n   * [`rpn-dataentryfine`](InputChannel#event:rpn-dataentryfine)\n   * [`rpn-dataincrement`](InputChannel#event:rpn-dataincrement)\n   * [`rpn-datadecrement`](InputChannel#event:rpn-datadecrement)\n\n\n  **Parameters**\n\n  > Signature: `addOneTimeListener(event, listener, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br /> ||The type of the event.|\n    |**`listener`** | function<br /> ||A callback function to execute when the specified event is detected. This function will receive an event parameter object. For details on this object's properties, check out the documentation for the various events (links above).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> ||An integer between 1 and 16 or an array of such integers representing the MIDI channel(s) to listen on. This parameter is ignored for input-wide events.|\n    |[**`options.context`**] | object<br /> |this|The value of `this` in the callback function.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus be triggered before others.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Listener>`<br />\n\nAn array of all [`Listener`](Listener) objects that were created.\n\n\n\n\n### `.close()` {#close}\n\n**Attributes**: async\n\nCloses the input. When an input is closed, it cannot be used to listen to MIDI messages until\nthe input is opened again by calling [`Input.open()`](Input#open).\n\n**Note**: if what you want to do is stop events from being dispatched, you should use\n[`eventsSuspended`](#eventsSuspended) instead.\n\n\n**Return Value**\n\n> Returns: `Promise.<Input>`<br />\n\nThe promise is fulfilled with the `Input` object\n\n\n\n\n### `.destroy()` {#destroy}\n\n**Attributes**: async\n\nDestroys the `Input` by removing all listeners, emptying the [`channels`](#channels) array and\nunlinking the MIDI subsystem. This is mostly for internal use.\n\n\n**Return Value**\n\n> Returns: `Promise.<void>`<br />\n\n\n\n\n### `.emit(...)` {#emit}\n\n\nExecutes the callback function of all the [`Listener`](Listener) objects registered for\na given event. The callback functions are passed the additional arguments passed to `emit()`\n(if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of\nthe [`Listener`](Listener) object (if any).\n\nIf the [`eventsSuspended`](#eventsSuspended) property is `true` or the\n[`Listener.suspended`](Listener#suspended) property is `true`, the callback functions\nwill not be executed.\n\nThis function returns an array containing the return values of each of the callbacks.\n\nIt should be noted that the regular listeners are triggered first followed by the global\nlisteners (those added with [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\n\n  **Parameters**\n\n  > Signature: `emit(event, ...args)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br /> ||The event|\n    |**`args`** | *<br /> ||Arbitrary number of arguments to pass along to the callback functions|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array`<br />\n\nAn array containing the return value of each of the executed listener\nfunctions.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string.\n\n\n### `.getListenerCount(...)` {#getListenerCount}\n\n\nReturns the number of listeners registered for a specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) do not count towards the remaining\nnumber for a \"regular\" event. To get the number of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListenerCount(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event which is usually a string but can also be the special [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) symbol.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer representing the number of listeners registered for the specified\nevent.\n\n\n\n\n### `.getListeners(...)` {#getListeners}\n\n\nReturns an array of all the [`Listener`](Listener) objects that have been registered for\na specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) are not returned for \"regular\"\nevents. To get the list of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListeners(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to get listeners for.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Listener>`<br />\n\nAn array of [`Listener`](Listener) objects.\n\n\n\n\n### `.hasForwarder(...)` {#hasForwarder}\n\n\nChecks whether the specified [`Forwarder`](Forwarder) object has already been attached to this\ninput.\n\n\n  **Parameters**\n\n  > Signature: `hasForwarder(forwarder)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`forwarder`** | Forwarder<br /> ||The [`Forwarder`](Forwarder) to check for (the [`Forwarder`](Forwarder) object is returned when calling [`addForwarder()`](#addForwarder).|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\n\n\n\n### `.hasListener(...)` {#hasListener}\n\n\nChecks if the specified event type is already defined to trigger the specified callback\nfunction. For channel-specific events, the function will return `true` only if all channels\nhave the listener defined.\n\n\n  **Parameters**\n\n  > Signature: `hasListener(event, listener, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The type of the event.|\n    |**`listener`** | function<br /> ||The callback function to check for.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> ||An integer between 1 and 16 or an array of such integers representing the MIDI channel(s) to check. This parameter is ignored for input-wide events.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\nBoolean value indicating whether or not the `Input` or\n[`InputChannel`](InputChannel) already has this listener defined.\n\n\n\n\n### `.open()` {#open}\n\n**Attributes**: async\n\nOpens the input for usage. This is usually unnecessary as the port is opened automatically when\nWebMidi is enabled.\n\n\n**Return Value**\n\n> Returns: `Promise.<Input>`<br />\n\nThe promise is fulfilled with the `Input` object.\n\n\n\n\n### `.removeForwarder(...)` {#removeForwarder}\n\n\nRemoves the specified [`Forwarder`](Forwarder) object from the input.\n\n\n  **Parameters**\n\n  > Signature: `removeForwarder(forwarder)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`forwarder`** | Forwarder<br /> ||The [`Forwarder`](Forwarder) to remove (the [`Forwarder`](Forwarder) object is returned when calling `addForwarder()`.|\n\n  </div>\n\n\n\n\n\n\n### `.removeListener(...)` {#removeListener}\n\n\nRemoves the specified event listener. If no listener is specified, all listeners matching the\nspecified event will be removed. If the event is channel-specific, the listener will be removed\nfrom all [`InputChannel`](InputChannel) objects belonging to that channel. If no event is\nspecified, all listeners for the `Input` as well as all listeners for all\n[`InputChannel`](InputChannel) objects belonging to the `Input` will be removed.\n\nBy default, channel-specific listeners will be removed from all\n[`InputChannel`](InputChannel) objects unless the `options.channel` narrows it down.\n\n\n  **Parameters**\n\n  > Signature: `removeListener([type], [listener], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`type`**] | string<br /> ||The type of the event.|\n    |[**`listener`**] | function<br /> ||The callback function to check for.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> ||An integer between 1 and 16 or an array of such integers representing the MIDI channel(s) to match. This parameter is ignored for input-wide events.|\n    |[**`options.context`**] | *<br /> ||Only remove the listeners that have this exact context.|\n    |[**`options.remaining`**] | number<br /> ||Only remove the listener if it has exactly that many remaining times to be executed.|\n\n  </div>\n\n\n\n\n\n\n### `.suspendEvent(...)` {#suspendEvent}\n\n\nSuspends execution of all callbacks functions registered for the specified event type.\n\nYou can suspend execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `suspendEvent()`. Beware that this\nwill not suspend all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem counter-intuitive\nat first glance, it allows the selective suspension of global listeners while leaving other\nlisteners alone. If you truly want to suspends all callbacks for a specific\n[`EventEmitter`](EventEmitter), simply set its `eventsSuspended` property to `true`.\n\n\n  **Parameters**\n\n  > Signature: `suspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to suspend execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.unsuspendEvent(...)` {#unsuspendEvent}\n\n\nResumes execution of all suspended callback functions registered for the specified event type.\n\nYou can resume execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `unsuspendEvent()`. Beware that\nthis will not resume all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem\ncounter-intuitive, it allows the selective unsuspension of global listeners while leaving other\ncallbacks alone.\n\n\n  **Parameters**\n\n  > Signature: `unsuspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to resume execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.waitFor(...)` {#waitFor}\n\n**Attributes**: async\n\nThe `waitFor()` method is an async function which returns a promise. The promise is fulfilled\nwhen the specified event occurs. The event can be a regular event or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) (if you want to resolve as soon as any\nevent is emitted).\n\nIf the `duration` option is set, the promise will only be fulfilled if the event is emitted\nwithin the specified duration. If the event has not been fulfilled after the specified\nduration, the promise is rejected. This makes it super easy to wait for an event and timeout\nafter a certain time if the event is not triggered.\n\n\n  **Parameters**\n\n  > Signature: `waitFor(event, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to wait for|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds to wait before the promise is automatically rejected.|\n\n  </div>\n\n\n\n\n\n\n***\n\n## Events\n\n### `activesensing` {#event-activesensing}\n\n<a id=\"event:activesensing\"></a>\n\n\nInput-wide (system) event emitted when an **active sensing** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`activesensing`|\n\n\n### `clock` {#event-clock}\n\n<a id=\"event:clock\"></a>\n\n\nInput-wide (system) event emitted when a **timing clock** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`clock`|\n\n\n### `closed` {#event-closed}\n\n<a id=\"event:closed\"></a>\n\n\nEvent emitted when the `Input` has been closed by calling the\n[`close()`](#close) method.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`closed`|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n\n\n### `continue` {#event-continue}\n\n<a id=\"event:continue\"></a>\n\n\nInput-wide (system) event emitted when a **continue** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`continue`|\n\n\n### `disconnected` {#event-disconnected}\n\n<a id=\"event:disconnected\"></a>\n\n\nEvent emitted when the `Input` becomes unavailable. This event is typically fired\nwhen the MIDI device is unplugged.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`disconnected`|\n  |**`port`** |Input|Object with properties describing the [Input](Input) that was disconnected. This is not the actual `Input` as it is no longer available.|\n  |**`target`** |Input|The object that dispatched the event.|\n\n\n### `midimessage` {#event-midimessage}\n\n<a id=\"event:midimessage\"></a>\n\n\nEvent emitted when any MIDI message is received on an `Input`.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`midimessage`|\n\n\n### `opened` {#event-opened}\n\n<a id=\"event:opened\"></a>\n\n\nEvent emitted when the `Input` has been opened by calling the [`open()`](#open)\nmethod.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`opened`|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n\n\n### `reset` {#event-reset}\n\n<a id=\"event:reset\"></a>\n\n\nInput-wide (system) event emitted when a **reset** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`reset`|\n\n\n### `songposition` {#event-songposition}\n\n<a id=\"event:songposition\"></a>\n\n\nInput-wide (system) event emitted when a **song position** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`songposition`|\n\n\n### `songselect` {#event-songselect}\n\n<a id=\"event:songselect\"></a>\n\n\nInput-wide (system) event emitted when a **song select** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`value`** |string|Song (or sequence) number to select (0-127)|\n  |**`rawValue`** |string|Song (or sequence) number to select (0-127)|\n\n\n### `start` {#event-start}\n\n<a id=\"event:start\"></a>\n\n\nInput-wide (system) event emitted when a **start** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`start`|\n\n\n### `stop` {#event-stop}\n\n<a id=\"event:stop\"></a>\n\n\nInput-wide (system) event emitted when a **stop** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`stop`|\n\n\n### `sysex` {#event-sysex}\n\n<a id=\"event:sysex\"></a>\n\n\nInput-wide (system) event emitted when a **system exclusive** message has been received.\nYou should note that, to receive `sysex` events, you must call the\n[`WebMidi.enable()`](WebMidi#enable()) method with the `sysex` option set to `true`:\n\n```js\nWebMidi.enable({sysex: true})\n .then(() => console.log(\"WebMidi has been enabled with sysex support.\"))\n```\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`sysex`|\n\n\n### `timecode` {#event-timecode}\n\n<a id=\"event:timecode\"></a>\n\n\nInput-wide (system) event emitted when a **time code quarter frame** message has been\nreceived.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`timecode`|\n\n\n### `tunerequest` {#event-tunerequest}\n\n<a id=\"event:tunerequest\"></a>\n\n\nInput-wide (system) event emitted when a **tune request** message has been received.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`tunerequest`|\n\n\n### `unknownmessage` {#event-unknownmessage}\n\n<a id=\"event:unknownmessage\"></a>\n\n\nInput-wide (system) event emitted when an unknown MIDI message has been received. It could\nbe, for example, one of the undefined/reserved messages.\n\n**Since**: 2.1\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`target`** |Input|The object that dispatched the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`unknownmessage`|\n\n\n\n"
  },
  {
    "path": "website/api/classes/InputChannel.md",
    "content": "\n# InputChannel\n\nThe `InputChannel` class represents a single MIDI input channel (1-16) from a single input\ndevice. This object is derived from the host's MIDI subsystem and should not be instantiated\ndirectly.\n\nAll 16 `InputChannel` objects can be found inside the input's [`channels`](Input#channels)\nproperty.\n\n**Since**: 3.0.0\n\n**Extends**: [`EventEmitter`](EventEmitter)\n<!--**Extends**: EventEmitter-->\n\n**Fires**: [`allnotesoff`](#event:allnotesoff), [`allsoundoff`](#event:allsoundoff), [`channelaftertouch`](#event:channelaftertouch), [`controlchange`](#event:controlchange), [`controlchange-allnotesoff`](#event:controlchange-allnotesoff), [`controlchange-allsoundoff`](#event:controlchange-allsoundoff), [`controlchange-attacktime`](#event:controlchange-attacktime), [`controlchange-balancecoarse`](#event:controlchange-balancecoarse), [`controlchange-balancefine`](#event:controlchange-balancefine), [`controlchange-bankselectcoarse`](#event:controlchange-bankselectcoarse), [`controlchange-bankselectfine`](#event:controlchange-bankselectfine), [`controlchange-breathcontrollercoarse`](#event:controlchange-breathcontrollercoarse), [`controlchange-breathcontrollerfine`](#event:controlchange-breathcontrollerfine), [`controlchange-brightness`](#event:controlchange-brightness), [`controlchange-channelvolumefine`](#event:controlchange-channelvolumefine), [`controlchange-controllerxxx`](#event:controlchange-controllerxxx), [`controlchange-damperpedal`](#event:controlchange-damperpedal), [`controlchange-datadecrement`](#event:controlchange-datadecrement), [`controlchange-dataentrycoarse`](#event:controlchange-dataentrycoarse), [`controlchange-dataentryfine`](#event:controlchange-dataentryfine), [`controlchange-dataincrement`](#event:controlchange-dataincrement), [`controlchange-decaytime`](#event:controlchange-decaytime), [`controlchange-effect1depth`](#event:controlchange-effect1depth), [`controlchange-effect2depth`](#event:controlchange-effect2depth), [`controlchange-effect3depth`](#event:controlchange-effect3depth), [`controlchange-effect4depth`](#event:controlchange-effect4depth), [`controlchange-effect5depth`](#event:controlchange-effect5depth), [`controlchange-effectcontrol1coarse`](#event:controlchange-effectcontrol1coarse), [`controlchange-effectcontrol1fine`](#event:controlchange-effectcontrol1fine), [`controlchange-effectcontrol2coarse`](#event:controlchange-effectcontrol2coarse), [`controlchange-effectcontrol2fine`](#event:controlchange-effectcontrol2fine), [`controlchange-expressioncoarse`](#event:controlchange-expressioncoarse), [`controlchange-expressionfine`](#event:controlchange-expressionfine), [`controlchange-footcontrollercoarse`](#event:controlchange-footcontrollercoarse), [`controlchange-footcontrollerfine`](#event:controlchange-footcontrollerfine), [`controlchange-generalpurposecontroller1`](#event:controlchange-generalpurposecontroller1), [`controlchange-generalpurposecontroller2`](#event:controlchange-generalpurposecontroller2), [`controlchange-generalpurposecontroller3`](#event:controlchange-generalpurposecontroller3), [`controlchange-generalpurposecontroller4`](#event:controlchange-generalpurposecontroller4), [`controlchange-generalpurposecontroller5`](#event:controlchange-generalpurposecontroller5), [`controlchange-generalpurposecontroller6`](#event:controlchange-generalpurposecontroller6), [`controlchange-generalpurposecontroller7`](#event:controlchange-generalpurposecontroller7), [`controlchange-generalpurposecontroller8`](#event:controlchange-generalpurposecontroller8), [`controlchange-highresolutionvelocityprefix`](#event:controlchange-highresolutionvelocityprefix), [`controlchange-hold2`](#event:controlchange-hold2), [`controlchange-legatopedal`](#event:controlchange-legatopedal), [`controlchange-localcontrol`](#event:controlchange-localcontrol), [`controlchange-modulationwheelcoarse`](#event:controlchange-modulationwheelcoarse), [`controlchange-modulationwheelfine`](#event:controlchange-modulationwheelfine), [`controlchange-monomodeon`](#event:controlchange-monomodeon), [`controlchange-nonregisteredparametercoarse`](#event:controlchange-nonregisteredparametercoarse), [`controlchange-nonregisteredparameterfine`](#event:controlchange-nonregisteredparameterfine), [`controlchange-omnimodeoff`](#event:controlchange-omnimodeoff), [`controlchange-omnimodeon`](#event:controlchange-omnimodeon), [`controlchange-pancoarse`](#event:controlchange-pancoarse), [`controlchange-panfine`](#event:controlchange-panfine), [`controlchange-polymodeon`](#event:controlchange-polymodeon), [`controlchange-portamento`](#event:controlchange-portamento), [`controlchange-portamentocontrol`](#event:controlchange-portamentocontrol), [`controlchange-portamentotimecoarse`](#event:controlchange-portamentotimecoarse), [`controlchange-portamentotimefine`](#event:controlchange-portamentotimefine), [`controlchange-registeredparametercoarse`](#event:controlchange-registeredparametercoarse), [`controlchange-registeredparameterfine`](#event:controlchange-registeredparameterfine), [`controlchange-releasetime`](#event:controlchange-releasetime), [`controlchange-resetallcontrollers`](#event:controlchange-resetallcontrollers), [`controlchange-resonance`](#event:controlchange-resonance), [`controlchange-softpedal`](#event:controlchange-softpedal), [`controlchange-sostenuto`](#event:controlchange-sostenuto), [`controlchange-soundvariation`](#event:controlchange-soundvariation), [`controlchange-vibratodelay`](#event:controlchange-vibratodelay), [`controlchange-vibratodepth`](#event:controlchange-vibratodepth), [`controlchange-vibratorate`](#event:controlchange-vibratorate), [`controlchange-volumecoarse`](#event:controlchange-volumecoarse), [`event`](#event:event), [`keyaftertouch`](#event:keyaftertouch), [`localcontrol`](#event:localcontrol), [`midimessage`](#event:midimessage), [`monomode`](#event:monomode), [`noteoff`](#event:noteoff), [`noteon`](#event:noteon), [`nrpn`](#event:nrpn), [`nrpn-datadecrement`](#event:nrpn-datadecrement), [`nrpn-dataentrycoarse`](#event:nrpn-dataentrycoarse), [`nrpn-dataentryfine`](#event:nrpn-dataentryfine), [`nrpn-dataincrement`](#event:nrpn-dataincrement), [`omnimode`](#event:omnimode), [`pitchbend`](#event:pitchbend), [`programchange`](#event:programchange), [`resetallcontrollers`](#event:resetallcontrollers), [`rpn`](#event:rpn), [`rpn-datadecrement`](#event:rpn-datadecrement), [`rpn-dataentrycoarse`](#event:rpn-dataentrycoarse), [`rpn-dataentryfine`](#event:rpn-dataentryfine), [`rpn-dataincrement`](#event:rpn-dataincrement), [`unknownmessage`](#event:unknownmessage)\n\n### `Constructor`\n\nCreates an `InputChannel` object.\n\n\n  **Parameters**\n\n  > `new InputChannel(input, number)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`input`** | Input<br /> ||The [`Input`](Input) object this channel belongs to.|\n    |**`number`** | number<br /> ||The channel's MIDI number (1-16).|\n\n  </div>\n\n\n\n***\n\n## Properties\n\n### `.eventCount` {#eventCount}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe number of unique events that have registered listeners.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventMap` {#eventMap}\n**Type**: Object<br />\n**Attributes**: read-only<br />\n\n\nAn object containing a property for each event with at least one registered listener. Each\nevent property contains an array of all the [`Listener`](Listener) objects registered\nfor the event.\n\n\n### `.eventNames` {#eventNames}\n**Type**: Array.&lt;string&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all the unique event names for which the emitter has at least one registered\nlistener.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventsSuspended` {#eventsSuspended}\n**Type**: boolean<br />\n\n\nWhether or not the execution of callbacks is currently suspended for this emitter.\n\n\n### `.input` {#input}\n**Since**: 3.0<br />\n**Type**: Input<br />\n\n\nThe [`Input`](Input) this channel belongs to.\n\n\n### `.notesState` {#notesState}\n**Type**: Array.&lt;boolean&gt;<br />\n\n\nContains the current playing state of all MIDI notes of this channel (0-127). The state is\n`true` for a currently playing note and `false` otherwise.\n\n\n### `.number` {#number}\n**Since**: 3.0<br />\n**Type**: number<br />\n\n\nThis channel's MIDI number (1-16).\n\n\n### `.octaveOffset` {#octaveOffset}\n**Since**: 3.0<br />\n**Type**: number<br />\n\n\nAn integer to offset the reported octave of incoming note-specific messages (`noteon`,\n`noteoff` and `keyaftertouch`). By default, middle C (MIDI note number 60) is placed on the 4th\noctave (C4).\n\nIf, for example, `octaveOffset` is set to 2, MIDI note number 60 will be reported as C6. If\n`octaveOffset` is set to -1, MIDI note number 60 will be reported as C3.\n\nNote that this value is combined with the global offset value defined by\n[`WebMidi.octaveOffset`](WebMidi#octaveOffset) object and with the value defined on the parent\ninput object with [`Input.octaveOffset`](Input#octaveOffset).\n\n\n### `.parameterNumberEventsEnabled` {#parameterNumberEventsEnabled}\n**Type**: boolean<br />\n\n\nIndicates whether events for **Registered Parameter Number** and **Non-Registered Parameter\nNumber** should be dispatched. RPNs and NRPNs are composed of a sequence of specific\n**control change** messages. When a valid sequence of such control change messages is\nreceived, an [`rpn`](#event-rpn) or [`nrpn`](#event-nrpn) event will fire.\n\nIf an invalid or out-of-order **control change** message is received, it will fall through\nthe collector logic and all buffered **control change** messages will be discarded as\nincomplete.\n\n\n\n***\n\n## Methods\n\n\n### `.addListener(...)` {#addListener}\n\n\nAdds a listener for the specified event. It returns the [`Listener`](Listener) object\nthat was created and attached to the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](#ANY_EVENT) as the first parameter. Note that a global\nlistener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to.|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The value of `this` in the callback function.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.remaining`**] | number<br /> |Infinity|The number of times after which the callback should automatically be removed.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.addOneTimeListener(...)` {#addOneTimeListener}\n\n\nAdds a one-time listener for the specified event. The listener will be executed once and then\ndestroyed. It returns the [`Listener`](Listener) object that was created and attached\nto the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter. Note that a\nglobal listener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addOneTimeListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The context to invoke the callback function in.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.destroy()` {#destroy}\n\n\nDestroys the `InputChannel` by removing all listeners and severing the link with the MIDI\nsubsystem's input.\n\n\n\n\n\n\n### `.emit(...)` {#emit}\n\n\nExecutes the callback function of all the [`Listener`](Listener) objects registered for\na given event. The callback functions are passed the additional arguments passed to `emit()`\n(if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of\nthe [`Listener`](Listener) object (if any).\n\nIf the [`eventsSuspended`](#eventsSuspended) property is `true` or the\n[`Listener.suspended`](Listener#suspended) property is `true`, the callback functions\nwill not be executed.\n\nThis function returns an array containing the return values of each of the callbacks.\n\nIt should be noted that the regular listeners are triggered first followed by the global\nlisteners (those added with [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\n\n  **Parameters**\n\n  > Signature: `emit(event, ...args)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br /> ||The event|\n    |**`args`** | *<br /> ||Arbitrary number of arguments to pass along to the callback functions|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array`<br />\n\nAn array containing the return value of each of the executed listener\nfunctions.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string.\n\n\n### `.getListenerCount(...)` {#getListenerCount}\n\n\nReturns the number of listeners registered for a specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) do not count towards the remaining\nnumber for a \"regular\" event. To get the number of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListenerCount(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event which is usually a string but can also be the special [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) symbol.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer representing the number of listeners registered for the specified\nevent.\n\n\n\n\n### `.getListeners(...)` {#getListeners}\n\n\nReturns an array of all the [`Listener`](Listener) objects that have been registered for\na specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) are not returned for \"regular\"\nevents. To get the list of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListeners(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to get listeners for.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Listener>`<br />\n\nAn array of [`Listener`](Listener) objects.\n\n\n\n\n### `.getNoteState(...)` {#getNoteState}\n\n**Since**: version 3.0.0<br />\n\nReturns the playing status of the specified note (`true` if the note is currently playing,\n`false` if it is not). The `note` parameter can be an unsigned integer (0-127), a note\nidentifier (`\"C4\"`, `\"G#5\"`, etc.) or a [`Note`](Note) object.\n\nIF the note is specified using an integer (0-127), no octave offset will be applied.\n\n\n  **Parameters**\n\n  > Signature: `getNoteState(note)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />string<br />Note<br /> ||The note to get the state for. The [`octaveOffset`](#octaveOffset) (channel, input and global) will be factored in for note identifiers and [`Note`](Note) objects.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\n\n\n\n### `.hasListener(...)` {#hasListener}\n\n\nReturns `true` if the specified event has at least one registered listener. If no event is\nspecified, the method returns `true` if any event has at least one listener registered (this\nincludes global listeners registered to\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\nNote: to specifically check for global listeners added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT), use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `hasListener([event], [callback])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br />Symbol<br /> |(any event)|The event to check|\n    |[**`callback`**] | function<br />Listener<br /> |(any callback)|The actual function that was added to the event or the [Listener](Listener) object returned by `addListener()`.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\n\n\n\n### `.removeListener(...)` {#removeListener}\n\n\nRemoves all the listeners that were added to the object upon which the method is called and\nthat match the specified criterias. If no parameters are passed, all listeners added to this\nobject will be removed. If only the `event` parameter is passed, all listeners for that event\nwill be removed from that object. You can remove global listeners by using\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter.\n\nTo use more granular options, you must at least define the `event`. Then, you can specify the\ncallback to match or one or more of the additional options.\n\n\n  **Parameters**\n\n  > Signature: `removeListener([event], [callback], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br /> ||The event name.|\n    |[**`callback`**] | EventEmitter~callback<br /> ||Only remove the listeners that match this exact callback function.|\n    |[**`options`**] | Object<br /> |||\n    |[**`options.context`**] | *<br /> ||Only remove the listeners that have this exact context.|\n    |[**`options.remaining`**] | number<br /> ||Only remove the listener if it has exactly that many remaining times to be executed.|\n\n  </div>\n\n\n\n\n\n\n### `.suspendEvent(...)` {#suspendEvent}\n\n\nSuspends execution of all callbacks functions registered for the specified event type.\n\nYou can suspend execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `suspendEvent()`. Beware that this\nwill not suspend all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem counter-intuitive\nat first glance, it allows the selective suspension of global listeners while leaving other\nlisteners alone. If you truly want to suspends all callbacks for a specific\n[`EventEmitter`](EventEmitter), simply set its `eventsSuspended` property to `true`.\n\n\n  **Parameters**\n\n  > Signature: `suspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to suspend execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.unsuspendEvent(...)` {#unsuspendEvent}\n\n\nResumes execution of all suspended callback functions registered for the specified event type.\n\nYou can resume execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `unsuspendEvent()`. Beware that\nthis will not resume all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem\ncounter-intuitive, it allows the selective unsuspension of global listeners while leaving other\ncallbacks alone.\n\n\n  **Parameters**\n\n  > Signature: `unsuspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to resume execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.waitFor(...)` {#waitFor}\n\n**Attributes**: async\n\nThe `waitFor()` method is an async function which returns a promise. The promise is fulfilled\nwhen the specified event occurs. The event can be a regular event or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) (if you want to resolve as soon as any\nevent is emitted).\n\nIf the `duration` option is set, the promise will only be fulfilled if the event is emitted\nwithin the specified duration. If the event has not been fulfilled after the specified\nduration, the promise is rejected. This makes it super easy to wait for an event and timeout\nafter a certain time if the event is not triggered.\n\n\n  **Parameters**\n\n  > Signature: `waitFor(event, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to wait for|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds to wait before the promise is automatically rejected.|\n\n  </div>\n\n\n\n\n\n\n***\n\n## Events\n\n### `allnotesoff` {#event-allnotesoff}\n\n<a id=\"event:allnotesoff\"></a>\n\n\nEvent emitted when an \"all notes off\" channel-mode MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`allnotesoff`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n\n\n### `allsoundoff` {#event-allsoundoff}\n\n<a id=\"event:allsoundoff\"></a>\n\n\nEvent emitted when an \"all sound off\" channel-mode MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`allsoundoff`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n\n\n### `channelaftertouch` {#event-channelaftertouch}\n\n<a id=\"event:channelaftertouch\"></a>\n\n\nEvent emitted when a control change MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`channelaftertouch`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The raw MIDI value expressed as an integer between 0 and 127.|\n\n\n### `controlchange` {#event-controlchange}\n\n<a id=\"event:controlchange\"></a>\n\n\nEvent emitted when a **control change** MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange`|\n  |**`subtype`** |string|The type of control change message that was received.|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-allnotesoff` {#event-controlchange-allnotesoff}\n\n<a id=\"event:controlchange-allnotesoff\"></a>\n\n\nEvent emitted when a **controlchange-allnotesoff** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-allnotesoff`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-allsoundoff` {#event-controlchange-allsoundoff}\n\n<a id=\"event:controlchange-allsoundoff\"></a>\n\n\nEvent emitted when a **controlchange-allsoundoff** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-allsoundoff`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-attacktime` {#event-controlchange-attacktime}\n\n<a id=\"event:controlchange-attacktime\"></a>\n\n\nEvent emitted when a **controlchange-attacktime** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-attacktime`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-balancecoarse` {#event-controlchange-balancecoarse}\n\n<a id=\"event:controlchange-balancecoarse\"></a>\n\n\nEvent emitted when a **controlchange-balancecoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-balancecoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-balancefine` {#event-controlchange-balancefine}\n\n<a id=\"event:controlchange-balancefine\"></a>\n\n\nEvent emitted when a **controlchange-balancefine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-balancefine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-bankselectcoarse` {#event-controlchange-bankselectcoarse}\n\n<a id=\"event:controlchange-bankselectcoarse\"></a>\n\n\nEvent emitted when a **controlchange-bankselectcoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-bankselectcoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-bankselectfine` {#event-controlchange-bankselectfine}\n\n<a id=\"event:controlchange-bankselectfine\"></a>\n\n\nEvent emitted when a **controlchange-bankselectfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-bankselectfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-breathcontrollercoarse` {#event-controlchange-breathcontrollercoarse}\n\n<a id=\"event:controlchange-breathcontrollercoarse\"></a>\n\n\nEvent emitted when a **controlchange-breathcontrollercoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-breathcontrollercoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-breathcontrollerfine` {#event-controlchange-breathcontrollerfine}\n\n<a id=\"event:controlchange-breathcontrollerfine\"></a>\n\n\nEvent emitted when a **controlchange-breathcontrollerfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-breathcontrollerfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-brightness` {#event-controlchange-brightness}\n\n<a id=\"event:controlchange-brightness\"></a>\n\n\nEvent emitted when a **controlchange-brightness** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-brightness`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-channelvolumefine` {#event-controlchange-channelvolumefine}\n\n<a id=\"event:controlchange-channelvolumefine\"></a>\n\n\nEvent emitted when a **controlchange-channelvolumefine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-channelvolumefine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-controllerxxx` {#event-controlchange-controllerxxx}\n\n<a id=\"event:controlchange-controllerxxx\"></a>\n\n\nEvent emitted when a **control change** MIDI message has been received and that message is\ntargeting the controller numbered \"xxx\". Of course, \"xxx\" should be replaced by a valid\ncontroller number (0-127).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-controllerxxx`|\n  |**`subtype`** |string|The type of control change message that was received.|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-damperpedal` {#event-controlchange-damperpedal}\n\n<a id=\"event:controlchange-damperpedal\"></a>\n\n\nEvent emitted when a **controlchange-damperpedal** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-damperpedal`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-datadecrement` {#event-controlchange-datadecrement}\n\n<a id=\"event:controlchange-datadecrement\"></a>\n\n\nEvent emitted when a **controlchange-datadecrement** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-datadecrement`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-dataentrycoarse` {#event-controlchange-dataentrycoarse}\n\n<a id=\"event:controlchange-dataentrycoarse\"></a>\n\n\nEvent emitted when a **controlchange-dataentrycoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-dataentrycoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-dataentryfine` {#event-controlchange-dataentryfine}\n\n<a id=\"event:controlchange-dataentryfine\"></a>\n\n\nEvent emitted when a **controlchange-dataentryfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-dataentryfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-dataincrement` {#event-controlchange-dataincrement}\n\n<a id=\"event:controlchange-dataincrement\"></a>\n\n\nEvent emitted when a **controlchange-dataincrement** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-dataincrement`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-decaytime` {#event-controlchange-decaytime}\n\n<a id=\"event:controlchange-decaytime\"></a>\n\n\nEvent emitted when a **controlchange-decaytime** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-decaytime`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effect1depth` {#event-controlchange-effect1depth}\n\n<a id=\"event:controlchange-effect1depth\"></a>\n\n\nEvent emitted when a **controlchange-effect1depth** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effect1depth`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effect2depth` {#event-controlchange-effect2depth}\n\n<a id=\"event:controlchange-effect2depth\"></a>\n\n\nEvent emitted when a **controlchange-effect2depth** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effect2depth`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effect3depth` {#event-controlchange-effect3depth}\n\n<a id=\"event:controlchange-effect3depth\"></a>\n\n\nEvent emitted when a **controlchange-effect3depth** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effect3depth`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effect4depth` {#event-controlchange-effect4depth}\n\n<a id=\"event:controlchange-effect4depth\"></a>\n\n\nEvent emitted when a **controlchange-effect4depth** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effect4depth`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effect5depth` {#event-controlchange-effect5depth}\n\n<a id=\"event:controlchange-effect5depth\"></a>\n\n\nEvent emitted when a **controlchange-effect5depth** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effect5depth`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effectcontrol1coarse` {#event-controlchange-effectcontrol1coarse}\n\n<a id=\"event:controlchange-effectcontrol1coarse\"></a>\n\n\nEvent emitted when a **controlchange-effectcontrol1coarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effectcontrol1coarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effectcontrol1fine` {#event-controlchange-effectcontrol1fine}\n\n<a id=\"event:controlchange-effectcontrol1fine\"></a>\n\n\nEvent emitted when a **controlchange-effectcontrol1fine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effectcontrol1fine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effectcontrol2coarse` {#event-controlchange-effectcontrol2coarse}\n\n<a id=\"event:controlchange-effectcontrol2coarse\"></a>\n\n\nEvent emitted when a **controlchange-effectcontrol2coarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effectcontrol2coarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-effectcontrol2fine` {#event-controlchange-effectcontrol2fine}\n\n<a id=\"event:controlchange-effectcontrol2fine\"></a>\n\n\nEvent emitted when a **controlchange-effectcontrol2fine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-effectcontrol2fine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-expressioncoarse` {#event-controlchange-expressioncoarse}\n\n<a id=\"event:controlchange-expressioncoarse\"></a>\n\n\nEvent emitted when a **controlchange-expressioncoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-expressioncoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-expressionfine` {#event-controlchange-expressionfine}\n\n<a id=\"event:controlchange-expressionfine\"></a>\n\n\nEvent emitted when a **controlchange-expressionfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-expressionfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-footcontrollercoarse` {#event-controlchange-footcontrollercoarse}\n\n<a id=\"event:controlchange-footcontrollercoarse\"></a>\n\n\nEvent emitted when a **controlchange-footcontrollercoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-footcontrollercoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-footcontrollerfine` {#event-controlchange-footcontrollerfine}\n\n<a id=\"event:controlchange-footcontrollerfine\"></a>\n\n\nEvent emitted when a **controlchange-footcontrollerfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-footcontrollerfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller1` {#event-controlchange-generalpurposecontroller1}\n\n<a id=\"event:controlchange-generalpurposecontroller1\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller1** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller1`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller2` {#event-controlchange-generalpurposecontroller2}\n\n<a id=\"event:controlchange-generalpurposecontroller2\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller2** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller2`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller3` {#event-controlchange-generalpurposecontroller3}\n\n<a id=\"event:controlchange-generalpurposecontroller3\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller3** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller3`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller4` {#event-controlchange-generalpurposecontroller4}\n\n<a id=\"event:controlchange-generalpurposecontroller4\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller4** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller4`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller5` {#event-controlchange-generalpurposecontroller5}\n\n<a id=\"event:controlchange-generalpurposecontroller5\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller5** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller5`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller6` {#event-controlchange-generalpurposecontroller6}\n\n<a id=\"event:controlchange-generalpurposecontroller6\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller6** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller6`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller7` {#event-controlchange-generalpurposecontroller7}\n\n<a id=\"event:controlchange-generalpurposecontroller7\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller7** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller7`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-generalpurposecontroller8` {#event-controlchange-generalpurposecontroller8}\n\n<a id=\"event:controlchange-generalpurposecontroller8\"></a>\n\n\nEvent emitted when a **controlchange-generalpurposecontroller8** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-generalpurposecontroller8`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-highresolutionvelocityprefix` {#event-controlchange-highresolutionvelocityprefix}\n\n<a id=\"event:controlchange-highresolutionvelocityprefix\"></a>\n\n\nEvent emitted when a **controlchange-highresolutionvelocityprefix** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-highresolutionvelocityprefix`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-hold2` {#event-controlchange-hold2}\n\n<a id=\"event:controlchange-hold2\"></a>\n\n\nEvent emitted when a **controlchange-hold2** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-hold2`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-legatopedal` {#event-controlchange-legatopedal}\n\n<a id=\"event:controlchange-legatopedal\"></a>\n\n\nEvent emitted when a **controlchange-legatopedal** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-legatopedal`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-localcontrol` {#event-controlchange-localcontrol}\n\n<a id=\"event:controlchange-localcontrol\"></a>\n\n\nEvent emitted when a **controlchange-localcontrol** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-localcontrol`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-modulationwheelcoarse` {#event-controlchange-modulationwheelcoarse}\n\n<a id=\"event:controlchange-modulationwheelcoarse\"></a>\n\n\nEvent emitted when a **controlchange-modulationwheelcoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-modulationwheelcoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-modulationwheelfine` {#event-controlchange-modulationwheelfine}\n\n<a id=\"event:controlchange-modulationwheelfine\"></a>\n\n\nEvent emitted when a **controlchange-modulationwheelfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-modulationwheelfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-monomodeon` {#event-controlchange-monomodeon}\n\n<a id=\"event:controlchange-monomodeon\"></a>\n\n\nEvent emitted when a **controlchange-monomodeon** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-monomodeon`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-nonregisteredparametercoarse` {#event-controlchange-nonregisteredparametercoarse}\n\n<a id=\"event:controlchange-nonregisteredparametercoarse\"></a>\n\n\nEvent emitted when a **controlchange-nonregisteredparametercoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-nonregisteredparametercoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-nonregisteredparameterfine` {#event-controlchange-nonregisteredparameterfine}\n\n<a id=\"event:controlchange-nonregisteredparameterfine\"></a>\n\n\nEvent emitted when a **controlchange-nonregisteredparameterfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-nonregisteredparameterfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-omnimodeoff` {#event-controlchange-omnimodeoff}\n\n<a id=\"event:controlchange-omnimodeoff\"></a>\n\n\nEvent emitted when a **controlchange-omnimodeoff** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-omnimodeoff`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-omnimodeon` {#event-controlchange-omnimodeon}\n\n<a id=\"event:controlchange-omnimodeon\"></a>\n\n\nEvent emitted when a **controlchange-omnimodeon** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-omnimodeon`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-pancoarse` {#event-controlchange-pancoarse}\n\n<a id=\"event:controlchange-pancoarse\"></a>\n\n\nEvent emitted when a **controlchange-pancoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-pancoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-panfine` {#event-controlchange-panfine}\n\n<a id=\"event:controlchange-panfine\"></a>\n\n\nEvent emitted when a **controlchange-panfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-panfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-polymodeon` {#event-controlchange-polymodeon}\n\n<a id=\"event:controlchange-polymodeon\"></a>\n\n\nEvent emitted when a **controlchange-polymodeon** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-polymodeon`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-portamento` {#event-controlchange-portamento}\n\n<a id=\"event:controlchange-portamento\"></a>\n\n\nEvent emitted when a **controlchange-portamento** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-portamento`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-portamentocontrol` {#event-controlchange-portamentocontrol}\n\n<a id=\"event:controlchange-portamentocontrol\"></a>\n\n\nEvent emitted when a **controlchange-portamentocontrol** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-portamentocontrol`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-portamentotimecoarse` {#event-controlchange-portamentotimecoarse}\n\n<a id=\"event:controlchange-portamentotimecoarse\"></a>\n\n\nEvent emitted when a **controlchange-portamentotimecoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-portamentotimecoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-portamentotimefine` {#event-controlchange-portamentotimefine}\n\n<a id=\"event:controlchange-portamentotimefine\"></a>\n\n\nEvent emitted when a **controlchange-portamentotimefine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-portamentotimefine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-registeredparametercoarse` {#event-controlchange-registeredparametercoarse}\n\n<a id=\"event:controlchange-registeredparametercoarse\"></a>\n\n\nEvent emitted when a **controlchange-registeredparametercoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-registeredparametercoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-registeredparameterfine` {#event-controlchange-registeredparameterfine}\n\n<a id=\"event:controlchange-registeredparameterfine\"></a>\n\n\nEvent emitted when a **controlchange-registeredparameterfine** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-registeredparameterfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-releasetime` {#event-controlchange-releasetime}\n\n<a id=\"event:controlchange-releasetime\"></a>\n\n\nEvent emitted when a **controlchange-releasetime** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-releasetime`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-resetallcontrollers` {#event-controlchange-resetallcontrollers}\n\n<a id=\"event:controlchange-resetallcontrollers\"></a>\n\n\nEvent emitted when a **controlchange-resetallcontrollers** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-resetallcontrollers`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-resonance` {#event-controlchange-resonance}\n\n<a id=\"event:controlchange-resonance\"></a>\n\n\nEvent emitted when a **controlchange-resonance** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-resonance`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-softpedal` {#event-controlchange-softpedal}\n\n<a id=\"event:controlchange-softpedal\"></a>\n\n\nEvent emitted when a **controlchange-softpedal** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-softpedal`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-sostenuto` {#event-controlchange-sostenuto}\n\n<a id=\"event:controlchange-sostenuto\"></a>\n\n\nEvent emitted when a **controlchange-sostenuto** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-sostenuto`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-soundvariation` {#event-controlchange-soundvariation}\n\n<a id=\"event:controlchange-soundvariation\"></a>\n\n\nEvent emitted when a **controlchange-soundvariation** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-soundvariation`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-vibratodelay` {#event-controlchange-vibratodelay}\n\n<a id=\"event:controlchange-vibratodelay\"></a>\n\n\nEvent emitted when a **controlchange-vibratodelay** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-vibratodelay`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-vibratodepth` {#event-controlchange-vibratodepth}\n\n<a id=\"event:controlchange-vibratodepth\"></a>\n\n\nEvent emitted when a **controlchange-vibratodepth** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-vibratodepth`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-vibratorate` {#event-controlchange-vibratorate}\n\n<a id=\"event:controlchange-vibratorate\"></a>\n\n\nEvent emitted when a **controlchange-vibratorate** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-vibratorate`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `controlchange-volumecoarse` {#event-controlchange-volumecoarse}\n\n<a id=\"event:controlchange-volumecoarse\"></a>\n\n\nEvent emitted when a **controlchange-volumecoarse** MIDI message has been\nreceived.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`controlchange-volumecoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`controller`** |object||\n  |**`controller.number`** |object|The number of the controller.|\n  |**`controller.name`** |object|The usual name or function of the controller.|\n  |**`controller.description`** |object|A user-friendly representation of the controller's default function|\n  |**`controller.position`** |string|Whether the controller is meant to be an `msb` or `lsb`|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The value expressed as an integer (between 0 and 127).|\n\n\n### `keyaftertouch` {#event-keyaftertouch}\n\n<a id=\"event:keyaftertouch\"></a>\n\n\nEvent emitted when a **key-specific aftertouch** MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`\"keyaftertouch\"`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`note`** |object|A [`Note`](Note) object containing information such as note name and number.|\n  |**`value`** |number|The aftertouch amount expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The aftertouch amount expressed as an integer (between 0 and 127).|\n\n\n### `localcontrol` {#event-localcontrol}\n\n<a id=\"event:localcontrol\"></a>\n\n\nEvent emitted when a \"local control\" channel-mode MIDI message has been received. The value\nproperty of the event is set to either `true` (local control on) of `false` (local control\noff).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`localcontrol`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`value`** |boolean|For local control on, the value is `true`. For local control off, the value is `false`.|\n  |**`rawValue`** |boolean|For local control on, the value is `127`. For local control off, the value is `0`.|\n\n\n### `midimessage` {#event-midimessage}\n\n<a id=\"event:midimessage\"></a>\n\n\nEvent emitted when a MIDI message of any kind is received by an `InputChannel`\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`midimessage`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n\n\n### `monomode` {#event-monomode}\n\n<a id=\"event:monomode\"></a>\n\n\nEvent emitted when a \"mono/poly mode\" MIDI message has been received. The value property of\nthe event is set to either `true` (mono mode on / poly mode off) or `false` (mono mode off /\npoly mode on).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`monomode`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`value`** |boolean|The value is `true` for omni mode on and false for omni mode off.|\n  |**`rawValue`** |boolean|The raw MIDI value|\n\n\n### `noteoff` {#event-noteoff}\n\n<a id=\"event:noteoff\"></a>\n\n\nEvent emitted when a **note off** MIDI message has been received on the channel.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`noteoff`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment ([`DOMHighResTimeStamp`](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp)) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`note`** |object|A [`Note`](Note) object containing information such as note name, octave and release velocity.|\n  |**`value`** |number|The release velocity amount expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The release velocity amount expressed as an integer (between 0 and 127).|\n\n\n### `noteon` {#event-noteon}\n\n<a id=\"event:noteon\"></a>\n\n\nEvent emitted when a **note on** MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`noteon`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`note`** |object|A [`Note`](Note) object containing information such as note name, octave and release velocity.|\n  |**`value`** |number|The attack velocity amount expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The attack velocity amount expressed as an integer (between 0 and 127).|\n\n\n### `nrpn` {#event-nrpn}\n\n<a id=\"event:nrpn\"></a>\n\n\nEvent emitted when any NRPN message is received on the input. There are four subtypes of NRPN\nmessages:\n\n  * `nrpn-dataentrycoarse`\n  * `nrpn-dataentryfine`\n  * `nrpn-dataincrement`\n  * `nrpn-datadecrement`\n\nThe parameter to which the message applies can be found in the event's `parameter` property.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`nrpn`|\n  |**`subtype`** |string|The precise type of NRPN message that was received.|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |number|The non-registered parameter number (0-16383)|\n  |**`parameterMsb`** |number|The MSB portion of the non-registered parameter number (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the non-registered parameter number (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `nrpn-datadecrement` {#event-nrpn-datadecrement}\n\n<a id=\"event:nrpn-datadecrement\"></a>\n\n\nEvent emitted when an **NRPN data decrement** message is received on the input. The specific\nparameter to which the message applies can be found in the event's `parameter` property. It\nis one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`nrpn-datadecrement`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `nrpn-dataentrycoarse` {#event-nrpn-dataentrycoarse}\n\n<a id=\"event:nrpn-dataentrycoarse\"></a>\n\n\nEvent emitted when an **NRPN data entry coarse** message is received on the input. The\nspecific parameter to which the message applies can be found in the event's `parameter`\nproperty. It is one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`nrpn-dataentrycoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `nrpn-dataentryfine` {#event-nrpn-dataentryfine}\n\n<a id=\"event:nrpn-dataentryfine\"></a>\n\n\nEvent emitted when an **NRPN data entry fine** message is received on the input. The\nspecific parameter to which the message applies can be found in the event's `parameter`\nproperty. It is one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`nrpn-dataentryfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `nrpn-dataincrement` {#event-nrpn-dataincrement}\n\n<a id=\"event:nrpn-dataincrement\"></a>\n\n\nEvent emitted when an **NRPN data increment** message is received on the input. The specific\nparameter to which the message applies can be found in the event's `parameter` property. It\nis one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`nrpn-dataincrement`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `omnimode` {#event-omnimode}\n\n<a id=\"event:omnimode\"></a>\n\n\nEvent emitted when an \"omni mode\" channel-mode MIDI message has been received. The value\nproperty of the event is set to either `true` (omni mode on) of `false` (omni mode off).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`\"omnimode\"`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`value`** |boolean|The value is `true` for omni mode on and false for omni mode off.|\n  |**`rawValue`** |boolean|The raw MIDI value|\n\n\n### `pitchbend` {#event-pitchbend}\n\n<a id=\"event:pitchbend\"></a>\n\n\nEvent emitted when a pitch bend MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`pitchbend`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`value`** |number|The value expressed as a float between 0 and 1.|\n  |**`rawValue`** |number|The raw MIDI value expressed as an integer (between 0 and 16383).|\n\n\n### `programchange` {#event-programchange}\n\n<a id=\"event:programchange\"></a>\n\n\nEvent emitted when a **program change** MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`programchange`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`value`** |number|The value expressed as an integer between 0 and 127.|\n  |**`rawValue`** |number|The raw MIDI value expressed as an integer between 0 and 127.|\n\n\n### `resetallcontrollers` {#event-resetallcontrollers}\n\n<a id=\"event:resetallcontrollers\"></a>\n\n\nEvent emitted when a \"reset all controllers\" channel-mode MIDI message has been received.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n\n\n### `rpn` {#event-rpn}\n\n<a id=\"event:rpn\"></a>\n\n\nEvent emitted when any RPN message is received on the input. There are four subtypes of RPN\nmessages:\n\n  * `rpn-dataentrycoarse`\n  * `rpn-dataentryfine`\n  * `rpn-dataincrement`\n  * `rpn-datadecrement`\n\nThe parameter to which the message applies can be found in the event's `parameter` property.\nIt is one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`rpn`|\n  |**`subtype`** |string|The precise type of RPN message that was received.|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `rpn-datadecrement` {#event-rpn-datadecrement}\n\n<a id=\"event:rpn-datadecrement\"></a>\n\n\nEvent emitted when an **RPN data decrement** message is received on the input. The specific\nparameter to which the message applies can be found in the event's `parameter` property. It\nis one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`rpn-datadecrement`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `rpn-dataentrycoarse` {#event-rpn-dataentrycoarse}\n\n<a id=\"event:rpn-dataentrycoarse\"></a>\n\n\nEvent emitted when an **RPN data entry coarse** message is received on the input. The\nspecific parameter to which the message applies can be found in the event's `parameter`\nproperty. It is one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`rpn-dataentrycoarse`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `rpn-dataentryfine` {#event-rpn-dataentryfine}\n\n<a id=\"event:rpn-dataentryfine\"></a>\n\n\nEvent emitted when an **RPN data entry fine** message is received on the input. The\nspecific parameter to which the message applies can be found in the event's `parameter`\nproperty. It is one of the ones defined in\n[`EnumerationsREGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`rpn-dataentryfine`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n### `rpn-dataincrement` {#event-rpn-dataincrement}\n\n<a id=\"event:rpn-dataincrement\"></a>\n\n\nEvent emitted when an **RPN data increment** message is received on the input. The specific\nparameter to which the message applies can be found in the event's `parameter` property. It\nis one of the ones defined in\n[`Enumerations.REGISTERED_PARAMETERS`](Enumerations#REGISTERED_PARAMETERS).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`type`** |string|`rpn-dataincrement`|\n  |**`target`** |InputChannel|The object that dispatched the event.|\n  |**`port`** |Input|The `Input` that triggered the event.|\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`message`** |Message|A [`Message`](Message) object containing information about the incoming MIDI message.|\n  |**`parameter`** |string|The registered parameter's name|\n  |**`parameterMsb`** |number|The MSB portion of the registered parameter (0-127)|\n  |**`parameterLsb:`** |number|The LSB portion of the registered parameter (0-127)|\n  |**`value`** |number|The received value as a normalized number between 0 and 1.|\n  |**`rawValue`** |number|The value as received (0-127)|\n\n\n\n"
  },
  {
    "path": "website/api/classes/Listener.md",
    "content": "\n# Listener\n\nThe `Listener` class represents a single event listener object. Such objects keep all relevant\ncontextual information such as the event being listened to, the object the listener was attached\nto, the callback function and so on.\n\n\n\n\n### `Constructor`\n\nCreates a new `Listener` object\n\n\n  **Parameters**\n\n  > `new Listener(event, target, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event being listened to|\n    |**`target`** | EventEmitter<br /> ||The [`EventEmitter`](EventEmitter) object that the listener is attached to.|\n    |**`callback`** | EventEmitter~callback<br /> ||The function to call when the listener is triggered|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |target|The context to invoke the listener in (a.k.a. the value of `this` inside the callback function).|\n    |[**`options.remaining`**] | number<br /> |Infinity|The remaining number of times after which the callback should automatically be removed.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments that will be passed separately to the callback function upon execution. The array is stored in the [`arguments`](#arguments) property and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Throws**:\n* `TypeError` : The &#x60;event&#x60; parameter must be a string or\n[&#x60;EventEmitter.ANY_EVENT&#x60;](EventEmitter#ANY_EVENT).\n* `ReferenceError` : The &#x60;target&#x60; parameter is mandatory.\n* `TypeError` : The &#x60;callback&#x60; must be a function.\n\n***\n\n## Properties\n\n### `.arguments` {#arguments}\n**Type**: array<br />\n\n\nAn array of arguments to pass to the callback function upon execution.\n\n\n### `.callback` {#callback}\n**Type**: function<br />\n\n\nThe callback function to execute.\n\n\n### `.context` {#context}\n**Type**: Object<br />\n\n\nThe context to execute the callback function in (a.k.a. the value of `this` inside the\ncallback function)\n\n\n### `.count` {#count}\n**Type**: number<br />\n\n\nThe number of times the listener function was executed.\n\n\n### `.event` {#event}\n**Type**: string<br />\n\n\nThe event name.\n\n\n### `.remaining` {#remaining}\n**Type**: number<br />\n\n\nThe remaining number of times after which the callback should automatically be removed.\n\n\n### `.suspended` {#suspended}\n**Type**: boolean<br />\n\n\nWhether this listener is currently suspended or not.\n\n\n### `.target` {#target}\n**Type**: EventEmitter<br />\n\n\nThe object that the event is attached to (or that emitted the event).\n\n\n\n***\n\n## Methods\n\n\n### `.remove()` {#remove}\n\n\nRemoves the listener from its target.\n\n\n\n\n\n\n"
  },
  {
    "path": "website/api/classes/Message.md",
    "content": "\n# Message\n\nThe `Message` class represents a single MIDI message. It has several properties that make it\neasy to make sense of the binary data it contains.\n\n**Since**: 3.0.0\n\n\n\n### `Constructor`\n\nCreates a new `Message` object from raw MIDI data.\n\n\n  **Parameters**\n\n  > `new Message(data)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`data`** | Uint8Array<br /> ||The raw data of the MIDI message as a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) of integers between `0` and `255`.|\n\n  </div>\n\n\n\n***\n\n## Properties\n\n### `.channel` {#channel}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe MIDI channel number (`1` - `16`) that the message is targeting. This is only for\nchannel-specific messages. For system messages, this will be left `undefined`.\n\n\n### `.command` {#command}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nAn integer identifying the MIDI command. For channel-specific messages, the value is 4-bit\nand will be between `8` and `14`. For system messages, the value will be between `240` and\n`255`.\n\n\n### `.data` {#data}\n**Type**: Array.&lt;number&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array containing all the bytes of the MIDI message. Each byte is an integer between `0`\nand `255`.\n\n\n### `.dataBytes` {#dataBytes}\n**Type**: Array.&lt;number&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of the the data byte(s) of the MIDI message (as opposed to the status byte). When\nthe message is a system exclusive message (sysex), `dataBytes` explicitly excludes the\nmanufacturer ID and the sysex end byte so only the actual data is included.\n\n\n### `.isChannelMessage` {#isChannelMessage}\n**Type**: boolean<br />\n**Attributes**: read-only<br />\n\n\nA boolean indicating whether the MIDI message is a channel-specific message.\n\n\n### `.isSystemMessage` {#isSystemMessage}\n**Type**: boolean<br />\n**Attributes**: read-only<br />\n\n\nA boolean indicating whether the MIDI message is a system message (not specific to a\nchannel).\n\n\n### `.manufacturerId` {#manufacturerId}\n**Type**: Array.&lt;number&gt;<br />\n**Attributes**: read-only<br />\n\n\nWhen the message is a system exclusive message (sysex), this property contains an array with\neither 1 or 3 entries that identify the manufacturer targeted by the message.\n\nTo know how to translate these entries into manufacturer names, check out the official list:\nhttps://www.midi.org/specifications-old/item/manufacturer-id-numbers\n\n\n### `.rawData` {#rawData}\n**Type**: Uint8Array<br />\n**Attributes**: read-only<br />\n\n\nA\n[`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\ncontaining the bytes of the MIDI message. Each byte is an integer between `0` and `255`.\n\n\n### `.rawDataBytes` {#rawDataBytes}\n**Type**: Uint8Array<br />\n**Attributes**: read-only<br />\n\n\nA\n[`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\nof the data byte(s) of the MIDI message. When the message is a system exclusive message\n(sysex), `rawDataBytes` explicitly excludes the manufacturer ID and the sysex end byte so\nonly the actual data is included.\n\n\n### `.statusByte` {#statusByte}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe MIDI status byte of the message as an integer between `0` and `255`.\n\n\n### `.type` {#type}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nThe type of message as a string (`\"noteon\"`, `\"controlchange\"`, `\"sysex\"`, etc.)\n\n\n\n"
  },
  {
    "path": "website/api/classes/Note.md",
    "content": "\n# Note\n\nThe `Note` class represents a single musical note such as `\"D3\"`, `\"G#4\"`, `\"F-1\"`, `\"Gb7\"`, etc.\n\n`Note` objects can be played back on a single channel by calling\n[`OutputChannel.playNote()`](OutputChannel#playNote) or, on multiple channels of the same\noutput, by calling [`Output.playNote()`](Output#playNote).\n\nThe note has [`attack`](#attack) and [`release`](#release) velocities set at `0.5` by default.\nThese can be changed by passing in the appropriate option. It is also possible to set a\nsystem-wide default for attack and release velocities by using the\n[`WebMidi.defaults`](WebMidi#defaults) property.\n\nIf you prefer to work with raw MIDI values (`0` to `127`), you can use [`rawAttack`](#rawAttack) and\n[`rawRelease`](#rawRelease) to both get and set the values.\n\nThe note may have a [`duration`](#duration). If it does, playback will be automatically stopped\nwhen the duration has elapsed by sending a `\"noteoff\"` event. By default, the duration is set to\n`Infinity`. In this case, it will never stop playing unless explicitly stopped by calling a\nmethod such as [`OutputChannel.stopNote()`](OutputChannel#stopNote),\n[`Output.stopNote()`](Output#stopNote) or similar.\n\n**Since**: 3.0.0\n\n\n\n### `Constructor`\n\nCreates a `Note` object.\n\n\n  **Parameters**\n\n  > `new Note(value, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | string<br />number<br /> ||The value used to create the note. If an identifier string is used, it must start with the note letter, optionally followed by an accidental and followed by the octave number (`\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`, etc.). If a number is used, it must be an integer between 0 and 127. In this case, middle C is considered to be C4 (note number 60).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the note should be explicitly stopped.|\n    |[**`options.attack`**] | number<br /> |0.5|The note's attack velocity as a float between 0 and 1. If you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both `attack` and `rawAttack` are specified, the latter has precedence.|\n    |[**`options.release`**] | number<br /> |0.5|The note's release velocity as a float between 0 and 1. If you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both `release` and `rawRelease` are specified, the latter has precedence.|\n    |[**`options.rawAttack`**] | number<br /> |64|The note's attack velocity as an integer between 0 and 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both `attack` and `rawAttack` are specified, the latter has precedence.|\n    |[**`options.rawRelease`**] | number<br /> |64|The note's release velocity as an integer between 0 and 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both `release` and `rawRelease` are specified, the latter has precedence.|\n\n  </div>\n\n\n**Throws**:\n* `Error` : Invalid note identifier\n* `RangeError` : Invalid name value\n* `RangeError` : Invalid accidental value\n* `RangeError` : Invalid octave value\n* `RangeError` : Invalid duration value\n* `RangeError` : Invalid attack value\n* `RangeError` : Invalid release value\n\n***\n\n## Properties\n\n### `.accidental` {#accidental}\n**Since**: 3.0.0<br />\n**Type**: string<br />\n\n\nThe accidental (#, ##, b or bb) of the note.\n\n\n### `.attack` {#attack}\n**Since**: 3.0.0<br />\n**Type**: number<br />\n\n\nThe attack velocity of the note as a float between 0 and 1.\n\n\n### `.duration` {#duration}\n**Since**: 3.0.0<br />\n**Type**: number<br />\n\n\nThe duration of the note as a positive decimal number representing the number of milliseconds\nthat the note should play for.\n\n\n### `.identifier` {#identifier}\n**Since**: 3.0.0<br />\n**Type**: string<br />\n\n\nThe name, optional accidental and octave of the note, as a string.\n\n\n### `.name` {#name}\n**Since**: 3.0.0<br />\n**Type**: string<br />\n\n\nThe name (letter) of the note. If you need the full name with octave and accidental, you can\nuse the [`identifier`](#Note+identifier) property instead.\n\n\n### `.number` {#number}\n**Since**: 3.0.0<br />\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe MIDI number of the note (`0` - `127`). This number is derived from the note identifier\nusing C4 as a reference for middle C.\n\n\n### `.octave` {#octave}\n**Since**: 3.0.0<br />\n**Type**: number<br />\n\n\nThe octave of the note.\n\n\n### `.rawAttack` {#rawAttack}\n**Since**: 3.0.0<br />\n**Type**: number<br />\n\n\nThe attack velocity of the note as a positive integer between 0 and 127.\n\n\n### `.rawRelease` {#rawRelease}\n**Since**: 3.0.0<br />\n**Type**: number<br />\n\n\nThe release velocity of the note as a positive integer between 0 and 127.\n\n\n### `.release` {#release}\n**Since**: 3.0.0<br />\n**Type**: number<br />\n\n\nThe release velocity of the note as an integer between 0 and 1.\n\n\n\n***\n\n## Methods\n\n\n### `.getOffsetNumber(...)` {#getOffsetNumber}\n\n\nReturns a MIDI note number offset by octave and/or semitone. If the calculated value is less\nthan 0, 0 will be returned. If the calculated value is more than 127, 127 will be returned. If\nan invalid value is supplied, 0 will be used.\n\n\n  **Parameters**\n\n  > Signature: `getOffsetNumber([octaveOffset], [semitoneOffset])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`octaveOffset`**] | number<br /> |0|An integer to offset the note number by octave.|\n    |[**`semitoneOffset`**] | number<br /> |0|An integer to offset the note number by semitone.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer between 0 and 127\n\n\n\n\n"
  },
  {
    "path": "website/api/classes/Output.md",
    "content": "\n# Output\n\nThe `Output` class represents a single MIDI output port (not to be confused with a MIDI channel).\nA port is made available by a MIDI device. A MIDI device can advertise several input and output\nports. Each port has 16 MIDI channels which can be accessed via the [`channels`](#channels)\nproperty.\n\nThe `Output` object is automatically instantiated by the library according to the host's MIDI\nsubsystem and should not be directly instantiated.\n\nYou can access all available `Output` objects by referring to the\n[`WebMidi.outputs`](WebMidi#outputs) array or by using methods such as\n[`WebMidi.getOutputByName()`](WebMidi#getOutputByName) or\n[`WebMidi.getOutputById()`](WebMidi#getOutputById).\n\n\n**Extends**: [`EventEmitter`](EventEmitter)\n<!--**Extends**: EventEmitter-->\n\n**Fires**: [`closed`](#event:closed), [`disconnected`](#event:disconnected), [`opened`](#event:opened)\n\n### `Constructor`\n\nCreates an `Output` object.\n\n\n  **Parameters**\n\n  > `new Output(midiOutput)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`midiOutput`** | MIDIOutput<br /> ||[`MIDIOutput`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIOutput) object as provided by the MIDI subsystem.|\n\n  </div>\n\n\n\n***\n\n## Properties\n\n### `.channels` {#channels}\n**Type**: Array.&lt;OutputChannel&gt;<br />\n\n\nArray containing the 16 [`OutputChannel`](OutputChannel) objects available provided by\nthis `Output`. The channels are numbered 1 through 16.\n\n\n### `.connection` {#connection}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nOutput port's connection state: `pending`, `open` or `closed`.\n\n\n### `.eventCount` {#eventCount}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe number of unique events that have registered listeners.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventMap` {#eventMap}\n**Type**: Object<br />\n**Attributes**: read-only<br />\n\n\nAn object containing a property for each event with at least one registered listener. Each\nevent property contains an array of all the [`Listener`](Listener) objects registered\nfor the event.\n\n\n### `.eventNames` {#eventNames}\n**Type**: Array.&lt;string&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all the unique event names for which the emitter has at least one registered\nlistener.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventsSuspended` {#eventsSuspended}\n**Type**: boolean<br />\n\n\nWhether or not the execution of callbacks is currently suspended for this emitter.\n\n\n### `.id` {#id}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nID string of the MIDI output. The ID is host-specific. Do not expect the same ID on different\nplatforms. For example, Google Chrome and the Jazz-Plugin report completely different IDs for\nthe same port.\n\n\n### `.manufacturer` {#manufacturer}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nName of the manufacturer of the device that makes this output port available.\n\n\n### `.name` {#name}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nName of the MIDI output.\n\n\n### `.octaveOffset` {#octaveOffset}\n**Since**: 3.0<br />\n**Type**: number<br />\n\n\nAn integer to offset the octave of outgoing notes. By default, middle C (MIDI note number 60)\nis placed on the 4th octave (C4).\n\nNote that this value is combined with the global offset value defined in\n[`WebMidi.octaveOffset`](WebMidi#octaveOffset) (if any).\n\n\n### `.state` {#state}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nState of the output port: `connected` or `disconnected`.\n\n\n### `.type` {#type}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nType of the output port (it will always be: `output`).\n\n\n\n***\n\n## Methods\n\n\n### `.addListener(...)` {#addListener}\n\n\nAdds a listener for the specified event. It returns the [`Listener`](Listener) object\nthat was created and attached to the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](#ANY_EVENT) as the first parameter. Note that a global\nlistener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to.|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The value of `this` in the callback function.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.remaining`**] | number<br /> |Infinity|The number of times after which the callback should automatically be removed.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.addOneTimeListener(...)` {#addOneTimeListener}\n\n\nAdds a one-time listener for the specified event. The listener will be executed once and then\ndestroyed. It returns the [`Listener`](Listener) object that was created and attached\nto the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter. Note that a\nglobal listener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addOneTimeListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The context to invoke the callback function in.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.clear()` {#clear}\n\n\nClears all MIDI messages that have been queued and scheduled but not yet sent.\n\n**Warning**: this method is defined in the\n[Web MIDI API specification](https://www.w3.org/TR/webmidi/#MIDIOutput) but has not been\nimplemented by all browsers yet. You can follow\n[this issue](https://github.com/djipco/webmidi/issues/52) for more info.\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.close()` {#close}\n\n**Attributes**: async\n\nCloses the output connection. When an output is closed, it cannot be used to send MIDI messages\nuntil the output is opened again by calling [`open()`](#open). You can check\nthe connection status by looking at the [`connection`](#connection) property.\n\n\n**Return Value**\n\n> Returns: `Promise.<void>`<br />\n\n\n\n\n### `.destroy()` {#destroy}\n\n**Attributes**: async\n\nDestroys the `Output`. All listeners are removed, all channels are destroyed and the MIDI\nsubsystem is unlinked.\n\n\n**Return Value**\n\n> Returns: `Promise.<void>`<br />\n\n\n\n\n### `.emit(...)` {#emit}\n\n\nExecutes the callback function of all the [`Listener`](Listener) objects registered for\na given event. The callback functions are passed the additional arguments passed to `emit()`\n(if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of\nthe [`Listener`](Listener) object (if any).\n\nIf the [`eventsSuspended`](#eventsSuspended) property is `true` or the\n[`Listener.suspended`](Listener#suspended) property is `true`, the callback functions\nwill not be executed.\n\nThis function returns an array containing the return values of each of the callbacks.\n\nIt should be noted that the regular listeners are triggered first followed by the global\nlisteners (those added with [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\n\n  **Parameters**\n\n  > Signature: `emit(event, ...args)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br /> ||The event|\n    |**`args`** | *<br /> ||Arbitrary number of arguments to pass along to the callback functions|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array`<br />\n\nAn array containing the return value of each of the executed listener\nfunctions.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string.\n\n\n### `.getListenerCount(...)` {#getListenerCount}\n\n\nReturns the number of listeners registered for a specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) do not count towards the remaining\nnumber for a \"regular\" event. To get the number of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListenerCount(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event which is usually a string but can also be the special [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) symbol.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer representing the number of listeners registered for the specified\nevent.\n\n\n\n\n### `.getListeners(...)` {#getListeners}\n\n\nReturns an array of all the [`Listener`](Listener) objects that have been registered for\na specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) are not returned for \"regular\"\nevents. To get the list of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListeners(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to get listeners for.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Listener>`<br />\n\nAn array of [`Listener`](Listener) objects.\n\n\n\n\n### `.hasListener(...)` {#hasListener}\n\n\nReturns `true` if the specified event has at least one registered listener. If no event is\nspecified, the method returns `true` if any event has at least one listener registered (this\nincludes global listeners registered to\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\nNote: to specifically check for global listeners added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT), use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `hasListener([event], [callback])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br />Symbol<br /> |(any event)|The event to check|\n    |[**`callback`**] | function<br />Listener<br /> |(any callback)|The actual function that was added to the event or the [Listener](Listener) object returned by `addListener()`.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\n\n\n\n### `.open()` {#open}\n\n**Attributes**: async\n\nOpens the output for usage. When the library is enabled, all ports are automatically opened.\nThis method is only useful for ports that have been manually closed.\n\n\n**Return Value**\n\n> Returns: `Promise.<Output>`<br />\n\nThe promise is fulfilled with the `Output` object.\n\n\n\n\n### `.playNote(...)` {#playNote}\n\n\nPlays a note or an array of notes on one or more channels of this output. If you intend to play\nnotes on a single channel, you should probably use\n[`OutputChannel.playNote()`](OutputChannel#playNote) instead.\n\nThe first parameter is the note to play. It can be a single value or an array of the following\nvalid values:\n\n - A MIDI note number (integer between `0` and `127`)\n - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n - A [`Note`](Note) object\n\nThe `playNote()` method sends a **note on** MIDI message for all specified notes on all\nspecified channels. If no channel is specified, it will send to all channels. If a `duration`\nis set in the `options` parameter or in the [`Note`](Note) object's\n[`duration`](Note#duration) property, it will also schedule a **note off** message to end\nthe note after said duration. If no `duration` is set, the note will simply play until a\nmatching **note off** message is sent with [`stopNote()`](#stopNote).\n\nThe execution of the **note on** command can be delayed by using the `time` property of the\n`options` parameter.\n\nWhen using [`Note`](Note) objects, the durations and velocities defined in the\n[`Note`](Note) objects have precedence over the ones specified via the method's `options`\nparameter.\n\n**Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\nfunctionally equivalent to a **note off** message.\n\n\n  **Parameters**\n\n  > Signature: `playNote(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />string<br />Note<br />Array.&lt;number&gt;<br />Array.&lt;string&gt;<br />Array.&lt;Note&gt;<br /> ||The note(s) to play. The notes can be specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a [`Note`](Note) object or an array of the previous types. When using a note identifier, octave range must be between -1 and 9. The lowest note is C-1 (MIDI note number `0`) and the highest note is G9 (MIDI note number `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.duration`**] | number<br /> ||The number of milliseconds after which a **note off** message will be scheduled. If left undefined, only a **note on** message is sent.|\n    |[**`options.attack`**] | number<br /> |0.5|The velocity at which to play the note (between `0` and `1`). If the `rawAttack` option is also defined, it will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawAttack`**] | number<br /> |64|The attack velocity at which to play the note (between `0` and `127`). This has priority over the `attack` property. An invalid velocity value will silently trigger the default of 64.|\n    |[**`options.release`**] | number<br /> |0.5|The velocity at which to release the note (between `0` and `1`). If the `rawRelease` option is also defined, it will have priority. An invalid velocity value will silently trigger the default of `0.5`. This is only used with the **note off** event triggered when `options.duration` is set.|\n    |[**`options.rawRelease`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). This has priority over the `release` property. An invalid velocity value will silently trigger the default of 64. This is only used with the **note off** event triggered when `options.duration` is set.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.removeListener(...)` {#removeListener}\n\n\nRemoves all the listeners that were added to the object upon which the method is called and\nthat match the specified criterias. If no parameters are passed, all listeners added to this\nobject will be removed. If only the `event` parameter is passed, all listeners for that event\nwill be removed from that object. You can remove global listeners by using\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter.\n\nTo use more granular options, you must at least define the `event`. Then, you can specify the\ncallback to match or one or more of the additional options.\n\n\n  **Parameters**\n\n  > Signature: `removeListener([event], [callback], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br /> ||The event name.|\n    |[**`callback`**] | EventEmitter~callback<br /> ||Only remove the listeners that match this exact callback function.|\n    |[**`options`**] | Object<br /> |||\n    |[**`options.context`**] | *<br /> ||Only remove the listeners that have this exact context.|\n    |[**`options.remaining`**] | number<br /> ||Only remove the listener if it has exactly that many remaining times to be executed.|\n\n  </div>\n\n\n\n\n\n\n### `.send(...)` {#send}\n\n\nSends a MIDI message on the MIDI output port. If no time is specified, the message will be\nsent immediately. The message should be an array of 8 bit unsigned integers (0-225), a\n[`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\nobject or a [`Message`](Message) object.\n\nIt is usually not necessary to use this method directly as you can use one of the simpler\nhelper methods such as [`playNote()`](#playNote), [`stopNote()`](#stopNote),\n[`sendControlChange()`](#sendControlChange), etc.\n\nDetails on the format of MIDI messages are available in the summary of\n[MIDI messages](https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message)\nfrom the MIDI Manufacturers Association.\n\n\n  **Parameters**\n\n  > Signature: `send(message, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`message`** | Array.&lt;number&gt;<br />Uint8Array<br />Message<br /> ||An array of 8bit unsigned integers, a `Uint8Array` object (not available in Node.js) containing the message bytes or a `Message` object.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The first byte (status) must be an integer between 128 and 255.\n\n\n### `.sendActiveSensing(...)` {#sendActiveSensing}\n\n\nSends an **active sensing** real-time message. This tells the device connected to this port\nthat the connection is still good. Active sensing messages are often sent every 300 ms if there\nwas no other activity on the MIDI port.\n\n\n  **Parameters**\n\n  > Signature: `sendActiveSensing([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendAllNotesOff(...)` {#sendAllNotesOff}\n\n**Since**: 3.0.0<br />\n\nSends an **all notes off** channel mode message. This will make all currently playing notes\nfade out just as if their key had been released. This is different from the\n[`sendAllSoundOff()`](#sendAllSoundOff) method which mutes all sounds immediately.\n\n\n  **Parameters**\n\n  > Signature: `sendAllNotesOff([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\n\n\n\n### `.sendAllSoundOff(...)` {#sendAllSoundOff}\n\n**Since**: 3.0.0<br />\n\nSends an **all sound off** channel mode message. This will silence all sounds playing on that\nchannel but will not prevent new sounds from being triggered.\n\n\n  **Parameters**\n\n  > Signature: `sendAllSoundOff([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\n\n\n\n### `.sendChannelAftertouch(...)` {#sendChannelAftertouch}\n\n**Since**: 3.0.0<br />\n\nSends a MIDI **channel aftertouch** message to the specified channel(s). For key-specific\naftertouch, you should instead use [`setKeyAftertouch()`](#setKeyAftertouch).\n\n\n  **Parameters**\n\n  > Signature: `sendChannelAftertouch([pressure], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`pressure`**] | number<br /> |0.5|The pressure level (between `0` and `1`). An invalid pressure value will silently trigger the default behaviour. If the `rawValue` option is set to `true`, the pressure can be defined by using an integer between `0` and `127`.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.rawValue`**] | boolean<br /> |false|A boolean indicating whether the value should be considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendChannelMode(...)` {#sendChannelMode}\n\n\nSends a MIDI **channel mode** message to the specified channel(s). The channel mode message to\nsend can be specified numerically or by using one of the following common names:\n\n|  Type                |Number| Shortcut Method                                               |\n| ---------------------|------|-------------------------------------------------------------- |\n| `allsoundoff`        | 120  | [`sendAllSoundOff()`](#sendAllSoundOff)                 |\n| `resetallcontrollers`| 121  | [`sendResetAllControllers()`](#sendResetAllControllers) |\n| `localcontrol`       | 122  | [`sendLocalControl()`](#sendLocalControl)               |\n| `allnotesoff`        | 123  | [`sendAllNotesOff()`](#sendAllNotesOff)                 |\n| `omnimodeoff`        | 124  | [`sendOmniMode(false)`](#sendOmniMode)                  |\n| `omnimodeon`         | 125  | [`sendOmniMode(true)`](#sendOmniMode)                   |\n| `monomodeon`         | 126  | [`sendPolyphonicMode(\"mono\")`](#sendPolyphonicMode)     |\n| `polymodeon`         | 127  | [`sendPolyphonicMode(\"poly\")`](#sendPolyphonicMode)     |\n\nNote: as you can see above, to make it easier, all channel mode messages also have a matching\nhelper method.\n\nIt should also be noted that, per the MIDI specification, only `localcontrol` and `monomodeon`\nmay require a value that's not zero. For that reason, the `value` parameter is optional and\ndefaults to 0.\n\n\n  **Parameters**\n\n  > Signature: `sendChannelMode(command, [value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`command`** | number<br />string<br /> ||The numerical identifier of the channel mode message (integer between 120-127) or its name as a string.|\n    |[**`value`**] | number<br /> |0|The value to send (integer between 0-127).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `TypeError` : Invalid channel mode message name.\n  * `RangeError` : Channel mode controller numbers must be between 120 and 127.\n  * `RangeError` : Value must be an integer between 0 and 127.\n\n\n### `.sendClock(...)` {#sendClock}\n\n\nSends a MIDI **clock** real-time message. According to the standard, there are 24 MIDI clocks\nfor every quarter note.\n\n\n  **Parameters**\n\n  > Signature: `sendClock([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendContinue(...)` {#sendContinue}\n\n\nSends a **continue** real-time message. This resumes song playback where it was previously\nstopped or where it was last cued with a song position message. To start playback from the\nstart, use the [`sendStart()`](#Output+sendStart)` method.\n\n\n  **Parameters**\n\n  > Signature: `sendContinue([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendControlChange(...)` {#sendControlChange}\n\n\nSends a MIDI **control change** message to the specified channel(s) at the scheduled time. The\ncontrol change message to send can be specified numerically (0-127) or by using one of the\nfollowing common names:\n\n| Number | Name                          |\n|--------|-------------------------------|\n| 0      |`bankselectcoarse`             |\n| 1      |`modulationwheelcoarse`        |\n| 2      |`breathcontrollercoarse`       |\n| 4      |`footcontrollercoarse`         |\n| 5      |`portamentotimecoarse`         |\n| 6      |`dataentrycoarse`              |\n| 7      |`volumecoarse`                 |\n| 8      |`balancecoarse`                |\n| 10     |`pancoarse`                    |\n| 11     |`expressioncoarse`             |\n| 12     |`effectcontrol1coarse`         |\n| 13     |`effectcontrol2coarse`         |\n| 18     |`generalpurposeslider3`        |\n| 19     |`generalpurposeslider4`        |\n| 32     |`bankselectfine`               |\n| 33     |`modulationwheelfine`          |\n| 34     |`breathcontrollerfine`         |\n| 36     |`footcontrollerfine`           |\n| 37     |`portamentotimefine`           |\n| 38     |`dataentryfine`                |\n| 39     |`volumefine`                   |\n| 40     |`balancefine`                  |\n| 42     |`panfine`                      |\n| 43     |`expressionfine`               |\n| 44     |`effectcontrol1fine`           |\n| 45     |`effectcontrol2fine`           |\n| 64     |`holdpedal`                    |\n| 65     |`portamento`                   |\n| 66     |`sustenutopedal`               |\n| 67     |`softpedal`                    |\n| 68     |`legatopedal`                  |\n| 69     |`hold2pedal`                   |\n| 70     |`soundvariation`               |\n| 71     |`resonance`                    |\n| 72     |`soundreleasetime`             |\n| 73     |`soundattacktime`              |\n| 74     |`brightness`                   |\n| 75     |`soundcontrol6`                |\n| 76     |`soundcontrol7`                |\n| 77     |`soundcontrol8`                |\n| 78     |`soundcontrol9`                |\n| 79     |`soundcontrol10`               |\n| 80     |`generalpurposebutton1`        |\n| 81     |`generalpurposebutton2`        |\n| 82     |`generalpurposebutton3`        |\n| 83     |`generalpurposebutton4`        |\n| 91     |`reverblevel`                  |\n| 92     |`tremololevel`                 |\n| 93     |`choruslevel`                  |\n| 94     |`celestelevel`                 |\n| 95     |`phaserlevel`                  |\n| 96     |`dataincrement`                |\n| 97     |`datadecrement`                |\n| 98     |`nonregisteredparametercoarse` |\n| 99     |`nonregisteredparameterfine`   |\n| 100    |`registeredparametercoarse`    |\n| 101    |`registeredparameterfine`      |\n| 120    |`allsoundoff`                  |\n| 121    |`resetallcontrollers`          |\n| 122    |`localcontrol`                 |\n| 123    |`allnotesoff`                  |\n| 124    |`omnimodeoff`                  |\n| 125    |`omnimodeon`                   |\n| 126    |`monomodeon`                   |\n| 127    |`polymodeon`                   |\n\nNote: as you can see above, not all control change message have a matching name. This does not\nmean you cannot use the others. It simply means you will need to use their number (`0` - `127`)\ninstead of their name. While you can still use them, numbers `120` to `127` are usually\nreserved for *channel mode* messages. See [`sendChannelMode()`](#sendChannelMode) method\nfor more info.\n\nTo view a list of all available **control change** messages, please consult [Table 3 - Control\nChange Messages](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\nfrom the MIDI specification.\n\n\n  **Parameters**\n\n  > Signature: `sendControlChange(controller, [value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`controller`** | number<br />string<br /> ||The MIDI controller name or number (0-127).|\n    |[**`value`**] | number<br /> |0|The value to send (0-127).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : Controller numbers must be between 0 and 127.\n  * `RangeError` : Invalid controller name.\n\n\n### `.sendKeyAftertouch(...)` {#sendKeyAftertouch}\n\n**Since**: 3.0.0<br />\n\nSends a MIDI **key aftertouch** message to the specified channel(s) at the scheduled time. This\nis a key-specific aftertouch. For a channel-wide aftertouch message, use\n[`setChannelAftertouch()`](#setChannelAftertouch).\n\n\n  **Parameters**\n\n  > Signature: `sendKeyAftertouch(note, [pressure], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />Note<br />string<br />Array.&lt;number&gt;<br />Array.&lt;Note&gt;<br />Array.&lt;string&gt;<br /> ||The note(s) for which you are sending an aftertouch value. The notes can be specified by using a MIDI note number (`0` - `127`), a [`Note`](Note) object, a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number `127`).|\n    |[**`pressure`**] | number<br /> |0.5|The pressure level (between 0 and 1). An invalid pressure value will silently trigger the default behaviour. If the `rawValue` option is set to `true`, the pressure can be defined by using an integer between 0 and 127.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.rawValue`**] | boolean<br /> |false|A boolean indicating whether the value should be considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendLocalControl(...)` {#sendLocalControl}\n\n**Since**: 3.0.0<br />\n\nTurns local control on or off. Local control is usually enabled by default. If you disable it,\nthe instrument will no longer trigger its own sounds. It will only send the MIDI messages to\nits out port.\n\n\n  **Parameters**\n\n  > Signature: `sendLocalControl([state], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`state`**] | boolean<br /> |false|Whether to activate local control (`true`) or disable it (`false`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendMasterTuning(...)` {#sendMasterTuning}\n\n**Since**: 3.0.0<br />\n\nSends a master tuning message to the specified channel(s). The value is decimal and must be\nlarger than `-65` semitones and smaller than `64` semitones.\n\nBecause of the way the MIDI specification works, the decimal portion of the value will be\nencoded with a resolution of 14bit. The integer portion must be between -64 and 63\ninclusively. This function actually generates two MIDI messages: a **Master Coarse Tuning** and\na **Master Fine Tuning** RPN messages.\n\n\n  **Parameters**\n\n  > Signature: `sendMasterTuning([value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`value`**] | number<br /> |0.0|The desired decimal adjustment value in semitones (-65 < x < 64)|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The value must be a decimal number between larger than -65 and smaller\nthan 64.\n\n\n### `.sendModulationRange(...)` {#sendModulationRange}\n\n**Since**: 3.0.0<br />\n\nSends a **modulation depth range** message to the specified channel(s) so that they adjust the\ndepth of their modulation wheel's range. The range can be specified with the `semitones`\nparameter, the `cents` parameter or by specifying both parameters at the same time.\n\n\n  **Parameters**\n\n  > Signature: `sendModulationRange([semitones], [cents], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`semitones`**] | number<br /> |0|The desired adjustment value in semitones (integer between 0 and 127).|\n    |[**`cents`**] | number<br /> |0|The desired adjustment value in cents (integer between 0 and 127).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The msb value must be between 0 and 127\n  * `RangeError` : The lsb value must be between 0 and 127\n\n\n### `.sendNoteOff(...)` {#sendNoteOff}\n\n\nSends a **note off** message for the specified MIDI note number on the specified channel(s).\nThe first parameter is the note to stop. It can be a single value or an array of the following\nvalid values:\n\n - A MIDI note number (integer between `0` and `127`)\n - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n - A [`Note`](Note) object\n\nThe execution of the **note off** command can be delayed by using the `time` property of the\n`options` parameter.\n\n\n  **Parameters**\n\n  > Signature: `sendNoteOff(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />Note<br />string<br />Array.&lt;number&gt;<br />Array.&lt;Note&gt;<br />Array.&lt;string&gt;<br /> ||The note(s) to stop. The notes can be specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.release`**] | number<br /> |0.5|The velocity at which to release the note (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawRelease`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `64`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendNoteOn(...)` {#sendNoteOn}\n\n\nSends a **note on** message for the specified MIDI note number on the specified channel(s). The\nfirst parameter is the number. It can be a single value or an array of the following valid\nvalues:\n\n - A MIDI note number (integer between `0` and `127`)\n - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n - A [`Note`](Note) object\n\n The execution of the **note on** command can be delayed by using the `time` property of the\n`options` parameter.\n\n**Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\nfunctionally equivalent to a **note off** message.\n\n\n  **Parameters**\n\n  > Signature: `sendNoteOn(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />Note<br />string<br />Array.&lt;number&gt;<br />Array.&lt;Note&gt;<br />Array.&lt;string&gt;<br /> ||The note(s) to stop. The notes can be specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.attack`**] | number<br /> |0.5|The velocity at which to play the note (between `0` and `1`).  If the `rawAttack` option is also defined, `rawAttack` will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawAttack`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). If the `attack` option is also defined, `rawAttack` will have priority. An invalid velocity value will silently trigger the default of `64`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendNrpnValue(...)` {#sendNrpnValue}\n\n\nSets a non-registered parameter to the specified value. The NRPN is selected by passing a\ntwo-position array specifying the values of the two control bytes. The value is specified by\npassing a single integer (most cases) or an array of two integers.\n\nNRPNs are not standardized in any way. Each manufacturer is free to implement them any way\nthey see fit. For example, according to the Roland GS specification, you can control the\n**vibrato rate** using NRPN (`1`, `8`). Therefore, to set the **vibrato rate** value to `123`\nyou would use:\n\n```js\nWebMidi.outputs[0].sendNrpnValue([1, 8], 123);\n```\n\nYou probably want to should select a channel so the message is not sent to all channels. For\ninstance, to send to channel `1` of the first output port, you would use:\n\n```js\nWebMidi.outputs[0].sendNrpnValue([1, 8], 123, 1);\n```\n\nIn some rarer cases, you need to send two values with your NRPN messages. In such cases, you\nwould use a 2-position array. For example, for its **ClockBPM** parameter (`2`, `63`), Novation\nuses a 14-bit value that combines an MSB and an LSB (7-bit values). So, for example, if the\nvalue to send was `10`, you could use:\n\n```js\nWebMidi.outputs[0].sendNrpnValue([2, 63], [0, 10], 1);\n```\n\nFor further implementation details, refer to the manufacturer's documentation.\n\n\n  **Parameters**\n\n  > Signature: `sendNrpnValue(parameter, [data], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`parameter`** | Array.&lt;number&gt;<br /> ||A two-position array specifying the two control bytes (`0x63`, `0x62`) that identify the non-registered parameter.|\n    |[**`data`**] | number<br />Array.&lt;number&gt;<br /> |[]|An integer or an array of integers with a length of 1 or 2 specifying the desired data.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The control value must be between 0 and 127.\n  * `RangeError` : The msb value must be between 0 and 127\n\n\n### `.sendOmniMode(...)` {#sendOmniMode}\n\n**Since**: 3.0.0<br />\n\nSets OMNI mode to **on** or **off** for the specified channel(s). MIDI's OMNI mode causes the\ninstrument to respond to messages from all channels.\n\nIt should be noted that support for OMNI mode is not as common as it used to be.\n\n\n  **Parameters**\n\n  > Signature: `sendOmniMode([state], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`state`**] | boolean<br /> ||Whether to activate OMNI mode (`true`) or not (`false`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `TypeError` : Invalid channel mode message name.\n  * `RangeError` : Channel mode controller numbers must be between 120 and 127.\n  * `RangeError` : Value must be an integer between 0 and 127.\n\n\n### `.sendPitchBend(...)` {#sendPitchBend}\n\n**Since**: 3.0.0<br />\n\nSends a MIDI **pitch bend** message to the specified channel(s) at the scheduled time.\n\nThe resulting bend is relative to the pitch bend range that has been defined. The range can be\nset with [`sendPitchBendRange()`](#sendPitchBendRange). So, for example, if the pitch\nbend range has been set to 12 semitones, using a bend value of `-1` will bend the note 1 octave\nbelow its nominal value.\n\n\n  **Parameters**\n\n  > Signature: `sendPitchBend(value, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br />Array.&lt;number&gt;<br /> ||The intensity of the bend (between `-1.0` and `1.0`). A value of `0` means no bend. If an invalid value is specified, the nearest valid value will be used instead. If the `rawValue` option is set to `true`, the intensity of the bend can be defined by either using a single integer between `0` and `127` (MSB) or an array of two integers between `0` and `127` representing, respectively, the MSB (most significant byte) and the LSB (least significant byte). The MSB is expressed in semitones with `64` meaning no bend. A value lower than `64` bends downwards while a value higher than `64` bends upwards. The LSB is expressed in cents (1/100 of a semitone). An LSB of `64` also means no bend.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.rawValue`**] | boolean<br /> |false|A boolean indicating whether the value should be considered as a float between `-1.0` and `1.0` (default) or as raw integer between `0` and 127` (or an array of 2 integers if using both MSB and LSB).|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendPitchBendRange(...)` {#sendPitchBendRange}\n\n**Since**: 3.0.0<br />\n\nSends a **pitch bend range** message to the specified channel(s) at the scheduled time so that\nthey adjust the range used by their pitch bend lever. The range is specified by using the\n`semitones` and `cents` parameters. For example, setting the `semitones` parameter to `12`\nmeans that the pitch bend range will be 12 semitones above and below the nominal pitch.\n\n\n  **Parameters**\n\n  > Signature: `sendPitchBendRange([semitones], [cents], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`semitones`**] | number<br /> |0|The desired adjustment value in semitones (between `0` and `127`). While nothing imposes that in the specification, it is very common for manufacturers to limit the range to 2 octaves (-12 semitones to 12 semitones).|\n    |[**`cents`**] | number<br /> |0|The desired adjustment value in cents (integer between `0` and `127`).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The msb value must be between 0 and 127.\n  * `RangeError` : The lsb value must be between 0 and 127.\n\n\n### `.sendPolyphonicMode(...)` {#sendPolyphonicMode}\n\n**Since**: 3.0.0<br />\n\nSets the polyphonic mode. In `poly` mode (usually the default), multiple notes can be played\nand heard at the same time. In `mono` mode, only one note will be heard at once even if\nmultiple notes are being played.\n\n\n  **Parameters**\n\n  > Signature: `sendPolyphonicMode(mode, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`mode`** | string<br /> ||The mode to use: `mono` or `poly`.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendProgramChange(...)` {#sendProgramChange}\n\n**Since**: 3.0.0<br />\n\nSends a MIDI **program change** message to the specified channel(s) at the scheduled time.\n\n\n  **Parameters**\n\n  > Signature: `sendProgramChange([program], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`program`**] | number<br /> |0|The MIDI patch (program) number (integer between `0` and `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `TypeError` : Failed to execute 'send' on 'MIDIOutput': The value at index 1 is greater\nthan 0xFF.\n\n\n### `.sendReset(...)` {#sendReset}\n\n\nSends a **reset** real-time message. This tells the device connected to this output that it\nshould reset itself to a default state.\n\n\n  **Parameters**\n\n  > Signature: `sendReset([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendResetAllControllers(...)` {#sendResetAllControllers}\n\n\nSends a **reset all controllers** channel mode message. This resets all controllers, such as\nthe pitch bend, to their default value.\n\n\n  **Parameters**\n\n  > Signature: `sendResetAllControllers([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\n\n\n\n### `.sendRpnDecrement(...)` {#sendRpnDecrement}\n\n\nDecrements the specified MIDI registered parameter by 1. Here is the full list of parameter\nnames that can be used with this method:\n\n * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n * Gain (0x3D, 0x02): `\"gain\"`\n * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n\n\n  **Parameters**\n\n  > Signature: `sendRpnDecrement(parameter, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`parameter`** | String<br />Array.&lt;number&gt;<br /> ||A string identifying the parameter's name (see above) or a two-position array specifying the two control bytes (0x65, 0x64) that identify the registered parameter.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * TypeError The specified parameter is not available.\n\n\n### `.sendRpnIncrement(...)` {#sendRpnIncrement}\n\n\nIncrements the specified MIDI registered parameter by 1. Here is the full list of parameter\nnames that can be used with this method:\n\n * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n * Gain (0x3D, 0x02): `\"gain\"`\n * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n\n\n  **Parameters**\n\n  > Signature: `sendRpnIncrement(parameter, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`parameter`** | String<br />Array.&lt;number&gt;<br /> ||A string identifying the parameter's name (see above) or a two-position array specifying the two control bytes (0x65, 0x64) that identify the registered parameter.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendRpnValue(...)` {#sendRpnValue}\n\n\nSets the specified MIDI registered parameter to the desired value. The value is defined with\nup to two bytes of data (msb, lsb) that each can go from `0` to `127`.\n\nMIDI\n[registered parameters](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\nextend the original list of control change messages. The MIDI 1.0 specification lists only a\nlimited number of them:\n\n| Numbers      | Function                 |\n|--------------|--------------------------|\n| (0x00, 0x00) | `pitchbendrange`         |\n| (0x00, 0x01) | `channelfinetuning`      |\n| (0x00, 0x02) | `channelcoarsetuning`    |\n| (0x00, 0x03) | `tuningprogram`          |\n| (0x00, 0x04) | `tuningbank`             |\n| (0x00, 0x05) | `modulationrange`        |\n| (0x3D, 0x00) | `azimuthangle`           |\n| (0x3D, 0x01) | `elevationangle`         |\n| (0x3D, 0x02) | `gain`                   |\n| (0x3D, 0x03) | `distanceratio`          |\n| (0x3D, 0x04) | `maximumdistance`        |\n| (0x3D, 0x05) | `maximumdistancegain`    |\n| (0x3D, 0x06) | `referencedistanceratio` |\n| (0x3D, 0x07) | `panspreadangle`         |\n| (0x3D, 0x08) | `rollangle`              |\n\nNote that the `tuningprogram` and `tuningbank` parameters are part of the *MIDI Tuning\nStandard*, which is not widely implemented.\n\n\n  **Parameters**\n\n  > Signature: `sendRpnValue(parameter, [data], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`parameter`** | string<br />Array.&lt;number&gt;<br /> ||A string identifying the parameter's name (see above) or a two-position array specifying the two control bytes (e.g. `[0x65, 0x64]`) that identify the registered parameter.|\n    |[**`data`**] | number<br />Array.&lt;number&gt;<br /> |[]|A single integer or an array of integers with a maximum length of 2 specifying the desired data.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendSongPosition(...)` {#sendSongPosition}\n\n**Since**: 3.0.0<br />\n\nSends a **song position** MIDI message. The value is expressed in MIDI beats (between `0` and\n`16383`) which are 16th note. Position `0` is always the start of the song.\n\n\n  **Parameters**\n\n  > Signature: `sendSongPosition([value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`value`**] | number<br /> |0|The MIDI beat to cue to (integer between `0` and `16383`).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendSongSelect(...)` {#sendSongSelect}\n\n**Since**: 3.0.0<br />\n\nSends a **song select** MIDI message.\n\n\n  **Parameters**\n\n  > Signature: `sendSongSelect([value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`value`**] | number<br /> |0|The number of the song to select (integer between `0` and `127`).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * The song number must be between 0 and 127.\n\n\n### `.sendStart(...)` {#sendStart}\n\n\nSends a **start** real-time message. A MIDI Start message starts the playback of the current\nsong at beat 0. To start playback elsewhere in the song, use the\n[`sendContinue()`](#sendContinue) method.\n\n\n  **Parameters**\n\n  > Signature: `sendStart([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendStop(...)` {#sendStop}\n\n\nSends a **stop** real-time message. This tells the device connected to this output to stop\nplayback immediately (or at the scheduled time, if specified).\n\n\n  **Parameters**\n\n  > Signature: `sendStop([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendSysex(...)` {#sendSysex}\n\n\nSends a MIDI [**system exclusive**](https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages)\n(*sysex*) message. There are two categories of system exclusive messages: manufacturer-specific\nmessages and universal messages. Universal messages are further divided into three subtypes:\n\n  * Universal non-commercial (for research and testing): `0x7D`\n  * Universal non-realtime: `0x7E`\n  * Universal realtime: `0x7F`\n\nThe method's first parameter (`identification`) identifies the type of message. If the value of\n`identification` is `0x7D` (125), `0x7E` (126) or `0x7F` (127), the message will be identified\nas a **universal non-commercial**, **universal non-realtime** or **universal realtime** message\n(respectively).\n\nIf the `identification` value is an array or an integer between 0 and 124, it will be used to\nidentify the manufacturer targeted by the message. The *MIDI Manufacturers Association*\nmaintains a full list of\n[Manufacturer ID Numbers](https://www.midi.org/specifications-old/item/manufacturer-id-numbers).\n\nThe `data` parameter should only contain the data of the message. When sending out the actual\nMIDI message, WEBMIDI.js will automatically prepend the data with the **sysex byte** (`0xF0`)\nand the identification byte(s). It will also automatically terminate the message with the\n**sysex end byte** (`0xF7`).\n\nTo use the `sendSysex()` method, system exclusive message support must have been enabled. To\ndo so, you must set the `sysex` option to `true` when calling\n[`WebMidi.enable()`](WebMidi#enable):\n\n```js\nWebMidi.enable({sysex: true})\n  .then(() => console.log(\"System exclusive messages are enabled\");\n```\n\n##### Examples of manufacturer-specific system exclusive messages\n\nIf you want to send a sysex message to a Korg device connected to the first output, you would\nuse the following code:\n\n```js\nWebMidi.outputs[0].sendSysex(0x42, [0x1, 0x2, 0x3, 0x4, 0x5]);\n```\nIn this case `0x42` is the ID of the manufacturer (Korg) and `[0x1, 0x2, 0x3, 0x4, 0x5]` is the\ndata being sent.\n\nThe parameters can be specified using any number notation (decimal, hex, binary, etc.).\nTherefore, the code above is equivalent to this code:\n\n```js\nWebMidi.outputs[0].sendSysex(66, [1, 2, 3, 4, 5]);\n```\n\nSome manufacturers are identified using 3 bytes. In this case, you would use a 3-position array\nas the first parameter. For example, to send the same sysex message to a\n*Native Instruments* device:\n\n```js\nWebMidi.outputs[0].sendSysex([0x00, 0x21, 0x09], [0x1, 0x2, 0x3, 0x4, 0x5]);\n```\n\nThere is no limit for the length of the data array. However, it is generally suggested to keep\nsystem exclusive messages to 64Kb or less.\n\n##### Example of universal system exclusive message\n\nIf you want to send a universal sysex message, simply assign the correct identification number\nin the first parameter. Number `0x7D` (125) is for non-commercial, `0x7E` (126) is for\nnon-realtime and `0x7F` (127) is for realtime.\n\nSo, for example, if you wanted to send an identity request non-realtime message (`0x7E`), you\ncould use the following:\n\n```js\nWebMidi.outputs[0].sendSysex(0x7E, [0x7F, 0x06, 0x01]);\n```\n\nFor more details on the format of universal messages, consult the list of\n[universal sysex messages](https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages).\n\n\n  **Parameters**\n\n  > Signature: `sendSysex(identification, [data], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`identification`** | number<br />Array.&lt;number&gt;<br /> ||An unsigned integer or an array of three unsigned integers between `0` and `127` that either identify the manufacturer or sets the message to be a **universal non-commercial message** (`0x7D`), a **universal non-realtime message** (`0x7E`) or a **universal realtime message** (`0x7F`). The *MIDI Manufacturers Association* maintains a full list of [Manufacturer ID Numbers](https://www.midi.org/specifications-old/item/manufacturer-id-numbers).|\n    |[**`data`**] | Array.&lt;number&gt;<br />Uint8Array<br /> ||A `Uint8Array` or an array of unsigned integers between `0` and `127`. This is the data you wish to transfer.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `DOMException` : Failed to execute 'send' on 'MIDIOutput': System exclusive message is\nnot allowed.\n  * `TypeError` : Failed to execute 'send' on 'MIDIOutput': The value at index x is greater\nthan 0xFF.\n\n\n### `.sendTimecodeQuarterFrame(...)` {#sendTimecodeQuarterFrame}\n\n\nSends a MIDI **timecode quarter frame** message. Please note that no processing is being done\non the data. It is up to the developer to format the data according to the\n[MIDI Timecode](https://en.wikipedia.org/wiki/MIDI_timecode) format.\n\n\n  **Parameters**\n\n  > Signature: `sendTimecodeQuarterFrame(value, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br /> ||The quarter frame message content (integer between 0 and 127).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendTuneRequest(...)` {#sendTuneRequest}\n\n**Since**: 3.0.0<br />\n\nSends a MIDI **tune request** real-time message.\n\n\n  **Parameters**\n\n  > Signature: `sendTuneRequest([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.sendTuningBank(...)` {#sendTuningBank}\n\n**Since**: 3.0.0<br />\n\nSets the MIDI tuning bank to use. Note that the **Tuning Bank** parameter is part of the\n*MIDI Tuning Standard*, which is not widely implemented.\n\n\n  **Parameters**\n\n  > Signature: `sendTuningBank([value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`value`**] | number<br /> |0|The desired tuning bank (integer between `0` and `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The bank value must be between 0 and 127.\n\n\n### `.sendTuningProgram(...)` {#sendTuningProgram}\n\n**Since**: 3.0.0<br />\n\nSets the MIDI tuning program to use. Note that the **Tuning Program** parameter is part of the\n*MIDI Tuning Standard*, which is not widely implemented.\n\n\n  **Parameters**\n\n  > Signature: `sendTuningProgram(value, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br /> ||The desired tuning program (integer between `0` and `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The program value must be between 0 and 127.\n\n\n### `.stopNote(...)` {#stopNote}\n\n\nSends a **note off** message for the specified MIDI note number on the specified channel(s).\nThe first parameter is the note to stop. It can be a single value or an array of the following\nvalid values:\n\n - A MIDI note number (integer between `0` and `127`)\n - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n - A [`Note`](Note) object\n\nThe execution of the **note off** command can be delayed by using the `time` property of the\n`options` parameter.\n\n\n  **Parameters**\n\n  > Signature: `stopNote(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />Note<br />string<br />Array.&lt;number&gt;<br />Array.&lt;Note&gt;<br />Array.&lt;string&gt;<br /> ||The note(s) to stop. The notes can be specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.channels`**] | number<br />Array.&lt;number&gt;<br /> |[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]|The MIDI channel number (between `1` and `16`) or an array of channel numbers to use. If no channel is specified, all channels will be used.|\n    |[**`options.release`**] | number<br /> |0.5|The velocity at which to release the note (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawRelease`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `64`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.suspendEvent(...)` {#suspendEvent}\n\n\nSuspends execution of all callbacks functions registered for the specified event type.\n\nYou can suspend execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `suspendEvent()`. Beware that this\nwill not suspend all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem counter-intuitive\nat first glance, it allows the selective suspension of global listeners while leaving other\nlisteners alone. If you truly want to suspends all callbacks for a specific\n[`EventEmitter`](EventEmitter), simply set its `eventsSuspended` property to `true`.\n\n\n  **Parameters**\n\n  > Signature: `suspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to suspend execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.unsuspendEvent(...)` {#unsuspendEvent}\n\n\nResumes execution of all suspended callback functions registered for the specified event type.\n\nYou can resume execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `unsuspendEvent()`. Beware that\nthis will not resume all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem\ncounter-intuitive, it allows the selective unsuspension of global listeners while leaving other\ncallbacks alone.\n\n\n  **Parameters**\n\n  > Signature: `unsuspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to resume execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.waitFor(...)` {#waitFor}\n\n**Attributes**: async\n\nThe `waitFor()` method is an async function which returns a promise. The promise is fulfilled\nwhen the specified event occurs. The event can be a regular event or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) (if you want to resolve as soon as any\nevent is emitted).\n\nIf the `duration` option is set, the promise will only be fulfilled if the event is emitted\nwithin the specified duration. If the event has not been fulfilled after the specified\nduration, the promise is rejected. This makes it super easy to wait for an event and timeout\nafter a certain time if the event is not triggered.\n\n\n  **Parameters**\n\n  > Signature: `waitFor(event, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to wait for|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds to wait before the promise is automatically rejected.|\n\n  </div>\n\n\n\n\n\n\n***\n\n## Events\n\n### `closed` {#event-closed}\n\n<a id=\"event:closed\"></a>\n\n\nEvent emitted when the [Output](Output) has been closed by calling the\n[close()](Output#close) method.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`\"closed\"`|\n  |**`target`** |Output|The object to which the listener was originally added (`Output`).|\n  |**`port`** |Output|The port that was closed|\n\n\n### `disconnected` {#event-disconnected}\n\n<a id=\"event:disconnected\"></a>\n\n\nEvent emitted when the [Output](Output) becomes unavailable. This event is typically fired\nwhen the MIDI device is unplugged.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp0 when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`\"disconnected\"`|\n  |**`target`** |Output|The object to which the listener was originally added (`Output`).|\n  |**`port`** |object|Object with properties describing the [Output](Output) that was disconnected. This is not the actual `Output` as it is no longer available.|\n\n\n### `opened` {#event-opened}\n\n<a id=\"event:opened\"></a>\n\n\nEvent emitted when the [Output](Output) has been opened by calling the\n[open()](Output#open) method.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`\"opened\"`|\n  |**`target`** |Output|The object to which the listener was originally added (`Output`).|\n  |**`port`** |Output|The port that was opened|\n\n\n\n"
  },
  {
    "path": "website/api/classes/OutputChannel.md",
    "content": "\n# OutputChannel\n\nThe `OutputChannel` class represents a single output MIDI channel. `OutputChannel` objects are\nprovided by an [`Output`](Output) port which, itself, is made available by a device. The\n`OutputChannel` object is derived from the host's MIDI subsystem and should not be instantiated\ndirectly.\n\nAll 16 `OutputChannel` objects can be found inside the parent output's\n[`channels`](Output#channels) property.\n\n**Since**: 3.0.0\n\n**Extends**: [`EventEmitter`](EventEmitter)\n<!--**Extends**: EventEmitter-->\n\n\n### `Constructor`\n\nCreates an `OutputChannel` object.\n\n\n  **Parameters**\n\n  > `new OutputChannel(output, number)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type         | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`output`** | Output<br /> ||The [`Output`](Output) this channel belongs to.|\n    |**`number`** | number<br /> ||The MIDI channel number (`1` - `16`).|\n\n  </div>\n\n\n\n***\n\n## Properties\n\n### `.eventCount` {#eventCount}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe number of unique events that have registered listeners.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventMap` {#eventMap}\n**Type**: Object<br />\n**Attributes**: read-only<br />\n\n\nAn object containing a property for each event with at least one registered listener. Each\nevent property contains an array of all the [`Listener`](Listener) objects registered\nfor the event.\n\n\n### `.eventNames` {#eventNames}\n**Type**: Array.&lt;string&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all the unique event names for which the emitter has at least one registered\nlistener.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventsSuspended` {#eventsSuspended}\n**Type**: boolean<br />\n\n\nWhether or not the execution of callbacks is currently suspended for this emitter.\n\n\n### `.number` {#number}\n**Since**: 3.0<br />\n**Type**: number<br />\n\n\nThis channel's MIDI number (`1` - `16`).\n\n\n### `.octaveOffset` {#octaveOffset}\n**Since**: 3.0<br />\n**Type**: number<br />\n\n\nAn integer to offset the reported octave of outgoing note-specific messages (`noteon`,\n`noteoff` and `keyaftertouch`). By default, middle C (MIDI note number 60) is placed on the 4th\noctave (C4).\n\nNote that this value is combined with the global offset value defined in\n[`WebMidi.octaveOffset`](WebMidi#octaveOffset) and with the parent value defined in\n[`Output.octaveOffset`](Output#octaveOffset).\n\n\n### `.output` {#output}\n**Since**: 3.0<br />\n**Type**: Output<br />\n\n\nThe parent [`Output`](Output) this channel belongs to.\n\n\n\n***\n\n## Methods\n\n\n### `.addListener(...)` {#addListener}\n\n\nAdds a listener for the specified event. It returns the [`Listener`](Listener) object\nthat was created and attached to the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](#ANY_EVENT) as the first parameter. Note that a global\nlistener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to.|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The value of `this` in the callback function.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.remaining`**] | number<br /> |Infinity|The number of times after which the callback should automatically be removed.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.addOneTimeListener(...)` {#addOneTimeListener}\n\n\nAdds a one-time listener for the specified event. The listener will be executed once and then\ndestroyed. It returns the [`Listener`](Listener) object that was created and attached\nto the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter. Note that a\nglobal listener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addOneTimeListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The context to invoke the callback function in.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.emit(...)` {#emit}\n\n\nExecutes the callback function of all the [`Listener`](Listener) objects registered for\na given event. The callback functions are passed the additional arguments passed to `emit()`\n(if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of\nthe [`Listener`](Listener) object (if any).\n\nIf the [`eventsSuspended`](#eventsSuspended) property is `true` or the\n[`Listener.suspended`](Listener#suspended) property is `true`, the callback functions\nwill not be executed.\n\nThis function returns an array containing the return values of each of the callbacks.\n\nIt should be noted that the regular listeners are triggered first followed by the global\nlisteners (those added with [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\n\n  **Parameters**\n\n  > Signature: `emit(event, ...args)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br /> ||The event|\n    |**`args`** | *<br /> ||Arbitrary number of arguments to pass along to the callback functions|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array`<br />\n\nAn array containing the return value of each of the executed listener\nfunctions.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string.\n\n\n### `.getListenerCount(...)` {#getListenerCount}\n\n\nReturns the number of listeners registered for a specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) do not count towards the remaining\nnumber for a \"regular\" event. To get the number of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListenerCount(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event which is usually a string but can also be the special [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) symbol.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer representing the number of listeners registered for the specified\nevent.\n\n\n\n\n### `.getListeners(...)` {#getListeners}\n\n\nReturns an array of all the [`Listener`](Listener) objects that have been registered for\na specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) are not returned for \"regular\"\nevents. To get the list of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListeners(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to get listeners for.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Listener>`<br />\n\nAn array of [`Listener`](Listener) objects.\n\n\n\n\n### `.hasListener(...)` {#hasListener}\n\n\nReturns `true` if the specified event has at least one registered listener. If no event is\nspecified, the method returns `true` if any event has at least one listener registered (this\nincludes global listeners registered to\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\nNote: to specifically check for global listeners added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT), use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `hasListener([event], [callback])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br />Symbol<br /> |(any event)|The event to check|\n    |[**`callback`**] | function<br />Listener<br /> |(any callback)|The actual function that was added to the event or the [Listener](Listener) object returned by `addListener()`.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\n\n\n\n### `.playNote(...)` {#playNote}\n\n\nPlays a note or an array of notes on the channel. The first parameter is the note to play. It\ncan be a single value or an array of the following valid values:\n\n - A [`Note`](Note) object\n - A MIDI note number (integer between `0` and `127`)\n - A note name, followed by the octave (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n\nThe `playNote()` method sends a **note on** MIDI message for all specified notes. If a\n`duration` is set in the `options` parameter or in the [`Note`](Note) object's\n[`duration`](Note#duration) property, it will also schedule a **note off** message\nto end the note after said duration. If no `duration` is set, the note will simply play until\na matching **note off** message is sent with [`stopNote()`](#OutputChannel+stopNote) or\n[`sendNoteOff()`](#OutputChannel+sendNoteOff).\n\n The execution of the **note on** command can be delayed by using the `time` property of the\n`options` parameter.\n\nWhen using [`Note`](Note) objects, the durations and velocities defined in the\n[`Note`](Note) objects have precedence over the ones specified via the method's `options`\nparameter.\n\n**Note**: per the MIDI standard, a **note on** message with an attack velocity of `0` is\nfunctionally equivalent to a **note off** message.\n\n\n  **Parameters**\n\n  > Signature: `playNote(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />string<br />Note<br />Array.&lt;number&gt;<br />Array.&lt;string&gt;<br />Array.&lt;Note&gt;<br /> ||The note(s) to play. The notes can be specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`), a [`Note`](Note) object or an array of the previous types. When using a note identifier, the octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number `127`).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.duration`**] | number<br /> ||A positive decimal number larger than `0` representing the number of milliseconds to wait before sending a **note off** message. If invalid or left undefined, only a **note on** message will be sent.|\n    |[**`options.attack`**] | number<br /> |0.5|The velocity at which to play the note (between `0` and `1`). If the `rawAttack` option is also defined, it will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawAttack`**] | number<br /> |64|The attack velocity at which to play the note (between `0` and `127`). This has priority over the `attack` property. An invalid velocity value will silently trigger the default of 64.|\n    |[**`options.release`**] | number<br /> |0.5|The velocity at which to release the note (between `0` and `1`). If the `rawRelease` option is also defined, it will have priority. An invalid velocity value will silently trigger the default of `0.5`. This is only used with the **note off** event triggered when `options.duration` is set.|\n    |[**`options.rawRelease`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). This has priority over the `release` property. An invalid velocity value will silently trigger the default of 64. This is only used with the **note off** event triggered when `options.duration` is set.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.removeListener(...)` {#removeListener}\n\n\nRemoves all the listeners that were added to the object upon which the method is called and\nthat match the specified criterias. If no parameters are passed, all listeners added to this\nobject will be removed. If only the `event` parameter is passed, all listeners for that event\nwill be removed from that object. You can remove global listeners by using\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter.\n\nTo use more granular options, you must at least define the `event`. Then, you can specify the\ncallback to match or one or more of the additional options.\n\n\n  **Parameters**\n\n  > Signature: `removeListener([event], [callback], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br /> ||The event name.|\n    |[**`callback`**] | EventEmitter~callback<br /> ||Only remove the listeners that match this exact callback function.|\n    |[**`options`**] | Object<br /> |||\n    |[**`options.context`**] | *<br /> ||Only remove the listeners that have this exact context.|\n    |[**`options.remaining`**] | number<br /> ||Only remove the listener if it has exactly that many remaining times to be executed.|\n\n  </div>\n\n\n\n\n\n\n### `.send(...)` {#send}\n\n\nSends a MIDI message on the MIDI output port. If no time is specified, the message will be\nsent immediately. The message should be an array of 8-bit unsigned integers (`0` - `225`),\na\n[`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\nobject or a [`Message`](Message) object.\n\nIt is usually not necessary to use this method directly as you can use one of the simpler\nhelper methods such as [`playNote()`](#playNote), [`stopNote()`](#stopNote),\n[`sendControlChange()`](#sendControlChange), etc.\n\nDetails on the format of MIDI messages are available in the summary of\n[MIDI messages](https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message)\nfrom the MIDI Manufacturers Association.\n\n\n  **Parameters**\n\n  > Signature: `send(message, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`message`** | Array.&lt;number&gt;<br />Uint8Array<br />Message<br /> ||A `Message` object, an array of 8-bit unsigned integers or a `Uint8Array` object (not available in Node.js) containing the message bytes.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The first byte (status) must be an integer between 128 and 255.\n  * `RangeError` : Data bytes must be integers between 0 and 255.\n\n\n### `.sendAllNotesOff(...)` {#sendAllNotesOff}\n\n\nSends an **all notes off** channel mode message. This will make all currently playing notes\nfade out just as if their key had been released. This is different from the\n[`sendAllSoundOff()`](#sendAllSoundOff) method which mutes all sounds immediately.\n\n\n  **Parameters**\n\n  > Signature: `sendAllNotesOff([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendAllSoundOff(...)` {#sendAllSoundOff}\n\n\nSends an **all sound off** channel mode message. This will silence all sounds playing on that\nchannel but will not prevent new sounds from being triggered.\n\n\n  **Parameters**\n\n  > Signature: `sendAllSoundOff([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendChannelAftertouch(...)` {#sendChannelAftertouch}\n\n\nSends a MIDI **channel aftertouch** message. For key-specific aftertouch, you should instead\nuse [`sendKeyAftertouch()`](#sendKeyAftertouch).\n\n\n  **Parameters**\n\n  > Signature: `sendChannelAftertouch([pressure], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`pressure`**] | number<br /> ||The pressure level (between `0` and `1`). If the `rawValue` option is set to `true`, the pressure can be defined by using an integer between `0` and `127`.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.rawValue`**] | boolean<br /> |false|A boolean indicating whether the value should be considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * RangeError Invalid channel aftertouch value.\n\n\n### `.sendChannelMode(...)` {#sendChannelMode}\n\n\nSends a MIDI **channel mode** message. The channel mode message to send can be specified\nnumerically or by using one of the following common names:\n\n|  Type                |Number| Shortcut Method                                               |\n| ---------------------|------|-------------------------------------------------------------- |\n| `allsoundoff`        | 120  | [`sendAllSoundOff()`](#sendAllSoundOff)                 |\n| `resetallcontrollers`| 121  | [`sendResetAllControllers()`](#sendResetAllControllers) |\n| `localcontrol`       | 122  | [`sendLocalControl()`](#sendLocalControl)               |\n| `allnotesoff`        | 123  | [`sendAllNotesOff()`](#sendAllNotesOff)                 |\n| `omnimodeoff`        | 124  | [`sendOmniMode(false)`](#sendOmniMode)                  |\n| `omnimodeon`         | 125  | [`sendOmniMode(true)`](#sendOmniMode)                   |\n| `monomodeon`         | 126  | [`sendPolyphonicMode(\"mono\")`](#sendPolyphonicMode)     |\n| `polymodeon`         | 127  | [`sendPolyphonicMode(\"poly\")`](#sendPolyphonicMode)     |\n\n**Note**: as you can see above, to make it easier, all channel mode messages also have a matching\nhelper method.\n\nIt should be noted that, per the MIDI specification, only `localcontrol` and `monomodeon` may\nrequire a value that's not zero. For that reason, the `value` parameter is optional and\ndefaults to 0.\n\n\n  **Parameters**\n\n  > Signature: `sendChannelMode(command, [value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`command`** | number<br />string<br /> ||The numerical identifier of the channel mode message (integer between `120` and `127`) or its name as a string.|\n    |[**`value`**] | number<br /> |0|The value to send (integer between `0` - `127`).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendControlChange(...)` {#sendControlChange}\n\n**Since**: 3.0.0<br />\n\nSends a MIDI **control change** message to the channel at the scheduled time. The control\nchange message to send can be specified numerically (`0` to `127`) or by using one of the\nfollowing common names:\n\n| Number | Name                          |\n|--------|-------------------------------|\n| 0      |`bankselectcoarse`             |\n| 1      |`modulationwheelcoarse`        |\n| 2      |`breathcontrollercoarse`       |\n| 4      |`footcontrollercoarse`         |\n| 5      |`portamentotimecoarse`         |\n| 6      |`dataentrycoarse`              |\n| 7      |`volumecoarse`                 |\n| 8      |`balancecoarse`                |\n| 10     |`pancoarse`                    |\n| 11     |`expressioncoarse`             |\n| 12     |`effectcontrol1coarse`         |\n| 13     |`effectcontrol2coarse`         |\n| 18     |`generalpurposeslider3`        |\n| 19     |`generalpurposeslider4`        |\n| 32     |`bankselectfine`               |\n| 33     |`modulationwheelfine`          |\n| 34     |`breathcontrollerfine`         |\n| 36     |`footcontrollerfine`           |\n| 37     |`portamentotimefine`           |\n| 38     |`dataentryfine`                |\n| 39     |`volumefine`                   |\n| 40     |`balancefine`                  |\n| 42     |`panfine`                      |\n| 43     |`expressionfine`               |\n| 44     |`effectcontrol1fine`           |\n| 45     |`effectcontrol2fine`           |\n| 64     |`holdpedal`                    |\n| 65     |`portamento`                   |\n| 66     |`sustenutopedal`               |\n| 67     |`softpedal`                    |\n| 68     |`legatopedal`                  |\n| 69     |`hold2pedal`                   |\n| 70     |`soundvariation`               |\n| 71     |`resonance`                    |\n| 72     |`soundreleasetime`             |\n| 73     |`soundattacktime`              |\n| 74     |`brightness`                   |\n| 75     |`soundcontrol6`                |\n| 76     |`soundcontrol7`                |\n| 77     |`soundcontrol8`                |\n| 78     |`soundcontrol9`                |\n| 79     |`soundcontrol10`               |\n| 80     |`generalpurposebutton1`        |\n| 81     |`generalpurposebutton2`        |\n| 82     |`generalpurposebutton3`        |\n| 83     |`generalpurposebutton4`        |\n| 91     |`reverblevel`                  |\n| 92     |`tremololevel`                 |\n| 93     |`choruslevel`                  |\n| 94     |`celestelevel`                 |\n| 95     |`phaserlevel`                  |\n| 96     |`dataincrement`                |\n| 97     |`datadecrement`                |\n| 98     |`nonregisteredparametercoarse` |\n| 99     |`nonregisteredparameterfine`   |\n| 100    |`registeredparametercoarse`    |\n| 101    |`registeredparameterfine`      |\n| 120    |`allsoundoff`                  |\n| 121    |`resetallcontrollers`          |\n| 122    |`localcontrol`                 |\n| 123    |`allnotesoff`                  |\n| 124    |`omnimodeoff`                  |\n| 125    |`omnimodeon`                   |\n| 126    |`monomodeon`                   |\n| 127    |`polymodeon`                   |\n\nAs you can see above, not all control change message have a matching name. This does not mean\nyou cannot use the others. It simply means you will need to use their number\n(`0` to `127`) instead of their name. While you can still use them, numbers `120` to `127` are\nusually reserved for *channel mode* messages. See\n[`sendChannelMode()`](#OutputChannel+sendChannelMode) method for more info.\n\nTo view a detailed list of all available **control change** messages, please consult \"Table 3 -\nControl Change Messages\" from the [MIDI Messages](\nhttps://www.midi.org/specifications/item/table-3-control-change-messages-data-bytes-2)\nspecification.\n\n**Note**: messages #0-31 (MSB) are paired with messages #32-63 (LSB). For example, message #1\n(`modulationwheelcoarse`) can be accompanied by a second control change message for\n`modulationwheelfine` to achieve a greater level of precision. if you want to specify both MSB\nand LSB for messages between `0` and `31`, you can do so by passing a 2-value array as the\nsecond parameter.\n\n\n  **Parameters**\n\n  > Signature: `sendControlChange(controller, value, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`controller`** | number<br />string<br /> ||The MIDI controller name or number (`0` - `127`).|\n    |**`value`** | number<br />Array.&lt;number&gt;<br /> ||The value to send (0-127). You can also use a two-position array for controllers 0 to 31. In this scenario, the first value will be sent as usual and the second value will be sent to the matching LSB controller (which is obtained by adding 32 to the first controller)|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : Controller numbers must be between 0 and 127.\n  * `RangeError` : Invalid controller name.\n  * `TypeError` : The value array must have a length of 2.\n\n\n### `.sendKeyAftertouch(...)` {#sendKeyAftertouch}\n\n\nSends a MIDI **key aftertouch** message at the scheduled time. This is a key-specific\naftertouch. For a channel-wide aftertouch message, use\n[`sendChannelAftertouch()`](#sendChannelAftertouch).\n\n\n  **Parameters**\n\n  > Signature: `sendKeyAftertouch(target, [pressure], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`target`** | number<br />Note<br />string<br />Array.&lt;number&gt;<br />Array.&lt;Note&gt;<br />Array.&lt;string&gt;<br /> ||The note(s) for which you are sending an aftertouch value. The notes can be specified by using a MIDI note number (`0` - `127`), a [`Note`](Note) object, a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number `127`). When using a note identifier, the octave value will be offset by the local [`octaveOffset`](#octaveOffset) and by [`Output.octaveOffset`](Output#octaveOffset) and [`WebMidi.octaveOffset`](WebMidi#octaveOffset) (if those values are not `0`). When using a key number, `octaveOffset` values are ignored.|\n    |[**`pressure`**] | number<br /> |0.5|The pressure level (between `0` and `1`). An invalid pressure value will silently trigger the default behaviour. If the `rawValue` option is set to `true`, the pressure is defined by using an integer between `0` and `127`.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.rawValue`**] | boolean<br /> |false|A boolean indicating whether the value should be considered a float between `0` and `1.0` (default) or a raw integer between `0` and `127`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * RangeError Invalid key aftertouch value.\n\n\n### `.sendLocalControl(...)` {#sendLocalControl}\n\n\nTurns local control on or off. Local control is usually enabled by default. If you disable it,\nthe instrument will no longer trigger its own sounds. It will only send the MIDI messages to\nits out port.\n\n\n  **Parameters**\n\n  > Signature: `sendLocalControl([state], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`state`**] | boolean<br /> |false|Whether to activate local control (`true`) or disable it (`false`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendMasterTuning(...)` {#sendMasterTuning}\n\n\nSends a **master tuning** message. The value is decimal and must be larger than -65 semitones\nand smaller than 64 semitones.\n\nBecause of the way the MIDI specification works, the decimal portion of the value will be\nencoded with a resolution of 14bit. The integer portion must be between -64 and 63\ninclusively. This function actually generates two MIDI messages: a **Master Coarse Tuning** and\na **Master Fine Tuning** RPN messages.\n\n\n  **Parameters**\n\n  > Signature: `sendMasterTuning([value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`value`**] | number<br /> |0.0|The desired decimal adjustment value in semitones (-65 < x < 64)|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The value must be a decimal number between larger than -65 and smaller\nthan 64.\n\n\n### `.sendModulationRange(...)` {#sendModulationRange}\n\n\nSends a **modulation depth range** message to adjust the depth of the modulation wheel's range.\nThe range can be specified with the `semitones` parameter, the `cents` parameter or by\nspecifying both parameters at the same time.\n\n\n  **Parameters**\n\n  > Signature: `sendModulationRange(semitones, [cents], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`semitones`** | number<br /> ||The desired adjustment value in semitones (integer between 0 and 127).|\n    |[**`cents`**] | number<br /> |0|The desired adjustment value in cents (integer between 0 and 127).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendNoteOff(...)` {#sendNoteOff}\n\n\nSends a **note off** message for the specified notes on the channel. The first parameter is the\nnote. It can be a single value or an array of the following valid values:\n\n - A MIDI note number (integer between `0` and `127`)\n - A note name, followed by the octave (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n - A [`Note`](Note) object\n\nThe execution of the **note off** command can be delayed by using the `time` property of the\n`options` parameter.\n\nWhen using [`Note`](Note) objects, the release velocity defined in the\n[`Note`](Note) objects has precedence over the one specified via the method's `options`\nparameter.\n\n\n  **Parameters**\n\n  > Signature: `sendNoteOff(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />string<br />Note<br />Array.&lt;number&gt;<br />Array.&lt;string&gt;<br />Array.&lt;Note&gt;<br /> ||The note(s) to stop. The notes can be specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a [`Note`](Note) object or an array of the previous types. When using a note name, octave range must be between -1 and 9. The lowest note is C-1 (MIDI note number 0) and the highest note is G9 (MIDI note number 127).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n    |[**`options.release`**] | number<br /> |0.5|The velocity at which to release the note (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawRelease`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `64`.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendNoteOn(...)` {#sendNoteOn}\n\n\nSends a **note on** message for the specified note(s) on the channel. The first parameter is\nthe note. It can be a single value or an array of the following valid values:\n\n - A [`Note`](Note) object\n - A MIDI note number (integer between `0` and `127`)\n - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n\n When passing a [`Note`](Note)object or a note name, the `octaveOffset` will be applied.\n This is not the case when using a note number. In this case, we assume you know exactly which\n MIDI note number should be sent out.\n\nThe execution of the **note on** command can be delayed by using the `time` property of the\n`options` parameter.\n\nWhen using [`Note`](Note) objects, the attack velocity defined in the\n[`Note`](Note) objects has precedence over the one specified via the method's `options`\nparameter. Also, the `duration` is ignored. If you want to also send a **note off** message,\nuse the [`playNote()`](#playNote) method instead.\n\n**Note**: As per the MIDI standard, a **note on** message with an attack velocity of `0` is\nfunctionally equivalent to a **note off** message.\n\n\n  **Parameters**\n\n  > Signature: `sendNoteOn(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />string<br />Note<br />Array.&lt;number&gt;<br />Array.&lt;string&gt;<br />Array.&lt;Note&gt;<br /> ||The note(s) to play. The notes can be specified by using a MIDI note number (0-127), a note identifier (e.g. C3, G#4, F-1, Db7), a [`Note`](Note) object or an array of the previous types.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n    |[**`options.attack`**] | number<br /> |0.5|The velocity at which to play the note (between `0` and `1`).  If the `rawAttack` option is also defined, `rawAttack` will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawAttack`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). If the `attack` option is also defined, `rawAttack` will have priority. An invalid velocity value will silently trigger the default of `64`.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendNrpnValue(...)` {#sendNrpnValue}\n\n\nSets a non-registered parameter (NRPN) to the specified value. The NRPN is selected by passing\nin a two-position array specifying the values of the two control bytes. The value is specified\nby passing in a single integer (most cases) or an array of two integers.\n\nNRPNs are not standardized in any way. Each manufacturer is free to implement them any way\nthey see fit. For example, according to the Roland GS specification, you can control the\n**vibrato rate** using NRPN (1, 8). Therefore, to set the **vibrato rate** value to **123** you\nwould use:\n\n```js\nWebMidi.outputs[0].channels[0].sendNrpnValue([1, 8], 123);\n```\n\nIn some rarer cases, you need to send two values with your NRPN messages. In such cases, you\nwould use a 2-position array. For example, for its **ClockBPM** parameter (2, 63), Novation\nuses a 14-bit value that combines an MSB and an LSB (7-bit values). So, for example, if the\nvalue to send was 10, you could use:\n\n```js\nWebMidi.outputs[0].channels[0].sendNrpnValue([2, 63], [0, 10]);\n```\n\nFor further implementation details, refer to the manufacturer's documentation.\n\n\n  **Parameters**\n\n  > Signature: `sendNrpnValue(nrpn, [data], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`nrpn`** | Array.&lt;number&gt;<br /> ||A two-position array specifying the two control bytes (0x63, 0x62) that identify the non-registered parameter.|\n    |[**`data`**] | number<br />Array.&lt;number&gt;<br /> |[]|An integer or an array of integers with a length of 1 or 2 specifying the desired data.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The control value must be between 0 and 127.\n  * `RangeError` : The msb value must be between 0 and 127\n\n\n### `.sendOmniMode(...)` {#sendOmniMode}\n\n\nSets OMNI mode to `\"on\"` or `\"off\"`. MIDI's OMNI mode causes the instrument to respond to\nmessages from all channels.\n\nIt should be noted that support for OMNI mode is not as common as it used to be.\n\n\n  **Parameters**\n\n  > Signature: `sendOmniMode([state], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`state`**] | boolean<br /> |true|Whether to activate OMNI mode (`true`) or not (`false`).|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `TypeError` : Invalid channel mode message name.\n  * `RangeError` : Channel mode controller numbers must be between 120 and 127.\n  * `RangeError` : Value must be an integer between 0 and 127.\n\n\n### `.sendPitchBend(...)` {#sendPitchBend}\n\n\nSends a MIDI **pitch bend** message at the scheduled time. The resulting bend is relative to\nthe pitch bend range that has been defined. The range can be set with\n[`sendPitchBendRange()`](#sendPitchBendRange). So, for example, if the pitch\nbend range has been set to 12 semitones, using a bend value of -1 will bend the note 1 octave\nbelow its nominal value.\n\n\n  **Parameters**\n\n  > Signature: `sendPitchBend([value], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`value`**] | number<br />Array.&lt;number&gt;<br /> ||The intensity of the bend (between -1.0 and 1.0). A value of zero means no bend. If the `rawValue` option is set to `true`, the intensity of the bend can be defined by either using a single integer between 0 and 127 (MSB) or an array of two integers between 0 and 127 representing, respectively, the MSB (most significant byte) and the LSB (least significant byte). The MSB is expressed in semitones with `64` meaning no bend. A value lower than `64` bends downwards while a value higher than `64` bends upwards. The LSB is expressed in cents (1/100 of a semitone). An LSB of `64` also means no bend.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.rawValue`**] | boolean<br /> |false|A boolean indicating whether the value should be considered as a float between -1.0 and 1.0 (default) or as raw integer between 0 and 127 (or an array of 2 integers if using both MSB and LSB).|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendPitchBendRange(...)` {#sendPitchBendRange}\n\n\nSends a **pitch bend range** message at the scheduled time to adjust the range used by the\npitch bend lever. The range is specified by using the `semitones` and `cents` parameters. For\nexample, setting the `semitones` parameter to `12` means that the pitch bend range will be 12\nsemitones above and below the nominal pitch.\n\n\n  **Parameters**\n\n  > Signature: `sendPitchBendRange(semitones, [cents], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`semitones`** | number<br /> ||The desired adjustment value in semitones (between 0 and 127). While nothing imposes that in the specification, it is very common for manufacturers to limit the range to 2 octaves (-12 semitones to 12 semitones).|\n    |[**`cents`**] | number<br /> |0|The desired adjustment value in cents (integer between 0-127).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The semitones value must be an integer between 0 and 127.\n  * `RangeError` : The cents value must be an integer between 0 and 127.\n\n\n### `.sendPolyphonicMode(...)` {#sendPolyphonicMode}\n\n\nSets the polyphonic mode. In `\"poly\"` mode (usually the default), multiple notes can be played\nand heard at the same time. In `\"mono\"` mode, only one note will be heard at once even if\nmultiple notes are being played.\n\n\n  **Parameters**\n\n  > Signature: `sendPolyphonicMode([mode], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`mode`**] | string<br /> |poly|The mode to use: `\"mono\"` or `\"poly\"`.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendProgramChange(...)` {#sendProgramChange}\n\n\nSends a MIDI **program change** message at the scheduled time.\n\n\n  **Parameters**\n\n  > Signature: `sendProgramChange([program], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`program`**] | number<br /> |1|The MIDI patch (program) number (integer between `0` and `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `TypeError` : Failed to execute 'send' on 'MIDIOutput': The value at index 1 is greater\nthan 0xFF.\n\n\n### `.sendResetAllControllers(...)` {#sendResetAllControllers}\n\n\nSends a **reset all controllers** channel mode message. This resets all controllers, such as\nthe pitch bend, to their default value.\n\n\n  **Parameters**\n\n  > Signature: `sendResetAllControllers([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendRpnDecrement(...)` {#sendRpnDecrement}\n\n\nDecrements the specified MIDI registered parameter by 1. Here is the full list of parameter\nnames that can be used with this function:\n\n * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n * Gain (0x3D, 0x02): `\"gain\"`\n * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n\n\n  **Parameters**\n\n  > Signature: `sendRpnDecrement(parameter, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`parameter`** | String<br />Array.&lt;number&gt;<br /> ||A string identifying the parameter's name (see above) or a two-position array specifying the two control bytes (0x65, 0x64) that identify the registered parameter.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * TypeError The specified registered parameter is invalid.\n\n\n### `.sendRpnIncrement(...)` {#sendRpnIncrement}\n\n\nIncrements the specified MIDI registered parameter by 1. Here is the full list of parameter\nnames that can be used with this function:\n\n * Pitchbend Range (0x00, 0x00): `\"pitchbendrange\"`\n * Channel Fine Tuning (0x00, 0x01): `\"channelfinetuning\"`\n * Channel Coarse Tuning (0x00, 0x02): `\"channelcoarsetuning\"`\n * Tuning Program (0x00, 0x03): `\"tuningprogram\"`\n * Tuning Bank (0x00, 0x04): `\"tuningbank\"`\n * Modulation Range (0x00, 0x05): `\"modulationrange\"`\n * Azimuth Angle (0x3D, 0x00): `\"azimuthangle\"`\n * Elevation Angle (0x3D, 0x01): `\"elevationangle\"`\n * Gain (0x3D, 0x02): `\"gain\"`\n * Distance Ratio (0x3D, 0x03): `\"distanceratio\"`\n * Maximum Distance (0x3D, 0x04): `\"maximumdistance\"`\n * Maximum Distance Gain (0x3D, 0x05): `\"maximumdistancegain\"`\n * Reference Distance Ratio (0x3D, 0x06): `\"referencedistanceratio\"`\n * Pan Spread Angle (0x3D, 0x07): `\"panspreadangle\"`\n * Roll Angle (0x3D, 0x08): `\"rollangle\"`\n\n\n  **Parameters**\n\n  > Signature: `sendRpnIncrement(parameter, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`parameter`** | String<br />Array.&lt;number&gt;<br /> ||A string identifying the parameter's name (see above) or a two-position array specifying the two control bytes (0x65, 0x64) that identify the registered parameter.|\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * TypeError The specified registered parameter is invalid.\n\n\n### `.sendRpnValue(...)` {#sendRpnValue}\n\n\nSets the specified MIDI registered parameter to the desired value. The value is defined with\nup to two bytes of data (msb, lsb) that each can go from 0 to 127.\n\nMIDI\n[registered parameters](https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2)\nextend the original list of control change messages. The MIDI 1.0 specification lists only a\nlimited number of them:\n\n| Numbers      | Function                 |\n|--------------|--------------------------|\n| (0x00, 0x00) | `pitchbendrange`         |\n| (0x00, 0x01) | `channelfinetuning`      |\n| (0x00, 0x02) | `channelcoarsetuning`    |\n| (0x00, 0x03) | `tuningprogram`          |\n| (0x00, 0x04) | `tuningbank`             |\n| (0x00, 0x05) | `modulationrange`        |\n| (0x3D, 0x00) | `azimuthangle`           |\n| (0x3D, 0x01) | `elevationangle`         |\n| (0x3D, 0x02) | `gain`                   |\n| (0x3D, 0x03) | `distanceratio`          |\n| (0x3D, 0x04) | `maximumdistance`        |\n| (0x3D, 0x05) | `maximumdistancegain`    |\n| (0x3D, 0x06) | `referencedistanceratio` |\n| (0x3D, 0x07) | `panspreadangle`         |\n| (0x3D, 0x08) | `rollangle`              |\n\nNote that the **Tuning Program** and **Tuning Bank** parameters are part of the *MIDI Tuning\nStandard*, which is not widely implemented.\n\n\n  **Parameters**\n\n  > Signature: `sendRpnValue(rpn, [data], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`rpn`** | string<br />Array.&lt;number&gt;<br /> ||A string identifying the parameter's name (see above) or a two-position array specifying the two control bytes (e.g. `[0x65, 0x64]`) that identify the registered parameter.|\n    |[**`data`**] | number<br />Array.&lt;number&gt;<br /> |[]|An single integer or an array of integers with a maximum length of 2 specifying the desired data.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n\n\n### `.sendTuningBank(...)` {#sendTuningBank}\n\n\nSets the MIDI tuning bank to use. Note that the **Tuning Bank** parameter is part of the\n*MIDI Tuning Standard*, which is not widely implemented.\n\n\n  **Parameters**\n\n  > Signature: `sendTuningBank(value, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br /> ||The desired tuning bank (integer between `0` and `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The bank value must be between 0 and 127.\n\n\n### `.sendTuningProgram(...)` {#sendTuningProgram}\n\n\nSets the MIDI tuning program to use. Note that the **Tuning Program** parameter is part of the\n*MIDI Tuning Standard*, which is not widely implemented.\n\n\n  **Parameters**\n\n  > Signature: `sendTuningProgram(value, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br /> ||The desired tuning program (integer between `0` and `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `OutputChannel`<br />\n\nReturns the `OutputChannel` object so methods can be chained.\n\n\n**Throws**:\n  * `RangeError` : The program value must be between 0 and 127.\n\n\n### `.stopNote(...)` {#stopNote}\n\n\nSends a **note off** message for the specified MIDI note number. The first parameter is the\nnote to stop. It can be a single value or an array of the following valid values:\n\n - A MIDI note number (integer between `0` and `127`)\n - A note identifier (e.g. `\"C3\"`, `\"G#4\"`, `\"F-1\"`, `\"Db7\"`)\n - A [`Note`](Note) object\n\nThe execution of the **note off** command can be delayed by using the `time` property of the\n`options` parameter.\n\n\n  **Parameters**\n\n  > Signature: `stopNote(note, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`note`** | number<br />Note<br />string<br />Array.&lt;number&gt;<br />Array.&lt;Note&gt;<br />Array.&lt;string&gt;<br /> ||The note(s) to stop. The notes can be specified by using a MIDI note number (`0` - `127`), a note identifier (e.g. `C3`, `G#4`, `F-1`, `Db7`) or an array of the previous types. When using a note identifier, octave range must be between `-1` and `9`. The lowest note is `C-1` (MIDI note number `0`) and the highest note is `G9` (MIDI note number `127`).|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.release`**] | number<br /> |0.5|The velocity at which to release the note (between `0` and `1`).  If the `rawRelease` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `0.5`.|\n    |[**`options.rawRelease`**] | number<br /> |64|The velocity at which to release the note (between `0` and `127`). If the `release` option is also defined, `rawRelease` will have priority. An invalid velocity value will silently trigger the default of `64`.|\n    |[**`options.time`**] | number<br />string<br /> |(now)|If `time` is a string prefixed with `\"+\"` and followed by a number, the message will be delayed by that many milliseconds. If the value is a positive number ([`DOMHighResTimeStamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp)), the operation will be scheduled for that time. The current time can be retrieved with [`WebMidi.time`](WebMidi#time). If `options.time` is omitted, or in the past, the operation will be carried out as soon as possible.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nReturns the `Output` object so methods can be chained.\n\n\n\n\n### `.suspendEvent(...)` {#suspendEvent}\n\n\nSuspends execution of all callbacks functions registered for the specified event type.\n\nYou can suspend execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `suspendEvent()`. Beware that this\nwill not suspend all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem counter-intuitive\nat first glance, it allows the selective suspension of global listeners while leaving other\nlisteners alone. If you truly want to suspends all callbacks for a specific\n[`EventEmitter`](EventEmitter), simply set its `eventsSuspended` property to `true`.\n\n\n  **Parameters**\n\n  > Signature: `suspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to suspend execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.unsuspendEvent(...)` {#unsuspendEvent}\n\n\nResumes execution of all suspended callback functions registered for the specified event type.\n\nYou can resume execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `unsuspendEvent()`. Beware that\nthis will not resume all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem\ncounter-intuitive, it allows the selective unsuspension of global listeners while leaving other\ncallbacks alone.\n\n\n  **Parameters**\n\n  > Signature: `unsuspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to resume execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.waitFor(...)` {#waitFor}\n\n**Attributes**: async\n\nThe `waitFor()` method is an async function which returns a promise. The promise is fulfilled\nwhen the specified event occurs. The event can be a regular event or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) (if you want to resolve as soon as any\nevent is emitted).\n\nIf the `duration` option is set, the promise will only be fulfilled if the event is emitted\nwithin the specified duration. If the event has not been fulfilled after the specified\nduration, the promise is rejected. This makes it super easy to wait for an event and timeout\nafter a certain time if the event is not triggered.\n\n\n  **Parameters**\n\n  > Signature: `waitFor(event, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to wait for|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds to wait before the promise is automatically rejected.|\n\n  </div>\n\n\n\n\n\n\n"
  },
  {
    "path": "website/api/classes/Utilities.md",
    "content": "\n# Utilities\n\nThe `Utilities` class contains general-purpose utility methods. All methods are static and\nshould be called using the class name. For example: `Utilities.getNoteDetails(\"C4\")`.\n\n**Since**: 3.0.0\n\n\n***\n\n## Properties\n\n### `.isBrowser` {#isBrowser}\n**Type**: boolean<br />\n\n\nIndicates whether the execution environment is a browser (`true`) or not (`false`)\n\n\n### `.isNode` {#isNode}\n**Type**: boolean<br />\n\n\nIndicates whether the execution environment is Node.js (`true`) or not (`false`)\n\n\n\n***\n\n## Methods\n\n\n### `.buildNote(...)` {#buildNote}\n\n**Since**: version 3.0.0<br />\n\nConverts the `input` parameter to a valid [`Note`](Note) object. The input usually is an\nunsigned integer (0-127) or a note identifier (`\"C4\"`, `\"G#5\"`, etc.). If the input is a\n[`Note`](Note) object, it will be returned as is.\n\nIf the input is a note number or identifier, it is possible to specify options by providing the\n`options` parameter.\n\n\n  **Parameters**\n\n  > Signature: `buildNote([input], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`input`**] | number<br />string<br />Note<br /> |||\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the note should be explicitly stopped.|\n    |[**`options.attack`**] | number<br /> |0.5|The note's attack velocity as a float between 0 and 1. If you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both `attack` and `rawAttack` are specified, the latter has precedence.|\n    |[**`options.release`**] | number<br /> |0.5|The note's release velocity as a float between 0 and 1. If you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both `release` and `rawRelease` are specified, the latter has precedence.|\n    |[**`options.rawAttack`**] | number<br /> |64|The note's attack velocity as an integer between 0 and 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both `attack` and `rawAttack` are specified, the latter has precedence.|\n    |[**`options.rawRelease`**] | number<br /> |64|The note's release velocity as an integer between 0 and 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both `release` and `rawRelease` are specified, the latter has precedence.|\n    |[**`options.octaveOffset`**] | number<br /> |0|An integer to offset the octave by. **This is only used when the input value is a note identifier.**|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Note`<br />\n\n\n**Attributes**: static\n\n**Throws**:\n  * TypeError The input could not be parsed to a note\n\n\n### `.buildNoteArray(...)` {#buildNoteArray}\n\n**Since**: 3.0.0<br />\n\nConverts an input value, which can be an unsigned integer (0-127), a note identifier, a\n[`Note`](Note)  object or an array of the previous types, to an array of\n[`Note`](Note)  objects.\n\n[`Note`](Note)  objects are returned as is. For note numbers and identifiers, a\n[`Note`](Note) object is created with the options specified. An error will be thrown when\nencountering invalid input.\n\nNote: if both the `attack` and `rawAttack` options are specified, the later has priority. The\nsame goes for `release` and `rawRelease`.\n\n\n  **Parameters**\n\n  > Signature: `buildNoteArray([notes], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`notes`**] | number<br />string<br />Note<br />Array.&lt;number&gt;<br />Array.&lt;string&gt;<br />Array.&lt;Note&gt;<br /> |||\n    |[**`options`**] | object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the note should be explicitly stopped.|\n    |[**`options.attack`**] | number<br /> |0.5|The note's attack velocity as a float between 0 and 1. If you wish to use an integer between 0 and 127, use the `rawAttack` option instead. If both `attack` and `rawAttack` are specified, the latter has precedence.|\n    |[**`options.release`**] | number<br /> |0.5|The note's release velocity as a float between 0 and 1. If you wish to use an integer between 0 and 127, use the `rawRelease` option instead. If both `release` and `rawRelease` are specified, the latter has precedence.|\n    |[**`options.rawAttack`**] | number<br /> |64|The note's attack velocity as an integer between 0 and 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both `attack` and `rawAttack` are specified, the latter has precedence.|\n    |[**`options.rawRelease`**] | number<br /> |64|The note's release velocity as an integer between 0 and 127. If you wish to use a float between 0 and 1, use the `release` option instead. If both `release` and `rawRelease` are specified, the latter has precedence.|\n    |[**`options.octaveOffset`**] | number<br /> |0|An integer to offset the octave by. **This is only used when the input value is a note identifier.**|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Note>`<br />\n\n\n**Attributes**: static\n\n**Throws**:\n  * TypeError An element could not be parsed as a note.\n\n\n### `.from7bitToFloat(...)` {#from7bitToFloat}\n\n\nReturns a number between 0 and 1 representing the ratio of the input value divided by 127 (7\nbit). The returned value is restricted between 0 and 1 even if the input is greater than 127 or\nsmaller than 0.\n\nPassing `Infinity` will return `1` and passing `-Infinity` will return `0`. Otherwise, when the\ninput value cannot be converted to an integer, the method returns 0.\n\n\n  **Parameters**\n\n  > Signature: `from7bitToFloat(value)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br /> ||A positive integer between 0 and 127 (inclusive)|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nA number between 0 and 1 (inclusive)\n\n\n**Attributes**: static\n\n\n\n### `.fromFloatTo7Bit(...)` {#fromFloatTo7Bit}\n\n\nReturns an integer between 0 and 127 which is the result of multiplying the input value by\n127. The input value should be a number between 0 and 1 (inclusively). The returned value is\nrestricted between 0 and 127 even if the input is greater than 1 or smaller than 0.\n\nPassing `Infinity` will return `127` and passing `-Infinity` will return `0`. Otherwise, when\nthe input value cannot be converted to a number, the method returns 0.\n\n\n  **Parameters**\n\n  > Signature: `fromFloatTo7Bit(value)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br /> ||A positive float between 0 and 1 (inclusive)|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nA number between 0 and 127 (inclusive)\n\n\n**Attributes**: static\n\n\n\n### `.fromFloatToMsbLsb(...)` {#fromFloatToMsbLsb}\n\n\nExtracts 7bit MSB and LSB values from the supplied float.\n\n\n  **Parameters**\n\n  > Signature: `fromFloatToMsbLsb(value)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | number<br /> ||A float between 0 and 1|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Object`<br />\n\n\n**Attributes**: static\n\n\n\n### `.fromMsbLsbToFloat(...)` {#fromMsbLsbToFloat}\n\n\nCombines and converts MSB and LSB values (0-127) to a float between 0 and 1. The returned value\nis within between 0 and 1 even if the result is greater than 1 or smaller than 0.\n\n\n  **Parameters**\n\n  > Signature: `fromMsbLsbToFloat(msb, [lsb])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`msb`** | number<br /> ||The most significant byte as a integer between 0 and 127.|\n    |[**`lsb`**] | number<br /> |0|The least significant byte as a integer between 0 and 127.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nA float between 0 and 1.\n\n\n**Attributes**: static\n\n\n\n### `.getCcNameByNumber(...)` {#getCcNameByNumber}\n\n\nReturns the name of a control change message matching the specified number (0-127). Some valid\ncontrol change numbers do not have a specific name or purpose assigned in the MIDI\n[spec](https://midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2).\nIn these cases, the method returns `controllerXXX` (where XXX is the number).\n\n\n  **Parameters**\n\n  > Signature: `getCcNameByNumber(number)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`number`** | number<br /> ||An integer (0-127) representing the control change message|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `string` or `undefined`<br />\n\nThe matching control change name or `undefined` if no match was\nfound.\n\n\n**Attributes**: static\n\n\n\n### `.getCcNumberByName(...)` {#getCcNumberByName}\n\n**Since**: 3.1<br />\n\nReturns the number of a control change message matching the specified name.\n\n\n  **Parameters**\n\n  > Signature: `getCcNumberByName(name)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`name`** | string<br /> ||A string representing the control change message|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `string` or `undefined`<br />\n\nThe matching control change number or `undefined` if no match was\nfound.\n\n\n**Attributes**: static\n\n\n\n### `.getChannelModeByNumber(...)` {#getChannelModeByNumber}\n\n**Since**: 2.0.0<br />\n\nReturns the channel mode name matching the specified number. If no match is found, the function\nreturns `false`.\n\n\n  **Parameters**\n\n  > Signature: `getChannelModeByNumber(number)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`number`** | number<br /> ||An integer representing the channel mode message (120-127)|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `string` or `false`<br />\n\nThe name of the matching channel mode or `false` if no match could be\nfound.\n\n\n**Attributes**: static\n\n\n\n### `.getNoteDetails(...)` {#getNoteDetails}\n\n**Since**: 3.0.0<br />\n\nGiven a proper note identifier (`C#4`, `Gb-1`, etc.) or a valid MIDI note number (0-127), this\nmethod returns an object containing broken down details about the specified note (uppercase\nletter, accidental and octave).\n\nWhen a number is specified, the translation to note is done using a value of 60 for middle C\n(C4 = middle C).\n\n\n  **Parameters**\n\n  > Signature: `getNoteDetails(value)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`value`** | string<br />number<br /> ||A note identifier A  atring (\"C#4\", \"Gb-1\", etc.) or a MIDI note number (0-127).|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Object`<br />\n\n\n**Attributes**: static\n\n**Throws**:\n  * TypeError Invalid note identifier\n\n\n### `.getPropertyByValue(...)` {#getPropertyByValue}\n\n\nReturns the name of the first property of the supplied object whose value is equal to the one\nsupplied. If nothing is found, `undefined` is returned.\n\n\n  **Parameters**\n\n  > Signature: `getPropertyByValue(object, value)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`object`** | object<br /> ||The object to look for the property in.|\n    |**`value`** | *<br /> ||Any value that can be expected to be found in the object's properties.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `string` or `undefined`<br />\n\nThe name of the matching property or `undefined` if nothing is\nfound.\n\n\n**Attributes**: static\n\n\n\n### `.guessNoteNumber(...)` {#guessNoteNumber}\n\n**Since**: 3.0.0<br />\n\nReturns a valid MIDI note number (0-127) given the specified input. The input usually is a\nstring containing a note identifier (`\"C3\"`, `\"F#4\"`, `\"D-2\"`, `\"G8\"`, etc.). If an integer\nbetween 0 and 127 is passed, it will simply be returned as is (for convenience). Other strings\nwill be parsed for integer value, if possible.\n\nIf the input is an identifier, the resulting note number is offset by the `octaveOffset`\nparameter. For example, if you pass in \"C4\" (note number 60) and the `octaveOffset` value is\n-2, the resulting MIDI note number will be 36.\n\n\n  **Parameters**\n\n  > Signature: `guessNoteNumber(input, octaveOffset)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`input`** | string<br />number<br /> ||A string or number to extract the MIDI note number from.|\n    |**`octaveOffset`** | number<br /> ||An integer to offset the octave by|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number` or `false`<br />\n\nA valid MIDI note number (0-127) or `false` if the input could not\nsuccessfully be parsed to a note number.\n\n\n**Attributes**: static\n\n\n\n### `.offsetNumber(...)` {#offsetNumber}\n\n\nReturns the supplied MIDI note number offset by the requested octave and semitone values. If\nthe calculated value is less than 0, 0 will be returned. If the calculated value is more than\n127, 127 will be returned. If an invalid offset value is supplied, 0 will be used.\n\n\n  **Parameters**\n\n  > Signature: `offsetNumber(number, octaveOffset, octaveOffset)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`number`** | number<br /> ||The MIDI note to offset as an integer between 0 and 127.|\n    |**`octaveOffset`** | number<br /> |0|An integer to offset the note by (in octave)|\n    |**`octaveOffset`** | number<br /> ||An integer to offset the note by (in semitones)|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer between 0 and 127\n\n\n**Attributes**: static\n\n**Throws**:\n  * `Error` : Invalid note number\n\n\n### `.sanitizeChannels(...)` {#sanitizeChannels}\n\n**Since**: 3.0.0<br />\n\nReturns a sanitized array of valid MIDI channel numbers (1-16). The parameter should be a\nsingle integer or an array of integers.\n\nFor backwards-compatibility, passing `undefined` as a parameter to this method results in all\nchannels being returned (1-16). Otherwise, parameters that cannot successfully be parsed to\nintegers between 1 and 16 are silently ignored.\n\n\n  **Parameters**\n\n  > Signature: `sanitizeChannels([channel])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`channel`**] | number<br />Array.&lt;number&gt;<br /> ||An integer or an array of integers to parse as channel numbers.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<number>`<br />\n\nAn array of 0 or more valid MIDI channel numbers.\n\n\n**Attributes**: static\n\n\n\n### `.toNoteIdentifier(...)` {#toNoteIdentifier}\n\n**Since**: 3.0.0<br />\n\nReturns an identifier string representing a note name (with optional accidental) followed by an\noctave number. The octave can be offset by using the `octaveOffset` parameter.\n\n\n  **Parameters**\n\n  > Signature: `toNoteIdentifier(number, octaveOffset)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`number`** | number<br /> ||The MIDI note number to convert to a note identifier|\n    |**`octaveOffset`** | number<br /> ||An offset to apply to the resulting octave|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `string`<br />\n\n\n**Attributes**: static\n\n**Throws**:\n  * RangeError Invalid note number\n  * RangeError Invalid octaveOffset value\n\n\n### `.toNoteNumber(...)` {#toNoteNumber}\n\n**Since**: 3.0.0<br />\n\nReturns a MIDI note number matching the identifier passed in the form of a string. The\nidentifier must include the octave number. The identifier also optionally include a sharp (#),\na double sharp (##), a flat (b) or a double flat (bb) symbol. For example, these are all valid\nidentifiers: C5, G4, D#-1, F0, Gb7, Eb-1, Abb4, B##6, etc.\n\nWhen converting note identifiers to numbers, C4 is considered to be middle C (MIDI note number\n60) as per the scientific pitch notation standard.\n\nThe resulting note number can be offset by using the `octaveOffset` parameter.\n\n\n  **Parameters**\n\n  > Signature: `toNoteNumber(identifier, [octaveOffset])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`identifier`** | string<br /> ||The identifier in the form of a letter, followed by an optional \"#\", \"##\", \"b\" or \"bb\" followed by the octave number. For exemple: C5, G4, D#-1, F0, Gb7, Eb-1, Abb4, B##6, etc.|\n    |[**`octaveOffset`**] | number<br /> |0|A integer to offset the octave by.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nThe MIDI note number (an integer between 0 and 127).\n\n\n**Attributes**: static\n\n**Throws**:\n  * RangeError Invalid 'octaveOffset' value\n  * TypeError Invalid note identifier\n\n\n### `.toTimestamp(...)` {#toTimestamp}\n\n**Since**: 3.0.0<br />\n\nReturns a valid timestamp, relative to the navigation start of the document, derived from the\n`time` parameter. If the parameter is a string starting with the \"+\" sign and followed by a\nnumber, the resulting timestamp will be the sum of the current timestamp plus that number. If\nthe parameter is a positive number, it will be returned as is. Otherwise, false will be\nreturned.\n\n\n  **Parameters**\n\n  > Signature: `toTimestamp([time])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`time`**] | number<br />string<br /> ||The time string (e.g. `\"+2000\"`) or number to parse|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number` or `false`<br />\n\nA positive number or `false` (if the time cannot be converted)\n\n\n**Attributes**: static\n\n\n\n"
  },
  {
    "path": "website/api/classes/WebMidi.md",
    "content": "\n# WebMidi\n\nThe `WebMidi` object makes it easier to work with the low-level Web MIDI API. Basically, it\nsimplifies sending outgoing MIDI messages and reacting to incoming MIDI messages.\n\nWhen using the WebMidi.js library, you should know that the `WebMidi` class has already been\ninstantiated. You cannot instantiate it yourself. If you use the **IIFE** version, you should\nsimply use the global object called `WebMidi`. If you use the **CJS** (CommonJS) or **ESM** (ES6\nmodule) version, you get an already-instantiated object when you import the module.\n\n\n**Extends**: [`EventEmitter`](EventEmitter)\n<!--**Extends**: EventEmitter-->\n\n**Fires**: [`connected`](#event:connected), [`disabled`](#event:disabled), [`disconnected`](#event:disconnected), [`enabled`](#event:enabled), [`error`](#event:error), [`midiaccessgranted`](#event:midiaccessgranted), [`portschanged`](#event:portschanged)\n\n### `Constructor`\n\nThe WebMidi class is a singleton and you cannot instantiate it directly. It has already been\ninstantiated for you.\n\n\n\n***\n\n## Properties\n\n### `.defaults` {#defaults}\n**Type**: object<br />\n\n\nObject containing system-wide default values that can be changed to customize how the library\nworks.\n\n\n  **Properties**\n\n  | Property     | Type         | Description  |\n  | ------------ | ------------ | ------------ |\n    |**`defaults.note`** |object|Default values relating to note|\n    |**`defaults.note.attack`** |number|A number between 0 and 127 representing the default attack velocity of notes. Initial value is 64.|\n    |**`defaults.note.release`** |number|A number between 0 and 127 representing the default release velocity of notes. Initial value is 64.|\n    |**`defaults.note.duration`** |number|A number representing the default duration of notes (in seconds). Initial value is Infinity.|\n\n\n### `.enabled` {#enabled}\n**Type**: boolean<br />\n**Attributes**: read-only<br />\n\n\nIndicates whether access to the host's MIDI subsystem is active or not.\n\n\n### `.eventCount` {#eventCount}\n**Type**: number<br />\n**Attributes**: read-only<br />\n\n\nThe number of unique events that have registered listeners.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventMap` {#eventMap}\n**Type**: Object<br />\n**Attributes**: read-only<br />\n\n\nAn object containing a property for each event with at least one registered listener. Each\nevent property contains an array of all the [`Listener`](Listener) objects registered\nfor the event.\n\n\n### `.eventNames` {#eventNames}\n**Type**: Array.&lt;string&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all the unique event names for which the emitter has at least one registered\nlistener.\n\nNote: this excludes global events registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) because they are not tied to a\nspecific event.\n\n\n### `.eventsSuspended` {#eventsSuspended}\n**Type**: boolean<br />\n\n\nWhether or not the execution of callbacks is currently suspended for this emitter.\n\n\n### `.flavour` {#flavour}\n**Since**: 3.0.25<br />\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nThe flavour of the library. Can be one of:\n\n* `esm`: ECMAScript Module\n* `cjs`: CommonJS Module\n* `iife`: Immediately-Invoked Function Expression\n\n\n### `.inputs` {#inputs}\n**Type**: Array.&lt;Input&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all currently available MIDI inputs.\n\n\n### `.interface` {#interface}\n**Type**: MIDIAccess<br />\n**Attributes**: read-only<br />\n\n\nThe [`MIDIAccess`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess)\ninstance used to talk to the lower-level Web MIDI API. This should not be used directly\nunless you know what you are doing.\n\n\n### `.octaveOffset` {#octaveOffset}\n**Since**: 2.1<br />\n**Type**: number<br />\n\n\nAn integer to offset the octave of notes received from external devices or sent to external\ndevices.\n\nWhen a MIDI message comes in on an input channel the reported note name will be offset. For\nexample, if the `octaveOffset` is set to `-1` and a [`\"noteon\"`](InputChannel#event:noteon)\nmessage with MIDI number 60 comes in, the note will be reported as C3 (instead of C4).\n\nBy the same token, when [`OutputChannel.playNote()`](OutputChannel#playNote) is called, the\nMIDI note number being sent will be offset. If `octaveOffset` is set to `-1`, the MIDI note\nnumber sent will be 72 (instead of 60).\n\n\n### `.outputs` {#outputs}\n**Type**: Array.&lt;Output&gt;<br />\n**Attributes**: read-only<br />\n\n\nAn array of all currently available MIDI outputs as [`Output`](Output) objects.\n\n\n### `.supported` {#supported}\n**Type**: boolean<br />\n**Attributes**: read-only<br />\n\n\nIndicates whether the environment provides support for the Web MIDI API or not.\n\n**Note**: in environments that do not offer built-in MIDI support, this will report `true` if\nthe\n[`navigator.requestMIDIAccess`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess)\nfunction is available. For example, if you have installed WebMIDIAPIShim.js but no plugin, this\nproperty will be `true` even though actual support might not be there.\n\n\n### `.sysexEnabled` {#sysexEnabled}\n**Type**: boolean<br />\n**Attributes**: read-only<br />\n\n\nIndicates whether MIDI system exclusive messages have been activated when WebMidi.js was\nenabled via the [`enable()`](#enable) method.\n\n\n### `.time` {#time}\n**Type**: DOMHighResTimeStamp<br />\n**Attributes**: read-only<br />\n\n\nThe elapsed time, in milliseconds, since the time\n[origin](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin).\nSaid simply, it is the number of milliseconds that passed since the page was loaded. Being a\nfloating-point number, it has sub-millisecond accuracy. According to the\n[documentation](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp), the\ntime should be accurate to 5 µs (microseconds). However, due to various constraints, the\nbrowser might only be accurate to one millisecond.\n\nNote: `WebMidi.time` is simply an alias to `performance.now()`.\n\n\n### `.validation` {#validation}\n**Type**: boolean<br />\n\n\nIndicates whether argument validation and backwards-compatibility checks are performed\nthroughout the WebMidi.js library for object methods and property setters.\n\nThis is an advanced setting that should be used carefully. Setting `validation` to `false`\nimproves performance but should only be done once the project has been thoroughly tested with\n`validation` turned on.\n\n\n### `.version` {#version}\n**Type**: string<br />\n**Attributes**: read-only<br />\n\n\nThe version of the library as a [semver](https://semver.org/) string.\n\n\n\n***\n\n## Methods\n\n\n### `.addListener(...)` {#addListener}\n\n\nAdds a listener for the specified event. It returns the [`Listener`](Listener) object\nthat was created and attached to the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](#ANY_EVENT) as the first parameter. Note that a global\nlistener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to.|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs.|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The value of `this` in the callback function.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.remaining`**] | number<br /> |Infinity|The number of times after which the callback should automatically be removed.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.addOneTimeListener(...)` {#addOneTimeListener}\n\n\nAdds a one-time listener for the specified event. The listener will be executed once and then\ndestroyed. It returns the [`Listener`](Listener) object that was created and attached\nto the event.\n\nTo attach a global listener that will be triggered for any events, use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter. Note that a\nglobal listener will also be triggered by non-registered events.\n\n\n  **Parameters**\n\n  > Signature: `addOneTimeListener(event, callback, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to listen to|\n    |**`callback`** | EventEmitter~callback<br /> ||The callback function to execute when the event occurs|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.context`**] | Object<br /> |this|The context to invoke the callback function in.|\n    |[**`options.prepend`**] | boolean<br /> |false|Whether the listener should be added at the beginning of the listeners array and thus executed first.|\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds before the listener automatically expires.|\n    |[**`options.arguments`**] | array<br /> ||An array of arguments which will be passed separately to the callback function. This array is stored in the [`arguments`](Listener#arguments) property of the [`Listener`](Listener) object and can be retrieved or modified as desired.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Listener`<br />\n\nThe newly created [`Listener`](Listener) object.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT).\n  * `TypeError` : The `callback` parameter must be a function.\n\n\n### `.disable()` {#disable}\n\n**Since**: 2.0.0<br />\n**Attributes**: async\n\nCompletely disables **WebMidi.js** by unlinking the MIDI subsystem's interface and closing all\n[`Input`](Input) and [`Output`](Output) objects that may have been opened. This also means that\nlisteners added to [`Input`](Input) objects, [`Output`](Output) objects or to `WebMidi` itself\nare also destroyed.\n\n\n**Return Value**\n\n> Returns: `Promise.<Array>`<br />\n\n\n**Throws**:\n  * `Error` : The Web MIDI API is not supported by your environment.\n\n\n### `.emit(...)` {#emit}\n\n\nExecutes the callback function of all the [`Listener`](Listener) objects registered for\na given event. The callback functions are passed the additional arguments passed to `emit()`\n(if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of\nthe [`Listener`](Listener) object (if any).\n\nIf the [`eventsSuspended`](#eventsSuspended) property is `true` or the\n[`Listener.suspended`](Listener#suspended) property is `true`, the callback functions\nwill not be executed.\n\nThis function returns an array containing the return values of each of the callbacks.\n\nIt should be noted that the regular listeners are triggered first followed by the global\nlisteners (those added with [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\n\n  **Parameters**\n\n  > Signature: `emit(event, ...args)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br /> ||The event|\n    |**`args`** | *<br /> ||Arbitrary number of arguments to pass along to the callback functions|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array`<br />\n\nAn array containing the return value of each of the executed listener\nfunctions.\n\n\n**Throws**:\n  * `TypeError` : The `event` parameter must be a string.\n\n\n### `.enable(...)` {#enable}\n\n**Attributes**: async\n\nChecks if the Web MIDI API is available in the current environment and then tries to connect to\nthe host's MIDI subsystem. This is an asynchronous operation and it causes a security prompt to\nbe displayed to the user.\n\nTo enable the use of MIDI system exclusive messages, the `sysex` option should be set to\n`true`. However, under some environments (e.g. Jazz-Plugin), the `sysex` option is ignored\nand system exclusive messages are always enabled. You can check the\n[`sysexEnabled`](#sysexEnabled) property to confirm.\n\nTo enable access to software synthesizers available on the host, you would set the `software`\noption to `true`. However, this option is only there to future-proof the library as support for\nsoftware synths has not yet been implemented in any browser (as of September 2021).\n\nBy the way, if you call the [`enable()`](#enable) method while WebMidi.js is already enabled,\nthe callback function will be executed (if any), the promise will resolve but the events\n([`\"midiaccessgranted\"`](#event:midiaccessgranted), [`\"connected\"`](#event:connected) and\n[`\"enabled\"`](#event:enabled)) will not be fired.\n\nThere are 3 ways to execute code after `WebMidi` has been enabled:\n\n- Pass a callback function in the `options`\n- Listen to the [`\"enabled\"`](#event:enabled) event\n- Wait for the promise to resolve\n\nIn order, this is what happens towards the end of the enabling process:\n\n1. [`\"midiaccessgranted\"`](#event:midiaccessgranted) event is triggered once the user has\ngranted access to use MIDI.\n2. [`\"connected\"`](#event:connected) events are triggered (for each available input and output)\n3. [`\"enabled\"`](#event:enabled) event is triggered when WebMidi.js is fully ready\n4. specified callback (if any) is executed\n5. promise is resolved and fulfilled with the `WebMidi` object.\n\n**Important note**: starting with Chrome v77, a page using Web MIDI API must be hosted on a\nsecure origin (`https://`, `localhost` or `file:///`) and the user will always be prompted to\nauthorize the operation (no matter if the `sysex` option is `true` or not).\n\n##### Example\n```js\n// Enabling WebMidi and using the promise\nWebMidi.enable().then(() => {\n  console.log(\"WebMidi.js has been enabled!\");\n})\n```\n\n\n  **Parameters**\n\n  > Signature: `enable([options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`options`**] | object<br /> |||\n    |[**`options.callback`**] | function<br /> ||A function to execute once the operation completes. This function will receive an `Error` object if enabling the Web MIDI API failed.|\n    |[**`options.sysex`**] | boolean<br /> |false|Whether to enable MIDI system exclusive messages or not.|\n    |[**`options.validation`**] | boolean<br /> |true|Whether to enable library-wide validation of method arguments and setter values. This is an advanced setting that should be used carefully. Setting [`validation`](#validation) to `false` improves performance but should only be done once the project has been thoroughly tested with [`validation`](#validation)  turned on.|\n    |[**`options.software`**] | boolean<br /> |false|Whether to request access to software synthesizers on the host system. This is part of the spec but has not yet been implemented by most browsers as of April 2020.|\n    |[**`options.requestMIDIAccessFunction`**] | function<br /> ||A custom function to use to return the MIDIAccess object. This is useful if you want to use a polyfill for the Web MIDI API or if you want to use a custom implementation of the Web MIDI API - probably for testing purposes.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Promise.<WebMidi>`<br />\n\nThe promise is fulfilled with the `WebMidi` object for\nchainability\n\n\n**Throws**:\n  * `Error` : The Web MIDI API is not supported in your environment.\n  * `Error` : Jazz-Plugin must be installed to use WebMIDIAPIShim.\n\n\n### `.getInputById(...)` {#getInputById}\n\n**Since**: 2.0.0<br />\n\nReturns the [`Input`](Input) object that matches the specified ID string or `false` if no\nmatching input is found. As per the Web MIDI API specification, IDs are strings (not integers).\n\nPlease note that IDs change from one host to another. For example, Chrome does not use the same\nkind of IDs as Jazz-Plugin.\n\n\n  **Parameters**\n\n  > Signature: `getInputById(id, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`id`** | string<br /> ||The ID string of the input. IDs can be viewed by looking at the [`WebMidi.inputs`](WebMidi#inputs) array. Even though they sometimes look like integers, IDs are strings.|\n    |[**`options`**] | object<br /> |||\n    |[**`options.disconnected`**] | boolean<br /> ||Whether to retrieve a disconnected input|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Input`<br />\n\nAn [`Input`](Input) object matching the specified ID string or `undefined`\nif no matching input can be found.\n\n\n**Throws**:\n  * `Error` : WebMidi is not enabled.\n\n\n### `.getInputByName(...)` {#getInputByName}\n\n**Since**: 2.0.0<br />\n\nReturns the first [`Input`](Input) object whose name **contains** the specified string. Note\nthat the port names change from one environment to another. For example, Chrome does not report\ninput names in the same way as the Jazz-Plugin does.\n\n\n  **Parameters**\n\n  > Signature: `getInputByName(name, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`name`** | string<br /> ||The non-empty string to look for within the name of MIDI inputs (such as those visible in the [inputs](WebMidi#inputs) array).|\n    |[**`options`**] | object<br /> |||\n    |[**`options.disconnected`**] | boolean<br /> ||Whether to retrieve a disconnected input|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Input`<br />\n\nThe [`Input`](Input) that was found or `undefined` if no input contained the\nspecified name.\n\n\n**Throws**:\n  * `Error` : WebMidi is not enabled.\n\n\n### `.getListenerCount(...)` {#getListenerCount}\n\n\nReturns the number of listeners registered for a specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) do not count towards the remaining\nnumber for a \"regular\" event. To get the number of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListenerCount(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event which is usually a string but can also be the special [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) symbol.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `number`<br />\n\nAn integer representing the number of listeners registered for the specified\nevent.\n\n\n\n\n### `.getListeners(...)` {#getListeners}\n\n\nReturns an array of all the [`Listener`](Listener) objects that have been registered for\na specific event.\n\nPlease note that global events (those added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)) are not returned for \"regular\"\nevents. To get the list of global listeners, specifically use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `getListeners(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to get listeners for.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Array.<Listener>`<br />\n\nAn array of [`Listener`](Listener) objects.\n\n\n\n\n### `.getOutputById(...)` {#getOutputById}\n\n**Since**: 2.0.0<br />\n\nReturns the [`Output`](Output) object that matches the specified ID string or `false` if no\nmatching output is found. As per the Web MIDI API specification, IDs are strings (not\nintegers).\n\nPlease note that IDs change from one host to another. For example, Chrome does not use the same\nkind of IDs as Jazz-Plugin.\n\n\n  **Parameters**\n\n  > Signature: `getOutputById(id, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`id`** | string<br /> ||The ID string of the port. IDs can be viewed by looking at the [`WebMidi.outputs`](WebMidi#outputs) array.|\n    |[**`options`**] | object<br /> |||\n    |[**`options.disconnected`**] | boolean<br /> ||Whether to retrieve a disconnected output|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nAn [`Output`](Output) object matching the specified ID string. If no\nmatching output can be found, the method returns `undefined`.\n\n\n**Throws**:\n  * `Error` : WebMidi is not enabled.\n\n\n### `.getOutputByName(...)` {#getOutputByName}\n\n**Since**: 2.0.0<br />\n\nReturns the first [`Output`](Output) object whose name **contains** the specified string. Note\nthat the port names change from one environment to another. For example, Chrome does not report\ninput names in the same way as the Jazz-Plugin does.\n\n\n  **Parameters**\n\n  > Signature: `getOutputByName(name, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`name`** | string<br /> ||The non-empty string to look for within the name of MIDI inputs (such as those visible in the [`outputs`](#outputs) array).|\n    |[**`options`**] | object<br /> |||\n    |[**`options.disconnected`**] | boolean<br /> ||Whether to retrieve a disconnected output|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `Output`<br />\n\nThe [`Output`](Output) that was found or `undefined` if no output matched\nthe specified name.\n\n\n**Throws**:\n  * `Error` : WebMidi is not enabled.\n\n\n### `.hasListener(...)` {#hasListener}\n\n\nReturns `true` if the specified event has at least one registered listener. If no event is\nspecified, the method returns `true` if any event has at least one listener registered (this\nincludes global listeners registered to\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT)).\n\nNote: to specifically check for global listeners added with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT), use\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the parameter.\n\n\n  **Parameters**\n\n  > Signature: `hasListener([event], [callback])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br />Symbol<br /> |(any event)|The event to check|\n    |[**`callback`**] | function<br />Listener<br /> |(any callback)|The actual function that was added to the event or the [Listener](Listener) object returned by `addListener()`.|\n\n  </div>\n\n\n**Return Value**\n\n> Returns: `boolean`<br />\n\n\n\n\n### `.removeListener(...)` {#removeListener}\n\n\nRemoves all the listeners that were added to the object upon which the method is called and\nthat match the specified criterias. If no parameters are passed, all listeners added to this\nobject will be removed. If only the `event` parameter is passed, all listeners for that event\nwill be removed from that object. You can remove global listeners by using\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) as the first parameter.\n\nTo use more granular options, you must at least define the `event`. Then, you can specify the\ncallback to match or one or more of the additional options.\n\n\n  **Parameters**\n\n  > Signature: `removeListener([event], [callback], [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |[**`event`**] | string<br /> ||The event name.|\n    |[**`callback`**] | EventEmitter~callback<br /> ||Only remove the listeners that match this exact callback function.|\n    |[**`options`**] | Object<br /> |||\n    |[**`options.context`**] | *<br /> ||Only remove the listeners that have this exact context.|\n    |[**`options.remaining`**] | number<br /> ||Only remove the listener if it has exactly that many remaining times to be executed.|\n\n  </div>\n\n\n\n\n\n\n### `.suspendEvent(...)` {#suspendEvent}\n\n\nSuspends execution of all callbacks functions registered for the specified event type.\n\nYou can suspend execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `suspendEvent()`. Beware that this\nwill not suspend all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem counter-intuitive\nat first glance, it allows the selective suspension of global listeners while leaving other\nlisteners alone. If you truly want to suspends all callbacks for a specific\n[`EventEmitter`](EventEmitter), simply set its `eventsSuspended` property to `true`.\n\n\n  **Parameters**\n\n  > Signature: `suspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to suspend execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.unsuspendEvent(...)` {#unsuspendEvent}\n\n\nResumes execution of all suspended callback functions registered for the specified event type.\n\nYou can resume execution of callbacks registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) by passing\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) to `unsuspendEvent()`. Beware that\nthis will not resume all callbacks but only those registered with\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). While this may seem\ncounter-intuitive, it allows the selective unsuspension of global listeners while leaving other\ncallbacks alone.\n\n\n  **Parameters**\n\n  > Signature: `unsuspendEvent(event)`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event name (or `EventEmitter.ANY_EVENT`) for which to resume execution of all callback functions.|\n\n  </div>\n\n\n\n\n\n\n### `.waitFor(...)` {#waitFor}\n\n**Attributes**: async\n\nThe `waitFor()` method is an async function which returns a promise. The promise is fulfilled\nwhen the specified event occurs. The event can be a regular event or\n[`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT) (if you want to resolve as soon as any\nevent is emitted).\n\nIf the `duration` option is set, the promise will only be fulfilled if the event is emitted\nwithin the specified duration. If the event has not been fulfilled after the specified\nduration, the promise is rejected. This makes it super easy to wait for an event and timeout\nafter a certain time if the event is not triggered.\n\n\n  **Parameters**\n\n  > Signature: `waitFor(event, [options])`\n\n  <div class=\"parameter-table-container\">\n\n  | Parameter    | Type(s)      | Default      | Description  |\n  | ------------ | ------------ | ------------ | ------------ |\n    |**`event`** | string<br />Symbol<br /> ||The event to wait for|\n    |[**`options`**] | Object<br /> |{}||\n    |[**`options.duration`**] | number<br /> |Infinity|The number of milliseconds to wait before the promise is automatically rejected.|\n\n  </div>\n\n\n\n\n\n\n***\n\n## Events\n\n### `connected` {#event-connected}\n\n<a id=\"event:connected\"></a>\n\n\nEvent emitted when an [`Input`](Input) or [`Output`](Output) becomes available. This event is\ntypically fired whenever a MIDI device is plugged in. Please note that it may fire several\ntimes if a device possesses multiple inputs and/or outputs (which is often the case).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`connected`|\n  |**`target`** |WebMidi|The object to which the listener was originally added (`WebMidi`)|\n  |**`port`** |Input|The [`Input`](Input) or [`Output`](Output) object that triggered the event.|\n\n\n### `disabled` {#event-disabled}\n\n<a id=\"event:disabled\"></a>\n\n\nEvent emitted once `WebMidi` has been successfully disabled.\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |DOMHighResTimeStamp|The moment when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`target`** |WebMidi|The object that triggered the event|\n  |**`type`** |string|`\"disabled\"`|\n\n\n### `disconnected` {#event-disconnected}\n\n<a id=\"event:disconnected\"></a>\n\n\nEvent emitted when an [`Input`](Input) or [`Output`](Output) becomes unavailable. This event\nis typically fired whenever a MIDI device is unplugged. Please note that it may fire several\ntimes if a device possesses multiple inputs and/or outputs (which is often the case).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |DOMHighResTimeStamp|The moment when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`disconnected`|\n  |**`target`** |WebMidi|The object to which the listener was originally added (`WebMidi`)|\n  |**`port`** |Input|The [`Input`](Input) or [`Output`](Output) object that triggered the event.|\n\n\n### `enabled` {#event-enabled}\n\n<a id=\"event:enabled\"></a>\n\n\nEvent emitted once `WebMidi` has been fully enabled\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |DOMHighResTimeStamp|The moment when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`target`** |WebMidi|The object that triggered the event|\n  |**`type`** |string|`\"enabled\"`|\n\n\n### `error` {#event-error}\n\n<a id=\"event:error\"></a>\n\n\nEvent emitted when an error occurs trying to enable `WebMidi`\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |DOMHighResTimeStamp|The moment when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`target`** |WebMidi|The object that triggered the event|\n  |**`type`** |string|`error`|\n  |**`error`** |*|Actual error that occurred|\n\n\n### `midiaccessgranted` {#event-midiaccessgranted}\n\n<a id=\"event:midiaccessgranted\"></a>\n\n\nEvent emitted once the MIDI interface has been successfully created (which implies user has\ngranted access to MIDI).\n\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |DOMHighResTimeStamp|The moment when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`target`** |WebMidi|The object that triggered the event|\n  |**`type`** |string|`midiaccessgranted`|\n\n\n### `portschanged` {#event-portschanged}\n\n<a id=\"event:portschanged\"></a>\n\n\nEvent emitted when an [`Input`](Input) or [`Output`](Output) port is connected or\ndisconnected. This event is typically fired whenever a MIDI device is plugged in or\nunplugged. Please note that it may fire several times if a device possesses multiple inputs\nand/or outputs (which is often the case).\n\n**Since**: 3.0.2\n\n\n**Event Properties**\n\n| Property                 | Type                     | Description              |\n| ------------------------ | ------------------------ | ------------------------ |\n  |**`timestamp`** |number|The moment (DOMHighResTimeStamp) when the event occurred (in milliseconds since the navigation start of the document).|\n  |**`type`** |string|`portschanged`|\n  |**`target`** |WebMidi|The object to which the listener was originally added (`WebMidi`)|\n  |**`port`** |Input|The [`Input`](Input) or [`Output`](Output) object that triggered the event.|\n\n\n\n"
  },
  {
    "path": "website/api/classes/_category_.json",
    "content": "{\n  \"label\": \"Classes & Objects\",\n  \"position\": 2,\n  \"collapsed\": false\n}\n"
  },
  {
    "path": "website/api/index.md",
    "content": "---\nsidebar_position: 1\ntitle: API Documentation\nslug: /\n---\n\n# API Documentation\n\n## Core Classes\n\nThese classes are the ones developers are most likely to be dealing with while working on their MIDI \nprojects. Note that all these classes are pre-instantiated within WEBMIDI.js.\n\n* [**WebMidi**](./classes/WebMidi.md)\n* [**Input**](./classes/Input.md)\n* [**InputChannel**](./classes/InputChannel.md)\n* [**Output**](./classes/Output.md)\n* [**OutputChannel**](./classes/OutputChannel.md)\n* [**Message**](./classes/Message.md)\n\nThe exception are the [`Note`](./classes/Note.md) class which you can instantiate when you need\nto store a musical note and the [`Forwarder`](./classes/Forwarder.md) class used to forward\nmessages from an input to an output:\n\n* [**Note**](./classes/Note.md)\n* [**Forwarder**](./classes/Forwarder.md)\n\n## Support Classes\n\nThese classes are mostly for internal use, but you might find them useful in some contexts. The \n[`Enumerations`](./classes/Enumerations.md) class contains static enums of MIDI messages,\nregistered parameters, etc. The [`Utilities`](./classes/Utilities.md) class contains various \nstatic methods. \n\n* [**Enumerations**](./classes/Enumerations.md)\n* [**Utilities**](./classes/Utilities.md)\n\n## DjipEvents Classes\n\nThe `EventEmitter` and `Listener` classes from the \n[DjipEvents](https://github.com/djipco/djipevents) module are extended by various WEBMIDI.js \nclasses. So, in the interest of completeness, we include their full documentation here and \ncross-reference it with the core classes\n\n* [**EventEmitter**](./classes/EventEmitter.md)\n* [**Listener**](./classes/Listener.md)\n"
  },
  {
    "path": "website/babel.config.js",
    "content": "module.exports = {\n  presets: [require.resolve(\"@docusaurus/core/lib/babel/preset\")],\n};\n"
  },
  {
    "path": "website/blog/2021-12-01/version-3-has-been-released.md",
    "content": "---\ntitle: WEBMIDI.js v3 is available now!\ndescription: Version 3 of WEBMIDI.js, the library that lets you interact with your MIDI instruments and devices, is now available. It features Node.js and TypeScript support, various new objects (Message, Note, etc.) and a completely rewritten engine.\nauthors:\n- name: Jean-Philippe Côté\n  title: Creator of WEBMIDI.js\n  url: /about\n  image_url: /img/blog/jean-philippe_cote.jpg\nhide_table_of_contents: false\nkeywords: [web midi api, music, instrument, midi, javascript]\nimage: /img/blog/2021-12-01/webmidijs-is-out.png\n---\n\nAfter a lot of work and testing, I am happy to announce today that version 3 of the go-to MIDI\nlibrary for JavaScript has been released! You can [try it out](https://webmidijs.org/docs) right \nnow!\n\n<!--truncate-->\n\n![](webmidi.js-is-available-now.png)\n\n### About WEBMIDI.js\n\n[**WEBMIDI.js**](https://webmidijs.org) exists to make it easier for developers to use the\n[Web MIDI API](https://webaudio.github.io/web-midi-api/). The Web MIDI API is a really exciting\naddition to the web platform allowing a web page to interact directly with MIDI musical instruments\nand devices.\n\nWhile great, many developers will find the API to be too low-level for their needs. Having to\nperform binary arithmetic or needing to constantly refer to the 300-page MIDI spec is no fun (trust\nme on this!). So, the goal for [**WEBMIDI.js**](https://webmidijs.org) is to get developers and \nmusicians started with their web-based MIDI projects as efficiently as possible.\n\nAs of today, [**WEBMIDI.js**](https://webmidijs.org) generates over **744K hits a month on\n[jsDelivr](https://www.jsdelivr.com/package/npm/webmidi)**. It is **downloaded over 4.4K times a\nmonth on [NPM](https://www.npmjs.com/package/webmidi)** and has been **starred by over\n[1000 developers](https://github.com/djipco/webmidi/stargazers)** on GitHub. Not too bad for a niche\nlibrary that grew out of a personal passion project. 😀\n\n### About the New Version 3\n\nVersion 3 has been rewritten from scratch to make it both future-proof and backwards-compatible. It \nuses a modern development paradigm and now has its own dedicated website at \n[**webmidijs.org**](https://webmidijs.org). The library offers numerous new features such as:\n\n* Long-awaited **support for Node.js** (thanks to the [jzz](https://www.npmjs.com/package/jzz)\n  module by Jazz-Soft). The exact same code can be used in supported browsers and in Node.js.\n\n* Distribution in **3 flavours**: **ESM** (ECMAScript module for modern browsers), **CJS** (CommonJS\n  module for Node.js) and **IIFE** (Immediately Invoked Function Expression for legacy browsers and\n  _ad hoc_ usage).\n\n* **TypeScript Support**. Every new release includes a TypeScript definition file for CJS and ESM in\nthe `dist` directory.\n\n* **New `InputChannel` and `OutputChannel`** objects. You can now work with a single MIDI channel if \nthat's appropriate for your needs.\n\n* **New `Note` object**. Makes it easier to work with notes and pass them around from one method to \nthe next.\n\n* **New `Message` object** that allows easier routing of MIDI messages, including the ability to \nautomatically **forward inbound MIDI messages** to one, or more, outputs (much like the good ol' \nphysical THRU port).\n\n* Improved support for **system exclusive** (sysex) messages.\n\n* **Support for promises** while preserving legacy callback support.\n\n* Improved **support for RPN/NRPN messages**.\n\n* Addition of **hundreds of unit tests** to make sure the library remains stable at all times.\n\n* and lots more...\n\n### Try it out!\n\nThe [documentation section](https://webmidijs.org/docs) of the new website has all the information \nto get you started. If you need help, you can exchange with fellow users and myself using the \n[GitHub Discussions](https://github.com/djipco/webmidi/discussions) platform.\n\nIf you use the library and find it useful, please think about \n[sponsoring](https://github.com/sponsors/djipco) 💜 the project.\n\nCheers!\n\nJean-Philippe\n\n\n\n\n"
  },
  {
    "path": "website/docs/archives/_category_.json",
    "content": "{\n  \"label\": \"Previous Versions\",\n  \"position\": 40\n}\n"
  },
  {
    "path": "website/docs/archives/v1.md",
    "content": "---\nsidebar_position: 2\nslug: /archives/v1\nsidebar_label: Version 1.0.0-beta.15\n---\n\n# Documentation for v1.0.0-beta.15 \n\n:::caution\n\nThere is no documentation per se for version 1.0.0-beta.15, However, you can still consult an\narchived copy of the full \n[API Reference](https://djipco.github.io/webmidi/archives/api/v1/classes/WebMidi.html).\n\n:::\n"
  },
  {
    "path": "website/docs/archives/v2.md",
    "content": "---\nsidebar_position: 1\nslug: /archives/v2\nsidebar_label: Version 2.5.3\n---\n\n# Documentation for v2.5.3\n\n:::caution\n\nVersion 2.5.3 will be the last version of the 2.x branch. This documentation and the 2.5.3 version\nwill not be updated after 2021.\n\n:::\n\n## Browser Support\n\nThis library works in all browsers that natively support the\n[Web MIDI API](https://webaudio.github.io/web-midi-api/). Currently (2021), the following browsers \nhave built-in support:\n\n* Chrome (macOS, GNU/Linux, Android & Windows)\n* Opera (macOS, GNU/Linux, Windows)\n* Android WebView component (KitKat and above)\n* Edge (Windows)\n\nIt is also possible to use this library in other browsers if you install version 1.4+ of\n[Jazz-Plugin](http://jazz-soft.net/) together with the\n[WebMIDIAPIShim](http://cwilso.github.io/WebMIDIAPIShim/) polyfill. This combination provides\nsupport for the following additional browsers:\n\n* Firefox v51 **or less** (Mac, GNU/Linux & Windows)\n* Safari (macOS)\n* Internet Explorer (Windows)\n\n>For details on how to use **WebMidi.js** with the Jazz-Plugin (and WebMIDIAPIShim, please skip\n>ahead to the [Using WebMidi.js with the Jazz-Plugin](#using-webmidijs-with-the-jazz-plugin)\n>section.\n\nFor **Firefox v52+ support**, you need to install two extensions made by\n[Jazz-Soft](https://www.jazz-soft.net/):\n\n* [Jazz-MIDI extension](https://addons.mozilla.org/en-US/firefox/addon/jazz-midi/) v1.5.1+\n* [Web MIDI API extension](https://addons.mozilla.org/en-US/firefox/addon/web-midi-api/)\n\nEarly tests show that WebMidi.js is working in Firefox when both these extensions installed. Further\ntesting will need to be done but it looks very promising.\n\nI invite you to communicate with the Firefox and Safari teams to let them know how having native Web\nMIDI support is important for you:\n\n* Safari: https://bugs.webkit.org/show_bug.cgi?id=107250\n* Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=836897\n\nNote that, in 2020, [Apple has announced](https://webkit.org/tracking-prevention/) that they would not\nimplement the Web MIDI API (and a host of other APIs) in Safari over fingerprinting concerns.\n\n## Node.js Support\n\nThere is no official Node.js support in WebMidi.js 2.5.x. Version 3 and above offer full Node.js \nsupport. \n\n## TypeScript Support\n\nTypeScript type definitions have been tentatively added to WebMidi.js with version 2.3 (thanks to\n[mmmveggies](https://www.github.com/mmmveggies)) but it should be noted that **TypeScript IS NOT\nofficially supported** in version 2.5.x. TypeScript is supported in version 3 and above.\n\nDespite the absence of official suuport, this has been reported to work in v2.5.3:\n\n```ts\nimport WebMidi from \"webmidi\";\n\nWebMidi.enable(...);\n```\nOr (thanks to [michaelcaterisano](https://www.github.com/michaelcaterisano)):\n\n```ts\nconst WebMidi: import(\"webmidi\").WebMidi = require(\"webmidi\");\n```\n\nYou can also import the types, if you need them:\n\n```ts\nimport WebMidi, { InputEventNoteon, InputEventNoteoff } from \"webmidi\";\n\ninput.addListener(\"noteon\", \"all\", (event: InputEventNoteon) => {\n  ...\n}) \n```\n\n## Installation\n\nDepending on your needs and environment, you can install **WebMidi.js** in a variety of different\nways.\n\n#### CDN\n\nThe easiest way to get started is to link the WebMidi.js library from the\n[jsDelivr](https://www.jsdelivr.com/) CDN (content delivery network). To retrieve the latest\nversion, just add this `<script>` tag to your HTML page:\n\n    <script src=\"https://cdn.jsdelivr.net/npm/webmidi@2.5.3\"></script>\n\n#### Manual Install\n\nObviously, you can also install **WebMidi.js** the old fashioned way by downloading the\n[2.5.3 release](https://github.com/djipco/webmidi/releases/tag/2.5.3) packaged as a zip file. \nUncompress the package, grab the `webmidi.min.js` file and copy it to your project. Link to it from \nyour HTML page as usual.\n\n#### NPM Install\n\nIf it's more convenient, you can install **WebMidi.js** with NPM. Simply issue the following command\nto perform the actual install:\n\n    npm install webmidi\n\nThen, just add a `<script>` tag to your HTML page and make it point to:\n\n    <script src=\"node_modules/webmidi/webmidi.min.js\"></script>\n\n#### Using with a Bundler\n\nIf you are using a bundler such as WebPack, you can import **WebMidi.js** in your project in this \nway:\n\n    import WebMidi from 'path/to/webmidi';\n\n## Insecure Origins\n\nStarting with version 77, \n[Chrome deprecates Web MIDI usage on insecure origins](https://www.chromestatus.com/feature/5138066234671104). \nThis means that, going forward, the page will need to be hosted on a secure origin (e.g. \n`https://`, `localhost:` or `file:///`) and the user will need to explicitely authorize usage (no\nmatter if `sysex` is used or not).\n\n\n## Quick Start\n\nGetting started is easy. The first thing to do is to enable **WebMidi.js**. To do that, you call\n`WebMidi.enable()` and pass it a function to execute when done. This function will receive an\n`Error` object if enabling `WebMidi` failed:\n\n```javascript\nWebMidi.enable(function (err) {\n\n  if (err) {\n    console.log(\"WebMidi could not be enabled.\", err);\n  } else {\n    console.log(\"WebMidi enabled!\");\n  }\n  \n});\n```\n\n\nTo send and receive MIDI messages, you will need to do so via the appropriate `Output` and `Input`\ndevice. To view all the available `Input` and `Output` ports, you can use the matching arrays:\n\n```javascript\nWebMidi.enable(function (err) {\n    console.log(WebMidi.inputs);\n    console.log(WebMidi.outputs);\n});\n```\n\nTo send MIDI messages to a device, you simply need to grab that device and call one of its output\nmethod (`playNote()`, `stopNote()`, `sendPitchBend()`, etc.). To retrieve a device, you can use\nits position in the `WebMidi.outputs` array. For instance, to grab the first output device, you\ncould use:\n\n```javascript\nvar output = WebMidi.outputs[0];\n```\n\nHowever, this is not very safe as the position of devices in the array could change. An alternative\nis to use the device's ID:\n\n```javascript\nvar output = WebMidi.getOutputById(\"1584982307\");\n```\nBeware that device IDs are not the same across browsers and platforms. You could also use the device's name (as\ndisplayed in the `WebMidi.outputs` array):\n\n```javascript\nvar output = WebMidi.getOutputByName(\"Axiom Pro 25 Ext Out\");\n```\n\nThen, you can call any of the output methods and all native MIDI communications will be handled for\nyou. For example, to play a \"C\" on the 3rd octave, you simply do:\n\n```javascript\noutput.playNote(\"C3\");\n```\n\nThat's it.\n\nReceiving messages works in a similar way: you retrieve the `Input` device you want to use, and then\nadd a callback function to be triggered when a specific MIDI message is received. For example, to\nlisten for pitch bend events on all channels of the device:\n\n```javascript\nvar input = WebMidi.getInputByName(\"Axiom Pro 25 USB A In\");\n\ninput.addListener('pitchbend', \"all\", function(e) {\n    console.log(\"Pitch value: \" + e.value);\n});\n```\n\n## API Documentation\n\nThe [API for WebMidi.js](https://webmidijs.org/archives/api/v2/) is fully documented. If you spot an\nerror (even something minor) or think a topic should be made clearer, do not hesitate to \n[file an issue](https://github.com/djipco/webmidi/issues) or, better yet, send a PR.\n\nHere is a link to the full **[API Reference](https://webmidijs.org/archives/api/v2/)**. \n\n## More code examples\n\nHere are various other examples to give you an idea of what is possible with **WebMidi.js**.\n\n```javascript\n// Enable WebMidi.js\nWebMidi.enable(function (err) {\n\n  if (err) {\n    console.log(\"WebMidi could not be enabled.\", err);\n  }\n\n  // Viewing available inputs and outputs\n  console.log(WebMidi.inputs);\n  console.log(WebMidi.outputs);\n  \n  // Reacting when a new device becomes available\n  WebMidi.addListener(\"connected\", function(e) {\n    console.log(e);\n  });\n  \n  // Reacting when a device becomes unavailable\n  WebMidi.addListener(\"disconnected\", function(e) {\n    console.log(e);\n  });\n\n  // Display the current time\n  console.log(WebMidi.time);\n\n  // Retrieving an output port/device using its id, name or index\n  var output = WebMidi.getOutputById(\"123456789\");\n  output = WebMidi.getOutputByName(\"Axiom Pro 25 Ext Out\");\n  output = WebMidi.outputs[0];\n\n  // Play a note on all channels of the selected output\n  output.playNote(\"C3\");\n\n  // Play a note on channel 3\n  output.playNote(\"Gb4\", 3);\n\n  // Play a chord on all available channels\n  output.playNote([\"C3\", \"D#3\", \"G3\"]);\n\n  // Play a chord on channel 7\n  output.playNote([\"C3\", \"D#3\", \"G3\"], 7);\n\n  // Play a note at full velocity on all channels)\n  output.playNote(\"F#-1\", \"all\", {velocity: 1});\n\n  // Play a note on channel 16 in 2 seconds (relative time)\n  output.playNote(\"F5\", 16, {time: \"+2000\"});\n\n  // Play a note on channel 1 at an absolute time in the future\n  output.playNote(\"F5\", 16, {time: WebMidi.time + 3000});\n\n  // Play a note for a duration of 2 seconds (will send a note off message in 2 seconds). Also use\n  // a low attack velocity\n  output.playNote(\"Gb2\", 10, {duration: 2000, velocity: 0.25});\n\n  // Stop a playing note on all channels\n  output.stopNote(\"C-1\");\n\n  // Stopping a playing note on channel 11\n  output.stopNote(\"F3\", 11);\n\n  // Stop a playing note on channel 11 and use a high release velocity\n  output.stopNote(\"G8\", 11, {velocity: 0.9});\n\n  // Stopping a playing note in 2.5 seconds\n  output.stopNote(\"Bb2\", 11, {time: \"+2500\"});\n\n  // Send polyphonic aftertouch message to channel 8\n  output.sendKeyAftertouch(\"C#3\", 8, 0.25);\n\n  // Send pitch bend (between -1 and 1) to channel 12\n  output.sendPitchBend(-1, 12);\n\n  // You can chain most method calls\n  output.playNote(\"G5\", 12)\n    .sendPitchBend(-0.5, 12, {time: 400}) // After 400 ms.\n    .sendPitchBend(0.5, 12, {time: 800})  // After 800 ms.\n    .stopNote(\"G5\", 12, {time: 1200});    // After 1.2 s.\n\n  // Retrieve an input by name, id or index\n  var input = WebMidi.getInputByName(\"nanoKEY2 KEYBOARD\");\n  input = WebMidi.getInputById(\"1809568182\");\n  input = WebMidi.inputs[0];\n\n  // Listen for a 'note on' message on all channels\n  input.addListener('noteon', \"all\",\n    function (e) {\n      console.log(\"Received 'noteon' message (\" + e.note.name + e.note.octave + \").\");\n    }\n  );\n\n  // Listen to pitch bend message on channel 3\n  input.addListener('pitchbend', 3,\n    function (e) {\n      console.log(\"Received 'pitchbend' message.\", e);\n    }\n  );\n\n  // Listen to control change message on all channels\n  input.addListener('controlchange', \"all\",\n    function (e) {\n      console.log(\"Received 'controlchange' message.\", e);\n    }\n  );\n  \n  // Listen to NRPN message on all channels\n  input.addListener('nrpn', \"all\",\n    function (e) {\n      if(e.controller.type === 'entry') {\n        console.log(\"Received 'nrpn' 'entry' message.\", e);\n      }\n      if(e.controller.type === 'decrement') {\n        console.log(\"Received 'nrpn' 'decrement' message.\", e);\n      }\n      if(e.controller.type === 'increment') {\n        console.log(\"Received 'nrpn' 'increment' message.\", e);\n      }\n      console.log(\"message value: \" + e.controller.value + \".\", e);\n    }\n  );\n\n  // Check for the presence of an event listener (in such cases, you cannot use anonymous functions).\n  function test(e) { console.log(e); }\n  input.addListener('programchange', 12, test);\n  console.log(\"Has event listener: \", input.hasListener('programchange', 12, test));\n\n  // Remove a specific listener\n  input.removeListener('programchange', 12, test);\n  console.log(\"Has event listener: \", input.hasListener('programchange', 12, test));\n\n  // Remove all listeners of a specific type on a specific channel\n  input.removeListener('noteoff', 12);\n\n  // Remove all listeners for 'noteoff' on all channels\n  input.removeListener('noteoff');\n\n  // Remove all listeners on the input\n  input.removeListener();\n\n});\n```\n## About Sysex Support\n\nPer the\n[Web MIDI API specification](https://webaudio.github.io/web-midi-api/#dom-navigator-requestmidiaccess),\nsystem exclusive (sysex) support is disabled by default. If you need to use sysex messages, you will\nneed to pass `true` as the second parameter to `WebMidi.enable()`:\n\n```javascript\nWebMidi.enable(function (err) {\n  if (err) {\n    console.warn(err);\n  } else {\n    console.log(\"Sysex is enabled!\");\n  }\n}, true);\n```\n**Important**: depending on the browser, version and platform, it may also be necessary to serve the\npage over https if you want to enable sysex support.\n\n## Migration Notes\n\nIf you are upgrading from version 1.x to 2.x, you should know that v2.x is not backwards compatible.\nSome important changes were made to the API to make it easier to use, more versatile and to better\nfuture-proof it.\n\nHere is a summary of the changes:\n\n* All the \"output\" functions (`playNote()`, `sendPitchBend()`, etc.) have been moved to the\n  `Output` object. A list of all available `Output` objects is available in `WebMidi.outputs`\n  (like before).\n\n* All the \"input\" functions (`addListener`, `removeListener()` and `hasListener()` have been\n  moved to the `Input` object. A list of all available `Input` objects is available in\n  `WebMidi.inputs` (also like before).\n\nThere might be a few other minor changes here and there but the refactoring mostly concerns the\nintroduction of `Input` and `Output` objects.\n\n## Using WebMidi.js with the Jazz-Plugin\n\nTo use **WebMidi.js** on Safari, Firefox and Internet Explorer, you will first need to install\nJazz-Plugin. Simply [download the plugin](http://jazz-soft.net/download/Jazz-Plugin/) and run the\ninstaller.\n\n> Users of Firefox v52+ are currently out of luck because Mozilla deactivated support for NPAPI\n> plugins. There is an add-on version of\n> [Jazz-Midi](https://addons.mozilla.org/en-US/firefox/addon/jazz-midi/) but, unfortunately, the\n> API is different and cannot be used as is. Firefox v52+ users will have to wait for native Web\n> MIDI support to be finalized.\n> [Reading from the comments on Bug 836897](https://bugzilla.mozilla.org/show_bug.cgi?id=836897),\n> this might take a while...\n\nThen, you will need to add the plugin to the page with the following HTML code:\n\n    <object id=\"Jazz1\" classid=\"CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90\" class=\"hidden\">\n      <object id=\"Jazz2\" type=\"audio/x-jazz\" class=\"hidden\">\n        <p><a href=http://jazz-soft.net>Jazz-Plugin</a> required!</p>\n      </object>\n    </object>\n\nTo support recent versions of Internet Explorer, you also need to add a `meta` tag to the `<head>`\nof the page:\n\n    <meta http-equiv=\"X-UA-Compatible\" content=\"requiresActiveX=true\"/>\n\nSince Jazz-Plugin does not use the same syntax as the native Web MIDI API, it is necessary to also\ninstall the [WebMIDIAPIShim](http://cwilso.github.io/WebMIDIAPIShim/) polyfill. You can do that by\nincluding the following in your page:\n\n    <script src='https://cwilso.github.io/WebMIDIAPIShim/build/WebMIDIAPI.min.js'></script>\n\nObviously, you can also\n[download a local copy](https://github.com/cwilso/WebMIDIAPIShim/zipball/master) and link to it.\n"
  },
  {
    "path": "website/docs/getting-started/_category_.json",
    "content": "{\n  \"label\": \"Getting Started\",\n  \"position\": 10\n}\n"
  },
  {
    "path": "website/docs/getting-started/basics.md",
    "content": "---\nsidebar_position: 3\n---\n\n# Basics\n\n## Enabling the Library \n\nThe first step to get started is to enable the library. To do that, you simply call\n[`WebMidi.enable()`](/api/classes/WebMidi#enable). Starting with v3, the `enable()` method returns a\npromise which is resolved when the library has been enabled:\n\n```javascript\nWebMidi\n  .enable()\n  .then(() => console.log(\"WebMidi enabled!\"))\n  .catch(err => alert(err));\n```\n\n:::caution\n\nIf you intend to use MIDI **system exclusive** messages, you must explicitly enable them by setting \nthe `sysex` option to `true`:\n\n```javascript\nWebMidi\n  .enable({sysex: true})\n  .then(() => console.log(\"WebMidi with sysex enabled!\"))\n  .catch(err => alert(err));\n```\n\n:::\n\n## Listing Available Devices\n\nTo interact with devices you need to know which `Input` and `Output` ports are available. Connect a\nMIDI device and try the following:\n\n```javascript\nWebMidi\n  .enable()\n  .then(onEnabled)\n  .catch(err => alert(err));\n\nfunction onEnabled() {\n  \n  // Inputs\n  WebMidi.inputs.forEach(input => console.log(input.manufacturer, input.name));\n  \n  // Outputs\n  WebMidi.outputs.forEach(output => console.log(output.manufacturer, output.name));\n\n}\n```\n\nYou should see your hardware and software devices appear in the console. Note that many devices make\navailable several input and/or output ports. \n\nYou can retrieve a reference to an [`Input`](/api/classes/Input) by using the \n[`getInputByName()`](/api/classes/WebMidi#getInputByName) or\n[`getInputById()`](/api/classes/WebMidi#getInputById) methods:\n\n```javascript\nconst myInput = WebMidi.getInputByName(\"MPK mini 3\");\n```\nOnce you have a reference to the input, you can add listeners that will react when a message (such\nas a note press) arrives.\n\n## Listening For Incoming MIDI Messages\n\nOn a MIDI device, an input has 16 discrete channels. If you want to listen on all of them, you can\nadd a listener directly on the [`Input`](/api/classes/Input) object:\n\n```javascript\nconst myInput = WebMidi.getInputByName(\"MPK mini 3\");\nmyInput.addListener(\"noteon\", e => {\n  console.log(e.note.identifier);\n})\n```\n\nTry playing a note on your device. You should see the note's name and octave in the console.\n\nObviously, you can listen to many more messages coming from your device. For a full list, check out\nthe [`Input.addListener()`](/api/classes/Input#addListener) documentation.\n\nIt is also possible to listen to messages coming from a specific MIDI channel. For example, when\nI press the drum pads on my Akai MPK Mini, the messages are sent to channel 10:\n\n```javascript\nconst myInput = WebMidi.getInputByName(\"MPK mini 3\");\nconst mySynth = myInput.channels[10]; // <-- the MIDI channel (10)\n\nmySynth.addListener(\"noteon\", e => {\n  console.log(e.note.identifier, e.message.channel);\n})\n```\nIn this case, the listener only listens to **noteon** messages coming in from channel 10 of the \ninput device.\n\n## Sending Outgoing MIDI Messages\n\nTo send messages to an external device, you must first get a reference to it. For that, you can use\nmethods such as [`getOutputByName()`](/api/classes/WebMidi#getOutputByName) or\n[`getOutputById()`](/api/classes/WebMidi#getOutputById):\n\n```javascript\nconst myOutput = WebMidi.getOutputByName(\"SP-404MKII\");\n```\n\nThen, you can use various methods to send your message. For example, if you want to tell you sampler\nto turn all sounds off, you could do the following:\n\n```javascript\nconst myOutput = WebMidi.getOutputByName(\"SP-404MKII\");\nmyOutput.sendAllSoundOff();\n```\n\nYou can learn about all the the methods available to send data by looking at the documentation for\nthe [`Output`](/api/classes/Output) object.\n\nYou can send messages to a specific MIDI channel by first grabbing a reference to the channel you \nwant. For example, on the Roland SP-404 MK II sampler, you can control a vocoder effet by sending a \n**pitchbend** message on channel 11:\n\n```javascript\nconst myOutput = WebMidi.getOutputByName(\"SP-404MKII\");\nconst vocoder = myOutput.channels[11];\nvocoder.sendPitchBend(-0.5);\n```\nIn this case, the `vocoder` constant contains an  [`OutputChannel`](/api/classes/OutputChannel) \nobject.\n\n\n## Code Examples\n\nHere are various other examples to give you an idea of what is possible with the library. All the\nexamples below only work if the library has first been properly enabled with \n[`WebMidi.enable()`](/api/classes/WebMidi#enable).\n\n\n### Retrieve an output port/device using its id, name or array index\n\nDifferent ways to retrieve an output. Beware that IDs are different from one platform to another and\non Node.js the `id` is the same as the `name`.\n\n```javascript\nlet output1 = WebMidi.getOutputById(\"123456789\");\nlet output2 = WebMidi.getOutputByName(\"Axiom Pro 25 Ext Out\");\nlet output3 = WebMidi.outputs[0];\n```\n\n### Play a note on a specific MIDI channel (1)\n\nThe `channels` property of an `Output` object contains references to 16 `OutputChannel` objects\n(1-16).\n\n```javascript\nlet output = WebMidi.outputs[0];\nlet channel = output.channels[1];\nchannel.playNote(\"C3\");\n```\n\n### Play a note on multiple channels at once\n\nYou can call [`playNote()`](/api/classes/Output#playNote) (and various other methods) directly on\nthe [`Output`](/api/classes/Output) object. This allows you to play a note on several channels at\nonce. For example, to play a note on channels 1, 2 and 3:\n\n```javascript\nlet output = WebMidi.outputs[0];\noutput.playNote(\"Gb4\", [1, 2, 3]);\n```\nYou can also create a [`Note`](/api/classes/Note) object and pass it to the \n[`playNote()`](/api/classes/Output#playNote) method:\n\n```javascript\nconst note = new Note(\"A4\");\nconst output = WebMidi.outputs[0];\noutput.playNote(note);\n```\n\n### Play a note on a specific MIDI channel\n\nTo play a note on a specific MIDI channel, you can use the \n[`playNote()`](/api/classes/OutputChannel#playNote) method of the \n[`OutputChannel`](/api/classes/OutputChannel) object (instead of the one on the\n[`Output`](/api/classes/Output) object).\n\nFor example, to play a chord on MIDI channel 1:\n\n```javascript\nlet output = WebMidi.outputs[0];\nlet channel = output.channels[1];\nchannel.playNote([\"C3\", \"D#3\", \"G3\"]);\n```\n\n### Control note velocity\n\nYou can control attack and release velocities when playing a note by using the `options` parameter.\n\n```javascript\nlet output = WebMidi.outputs[0];\nlet channel = output.channels[1];\nchannel.playNote(\"C3\", {attack: 0.5});\n```\nIf you prefer to use raw (7 bit) values between 0 and 127, you can use the `rawAttack` option \ninstead: \n\n```javascript\nlet output = WebMidi.outputs[0];\nlet channel = output.channels[1];\nchannel.playNote(\"C3\", {rawAttack: 123});\n```\n\n### Specify note duration\n\nIf you specify a duration (in decimal milliseconds) for the note, it will automatically be stopped\nafter the duration has expired. For example, to stop it after 1 second (1000 ms):\n\n```javascript\nlet output = WebMidi.outputs[0];\nlet channel = output.channels[1];\nchannel.playNote(\"C3\", {duration: 1000});\n```\n\n### Schedule notes\n\nYou can specify an absolute or relative time to schedule note playback in the future:\n\n```javascript\nWebMidi.outputs[0].channels[1].playNote(\"C3\", {time: WebMidi.time + 3000});\nWebMidi.outputs[0].channels[1].playNote(\"C3\", {time: \"+2000\"});\n```\n\nYou can retrieve the current time with [`WebMidi.time`](/api/classes/WebMidi#time). The time is in\nmilliseconds (decimal) relative to the navigation start of the document.\n\n### Manually stopping playback\n\nYou can stop playback of a note right away or in the future.\n\n```javascript\nWebMidi.outputs[0].channels[1].stopNote(\"C3\");\nWebMidi.outputs[0].channels[1].stopNote(\"C3\", {time: \"+2500\"});\n```\n\n### Sending a control change (a.k.a. CC) message\n\nThere are various ways to send a control change message. The most common way is to send the message\nto a single channel. The first parameter is the controller, the second number is the value:\n\n```javascript\n// Use controller number\nWebMidi.outputs[0].channels[1].sendControlChange(72, 64);\n\n// Use controller name\nWebMidi.outputs[0].channels[1].sendControlChange(\"volumecoarse\", 123);\n```\n\nAs you can see above, you can use either a name or number (0-127) to identify the controller to \ntarget. A \n[list of controller names](https://webmidijs.org/api/classes/OutputChannel#sendControlChange) can be\nfound in the API reference.\n\nYou can also send the control change message to several channels at once by using the \n`sendControlChange()` method of the `Output` object:\n\n```javascript\n// Send to channels 1 through 3\nWebMidi.outputs[0].sendControlChange(\"pancoarse\", 123, {channels: [1, 2, 3]});\n\n// Send to all channels\nWebMidi.outputs[0].sendControlChange(72, 56);\n```\n\n### Set polyphonic aftertouch\n\nSend polyphonic aftertouch message to channel 8:\n\n```javascript\nWebMidi.outputs[0].channels[8].sendKeyAftertouch(\"B#3\", 0.25);\n```\n\n### Set pitch bend value\n\nThe value is between -1 and 1 (a value of 0 means no bend).\n\n```javascript\nWebMidi.outputs[0].channels[8].sendPitchBend(-0.25);\n```\nYou can set the range of the bend with \n[`OutputChannel.sendPitchBendRange()`](/api/classes/OutputChannel#sendPitchBendRange).\n\n### Use Chained Methods\n\nMost methods return `this` so you can chain them:\n\n```javascript\nWebMidi.outputs[0].channels[8]\n    .sendPitchBend(-0.25)\n    .playNote(\"F4\");\n```\n\n### Listen to event on single channel\n\n```javascript\nWebMidi.inputs[0].channels[1].addListener(\"noteon\", e => {\n  console.log(`Received 'noteon' message (${e.note.name}${e.note.octave}).`);\n});\n```\n\n### Listen to event on multiple channels at once\n\nIf you add a listener to the `Input` instead of the `InputChannel`, you can listen on multiple \nchannels at once by using the `channels` option:\n\n```javascript\nWebMidi.inputs[0].addListener(\"controlchange\", e => {\n  console.log(`Received 'controlchange' message.`, e);\n}, {channels: [1, 2, 3]});\n```\nIf you do not specify a `channels` option, the listener will listen on all channels.\n\n### Check for the presence of an event listener\n\n```javascript\nlet channel = WebMidi.inputs[0].channels[1];\nlet test = e => console.log(e);\nchannel.addListener('programchange', test);\nconsole.log(\"Has event listener: \", channel.hasListener('programchange', test));\n```\n\n### Remove listeners\n\n```javascript\nlet channel = WebMidi.inputs[0].channels[1];\nlet test = e => console.log(e);\nchannel.removeListener(\"noteoff\", test);  // specifically this one\nchannel.removeListener(\"noteoff\");        // all noteoff on this channel\nchannel.removeListener();                 // all listeners\n```\n\n## What's Next\n\nI hope this short guide helped you getting started. Obviously, the library can do a whole lot more. \nSome of that is covered in the **Going Further** section but all of it is detailed in the [API \ndocumentation](../../api).\n\nIf you need help, you can ask questions in the \n[Forum](https://github.com/djipco/webmidi/discussions). If you want to stay posted, I suggest you \nsubscribe to our low-volume [newsletter](https://mailchi.mp/eeffe50651bd/webmidijs-newsletter) and \nfollow our [@webmidijs](https://twitter.com/webmidijs) account on Twitter.\n\nFinally, if this software proves useful I cannot encourage you enough to support it by becoming a \n[sponsor](https://github.com/sponsors/djipco) on GitHub. \n\n-- [Jean-Philippe](../../about#who-created-this)\n"
  },
  {
    "path": "website/docs/getting-started/installation.md",
    "content": "---\nsidebar_position: 2\n---\n\n# Installation\n\n## Distribution Flavours\n\nTo cater to various needs, WEBMIDI.js is distributed in 3 different flavours:\n\n* **Immediately Invoked Function Expression** (IIFE): This version adds its objects directly to the\n  global namespace. This is the legacy approach which is often easier for beginners.\n\n* **ES6 Module** (ESM): This is the modern approach which allows you to `import` the objects as\n  needed (works in newer versions of browsers and Node.js).\n\n* **CommonJS Module** (CJS): this is the flavour traditionnally used by Node.js and often with\n  bundling tools such as WebPack.\n\nAll 3 flavours come in full and minified versions with sourcemap.\n\n\n## Retrieving the Library\n\nDepending on your needs and environment, you can retrieve and install **WEBMIDI.js** in a variety of\ndifferent ways. Let's look at all of them.\n\n## Linking From CDN\n\nThe fastest way to get started is to link the library directly from the\n[jsDelivr](https://www.jsdelivr.com/package/npm/webmidi) CDN (Content Delivery Network). Just add a \n`<script>` tag to your HTML page:\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js\"></script>\n```\n\nYou can retrieve different versions and flavours of the library by modifying the URL. For example, \nto grab a different flavour replace `/dist/iife/webmidi.iife.js` by one of these:\n\n* `/dist/cjs/webmidi.cjs.js`\n* `/dist/cjs/webmidi.cjs.min.js`\n* `/dist/esm/webmidi.esm.js`\n* `/dist/esm/webmidi.esm.min.js`\n* `/dist/iife/webmidi.iife.js`\n* `/dist/iife/webmidi.iife.min.js`\n\nIf you want more control over versions and flavours, check out the \n[jsDelivr examples](https://www.jsdelivr.com/features).\n\n## Installing Manually\n\nObviously, you can also install the library the old-fashioned way by manually\n[downloading it](https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.min.js) and \nplacing it somewhere in your project. Link to it from your HTML page using a `<script>` tag as \nusual.\n\n## Installing with NPM\n\nArguably, the easiest approach is to install the library with [NPM](https://www.npmjs.com/) (Node \nPackage Manager) or [Yarn](https://yarnpkg.com/). At the root of your project, simply issue the \nfollowing command to perform the installation (obviously, [Node.js ](https://nodejs.org/en/) needs \nto be installed on your system):\n\n```shell\nnpm install webmidi\n```\n\nThen, you can use any of these approaches depending on your environment:\n\n  * ### IIFE\n\n  The **IIFE** (Immediately-Invoked Function Expression) approach creates objects directly in the global \n  namespace and might be easier for beginners.\n  \n  ```html\n  <!-- Script tag in HTML page -->\n  <script src=\"node_modules/webmidi/dist/iife/webmidi.iife.js\"></script>\n  ```\n  * ### CommonJS\n\n  Using **CommonJS** is the traditional approach for Node.js.\n  \n  ```javascript\n  const {WebMidi} = require(\"webmidi\");\n  ``` \n  * ### ES Module\n\n  This is the modern approach using the \n  [ECMAScript module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) format.\n  You can use it in browsers (see [compatibility](https://caniuse.com/es6-module-dynamic-import)) \n  and in Node.js v12+. **Going forward, this is the favoured approach.**\n\n  **Browsers:**\n  \n  ```javascript\n  import {WebMidi} from \"./node_modules/webmidi/dist/esm/webmidi.esm.min.js\";\n  ```\n  \n  **Node.js:**\n  \n  ```javascript\n  import {WebMidi} from \"webmidi\";\n  ```\n\n\n\n:::caution\n\n## Insecure Origins\n\nStarting with version 77,\n[Chrome deprecated Web MIDI usage on insecure origins](https://www.chromestatus.com/feature/5138066234671104).\nThis means that, going forward, any page using the library will need to be hosted on a secure\norigin:\n\n* `https://`\n* `localhost:`\n* `file:///`\n\nAlso, the user will need to explicitely authorize usage via a prompt (no matter if system exclusive\nmessages are used or not).\n\n:::\n"
  },
  {
    "path": "website/docs/getting-started/supported-environments.md",
    "content": "---\nsidebar_position: 1\nslug: /getting-started\n---\n\n# Supported Environments\n\nStarting with version 3, the library works in both the browser and Node.js. Let's quickly look at \nthe specificities of both these environments.\n\n## Browser Support\n\nThe library works in all browsers that natively [support](https://caniuse.com/midi) the\n[Web MIDI API](https://webaudio.github.io/web-midi-api/). Currently, the following major browsers\nhave native support:\n\n* Edge v79+\n* Chrome 43+\n* Opera 30+\n* Firefox 108+\n\nIt is also possible to use this library in other browsers if you install\n[Jazz-Plugin](https://jazz-soft.net/download/Jazz-Plugin/) v1.4+. This combination provides\nsupport for the following additional web browsers:\n\n* Safari\n* Internet Explorer\n\nNote that, in 2020, [Apple has announced](https://webkit.org/tracking-prevention/) that they would\nnot natively support the Web MIDI API (and a host of other APIs) in Safari because of fingerprinting\nconcerns.\n\n## Node.js Support\n\nVersion 3.0 of WEBMIDI.js introduced full Node.js support. Nothing special needs to be done, it\nshould just work in the following environments (with Node.js 8.5+):\n\n* GNU/Linux\n* macOS\n* Windows\n* Raspberry Pi\n\nSupport for the Node.js environment has been made possible in large part by the good folks of\n[Jazz-Soft](https://jazz-soft.net/) via their [JZZ](https://www.npmjs.com/package/jzz) module.\n\n## TypeScript Support\n\nStarting with version 3, TypeScript is officially supported. You will find the TypeScript definition\nfile in these locations in side tje library's folder:\n\n* `/dist/cjs/webmidi.cjs.d.ts` (Node.js module)\n* `/dist/esm/webmidi.esm.d.ts` (ECMAScript module)\n"
  },
  {
    "path": "website/docs/going-further/_category_.json",
    "content": "{\n  \"label\": \"Going Further\",\n  \"position\": 20\n}\n"
  },
  {
    "path": "website/docs/going-further/electron.md",
    "content": "---\nsidebar_position: 5\ntitle: Electron\n---\n\n# Electron\n\nWEBMIDI.js works fine inside [Electron](https://www.electronjs.org/) but you must make sure to \nproperly handle the permission request and permission check handlers from within the main process:\n\n```javascript\nmainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => {\n  if (permission === 'midi' || permission === 'midiSysex') {\n    callback(true);\n  } else {\n    callback(false);\n  }\n})\n\nmainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {\n  if (permission === 'midi' || permission === 'midiSysex') {\n    return true;\n  }\n  return false;\n});\n```\n"
  },
  {
    "path": "website/docs/going-further/forwarding.md",
    "content": "---\nsidebar_position: 3\ntitle: Forwarding\n---\n\n# Forwarding Messages\n\nStarting with version 3, it is now possible to forward messages from an \n[`Input`](/api/classes/Input) to an [`Output`](/api/classes/Output). This is done by using the\n`forward` method of the [`Input`](/api/classes/Input) object.\n"
  },
  {
    "path": "website/docs/going-further/middle-c.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Middle C & Octave Offset\n\n## Default Value for Middle C\n\nThe general MIDI 1.0 specification does not explicitly define a pitch for middle C but it does \nconsider middle C to be note number 60. The **MIDI Tuning Standard** states that note number 69\nshould be tuned at 440Hz by default, which would make middle C (60) to be C4. However, different \nmanufacturers have assigned middle C to various octaves/pitches (usually C3, C4 or C5).\n\nIn accordance with the **MIDI Tuning Standard** and the \n[**scientific pitch notation**](https://en.wikipedia.org/wiki/Scientific_pitch_notation), WEBMIDI.js \nconsiders middle C (261.626 Hz) to be C4 by default.\n\n## Offsetting Middle C\n\nYou can offset the reported note name and octave by using the `octaveOffset` property of various \nobjects. This will make it easier to interface with devices that do not place middle C at C4.\n\n### Inbound Note Example\n\nIf your external MIDI keyboard sends C3 and WEBMIDI.js reports it as C4, it is because your keyboard\nplaces middle C one octave lower than WEBMIDI.js does. To account for that, simply set\n[`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset) to `-1`. This way, when your keyboard \nsends C3, WEBMIDI.js will also report it as C3.\n\nIn both cases the actual note number (60) remains the same. It is just being reported differently.\n\n### Outbound Note Example\n\nIf you are sending F#4 to an external device and that device thinks it's receiving F#5, it means\nthat the external device places middle C one octave higher. In this case, set\n[`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset) to `1` to account for the difference.\n\n## Offsetting Granularity\n\nFor most scenarios, setting the global [`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset)\nis enough. However, the `octaveOffset` property is available for several objects to allow for better \ngranularity:\n\n* [Input](/api/classes/Input)\n* [InputChannel](/api/classes/InputChannel)\n* [Output](/api/classes/Output)\n* [OutputChannel](/api/classes/OutputChannel)\n* [Note](/api/classes/Note)\n* [WebMidi](/api/classes/WebMidi)\n\nIf you define `octaveOffset` on several objects, their value will be added. For example, if you \nset [`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset) to `-1` and set `octaveOffset` on a\nspecific channel to `1`, the resulting offset on that channel will be `0` (-1 + 1) while the offset \non other channels will be `1`.\n"
  },
  {
    "path": "website/docs/going-further/performance.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Performance\n\n## Targeting Channels\n\nTo complete.\n\n## Disabling Validation\n\nTo complete.\n"
  },
  {
    "path": "website/docs/going-further/sysex.md",
    "content": "---\nsidebar_position: 3\ntitle: Sysex\n---\n\n# System Exclusive Messages (sysex)\n"
  },
  {
    "path": "website/docs/going-further/typescript.md",
    "content": "---\nsidebar_position: 4\ntitle: TypeScript\n---\n\n# TypeScript\n\nTypeScript is supported in version 3+. However, it has not yet been tested extensively and some \nminor issues may remain.\n\nFor instance, some types have been defined as `any` for lack of a better option. One such example \nhas been described in \n[issue 229](https://github.com/djipco/webmidi/issues/229#issuecomment-1039036353). If you are a \nTypeScript expert, perhaps you can help.\n"
  },
  {
    "path": "website/docs/index.md",
    "content": "---\nsidebar_position: 1\nslug: /\n---\n\n# Quick Start For v3.x\n\n**You want to get started as quickly as possible?** This guide will let you establish a connection\nwith your MIDI instrument in less than 5 minutes.\n\n:::info\n\nDocumentation for [version 2.5.x](https://webmidijs.org/archives/api/v2/) and \n[version 1.0.0](http://webmidijs.org/archives/api/v1/classes/WebMidi.html) is also available.\n\n:::\n\n## Step 1 - Create the HTML page\n\n:::tip\n\nHint: You can **go even faster** by copying the\n[code](https://github.com/djipco/webmidi/blob/develop/examples/quick-start/index.html) from\nour GitHub repo.\n\n:::\n\nCreate an HTML document and link to the library: \n\n```html\n<!DOCTYPE html>\n\n<html lang=\"en\">\n\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>WebMidi.js Quick Start</title>\n    <script src=\"https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js\"></script>\n  </head>\n  \n  <body>\n    <h1>WebMidi.js Quick Start</h1>\n  </body>\n\n</html>\n```\n\n## Step 2 - Add a script\n\nAdd the following `<script>` tag to the `<head>` of the HTML page. This code waits for the library \nto be loaded, then enables it and then displays available MIDI input devices (such as synths, drum \nmachines, controllers, etc.):\n\n```html\n<script type=\"module\">\n\n  // Enable WEBMIDI.js and trigger the onEnabled() function when ready\n  WebMidi\n    .enable()\n    .then(onEnabled)\n    .catch(err => alert(err));\n\n  // Function triggered when WEBMIDI.js is ready\n  function onEnabled() {\n\n    // Display available MIDI input devices\n    if (WebMidi.inputs.length < 1) {\n      document.body.innerHTML+= \"No device detected.\";\n    } else {\n      WebMidi.inputs.forEach((device, index) => {\n        document.body.innerHTML+= `${index}: ${device.name} <br>`;\n      });\n    }\n\n  }\n  \n</script>\n```\n## Step 3 - Connect your device \n\n🎹 Connect an input MIDI device (synth, drum machine, controller, etc.) and load the HTML page in a \n[compatible browser](/docs/getting-started#browser-support). You will be \nprompted to authorize the MIDI connection.\n\nAfter authorization, the page should detect the connected MIDI devices and display their name.\n\n:::info\n\nIf nothing shows up, first make sure your MIDI device is detected at the operating system level.\n\n:::\n\n## Step 4 - Listen for MIDI messages\n\nIn the `onEnabled()` function, we first retrieve the input device we want to work with and store it\nin the `mySynth` variable. You can retrieve it by number or by name (as you wish).\n\nThen we use the `addListener()` method on MIDI channel 1 of the input device to add a \ncallback function that will be called each time a **noteon** event is received on that MIDI channel.\n\n```javascript\nfunction onEnabled() {\n\n  if (WebMidi.inputs.length < 1) {\n    document.body.innerHTML+= \"No device detected.\";\n  } else {\n    WebMidi.inputs.forEach((device, index) => {\n      document.body.innerHTML+= `${index}: ${device.name} <br>`;\n    });\n  }\n  \n  const mySynth = WebMidi.inputs[0];\n  // const mySynth = WebMidi.getInputByName(\"TYPE NAME HERE!\")\n  \n  mySynth.channels[1].addListener(\"noteon\", e => {\n    document.body.innerHTML+= `${e.note.name} <br>`;\n  });\n  \n}\n```\nAlternatively, if you wish to listen for notes from several channels at once, you can add the \nlistener directly on the input device itself:\n\n```javascript\n// Listen to 'note on' events on channels 1, 2 and 3 of the first input MIDI device\nWebMidi.inputs[0].addListener(\"noteon\", e => {\n  document.body.innerHTML+= `${e.note.name} <br>`;\n}, {channels: [1, 2, 3]});\n```\n\n## Step 5 - Have fun!\n\n**That's it!** To go further, please take some time to check out the \n[Getting Started](getting-started) section. It covers important topics such as installation \noptions, compatibility, security, etc.\n\n:::tip\n\nIf you ever need further help, you can also head over to the\n[GitHub Discussions](https://github.com/djipco/webmidi/discussions) page and ask all the questions\nyou want!\n\n:::\n"
  },
  {
    "path": "website/docs/migration/_category_.json",
    "content": "{\n  \"label\": \"Migration\",\n  \"position\": 30\n}\n"
  },
  {
    "path": "website/docs/migration/migration.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Migrating from 2.5.x to 3.x\n\n:::caution\n\nThis document is a work in progress. Your feedback is critical in identifying and, hopefully, \nmitigating migration pitfalls. 🙏🏻 Please **share your observations, suggestions and issues** in the \n[**Migration**](https://github.com/djipco/webmidi/discussions/categories/migration) section of the forum\nso they can benefit others! \n\n:::\n\n## Highlights of the New Version\n\nVersion 3.x is a **major** update. It moves WEBMIDI.js from a smallish hobby project to a more \nserious long-term endeavour. Here are some key highlights:\n\n- Modern architecture with **ESM** (ECMAScript module), **IIFE** (Immediately Invoked Function \nExpression) and **CJS** (CommonJS) flavours\n- Full **Node.js** support\n- **TypeScript** definitions files\n- Various **new objects**:\n  - [`InputChannel`](/api/classes/InputChannel) and [`OutputChannel`](/api/classes/OutputChannel) \n  objects to communicate with a single MIDI channel\n  - [`Note`](/api/classes/Note) object to store and pass around note information\n  - [`Message`](/api/classes/Message) object to better encapsulate MIDI messages\n  - [`Forwarder`](/api/classes/Forwarder) object to allow message forwarding from an input to an output\n  - and others...\n- More and better **unit tests** to prevent regression issues\n- Support for **promises** where appropriate (such as \n[`WebMidi.enable()`](/api/classes/WebMidi#enable))\n- More granular **events**. For example:\n  - `midiaccessgranted` to know when a user clicked the MIDI authorization prompt\n  - `controlchange-controllerXXX` to listen to a single type of control change message\n  - and various others...\n- Ability to query **current note state** (currently playing or not) with \n[`InputChannel.getNoteState()`](/api/classes/InputChannel#getNoteState) \nand [`InputChannel.notesState`](/api/classes/InputChannel#notesState) array\n- Ability to unplug and replug a device while **retaining its state**\n- Better **sysex** (system exclusive) message support\n- **Octave transposition** can be performed at the global, input/output or channel level\n- and so much more!\n\n## Backwards Compatibility\n\nBackwards compatibility was a major concern while developing the new version. While every effort \nhas been made to ease the transition, the new version might break a few things, mostly in edge \ncases.\n\nWhenever it was possible, we deprecated old practices instead of completely dropping support. This \nmeans that your code should continue to work but you may see warnings in the console about the new \nways to do things.\n\n:::info\n\nIf you expected certain things to keep working after upgrade and they don't, please reach out in the\n[**Migration**](https://github.com/djipco/webmidi/discussions/categories/migration) section of the\nforum so whe can assess if the behaviour is expected or not and troubleshoot from there.\n\n:::\n\n## Architecture Change\n\nIn v2, there was a top-level `WebMidi` object that provided access to a list of inputs and outputs. \nMost of the activity happened at the `Input` and `Output` level. This is where you would listen for\ninbound messages and send outbound messages.\n\nFor example, if you wanted to listen for a message on a single MIDI channel, you would have \nspecified it in the call to `addListener()` in this manner:\n\n```javascript\n// In WebMidi.js version 2.5.x\nWebMidi.inputs[0].addListener(\"noteon\", 7, someFunction);\n```\n\nThis would listen for **noteon** events on MIDI channel 7 of input 0. **While this still works in \nv3**, the preferred syntax would be: \n\n```javascript\nWebMidi.inputs[0].addListener(\"noteon\", someFunction, {channels: [7]});\n```\n\nYou may think (rightly so!) that this syntax is more cumbersome. The reasoning is that, if you want\nto listen to events on a single channel, you should do so on the channel itself \n(the new [`InputChannel`](/api/classes/InputChannel) object):\n\n```javascript\nWebMidi.inputs[0].channels[7].addListener(\"noteon\", someFunction);\n```\n\nHere, `WebMidi.inputs[0].channels[7]` refers to an\n[`InputChannel`](/api/classes/InputChannel) object that has, for the most part, the same methods \nas the [`Input`](/api/classes/Input) object you were used to in v2.5.x.\n\nSo, the idea is to use the [`Input`](/api/classes/Input) object if you need to listen to events on \nmore than one channel and to use the [`InputChannel`](/api/classes/InputChannel) object to listen\nfor events dispatched by a single channel.\n\nThe exact same logic applies to the [`Output`](/api/classes/Output) and \n[`OutputChannel`](/api/classes/OutputChannel) objects. For example, if you want to send a \n**controlchange** message to all channels of an output, you can use:\n\n```javascript\nWebMidi.outputs[0].sendControlChange(\"resonance\", 123);\n```\nTo send to multiple, but specific, channels, you can use:\n\n```javascript\nWebMidi.outputs[0].sendControlChange(\"resonance\", 123, {channels: [1, 2, 3]});\n```\n\nTo send the message to a single channel (e.g. 7), you would use:\n\n```javascript\nWebMidi.outputs[0].channels[7].sendControlChange(\"resonance\", 123);\n```\n\nIn the end, the idea is to target specifically what is appropriate. Therefore, a more idiomatic\nsnippet would be something like this:\n\n```javascript\nconst output = WebMidi.getOutputByName(\"My Awesome Midi Device\");\nconst synth = output.channels[10];\nsynth.playNote(\"A4\");\n```\n\nHaving said all that, let me reiterate that **the previous way of doing things will still work in \nv3**. This will give you a chance to smoothly transition to the new version.\n\nLet's recap. In v3, there is a top-level [`WebMidi`](/api/classes/WebMidi) object which has both \nan [`inputs`](/api/classes/WebMidi#inputs) and an [`outputs`](/api/classes/WebMidi#outputs) \narray. These arrays contain, respectively, a list [`Input`](/api/classes/Input) and \n[`Output`](/api/classes/Output) objects. The [`Input`](/api/classes/Input) and\n[`Output`](/api/classes/Output)  objects have a `channels` array that contains a list of \n[`InputChannel`](/api/classes/InputChannel) or [`OutputChannel`](/api/classes/OutputChannel)\nobjects.\n\n:::info\n\nYou can also check the [CHANGELOG.md](https://github.com/djipco/webmidi/blob/master/CHANGELOG.md) \nfile for more hints of what has changed in version 3.\n\n:::\n\n## Things to Watch Out For\n\n### The `WebMidi.enable()` method now returns a promise\n\n**You can still use a callback** with [`WebMidi.enable()`](/api/classes/WebMidi#enable) and it \nwill work just like before. However, you are now welcome to use the promise-based approach:\n\n```javascript\nWebMidi.enable().then(() => {\n  console.log(\"WebMidi.js has been enabled!\");\n})\n```\n"
  },
  {
    "path": "website/docs/roadmap/_category_.json",
    "content": "{\n  \"label\": \"Roadmap\",\n  \"position\": 50\n}\n"
  },
  {
    "path": "website/docs/roadmap/under-evaluation.md",
    "content": "---\nsidebar_label: Under Evaluation\nsidebar_position: 2\n---\n\n# Potential Enhancements To Evaluate\n\nThe analysis has not started yet. We will wait after the official launch of version 3. We do have a\nlot of ideas and suggestions in store. Depending on whether these features break the API or not, \nthey may make it into version 3.x or be deployed in v4.\n\n:::info\n\nIf you have suggestions, please post them for discussion to the\n[Feature Request](https://github.com/djipco/webmidi/discussions/categories/feature-requests)\ncategory of our GitHub Discussions.\n\n:::\n\n## Ideas & Suggestions to Evaluate\n\nIf you feel any of these ideas should be given priority, plese explain why in the \n[Feature Request](https://github.com/djipco/webmidi/discussions/categories/feature-requests)\ncategory of our GitHub Discussions so I can properly triage them.\n\n* Add support for Web BLE MIDI ([browser implementation](https://github.com/skratchdot/ble-midi),\n[Node implementation](https://github.com/natcl/ble-midi))\n\n* Explore compatibility with WebMidiLink. Could we create an output that points to a WebMidiLinked\ndevice?\n\n* Could we allow `WebMidi.time` to be reset? (see \n[discussion #213](https://github.com/djipco/webmidi/discussions/213))\n\n* Add throttling or delay option to `sendSysex` (see discussion\n[#235](https://github.com/djipco/webmidi/discussions/235)).\n\n* Calculate BPM from clock messages \n([Discussion #177](https://github.com/djipco/webmidi/discussions/177))\n\n* Allow the first argument of `output.playNote( )` to be ‘0:0’ as ‘A0’, ‘7:3’ as ‘E:3’ and so on.\n\n* Add a \"mute\" option for inputs/outputs\n\n* Include the ability to add MIDI event listeners at the WebMidi.js level \n([Issue #138](https://github.com/djipco/webmidi/issues/138))\n\n* Emit events on `send()` so outbound MIDI messages can be listened for \n([Discussion #171](https://github.com/djipco/webmidi/discussions/171))\n\n* Add a `stopAllNotes()` method\n\n* Calculate time values and make them directly available for `songposition` and `timecode` message\n\n* Make Istanbul (nyc) break down the coverage stats by test file.\n\n* Add the ability to send grouped messages for CC events (and potentially others)\n\n* Add expliocit support for \n[MIDI Polyphonic Expressions](https://www.midi.org/midi-articles/midi-polyphonic-expression-mpe).\n\n* Add explicit support for \n[Universal System Exclusive Messages](https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages)\n\n  * This would include a `sendIdentityRequest()` method to the output object (perhaps with a \n  `getIdentity()` companion method that waits for the response) ([Issue #117](https://github.com/djipco/webmidi/issues/117))\n\n  * This could also include the capability to query device for make/model (similar to \n  [jzz-midi-gear](https://www.npmjs.com/package/jzz-midi-gear))\n\n  * Implement show control protocol subset\n\n* Add ability to inject Jazz-Plugin code for browsers with no native Web MIDI API support.\n\n* Add the option to create sysex plugins for various devices \n[forum thread](https://webmidijs.org/forum/discussion/comment/97#Comment_97)\n\n* Add\n[issue and PR templates](https://help.github.com/en/github/building-a-strong-community/about-issue-and-pull-request-templates)\n\n* Add continuous integration tool\n\n* Add ability to read/write MIDI files\n\n* Solid timing, midi clock, sync, transport functionality\n\n* Helper functions that help to deal with sysex checksum from specific manufacturer (Roland, \nchecksum, etc.)\n\n* Add explicit support for Sample Dump Format (see discussion on \n[forum](https://webmidijs.org/forum/discussion/30/has-there-been-any-work-on-sample-dump-standard))\n\n* Allow third-party developers to develop modules that facilitate encoding and decoding of\ndevice-specific sysex messages (see [forum discussion](https://webmidijs.org/forum/discussion/37/))\n\n* Add timing capabilities such as syncing with Tone.js or being able to schedule events using\nmusical notes.\n\n* Add the ability to export a MIDI file (perhaps with another lib such as \n[MidiWriterJS](https://www.npmjs.com/package/midi-writer-js) or \n[Jazz-Soft](https://jazz-soft.net/demo/WriteMidiFile.html)\n\n* SMF Support\n\n* Check if something specific needs to be done to support Electron \n([this discussion](https://www.electronjs.org/docs/api/session#sessetpermissionrequesthandlerhandler)).\n\n* Evaluate whether if would be worth it to switch from the `midi` module to the `web-midi-test` \nmodule for unit tests (discussion [here](https://github.com/djipco/webmidi/discussions/223)).\n\n## Enhancements Put On Hold For Now\n\n* Consider usage of \n[pipelining operator](https://github.com/tc39/proposal-pipeline-operator/blob/master/README.md#introduction) \nfor patching webmidi function calls to a sequence\n \n* Consider using [middleware](https://github.com/unbug/js-middleware) approach for making the app \npluggable\n\n* Investigate the possibility to add a `Device` object that would group inputs and outputs for a\n  single device (see [discussion #280](https://github.com/djipco/webmidi/discussions/280) for \n  details)\n\n* Piano roll\n"
  },
  {
    "path": "website/docs/roadmap/v3.md",
    "content": "---\nsidebar_label: Version 3\nsidebar_position: 1\n---\n\n# Roadmap for version 3.x\n\n## Enhancements Still Remaining\n\n* Publish TypeScript definitions to \n[DefinitelyTyped](https://definitelytyped.org/guides/contributing.html) when v3 is stable (they are\ncurrently available in the `dist` directory)\n\n* Add `inputconnected`, `inputdisconnected`, `outputconnected` and `outputdisconnected` events.\n\n* There is something in `InputChannel.test.js` that prevents the tests from running correctly in \n`Output.test.js`\n\n* Unit tests should minimally check the 3 flavours of the library (ESM, CJS and IIFE) to make sure \nthey each properly run.\n\n* A combined CC0 / CC32 / ProgChange method to call program changes with bank selection in a single \nmethod\n\n* The callback for `WebMidi.addListener()` and `Input.addListener()` should probably have both a \n`target` and a `port` property when the target is not the same as the port. Perhaps both should be \nkept everywhere for consistency.\n\n## Enhancement Already Baked In\n\n* Added triggering of `portschanged` event in `WebMidi` object (v3.0.2).\n\n* Add `filter` option to allow listening to only a subset of events (e.g. specific controller change\nor NRPN messages, discussed in [PR #88](https://github.com/djipco/webmidi/pull/88))\n\n* Add a way to forward inbound messages on an `Input` object to an `Output` (to behave like a \nphysical MIDI THRU port). This could be expanded to a more elaborate filtering and routing system \n([example](https://github.com/shemeshg/RtMidiWrap#routing-configuration))\n\n* Add mechanism to Generate TypeScript type definitions (.d.ts files)\n\n* Add `getNoteState()` method to `InputChannel` so it it is possible to check if a note is currently \nplaying or not. This allows to check for chords when a **noteon** message is received.\n\n* Properly handle when a laptop's lid is closed then reopened\n([Issue #140](https://github.com/djipco/webmidi/issues/140))\n\n* As suggested by users, allow sending MSB and LSB at once when sending control change messages \n([Issue #57](https://github.com/djipco/webmidi/issues/57)). This would have to be done for CC \nmessages 0-31 which all have a matching LSB.\n\n* Rewrite the NRPN parsing mechanism in InputChannel. I do not think it works correctly. Here are \nstarting points:\n\n  - http://tetradev.blogspot.com/2010/03/nrpns-part-2-nrpns-in-ableton-with-max.html\n  - https://www.elektronauts.com/t/nrpn-tutorial-how-to/46669\n  - http://www.philrees.co.uk/nrpnq.htm\n  \n\n* Allow `sendSysex()` to accept `Uint8Array` ([Issue #124](https://github.com/djipco/webmidi/issues/124), [forum thread](https://webmidijs.org/forum/discussion/comment/97#Comment_97)) or perhaps to accept a `Message` object that can be built from a `Uint8Array` (this needs to be carefully examined)\n\n* Add a `Message` object\n\n* Add the ability to set default values for attack velocity, release velocity, etc. ( see [forum discussion](https://webmidijs.org/forum/discussion/44/things-in-webmidi-js-2-52-that-make-me-go-huh#latest))\n\n* Various utility methods should probably be stashed in a `Utils` class (e.g. getCcNameByNumber(), etc.)\n\n* Add convenience method to convert float and 7 bit: `to7bit()` and `toNormalized()`\n\n* Add the ability to individually transpose `Input`, `Output`, `InputChannel` and `OutputChannel`.\n\n* Add `InputChannel` and `OutputChannel` objects ([Issue #20](https://github.com/djipco/webmidi/issues/20))\n\n* Use ES6+ modern syntax and add default export so library can be imported with `import`\n(Issues [#49](https://github.com/djipco/webmidi/issues/49) and [#89](https://github.com/djipco/webmidi/issues/89))\n\n* Move to Rollup for packaging the library ([Issue #61](https://github.com/djipco/webmidi/issues/61))\n\n* Drop support for Bower ([Issue #60](https://github.com/djipco/webmidi/issues/60))\n\n* Extend a proper event library to allow for modern event support (probably \n[djipevents](https://github.com/djipco/djipevents)).\n\n* Implement port `statechange` events (`connected` and `disconnected`)\n\n* Make `WebMidi` a singleton (see example\n[here](https://www.sitepoint.com/javascript-design-patterns-singleton/))\n\n* WebMidi should dispatch 'enabled' and 'disabled' event\n\n* Check that disable() really does disable everything\n\n* Add methods for channel mode messages\n\n* Implement `clear()` method ([Issue #52](https://github.com/djipco/webmidi/issues/52)) [this will \nautomatically work when browsers add support for it but will show a warning in the console until \nthen).\n\n* Added the new ([software](https://webaudio.github.io/web-midi-api/#dom-midioptions)) parameter for\n`requestMIDIAccess` [this will automatically work when browsers add support for it).\n\n* Emit events for all channel mode messages\n\n* Add `statusByte` and `dataBytes` properties to the event triggered when receiving messages \n([#109](https://github.com/djipco/webmidi/issues/109))\n\n* Deprecate the ability to send on all channels (default behaviour). This clogs up the MIDI stream\nand I do not really see a good use case for it.\n\n* Create a `Note` object with `duration` and `velocity` property\n\n* Add official support for Node.js ([Issue #15](https://github.com/djipco/webmidi/issues/15))\n\n* Allow specifying different note durations and velocities in `playNote()` when using arrays \n([Issue #75](https://github.com/djipco/webmidi/issues/75) and \n[#90](https://github.com/djipco/webmidi/issues/90)). [this is now possible with `Note` objects].\n\n* Add [editorconfig file](https://atom.io/packages/editorconfig)\n\n* Complete test suite for all objects\n\n* Add `sendRaw()` method accepting either list of integers or `Uint8Array`.\n\n* Allow `send()` to accept `Uint8Array`\n"
  },
  {
    "path": "website/docs/roadmap/v4.md",
    "content": "---\nsidebar_label: Version 4\nsidebar_position: 2\n---\n\n# Features Planned for Version 4\n\nThe full analysis has not started yet. We do have a lot of [ideas](/docs/roadmap/under-evaluation)\nin store. Depending on whether these features break the API or not, they may make it into version \n3.x or be deployed in v4.\n\n:::info\n\nIf you have suggestions, please post them for discussion to the\n[Feature Request](https://github.com/djipco/webmidi/discussions/categories/feature-requests)\ncategory of our GitHub Discussions.\n\n:::\n"
  },
  {
    "path": "website/docusaurus.config.js",
    "content": "const lightCodeTheme = require(\"prism-react-renderer/themes/github\");\nconst darkCodeTheme = require(\"prism-react-renderer/themes/dracula\");\n\nconst BASE_URL = \"/\";\n\n/** @type {import(\"@docusaurus/types\").DocusaurusConfig} */\nmodule.exports = {\n\n  title: \"WEBMIDI.js\",\n  tagline: \"Kickstart your JavaScript MIDI projects!\",\n  url: \"https://webmidijs.org\",\n  baseUrl: BASE_URL,\n  onBrokenLinks: \"warn\",\n  onBrokenMarkdownLinks: \"warn\",\n  favicon: \"img/favicon.ico\",\n\n  organizationName: \"djipco\",\n  projectName: \"webmidi\",\n\n  // trailingSlash: false,\n\n  scripts: [\n\n  ],\n  themeConfig: {\n    navbar: {\n      // title: \"My Site\",\n      logo: {\n        alt: \"WEBMIDI.js\",\n        src: \"img/webmidijs-logo-dark.svg\",\n        srcDark: \"img/webmidijs-logo-light.svg\",\n      },\n      items: [\n        {\n          label: \"Docs\",\n          type: \"doc\",\n          docId: \"index\",\n          position: \"left\",\n        },\n        {\n          type: \"dropdown\",\n          label: \"API\",\n          position: \"left\",\n          items: [\n            {\n              label: \"3.x\",\n              to: \"api/\"\n            },\n            {\n              label: \"2.5.3\",\n              href: \"https://webmidijs.org/archives/api/v2/\",\n            },\n            {\n              label: \"1.0.0-beta.15\",\n              href: \"http://webmidijs.org/archives/api/v1/classes/WebMidi.html\"\n            }\n          ]\n        },\n        {\n          type: \"dropdown\",\n          label: \"Community\",\n          position: \"left\",\n          items: [\n            {\n              label: \"Sponsors\",\n              to: \"sponsors\"\n            },\n            {\n              label: \"Showcase\",\n              to: \"showcase\"\n            },\n            {\n              label: \"GitHub Discussions\",\n              href: \"https://github.com/djipco/webmidi/discussions\",\n              className: \"external\"\n            },\n            {\n              label: \"Newsletter Subscription\",\n              href: \"https://mailchi.mp/eeffe50651bd/webmidijs-newsletter\",\n              className: \"external\"\n            },\n            {\n              label: \"Forum (Archived)\",\n              href: \"https://webmidijs.org/forum/\",\n              className: \"external\"\n            }\n          ]\n        },\n\n        {\n          type: \"dropdown\",\n          label: \"Behind the Scenes\",\n          position: \"left\",\n          items: [\n            {\n              to: \"about\",\n              label: \"About\",\n            },\n            {\n              to: \"blog\",\n              label: \"Blog\",\n            },\n            {\n              to: \"research\",\n              label: \"Academic Research\",\n            },\n          ]\n        },\n\n\n\n\n\n        {\n          href: \"https://github.com/djipco/webmidi\",\n          position: \"right\",\n          className: \"header-github-link\",\n          \"aria-label\": \"GitHub Repo\"\n        },\n        {\n          href: \"https://twitter.com/webmidijs\",\n          position: \"right\",\n          className: \"header-twitter-link\",\n          \"aria-label\": \"Twitter Feed\"\n        },\n\n      ]\n    },\n    footer: {\n      style: \"dark\",\n      logo: {\n        alt: \"WebMidi.js\",\n        src: \"img/webmidijs-logo-dark.svg\",\n        srcDark: \"img/webmidijs-logo-light.svg\",\n      },\n      links: [\n        {\n          title: \"Docs\",\n          items: [\n            {\n              label: \"Quick Start\",\n              to: \"/docs\",\n            },\n            {\n              label: \"Getting Started\",\n              to: \"/docs/getting-started\",\n            },\n            {\n              label: \"Migration\",\n              to: \"/docs/migration\",\n            },\n          ],\n        },\n        {\n          title: \"Community\",\n          items: [\n            {\n              label: \"Showcase\",\n              href: \"/showcase\",\n            },\n            {\n              label: \"Twitter\",\n              href: \"https://twitter.com/webmidijs\",\n            },\n          ],\n        },\n        {\n          title: \"More\",\n          items: [\n            {\n              label: \"GitHub\",\n              href: \"https://github.com/djipco/webmidi\",\n            },\n          ],\n        },\n      ],\n      copyright: `© 2015-${new Date().getFullYear()} Jean-Philippe Côté`,\n    },\n    prism: {\n      theme: lightCodeTheme,\n      darkTheme: darkCodeTheme,\n    },\n    algolia: {\n      apiKey: \"417771b74406a78671b6592f451f2453\",\n      indexName: \"webmidi\",\n      appId: \"KHO24V8B5T\",\n\n      // Optional: see doc section below\n      contextualSearch: true,\n\n      // Optional: Algolia search parameters\n      searchParameters: {},\n\n      //... other Algolia params\n      placeholder: \"Search website...\"\n    },\n    image: \"img/og-card.png\",\n    metadata: [{ name: \"robots\", content: \"max-image-preview:large\" }],\n    announcementBar: {\n      id: \"sponsor-banner\",\n      content: \"<a target='_blank' href='https://github.com/sponsors/djipco'>\" +\n        \"<strong>Sponsor</strong></a> ❤️ WEBMIDI.js on GitHub!\"\n    }\n  },\n\n  presets: [\n    [\n      \"@docusaurus/preset-classic\",\n\n      {\n        theme: {\n          customCss: [\n            require.resolve(\"./src/css/custom.scss\"),\n            require.resolve(\"./src/css/index.scss\"),\n          ],\n        },\n\n        docs: {\n          path: \"docs\",\n          lastVersion: \"current\",\n          onlyIncludeVersions: [\"current\"],\n          sidebarPath: require.resolve(\"./sidebars.js\"),\n          editUrl: \"https://github.com/djipco/webmidi/edit/master/website/\",\n        },\n\n        blog: {\n          path: \"blog\",\n          blogTitle: \"Blog de Docusaurus !\",\n          blogDescription: \"Un blog alimenté par Docusaurus !\",\n          postsPerPage: \"ALL\",\n        },\n\n        pages: {},\n\n        gtag: {\n          // trackingID: \"UA-162785934-1\",\n          trackingID: \"G-Z65JF8XMJG\",\n        },\n\n        googleAnalytics: {\n          // trackingID: \"UA-162785934-1\",\n          trackingID: \"G-Z65JF8XMJG\",\n        }\n\n      }\n\n    ],\n  ],\n\n  plugins: [\n\n    [\n      \"@docusaurus/plugin-content-docs\",\n      {\n        id: \"api\",\n        path: \"api\",\n        routeBasePath: \"api\",\n        sidebarPath: require.resolve(\"./sidebars.js\"),\n      },\n    ],\n\n    [\n      \"docusaurus-plugin-sass\",\n      {}\n    ],\n\n    [\n      \"@docusaurus/plugin-client-redirects\",\n      {\n        redirects: [\n          {\n            from: [\"/latest/classes/WebMidi.html\"], // string | string[]\n            to: \"/api/\",\n          },\n        ],\n      },\n    ],\n\n  ],\n\n};\n"
  },
  {
    "path": "website/package.json",
    "content": "{\n  \"name\": \"docusaurus\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"start\": \"docusaurus start\",\n    \"build\": \"docusaurus build\",\n    \"swizzle\": \"docusaurus swizzle\",\n    \"deploy\": \"docusaurus deploy\",\n    \"clear\": \"docusaurus clear\",\n    \"serve\": \"docusaurus serve\",\n    \"write-translations\": \"docusaurus write-translations\",\n    \"write-heading-ids\": \"docusaurus write-heading-ids\"\n  },\n  \"dependencies\": {\n    \"@docusaurus/core\": \"^3.0.0\",\n    \"@docusaurus/plugin-client-redirects\": \"^3.0.0\",\n    \"@docusaurus/plugin-content-blog\": \"^3.0.0\",\n    \"@docusaurus/plugin-content-docs\": \"^3.0.0\",\n    \"@docusaurus/preset-classic\": \"^3.0.0\",\n    \"@docusaurus/theme-search-algolia\": \"^3.0.0\",\n    \"@mdx-js/react\": \"^1.6.21\",\n    \"@svgr/webpack\": \"^6.3.1\",\n    \"clsx\": \"^1.1.1\",\n    \"docusaurus-plugin-sass\": \"^0.2.5\",\n    \"file-loader\": \"^6.2.0\",\n    \"prism-react-renderer\": \"^1.2.1\",\n    \"react\": \"^17.0.1\",\n    \"react-dom\": \"^17.0.1\",\n    \"react-helmet\": \"^6.1.0\",\n    \"sass\": \"^1.43.4\",\n    \"typescript\": \"^4.4.2\",\n    \"url-loader\": \"^4.1.1\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.5%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "website/sidebars.js",
    "content": "/**\n * Creating a sidebar enables you to:\n - create an ordered group of docs\n - render a sidebar for each doc of that group\n - provide next/previous navigation\n\n The sidebars can be generated from the filesystem, or explicitly defined here.\n\n Create as many sidebars as you want.\n */\n\nmodule.exports = {\n  // By default, Docusaurus generates a sidebar from the docs folder structure\n  tutorialSidebar: [{type: \"autogenerated\", dirName: \".\"}],\n\n  // But you can create a sidebar manually\n  /*\n  tutorialSidebar: [\n    {\n      type: 'category',\n      label: 'Tutorial',\n      items: ['hello'],\n    },\n  ],\n   */\n};\n"
  },
  {
    "path": "website/src/components/Button.js",
    "content": "import React from \"react\";\nimport styles from \"./Button.module.scss\";\n\n/*export default function Button({children, type, href, target}) {\n  return (\n    <div className={`${styles.button} ${type}`} href={href} target={target}>\n      <a href=\"#\">{children}</a>\n      <div className=\"button--bg\"></div>\n    </div>\n  );\n}*/\n\nexport default function Button(props) {\n  const component = \"Button\";\n  const{\n    children,\n    href,\n    type,\n    target,\n  } = props;\n  return (\n    <div\n      className={`\n        ${component}\n        ${styles.Button}\n        ${type}_src-components-${component}-module\n      `}\n\n\n    >\n      <a href={href} target={target}>{children}</a>\n      <div className={`${styles.buttonBg}`} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "website/src/components/Button.module.css",
    "content": ".Button {\n  background-color: var(--color-accent);\n  width: fit-content;\n  height: fit-content;\n  border-radius: 10px;\n  position: relative;\n  overflow: hidden;\n  text-align: center;\n}\n.Button a {\n  display: block;\n  font: bold 1.56rem var(--font-secondary), sans-serif;\n  padding: var(--spacing-sm) 1.5em;\n  color: var(--color-text-primary);\n  text-decoration: none;\n  position: relative;\n  z-index: 2;\n}\n.Button .buttonBg {\n  width: 100%;\n  height: 100%;\n  position: absolute;\n}\n.Button.button-bg-full {\n  border: solid 4px var(--color-accent);\n}\n.Button.button-bg-full .buttonBg {\n  background-color: var(--color-accent-lighter);\n  z-index: 1;\n  top: 0;\n  left: -100%;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.Button.button-bg-full:hover {\n  border: solid 4px var(--color-accent-lighter);\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.Button.button-bg-full:hover .buttonBg {\n  left: 0;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.Button.button-bg-empty {\n  background-color: rgba(0, 0, 0, 0);\n  border: solid 4px var(--color-accent);\n}\n.Button.button-bg-empty a {\n  color: var(--color-accent);\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.Button.button-bg-empty .buttonBg {\n  background-color: var(--color-accent-lighter);\n  z-index: 1;\n  top: 0;\n  left: -100%;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.Button.button-bg-empty:hover {\n  border: solid 4px var(--color-accent-lighter);\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.Button.button-bg-empty:hover a {\n  color: var(--color-white);\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.Button.button-bg-empty:hover .buttonBg {\n  left: 0;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n\n/*# sourceMappingURL=Button.module.css.map */\n"
  },
  {
    "path": "website/src/components/Button.module.scss",
    "content": "$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);\n.Button {\n  background-color: var(--color-accent);\n  width: fit-content;\n  height: fit-content;\n  border-radius: 10px;\n  position: relative;\n  overflow: hidden;\n  text-align: center;\n\n  a {\n    display: block;\n    font: bold 1.56rem var(--font-secondary), sans-serif;\n    padding: var(--spacing-sm) 1.5em;\n    color: var(--color-text-primary);\n    text-decoration: none;\n\n    position: relative;\n    z-index: 2;\n  }\n  .buttonBg {\n    width: 100%;\n    height: 100%;\n    position: absolute;\n  }\n  &.button-bg-full {\n    border: solid 4px var(--color-accent);\n    .buttonBg {\n      background-color: var(--color-accent-lighter);\n\n      z-index: 1;\n      top: 0;\n      left: -100%;\n      transition: all 0.3s $ease-in-out-circ;\n    }\n    &:hover {\n      border: solid 4px var(--color-accent-lighter);\n      transition: all 0.3s $ease-in-out-circ;\n      .buttonBg {\n        left: 0;\n        transition: all 0.3s $ease-in-out-circ;\n      }\n    }\n  }\n  &.button-bg-empty {\n    background-color: rgba(0, 0, 0, 0%);\n    border: solid 4px var(--color-accent);\n    a {\n      color: var(--color-accent);\n      transition: all 0.3s $ease-in-out-circ;\n    }\n    .buttonBg {\n      background-color: var(--color-accent-lighter);\n\n      z-index: 1;\n      top: 0;\n      left: -100%;\n      transition: all 0.3s $ease-in-out-circ;\n    }\n    &:hover {\n      border: solid 4px var(--color-accent-lighter);\n      transition: all 0.3s $ease-in-out-circ;\n      a {\n        color: var(--color-white);\n        transition: all 0.3s $ease-in-out-circ;\n      }\n      .buttonBg {\n        left: 0;\n        transition: all 0.3s $ease-in-out-circ;\n      }\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "website/src/components/Column.js",
    "content": "import React from \"react\";\nimport styles from \"./Column.module.scss\";\n\nexport default function Column({children, type,}) {\n  const component = \"Column\";\n  return (\n    <div className={`\n      ${component}\n      ${styles.Column}\n      ${type}_src-components-${component}-module\n    `} >\n      {children}\n    </div>\n\n  );\n}\n"
  },
  {
    "path": "website/src/components/Column.module.css",
    "content": ".Column {\n  display: grid;\n}\n\n.col-2 {\n  grid-template-columns: 48% 48%;\n  justify-content: space-between;\n  align-items: center;\n}\n@media screen and (max-width: 1000px) {\n  .col-2 {\n    display: grid;\n    grid-template-columns: 1fr;\n    text-align: center;\n  }\n}\n\n/*# sourceMappingURL=Column.module.css.map */\n"
  },
  {
    "path": "website/src/components/Column.module.scss",
    "content": ".Column{\n  display: grid;\n}\n\n.col-2 {\n  grid-template-columns: 48% 48%;\n  justify-content: space-between;\n  align-items: center;\n\n  @media screen and(max-width: 1000px){\n    display: grid;\n    grid-template-columns: 1fr;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "website/src/components/HomepageFeatures.js",
    "content": "import React from \"react\";\nimport clsx from \"clsx\";\nimport styles from \"./HomepageFeatures.module.css\";\n\nconst FeatureList = [\n  // {\n  //   title: 'Easy to Use',\n  //   Svg: require('../../static/img/undraw_docusaurus_mountain.svg').default,\n  //   description: (\n  //     <>\n  //       Docusaurus was designed from the ground up to be easily installed and\n  //       used to get your website up and running quickly.\n  //     </>\n  //   ),\n  // },\n  // {\n  //   title: 'Focus on What Matters',\n  //   Svg: require('../../static/img/undraw_docusaurus_tree.svg').default,\n  //   description: (\n  //     <>\n  //       Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go\n  //       ahead and move your docs into the <code>docs</code> directory.\n  //     </>\n  //   ),\n  // },\n  // {\n  //   title: 'Powered by React',\n  //   Svg: require('../../static/img/undraw_docusaurus_react.svg').default,\n  //   description: (\n  //     <>\n  //       Extend or customize your website layout by reusing React. Docusaurus can\n  //       be extended while reusing the same header and footer.\n  //     </>\n  //   ),\n  // },\n];\n\nfunction Feature({Svg, title, description}) {\n  return (\n    <div className={clsx(\"col col--4\")}>\n      <div className=\"text--center\">\n        <Svg className={styles.featureSvg} alt={title} />\n      </div>\n      <div className=\"text--center padding-horiz--md\">\n        <h3>{title}</h3>\n        <p>{description}</p>\n      </div>\n    </div>\n  );\n}\n\nexport default function HomepageFeatures() {\n  return (\n    <section className={styles.features}>\n      <div className=\"container\">\n        <div className=\"row\">\n          {FeatureList.map((props, idx) => (\n            <Feature key={idx} {...props} />\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "website/src/components/HomepageFeatures.module.css",
    "content": "/* stylelint-disable docusaurus/copyright-header */\n\n.features {\n  display: flex;\n  align-items: center;\n  padding: 2rem 0;\n  width: 100%;\n}\n\n.featureSvg {\n  height: 200px;\n  width: 200px;\n}\n"
  },
  {
    "path": "website/src/components/InformationBar.js",
    "content": "import React from \"react\";\nimport styles from \"./InformationBar.module.scss\";\n\nexport default function InformationBar({children, type,}) {\n  const component = \"InformationBar\";\n  return (\n    <div className={`\n      ${component}\n      ${styles.InformationBar}\n      ${type}_src-components-${component}-module\n    `} >\n      <div className=\"container\">\n        <p>\n          {children}\n        </p>\n      </div>\n    </div>\n\n  );\n}\n"
  },
  {
    "path": "website/src/components/InformationBar.module.css",
    "content": ".InformationBar {\n  background-color: var(--color-bg-secondary);\n  padding: var(--spacing-xl) 0;\n  color: var(--color-white);\n  text-align: center;\n}\n.InformationBar p {\n  font-size: 1.87rem;\n  line-height: 120%;\n  margin: 0;\n}\n\n/*# sourceMappingURL=InformationBar.module.css.map */\n"
  },
  {
    "path": "website/src/components/InformationBar.module.scss",
    "content": ".InformationBar{\n    background-color: var(--color-bg-secondary);\n    padding: var(--spacing-xl) 0;\n    color: var(--color-white);\n  text-align: center;\n    p {\n      font-size: 1.87rem;\n      line-height: 120%;\n      margin:0;\n    }\n}\n"
  },
  {
    "path": "website/src/css/custom.css",
    "content": "/* stylelint-disable docusaurus/copyright-header */\n@import url(\"https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,400;0,900;1,900&family=Lato:wght@400;700&display=swap\");\n:root {\n  --ifm-color-primary: #f9d137;\n  --ifm-color-primary-dark: #f8ca19;\n  --ifm-color-primary-darker: #f8c70b;\n  --ifm-color-primary-darkest: #cfa506;\n  --ifm-color-primary-light: #fad855;\n  --ifm-color-primary-lighter: #fadb63;\n  --ifm-color-primary-lightest: #fce590;\n  --ifm-code-font-size: 95%;\n  --ifm-link-color: #759DCE;\n  --ifm-code-padding-horizontal: 0.2rem;\n  --color-white: #ffffff;\n  --color-black: #1c1e21;\n  --color-text-primary: var(--color-black);\n  --color-text-secondary: var(--color-white);\n  --color-text-tertiary: #999;\n  --color-bg-primary: var(--color-white);\n  --color-bg-secondary: var(--color-black);\n  --color-bg-tertiary: #F8F8F8;\n  --color-accent: #ffd000;\n  --color-accent-lighter: #ffe571;\n  --color-accent-darker: #e0ba0f;\n  --spacing-xs: 15px;\n  --spacing-sm: 20px;\n  --spacing-md: 30px;\n  --spacing-lg: 50px;\n  --spacing-xl: 100px;\n  --spacing-xxl: 175px;\n  --spacing-xxxl: 250px;\n  --font-primary: \"Lato\", sans-serif;\n  --font-secondary: \"Exo\", sans-serif;\n  --font-size-content-desktop: 1.125rem;\n  --font-size-content-mobile: 1rem;\n  --font-size-button: 1.25rem;\n  --font-size-h1-desktop: 3rem;\n  --font-size-h1-mobile: 2.5rem;\n  --font-size-h2-desktop: 1.8rem;\n  --font-size-h2-mobile: 1.6rem;\n  --font-size-h3-desktop: 1.2rem;\n  --font-size-h3-mobile: 1.125rem;\n}\n@media screen and (max-width: 500px) {\n  :root {\n    --font-size-h1-desktop: 3.5rem;\n  }\n}\n\nhtml[data-theme=dark]:root {\n  --color-text-primary: var(--color-white);\n  --color-text-secondary: var(--color-black);\n  --color-bg-primary: var(--color-black);\n  --color-bg-secondary: #222428;\n  --color-bg-tertiary: #222428;\n}\n\n.docusaurus-highlight-code-line {\n  background-color: rgba(0, 0, 0, 0.1);\n  display: block;\n  margin: 0 calc(-1 * var(--ifm-pre-padding));\n  padding: 0 var(--ifm-pre-padding);\n}\n\n.navbar__brand {\n  height: 3rem;\n}\n.navbar__brand .navbar__logo {\n  height: 3.5rem;\n}\n\n.docs-wrapper main .container article .markdown header:nth-child(2) {\n  display: none;\n}\n\nmain article a:link {\n  font-weight: var(--ifm-font-weight-bold);\n}\n\nhtml[data-theme=dark] .docusaurus-highlight-code-line {\n  background-color: rgba(0, 0, 0, 0.3);\n}\n\n.header-github-link {\n  padding: 4px;\n}\n\n.header-github-link:hover {\n  opacity: 0.6;\n}\n\n.header-github-link:before {\n  content: \"\";\n  width: 24px;\n  height: 24px;\n  display: flex;\n  background: url(\"data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E\") no-repeat;\n}\n\nhtml[data-theme=dark] .header-github-link:before {\n  filter: invert(1);\n}\n\n.header-twitter-link {\n  margin-right: 0.5em;\n  padding: 4px;\n}\n\n.header-twitter-link:hover {\n  opacity: 0.6;\n}\n\n.header-twitter-link:before {\n  content: \"\";\n  width: 25px;\n  height: 25px;\n  display: flex;\n  background: url(\"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version='1.0' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 24 24' enable-background='new 0 0 24 24' xml:space='preserve'%3E%3Cpath fill='%23191717' d='M12,0C5.4,0,0,5.4,0,12s5.4,12,12,12s12-5.4,12-12S18.6,0,12,0z M17.4,9.3c0,0.1,0,0.2,0,0.4 c0,3.6-2.8,7.8-7.8,7.8c-1.5,0-3-0.4-4.2-1.2c0.2,0,0.4,0,0.7,0c1.2,0,2.4-0.4,3.4-1.2c-1.2,0-2.2-0.8-2.6-1.9 c0.4,0.1,0.8,0.1,1.2,0c-1.3-0.3-2.2-1.4-2.2-2.7v0c0.4,0.2,0.8,0.3,1.3,0.3C5.9,9.9,5.5,8.3,6.2,7.1c1.4,1.7,3.5,2.8,5.7,2.9 c0-0.2-0.1-0.4-0.1-0.6c0-0.8,0.3-1.5,0.9-2c1.1-1,2.9-1,3.9,0.1c0.6-0.1,1.2-0.3,1.8-0.7c-0.2,0.6-0.6,1.2-1.2,1.5 c0.5-0.1,1.1-0.2,1.6-0.4C18.4,8.4,17.9,8.9,17.4,9.3z'/%3E%3C/svg%3E%0A\") no-repeat;\n}\n\nhtml[data-theme=dark] .header-twitter-link:before {\n  filter: invert(1);\n}\n\n.docs-wrapper article .markdown header:nth-of-type(2) {\n  display: none;\n}\n\ndiv[role=banner] {\n  background-color: var(--ifm-color-primary);\n}\n\nli a.dropdown__link svg {\n  display: none;\n}\n\nli a.recommended {\n  font-weight: bold;\n}\n\nli a.dropdown__link.external svg {\n  display: inline;\n}\n\n.parameter-table-container table {\n  display: table;\n  width: 100%;\n}\n.parameter-table-container table th:first-of-type {\n  width: 18%;\n}\n.parameter-table-container table th:nth-of-type(2) {\n  width: 18%;\n}\n.parameter-table-container table th:nth-of-type(3) {\n  width: 18%;\n}\n.parameter-table-container table th:nth-of-type(4) {\n  width: 46%;\n}\n\n/* ==========================================================================\n   HEADINGS / ELEMENT\n   ========================================================================== */\nh1 {\n  font-family: var(--font-secondary);\n  font-weight: 900;\n  font-size: var(--font-size-h1-mobile);\n}\n@media screen and (min-width: 1024px) {\n  h1 {\n    font-size: var(--font-size-h1-desktop);\n  }\n}\n\nh2 {\n  font-family: var(--font-secondary);\n  font-weight: 700;\n  font-size: var(--font-size-h2-mobile);\n  margin-top: 2.4rem;\n}\n@media screen and (min-width: 1024px) {\n  h2 {\n    font-size: var(--font-size-h2-desktop);\n  }\n}\n\nh3 {\n  font-family: var(--font-secondary);\n  font-weight: 700;\n  font-size: var(--font-size-h3-mobile);\n}\n@media screen and (min-width: 1024px) {\n  h3 {\n    font-size: var(--font-size-h3-desktop);\n  }\n}\n\nhtml[data-theme=light] a.cem-logo img {\n  filter: invert(1);\n}\n\na.user-icon img {\n  border-radius: 50px;\n  border: 1px solid black;\n  margin: 0.5rem;\n}\n\n/*# sourceMappingURL=custom.css.map */\n"
  },
  {
    "path": "website/src/css/custom.scss",
    "content": "/* stylelint-disable docusaurus/copyright-header */\n\n// Global custom stylesheet for Docusaurus. The 'classic' template bundles Infima. Infima is a CSS\n// framework designed for content-centric websites.\n\n// Import\n@import url(\"https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,400;0,900;1,900&family=Lato:wght@400;700&display=swap\");\n\n// Default Infima variables ////////////////////////////////////////////////////////////////////////\n:root {\n  --ifm-color-primary: #f9d137;\n  --ifm-color-primary-dark: #f8ca19;\n  --ifm-color-primary-darker: #f8c70b;\n  --ifm-color-primary-darkest: #cfa506;\n  --ifm-color-primary-light: #fad855;\n  --ifm-color-primary-lighter: #fadb63;\n  --ifm-color-primary-lightest: #fce590;\n  --ifm-code-font-size: 95%;\n  --ifm-link-color: #759DCE;\n  //--ifm-background-color: #eee;\n  --ifm-code-padding-horizontal: 0.2rem;\n\n  // Base color\n  --color-white: #ffffff;\n  --color-black: #1c1e21;\n\n  // Project Color\n  --color-text-primary: var(--color-black);\n  --color-text-secondary: var(--color-white);\n  --color-text-tertiary: #999;\n\n  --color-bg-primary: var(--color-white);\n  --color-bg-secondary: var(--color-black);\n  --color-bg-tertiary: #F8F8F8;\n\n  --color-accent: #ffd000;\n  --color-accent-lighter: #ffe571;\n  --color-accent-darker: #e0ba0f;\n\n  // Dimensions\n  --spacing-xs: 15px;\n  --spacing-sm: 20px;\n  --spacing-md: 30px;\n  --spacing-lg: 50px;\n  --spacing-xl: 100px;\n  --spacing-xxl: 175px;\n  --spacing-xxxl: 250px;\n\n  --font-primary: \"Lato\", sans-serif;\n  --font-secondary: \"Exo\", sans-serif;\n\n  --font-size-content-desktop: 1.125rem;\n  --font-size-content-mobile: 1rem;\n\n  --font-size-button: 1.25rem;\n\n  /// Grosseur des textes\n  --font-size-h1-desktop: 3rem;\n  --font-size-h1-mobile: 2.5rem;\n  --font-size-h2-desktop: 1.8rem;\n  --font-size-h2-mobile: 1.6rem;\n  --font-size-h3-desktop: 1.2rem;\n  --font-size-h3-mobile: 1.125rem;\n\n  @media screen and(max-width: 500px){\n    --font-size-h1-desktop: 3.5rem;\n  }\n\n}\n  html[data-theme='dark']:root{\n    --color-text-primary: var(--color-white);\n    --color-text-secondary: var(--color-black);\n\n    --color-bg-primary: var(--color-black);\n    --color-bg-secondary: #222428;\n    --color-bg-tertiary: #222428;\n  }\n\n$breakpoint-xs: 375px;\n$breakpoint-sm: 768px;\n$breakpoint-md: 1024px;\n$breakpoint-lg: 1440px;\n$breakpoint-xl: 1920px;\n\n.docusaurus-highlight-code-line {\n  background-color: rgba(0, 0, 0, 0.1);\n  display: block;\n  margin: 0 calc(-1 * var(--ifm-pre-padding));\n  padding: 0 var(--ifm-pre-padding);\n}\n\n\n.navbar__brand {\n\n  height: 3rem;\n\n  .navbar__logo {\n    height: 3.5rem;\n  }\n\n}\n\n.docs-wrapper main .container article .markdown header:nth-child(2) {\n  display: none;\n}\n\n\nmain article a:link {\n  font-weight: var(--ifm-font-weight-bold);\n}\n\nhtml[data-theme='dark'] .docusaurus-highlight-code-line {\n  background-color: rgba(0, 0, 0, 0.3);\n}\n\n.header-github-link {\n  //margin-right: 0.5em;\n  padding: 4px;\n}\n\n.header-github-link:hover {\n  opacity: 0.6;\n}\n\n.header-github-link:before {\n  content: '';\n  width: 24px;\n  height: 24px;\n  display: flex;\n  background: url(\"data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E\") no-repeat;\n}\n\nhtml[data-theme=dark] .header-github-link:before {\n  filter: invert(1);\n}\n\n\n.header-twitter-link {\n  margin-right: 0.5em;\n  padding: 4px;\n}\n\n.header-twitter-link:hover {\n  opacity: 0.6;\n}\n\n.header-twitter-link:before {\n  content: '';\n  width: 25px;\n  height: 25px;\n  display: flex;\n  background: url(\"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version='1.0' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 24 24' enable-background='new 0 0 24 24' xml:space='preserve'%3E%3Cpath fill='%23191717' d='M12,0C5.4,0,0,5.4,0,12s5.4,12,12,12s12-5.4,12-12S18.6,0,12,0z M17.4,9.3c0,0.1,0,0.2,0,0.4 c0,3.6-2.8,7.8-7.8,7.8c-1.5,0-3-0.4-4.2-1.2c0.2,0,0.4,0,0.7,0c1.2,0,2.4-0.4,3.4-1.2c-1.2,0-2.2-0.8-2.6-1.9 c0.4,0.1,0.8,0.1,1.2,0c-1.3-0.3-2.2-1.4-2.2-2.7v0c0.4,0.2,0.8,0.3,1.3,0.3C5.9,9.9,5.5,8.3,6.2,7.1c1.4,1.7,3.5,2.8,5.7,2.9 c0-0.2-0.1-0.4-0.1-0.6c0-0.8,0.3-1.5,0.9-2c1.1-1,2.9-1,3.9,0.1c0.6-0.1,1.2-0.3,1.8-0.7c-0.2,0.6-0.6,1.2-1.2,1.5 c0.5-0.1,1.1-0.2,1.6-0.4C18.4,8.4,17.9,8.9,17.4,9.3z'/%3E%3C/svg%3E%0A\") no-repeat;\n}\n\nhtml[data-theme=dark] .header-twitter-link:before {\n  filter: invert(1);\n}\n\n\n\n// Styles applicable to \"docs\" and \"api\" ///////////////////////////////////////////////////////////\n.docs-wrapper article .markdown header:nth-of-type(2) {\n  display: none;\n}\n\n\ndiv[role=banner] {\n  background-color: var(--ifm-color-primary);\n}\n\n\n// Styles applicable to menu ///////////////////////////////////////////////////////////////////////\nli a.dropdown__link svg {\n  display: none;\n}\n\nli a.recommended {\n  font-weight: bold;\n}\n\nli a.dropdown__link.external svg {\n  display: inline;\n}\n\n\n// Styles applicable to parameter tables in API ////////////////////////////////////////////////////\n.parameter-table-container {\n  table {\n\n    display: table;\n    width: 100%;\n\n    th:first-of-type {\n      width: 18%;\n    }\n\n    th:nth-of-type(2) {\n      width: 18%;\n    }\n\n    th:nth-of-type(3) {\n      width: 18%;\n    }\n\n    th:nth-of-type(4) {\n      width: 46%;\n    }\n  }\n}\n\n/* ==========================================================================\n   HEADINGS / ELEMENT\n   ========================================================================== */\n\nh1 {\n  font-family: var(--font-secondary);\n  font-weight: 900;\n  font-size: var(--font-size-h1-mobile);\n\n  @media screen and (min-width: $breakpoint-md) {\n    font-size: var(--font-size-h1-desktop);\n  }\n}\n\nh2 {\n  font-family: var(--font-secondary);\n  font-weight: 700;\n  font-size: var(--font-size-h2-mobile);\n  margin-top: 2.4rem;\n\n  @media screen and (min-width: $breakpoint-md) {\n    font-size: var(--font-size-h2-desktop);\n  }\n}\n\nh3 {\n  font-family: var(--font-secondary);\n  font-weight: 700;\n  font-size: var(--font-size-h3-mobile);\n\n  @media screen and (min-width: $breakpoint-md) {\n    font-size: var(--font-size-h3-desktop);\n  }\n}\n\n\nhtml[data-theme=light] a.cem-logo img {\n  filter: invert(1);\n}\n\n\na.user-icon img {\n  border-radius: 50px;\n  border: 1px solid black;\n  margin: 0.5rem;\n}\n"
  },
  {
    "path": "website/src/css/index.css",
    "content": ".hero {\n  padding: var(--spacing-lg) 0;\n  min-height: 60vh;\n  color: var(--color-text-primary);\n  display: flex;\n  align-items: center;\n  background: var(--color-bg-primary);\n  text-align: center;\n}\n@media screen and (max-width: 1000px) {\n  .hero {\n    min-height: 40vh;\n    padding-top: 0;\n  }\n}\n.hero .logo {\n  max-width: 600px;\n  height: 300px;\n  background: center url(\"/img/webmidijs-logo-dark.svg\") no-repeat;\n  margin: auto;\n}\n@media screen and (max-width: 500px) {\n  .hero .logo {\n    height: 10em;\n    margin: var(--spacing-md) 0;\n  }\n}\nhtml[data-theme=dark] .hero .logo {\n  background: center url(\"/img/webmidijs-logo-light.svg\") no-repeat;\n}\n.hero span {\n  font-family: var(--font-secondary);\n  font-size: 2rem;\n  display: block;\n  margin-bottom: var(--spacing-sm);\n  font-weight: bold;\n  color: var(--color-accent);\n}\n.hero .cta {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: center;\n  /*@media screen and(max-width: 1000px){\n    justify-content: center;\n\n  }*/\n}\n.hero .cta .Button {\n  margin: var(--spacing-sm) 0 0 var(--spacing-sm);\n}\n.hero .cta .Button:nth-child(1) {\n  margin-left: 0;\n}\n.hero .img {\n  margin: auto;\n}\n@media screen and (max-width: 1000px) {\n  .hero .texts {\n    order: 2;\n  }\n}\n\n/*======== Presentation ========*/\n.presentation {\n  margin: var(--spacing-lg) 0;\n}\n.presentation h2 {\n  text-align: center;\n}\n.presentation p {\n  font-size: 1.5rem;\n  margin: var(--spacing-sm) 0;\n}\n.presentation .media {\n  background-color: var(--color-bg-tertiary);\n  border-radius: 20px;\n  width: 100%;\n  height: 375px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  margin: var(--spacing-xl) auto;\n  box-shadow: 10px 10px 0 rgba(255, 208, 0, 0.5);\n}\n@media screen and (max-width: 1000px) {\n  .presentation .media {\n    width: 90%;\n    margin: var(--spacing-md) auto;\n  }\n}\n.presentation .media .imgMedia {\n  width: 80%;\n  height: 80%;\n}\nhtml[data-theme=light] .presentation .media .imgMedia {\n  filter: invert(1);\n}\n.presentation .media :nth-child(1) {\n  background: center url(\"/img/front-page/webmidi-demonstration.svg\") no-repeat;\n}\n@media screen and (max-width: 1000px) {\n  .presentation .media :nth-child(1) {\n    background: center url(\"/img/front-page/webmidi-demonstration-vertical.svg\") no-repeat;\n  }\n}\n\n/*# sourceMappingURL=index.css.map */\n"
  },
  {
    "path": "website/src/css/index.scss",
    "content": ".hero{\n  padding: var(--spacing-lg) 0;\n  min-height: 60vh;\n  color: var(--color-text-primary);\n  display: flex;\n  align-items: center;\n  background: var(--color-bg-primary);\n\n  text-align: center;\n\n  @media screen and(max-width: 1000px){\n    min-height: 40vh;\n    padding-top: 0;\n  }\n\n\n  .logo {\n    max-width: 600px;\n    height: 300px;\n    background: center url(\"/img/webmidijs-logo-dark.svg\") no-repeat;\n    margin: auto;\n\n    @media screen and(max-width: 500px){\n      height: 10em;\n      margin: var(--spacing-md) 0;\n    }\n\n    html[data-theme=dark] &{\n      background: center url(\"/img/webmidijs-logo-light.svg\") no-repeat;\n    }\n  }\n  span {\n    font-family: var(--font-secondary);\n    font-size: 2rem;\n    display: block;\n    margin-bottom: var(--spacing-sm);\n    font-weight: bold;\n    color: var(--color-accent);\n  }\n\n  .cta {\n    display: flex;\n    flex-wrap: wrap;\n\n    justify-content: center;\n\n    .Button{\n      margin: var(--spacing-sm) 0 0 var(--spacing-sm);\n    }\n    .Button:nth-child(1){\n      margin-left: 0;\n    }\n    /*@media screen and(max-width: 1000px){\n      justify-content: center;\n\n    }*/\n  }\n  .img{\n    margin: auto;\n  }\n  .texts{\n    @media screen and(max-width: 1000px){\n      order: 2;\n    }\n  }\n}\n/*======== Presentation ========*/\n.presentation {\n  margin: var(--spacing-lg) 0;\n  h2 {\n    text-align: center;\n  }\n  p{\n    font-size: 1.5rem;\n    margin: var(--spacing-sm) 0;\n  }\n  .media {\n    background-color: var(--color-bg-tertiary);\n    border-radius: 20px;\n\n    width: 100%;\n    height: 375px;\n\n    display: flex;\n    justify-content: center;\n    align-items: center;\n\n    margin: var(--spacing-xl) auto;\n\n    box-shadow: 10px 10px 0 rgba(255, 208, 0, 0.5);\n\n    @media screen and(max-width: 1000px){\n      width: 90%;\n      margin: var(--spacing-md) auto;\n    }\n\n    .imgMedia{\n      width: 80%;\n      height: 80%;\n\n      html[data-theme=light] &{\n        filter: invert(1);\n      }\n    }\n  }\n\n  .media :nth-child(1){\n    background: center url(\"/img/front-page/webmidi-demonstration.svg\") no-repeat;\n    @media screen and(max-width: 1000px){\n      background: center url(\"/img/front-page/webmidi-demonstration-vertical.svg\") no-repeat;\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "website/src/pages/about/index.md",
    "content": "# About\n\n## Who created this?\n\n**WEBMIDI.js** is a passion project of mine. I am Jean-Philippe Côté (a.k.a. \n[djip.co](https://djip.co)), an \n[academic](https://www.cegepmontpetit.ca/cegep/recherche/professeurs-chercheurs/jean-philippe-cote) \nand artist with particular interests in creative coding, interactive arts and music technology. You \ncan reach out to me in different ways:\n\n* Twitter: **[@djipco](https://twitter.com/djipco)** or **[@webmidijs](https://twitter.com/webmidijs)**\n* Website: **[https://djip.co/](https://djip.co)**\n* GitHub: **[https://github.com/djipco/](https://github.com/djipco)**\n\nOne of my students, **Jean-Marie Gariépy** has also been helping out in various capacities with the\ncreation of this website. Let's all thank him for his contribution! 👏\n\n## Sponsoring the project\n\nYou can [sponsor the project](https://github.com/sponsors/djipco/) by becoming a GitHub sponsor. All\nyou need is a GitHub account. If you see value in this library, please consider a contribution. 🙏🏻\n\n## Licensing\n\nStarting with version 3.0.0, this library is licensed under the **Apache License, Version 2.0**. You\nmay not use this library except in compliance with this license. You may obtain a copy at:\n\n  * [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software distributed under this license\nis distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\nimplied. See the above license for the specific language governing permissions and limitations.\n\n© 2015-2023, Jean-Philippe Côté.\n"
  },
  {
    "path": "website/src/pages/index.js",
    "content": "import React from \"react\";\nimport useBaseUrl from '@docusaurus/useBaseUrl';\nimport Layout from \"@theme/Layout\";\nimport useDocusaurusContext from \"@docusaurus/useDocusaurusContext\";\nimport Button from \"../components/Button\";\nimport Column from \"../components/Column\";\nimport InformationBar from \"../components/InformationBar\";\n\nfunction HomepageHero() {\n  const {siteConfig} = useDocusaurusContext();\n\n  return (\n    <section className=\"hero\">\n      <div className=\"container\">\n\n        <div className=\"texts\">\n          <div className=\"logo\">\n\n          </div>\n          <span>{siteConfig.tagline}</span>\n          <div className=\"cta\">\n            <Button\n              type=\"button-bg-full\"\n              href=\"./docs/\"\n              target=\"_self\"\n            >Get started in 5 minutes!\n            </Button>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n\nfunction Presentation() {\n  const {siteConfig} = useDocusaurusContext();\n  return (\n    <section className={\"presentation\"}>\n      <div className=\"container\">\n        <h2>What is {siteConfig.title}?</h2>\n        <Column\n          type=\"col-2\"\n        >\n          <p>\n            The existing <strong>Web MIDI API</strong> is a really exciting addition to the web platform\n            allowing a web page to interact with <strong>MIDI musical instruments</strong>.\n            However, while great, most developers will find the original API to be\n            too <em>low-level</em> for their needs. Having to perform binary arithmetic\n            or needing to read the 300-page MIDI spec is no fun (trust us on this!).\n            The goal behind <strong>WEBMIDI.js</strong> is to get you started with your web-based\n            MIDI project as efficiently as possible.\n          </p>\n\n          <div className=\"media\">\n            <div\n              className=\"imgMedia\"\n              src=\"\"\n              alt=\"\"\n            />\n          </div>\n        </Column>\n\n      </div>\n    </section>\n  );\n}\n\nexport default function Home() {\n  const {siteConfig} = useDocusaurusContext();\n  return (\n    <Layout\n      title={`${siteConfig.title}`}\n      description=\"A JavaScript library to kickstart your MIDI projects on the web and in Node.js.\">\n      <HomepageHero />\n      <main>\n        <InformationBar>\n          <strong>Version 3.0 has been released!</strong> <br/>\n          <a\n            rel=\"noreferrer\"\n            target=\"_blank\"\n            href=\"https://mailchi.mp/eeffe50651bd/webmidijs-newsletter\">Subscribe to the newsletter\n          </a> <br/>\n          to learn about all the new features.\n        </InformationBar>\n        <Presentation />\n      </main>\n    </Layout>\n  );\n}\n"
  },
  {
    "path": "website/src/pages/index.module.css",
    "content": "/* stylelint-disable docusaurus/copyright-header */\n/**\n * CSS files with the .module.css suffix will be treated as CSS modules\n * and scoped locally.\n */\n/*\n.heroBanner {\n  padding: 4rem 0;\n  text-align: center;\n  position: relative;\n  overflow: hidden;\n}\n\n@media screen and (max-width: 966px) {\n  .heroBanner {\n    padding: 2rem;\n  }\n}\n\n.buttons {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n*/\n.hero {\n  padding: var(--spacing-lg);\n  min-height: 60vh;\n  display: flex;\n  align-items: center;\n}\n.hero h1 {\n  margin: 0;\n  text-transform: uppercase;\n}\n.hero span {\n  font-family: var(--font-secondary);\n  font-size: 1.56rem;\n  display: block;\n  margin-bottom: var(--spacing-sm);\n}\n.hero .cta {\n  display: flex;\n}\n.hero .cta .button:nth-child(n) {\n  margin-left: var(--spacing-md);\n}\n.hero .cta .button:first-child {\n  margin-left: 0;\n}\n\n/*======== Boutons ========*/\n.button {\n  background-color: var(--color-accent);\n  width: fit-content;\n  border-radius: 10px;\n  position: relative;\n  overflow: hidden;\n}\n.button a {\n  display: block;\n  padding: var(--spacing-sm) var(--spacing-lg);\n  font-weight: bold;\n  font-family: var(--font-secondary);\n  font-size: 1.56rem;\n  position: relative;\n  z-index: 2;\n}\n.button .button--bg {\n  width: 100%;\n  height: 100%;\n  position: absolute;\n}\n.button.button-bg-full .button--bg {\n  background-color: var(--color-accent-lighter);\n  z-index: 1;\n  top: 0;\n  left: -100%;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.button.button-bg-full:hover .button--bg {\n  left: 0;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.button.button-bg-empty {\n  background-color: rgba(0, 0, 0, 0);\n  border: solid 4px var(--color-accent);\n}\n.button.button-bg-empty a {\n  color: var(--color-accent);\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.button.button-bg-empty .button--bg {\n  background-color: var(--color-accent-lighter);\n  z-index: 1;\n  top: 0;\n  left: -100%;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.button.button-bg-empty:hover a {\n  color: var(--color-white);\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.button.button-bg-empty:hover .button--bg {\n  left: 0;\n  transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n\n/*======== Columns ========*/\n.col-2 {\n  display: grid;\n  grid-template-columns: 48% 48%;\n  justify-content: space-between;\n  align-items: center;\n}\n\n/*# sourceMappingURL=index.module.css.map */\n"
  },
  {
    "path": "website/src/pages/index.module.scss",
    "content": "/* stylelint-disable docusaurus/copyright-header */\n\n/**\n * CSS files with the .module.css suffix will be treated as CSS modules\n * and scoped locally.\n */\n/*\n.heroBanner {\n  padding: 4rem 0;\n  text-align: center;\n  position: relative;\n  overflow: hidden;\n}\n\n@media screen and (max-width: 966px) {\n  .heroBanner {\n    padding: 2rem;\n  }\n}\n\n.buttons {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n*/\n.hero{\n  padding: var(--spacing-lg);\n  min-height: 60vh;\n\n  display: flex;\n  align-items: center;\n  h1 {\n    margin: 0;\n    text-transform: uppercase;\n  }\n  span {\n    font-family: var(--font-secondary);\n    font-size: 1.56rem;\n    display: block;\n    margin-bottom: var(--spacing-sm);\n  }\n\n  .cta {\n    display: flex;\n    .button:nth-child(n) {\n      margin-left: var(--spacing-md);\n    }\n    .button:first-child {\n      margin-left: 0;\n    }\n  }\n}\n\n/*======== Boutons ========*/\n$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);\n.button {\n  background-color: var(--color-accent);\n  width: fit-content;\n  border-radius: 10px;\n  position: relative;\n  overflow: hidden;\n\n  a {\n    display: block;\n    padding: var(--spacing-sm) var(--spacing-lg);\n    font-weight: bold;\n    font-family: var(--font-secondary);\n    font-size: 1.56rem;\n\n    position: relative;\n    z-index: 2;\n  }\n  .button--bg {\n    width: 100%;\n    height: 100%;\n    position: absolute;\n  }\n  &.button-bg-full {\n    .button--bg {\n      background-color: var(--color-accent-lighter);\n\n      z-index: 1;\n      top: 0;\n      left: -100%;\n      transition: all 0.3s $ease-in-out-circ;\n    }\n    &:hover {\n      .button--bg {\n        left: 0;\n        transition: all 0.3s $ease-in-out-circ;\n      }\n    }\n  }\n  &.button-bg-empty {\n    background-color: rgba(0, 0, 0, 0%);\n    border: solid 4px var(--color-accent);\n    a {\n      color: var(--color-accent);\n      transition: all 0.3s $ease-in-out-circ;\n    }\n    .button--bg {\n      background-color: var(--color-accent-lighter);\n\n      z-index: 1;\n      top: 0;\n      left: -100%;\n      transition: all 0.3s $ease-in-out-circ;\n    }\n    &:hover {\n      a {\n        color: var(--color-white);\n        transition: all 0.3s $ease-in-out-circ;\n      }\n      .button--bg {\n        left: 0;\n        transition: all 0.3s $ease-in-out-circ;\n      }\n    }\n  }\n\n}\n\n/*======== Columns ========*/\n.col-2 {\n  display: grid;\n  grid-template-columns: 48% 48%;\n  justify-content: space-between;\n  align-items: center;\n}\n\n\n"
  },
  {
    "path": "website/src/pages/research/index.md",
    "content": "# Academic Research\n\nI invite all academics and researchers to show their support for this project by properly citing it\nwherever appropriate in your publications and references.\n\n## Citing this Software\n\nI wrote a \n[paper about WEBMIDI.js](https://nime.pubpub.org/pub/user-friendly-midi-in-the-web-browser) and, \nmore specifically, about how it tries to address the usability shortcomings of the Web MIDI API. I\ninvite academics to cite it in their publication whenever appropriate: \n\n> Côté, J.-P. (2022). User-Friendly MIDI in the Web Browser. NIME 2022.\n> https://doi.org/10.21428/92fbeb44.388e4764\n\nYou can also cite the library itself like so (APA style):\n\n> Côté, J. P. (2025). WEBMIDI.js v3.1.14 [Computer Software]. Retrieved from \n> https://github.com/djipco/webmidi\n\n## Papers Citing Usage of WEBMIDI.js\n\n* Arora, A., Tandori, E., Marshall, J. et Favilla, S. (2025). **Designing Sensory NIME for Autism** \n  (p. 436‑442). Proceedings of the International Conference on New Interfaces for Musical \n  Expression. https://www.nime.org/proc/nime2025_63/index.html\n\n* Leischner, V., & Husa, P. (2023). Sonification of a Juggling Performance Using Spatial Audio. \n  Proceedings of the ACM on Computer Graphics and Interactive Techniques, 6(2), 1–6. \n  https://doi.org/10.1145/3597619\n\n* Baratè, A., & Ludovico, L. A. (2022). **Web MIDI API: State of the Art and Future Perspectives**. \n  Journal of the Audio Engineering Society, 70(11), 918–925.\n  https://www.aes.org/e-lib/browse.cfm?elib=22016\n\n* Graber, Z., & Henrion, W. (2021). **Music For Me**. Computer Science and Engineering Senior Theses.\n  https://scholarcommons.scu.edu/cseng_senior/203\n\n* Kostek, B. (Éd.). (2021). **Postępy badań w inżynierii dźwięku i obrazu**. Politechnika Wrocławska,\n  Oficyna Wydawnicza. https://doi.org/10.37190/ido2021\n\n* Walczak, M., & Łukasik, E. (2021). **Rozproszony system generowania, edycji i transmisji dźwięku \n  wykorzystujący interfejsy Web Audio API, WebRTC i Web MIDI API**. In Postępy badań w inżynierii\n  dźwięku i obrazu: Nowe trendy i zastosowania technologii dźwięku wielokanałowego oraz badania\n  jakości dźwięku (pp. 83–104). Oficyna Wydawnicza Politechniki Wrocławskiej.\n  https://doi.org/10.37190/ido2021\n\n* Krawczuk, J. (2020). **Real-Time and Post-Hoc-Visualizations of Guitar Performances as a Support\n  for Music Education**. https://doi.org/10/gkb622\n\n* Lundh Haaland, M. (2020). **The Player as a Conductor: Utilizing an Expressive Performance\n  System to Create an Interactive Video Game Soundtrack** (Dissertation). Retrieved from\n  http://urn.kb.se/resolve?urn=urn:nbn:se:kth:diva-281324\n\n* Bazin, T. & Hadjeres, G. (2019). **NONOTO: A Model-agnostic Web Interface for Interactive\n  Music Composition by Inpainting**, presented at 10th International Conference on Computational\n  Creativity, Charlotte, 2019. Retrieved from https://arxiv.org/abs/1907.10380\n\n* Smith, A. (2019). **Sonification: Turning the Yield Curve into Music**. FT.com.\n  http://search.proquest.com/docview/2191715473/fulltext/3D4C05EAFC6A4AEEPQ/1?accountid=14719\n\n* Cárdenas, A. & Mauricio B. (2018). **Diseño y desarrollo de un prototipo para integración de\n  Tecnología de Tracking 3d con Tecnología MIDI** [Doctoral dissertation, Pontificia Universidad\n  Católica del Ecuador]. Retrieved from http://repositorio.puce.edu.ec/handle/22000/15838\n\nIf you are using WEBMIDI.js in your research, I would love to know about it. To notify me, you can \nsimply [drop me a note](https://djip.co/contact).\n"
  },
  {
    "path": "website/src/pages/showcase/index.md",
    "content": "---\ntoc_max_heading_level: 2\n---\n\n# Showcase\n\nWebMidi.js is being used by amazing people to create awesome projects. Here are some examples of \nwhat the library can do.\n\n:::tip Contribute\n\nI would love for your project to be listed here. To add it, simply\n[modify](https://github.com/djipco/webmidi/edit/develop/website/src/pages/showcase/index.md) this\npage on GitHub and submit a pull request.\n\n:::\n\n---\n\n## DAWs & Editors\n\n* ### [DAWG](https://dawg.dev/)\n\n  Created by: **Jacob Smith, Amir Eldesoky, Alex ODonnell & Matt DeSilva**\n\n  Digital Audio Workstation (DAW) application built using web technologies and distributed as a \n  native application using Electron.\n\n## Education\n\n* ### [Learn Push 2](https://github.com/greyivy/learn-push2-with-svelte)\n  Created by: **Ivy**\n\n  A website to learn chords, scales and music theory with the Push 2 controller.\n\n* ### [Raaga](https://raaga.riteshkr.com/)\n  Created by: **Ritesh Kumar, Jack Hsu, Prateek Bhatnagar, Sruthi, Majid Hajian & Rohit Kokate**\n\n  An online app to play and learn music on a keyboard.\n\n* ### [Shared Piano](https://musiclab.chromeexperiments.com/Shared-Piano/)\n  Created by: **Yotam Mann**\n\n  Shared Piano is a simple tool for remote music teaching and collaboration that lets you play music\n  together live on the web.\n  \n* ### [Chromatone](https://chromatone.center/)\n  Created by: **Denis Starov**\n\n  An interactive educational web-site for the visual music language based on associating chromatic notes with spectral colors. \n\n* ### [Chromatone](https://chromatone.center/)\n  Created by: **Denis Starov**\n\n  An interactive educational web-site for the visual music language based on associating chromatic \n  notes with spectral colors.\n\n* ### [WebAudio Generator](https://webaudio.simmsreeve.com/)\n  Created by: **Joe Reeve & Magnus**\n\n  A user interface to facilitate the generation of linear WebAudio code.\n\n* ### [Midi Sandbox](https://midisandbox.com/)\n  Created by: **Jon Lee**\n\n  A collection of midi responsive widgets made to aid musicians, teachers, and students in their education,\n  creative process, and practice routine.\n\n* ### [Virtual Piano Keyboard](https://muted.io/piano/)\n  Created by: **Sébastien Noël**\n\n  A simple way to practice playing a piano keyboard online with helpers like chord identification and locking to a scale.\n\n## Experiments\n\n* ### [A.I. Duet](https://experiments.withgoogle.com/ai-duet)\n  Created by: **Yotam Mann**\n\n  This experiment lets you play a piano duet with the computer. Just play some notes, and the \n  computer will respond to your melody.\n\n* ### [Eternal](https://github.com/kousun12/eternal)\n  Created by: **Rob Cheung**\n\n  Eternal is node-based sound and music programming and exploration environment.\n\n* ### [Mideo](https://github.com/jwktje/mideo)\n  Created by: **Jan Willem Kilkman**\n\n  A Mac application that turns videos into music. It uses color tracking in combination with a grid \n  to generate MIDI data.\n\n* ### [Sonification: turning the yield curve into music](https://www.ft.com/content/80269930-40c3-11e9-b896-fe36ec32aece)\n  Created by: **Alan Smith**\n\n  This experiment turns the US yield curve into music and uses WEBMIDI.js to generate the MIDI note\n  messages.\n\n* ### [Seeing Music](https://experiments.withgoogle.com/seeing-music)\n  Created by: **Jay Alan Zimmerman, Yotam Mann, Claire Kearney-Volpe, Luisa Pereira, Kyle Phillips, \n  and Google Creative Lab**\n\n  An experiment to experience music visually. This is a tool for visualizing music. You can turn on \n  your mic to sing or play sounds. You can also drop in your own audio or video file.\n\n* ### [webmidirtc](https://github.com/philmillman/webmidirtc)\n  Created by: **philmillman**\n\n  This project is meant to demonstrate controlling hardware synths using Web MIDI (via WebMidi.js)\n  over a WebRTC video call (using Daily.)\n\n\n## Music Hardware Control\n\n* ### [Mercury7 editor](https://github.com/francoisgeorgy/mercury7-web-editor)\n  Created by: **François Georgy**\n\n  Control your Meris Mercury7 pedal with your web browser. View all the pedal's settings at once.\n\n* ### [MicroFreak Reader](https://studiocode.dev/doc/microfreak-reader/)\n  Created by: **François Georgy**\n\n  An application to read and display the presets stored in the Arturia MicroFreak memory.\n\n* ### [NTS1 Web Controller](https://directions4.github.io/nts1-web-controller/)\n  Created by: **Satoru Shikita**\n\n  Web browser interface to control the KORG Nu:Tekt NTS-1.\n\n* ### [Pacer Editor](https://studiocode.dev/pacer-editor/#/)\n  Created by: **François Georgy**\n\n  Web interface for the Nektar Pacer MIDI controller.\n\n* ### [Soundshed](https://soundshed.com/)\n  Created by: **Christopher Cook & Lavabyrd**\n\n  A Desktop app to browse and manage guitar amp tones. Control your bluetooth amp, jam to video\n  backing tracks.\n\n* ### [Sonicware Liven XFM Web Editor](https://aesqe.github.io/sonicware-liven-xfm-web-editor/)\n  Created by: **Bruno Babić**\n\n  Web editor for the Sonicware Liven XFM groovebox.\n\n\n## Components & Languages\n\n* ### [FAUST](https://faust.grame.fr/)\n  Created by: **Grame Research Lab**\n\n  The online Faust IDE can be used to edit, compile and run Faust code from the Web Browser.\n\n* ### [Midi Bricks](https://midi-bricks.timsusa.vercel.app/)\n  Created by: **Tim Susa**\n\n  A tool to build custom interfaces for MIDI control.\n\n* ### [React Audio Tools](http://react-audio-tools.surge.sh/)\n  Created by: **ambewas**\n\n  A set of React components to build things with the Web Audio and Web MIDI APIs.\n\n\n## Live Coding\n\n* ### [Sema](https://sema.codes/)\n  Created by: **Francisco Bernardo, Chris Kiefer & Thor Magnusson**\n\n  Sema is a playground where you can rapidly prototype live coding mini-languages for signal \n  synthesis, machine learning and machine listening.\n\n## Online Synthesizers\n\n* ### [synth.kitchen](https://synth.kitchen/)\n  Created by: **Rain Rudnick**\n\n  In-browser modular synthesis with Web Audio and Web MIDI.\n\n\n## Notation\n\n* ### [Inscore](https://inscore.grame.fr/)\n  Created by: **Dominique Fober, guillaumeg03 & Gabriel Leptit-Aimon**\n\n  An environment for the design of interactive, augmented, dynamic musical scores.\n\n* ### [Nonoto](https://github.com/SonyCSLParis/NONOTO)\n  Created by: **Théis Bazin, Sebastian Haas & Gaetan Hadjeres**\n\n  An A.I.-powered interactive score distributed as an Electron application. It allows users to\n  interact intuitively with any existing music composition algorithm.\n\n\n## Robotics\n\n* ### [Dexter Development Environment](https://www.hdrobotic.com/software)\n  Created by: **Haddington Dynamics**\n\n  The Dexter Development Environment, which is used to control the Dexter 7-axis robot arm, bundles\n  WEBMIDI.js for MIDI control of the device. \n\n## SysEx Librarian\n* ### [Patchup](https://www.patchup.app)\n  Created by: **Middledot Tech**\n\n  Patchup is a free web app for sending, receiving, saving, and managing System Exclusive messages. All data is saved to your browser's local storage.\n"
  },
  {
    "path": "website/src/pages/sponsors/index.md",
    "content": "# Sponsors\n\n:::tip Consider a sponsorship\n\nPlease help me grow and nurture WEBMIDI.js by ❤️ [sponsoring](https://github.com/sponsors/djipco) \nthe project on GitHub.\n\n:::\n\n## Sponsoring Organizations\n---\n\n<a href=\"https://www.cegepmontpetit.ca/\" class=\"cem-logo\">\n  <img class=\"logo-cem-dark\" src=\"/img/sponsors/edouard-montpetit-logo.svg\" width=\"150\" height=\"150\" />\n</a>\n\n## Sponsors\n---\n\n<!-- SPONSOR START -->\n\n<a class=\"user-icon\"><img src=\"/img/sponsors/user.png\" alt=\"Anonymous\" width=\"100\" height=\"100\" /></a>\n\n<a href=\"https://github.com/awatterott\" title=\"Andreas Watterott\" class=\"user-icon\">\n\t<img src=\"https://avatars.githubusercontent.com/u/1488433?u=2a498d433dd252a2959c2c846a212624098a03d9&v=4\" alt=\"Andreas Watterott\" width=\"100\" height=\"100\" />\n</a>\n\n<!-- SPONSOR END -->\n\n## About Software Development...\n\nAs you surely know, proper software development takes time. This time is spent on coding new\nfeatures but also on various other important tasks such as writing useful documentation, answering\nquestions, reviewing issues, fixing bugs, writing tests, etc.\n\nWhile WEBMIDI.js started as a hobby project, it is quietly becoming the \"go to\" library for MIDI on\nthe web. Hopefully, your contributions will make allow me to continue developing, maintaining and\nadvocating for this project that I cherish.\n\nThank you so much. 😀\n"
  },
  {
    "path": "website/src/pages/tester/index.js",
    "content": "import React from \"react\";\nimport Layout from \"@theme/Layout\";\nimport {Helmet} from \"react-helmet\";\n\n\n\n// import useBaseUrl from \"@docusaurus/useBaseUrl\";\n// import useDocusaurusContext from \"@docusaurus/useDocusaurusContext\";\n\n// const piano = new Nexus.Piano(\"#target\",{\n//   size: [500,125],\n//   mode: \"button\",  // \"button\", \"toggle\", or \"impulse\"\n//   lowNote: 24,\n//   highNote: 60\n// })\n\nfunction Tester() {\n  return (\n    <Layout title=\"Tester\">\n\n      <Helmet>\n        {/*<div id={}></div>*/}\n      </Helmet>\n\n      <div\n        style={{\n          display: \"flex\",\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          height: \"50vh\",\n          fontSize: \"20px\",\n        }}>\n        <p>\n          Edit <code>pages/helloReact.js</code> and save to reload.\n        </p>\n      </div>\n\n    </Layout>\n  );\n}\n\nexport default Tester;\n"
  },
  {
    "path": "website/src/theme/CodeBlock/index.js",
    "content": "import React from 'react';\nimport CodeBlock from '@theme-original/CodeBlock';\n\nexport default function CodeBlockWrapper(props) {\n  return (\n    <>\n      <CodeBlock {...props} />\n    </>\n  );\n}\n"
  },
  {
    "path": "website/src/theme/Footer/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React from \"react\";\nimport {useThemeConfig} from \"@docusaurus/theme-common\";\nimport useBaseUrl from \"@docusaurus/useBaseUrl\";\nimport styles from \"./styles.module.scss\";\nimport useDocusaurusContext from \"@docusaurus/useDocusaurusContext\";\nimport {Helmet} from \"react-helmet\";\n\nfunction Footer() {\n\n  const {footer} = useThemeConfig();\n  // eslint-disable-next-line no-unused-vars\n  const {sponsors = []} = useDocusaurusContext();\n  const {copyright,} = footer || {};\n\n\n  if (!footer) {\n    return null;\n  }\n\n  const sponsorLogoPath = useBaseUrl(\"img/sponsors/edouard-montpetit-logo.svg\");\n\n  return (\n    <footer\n      className={`footer ${styles.footer}`}>\n      <div className={`container ${styles.container}`}>\n        <div className={styles.sponsor}>\n          <p>This project is supported in part by:</p>\n          <div className={styles.sponsors}>\n            <a href=\"https://www.cegepmontpetit.ca/\" target={\"_blank\"} rel=\"noreferrer\">\n              <img\n                src={sponsorLogoPath}\n                alt=\"Logo cegep Edouard-Montpetit\"\n              />\n            </a>\n          </div>\n        </div>\n        {copyright ? (\n          <div\n            className={`footer__copyright ${styles.copyright}`} // Dev provided HTML, assume safe.\n            // eslint-disable-next-line react/no-danger\n            dangerouslySetInnerHTML={{\n              __html: copyright,\n            }}\n          />\n        ) : null}\n      </div>\n\n      <Helmet>\n        <script id=\"mcjs\" src=\"/js/newsletter-popup.js\" type=\"text/javascript\" />\n      </Helmet>\n\n    </footer>\n  );\n}\nexport default Footer;\n\n"
  },
  {
    "path": "website/src/theme/Footer/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n/*\n.footerLogoLink {\n  opacity: 0.5;\n  transition: opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default);\n}\n\n.footerLogoLink:hover {\n  opacity: 1;\n}\n*/\n.footer .container {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n@media screen and (max-width: 1000px) {\n  .footer .container {\n    flex-direction: column;\n  }\n  .footer .container .copyright {\n    margin-top: var(--spacing-md);\n  }\n}\n.footer .container p {\n  margin: 0 var(--spacing-md) 0 0;\n}\n.footer .container .sponsor {\n  display: flex;\n  align-items: center;\n}\n.footer .container .sponsor .sponsors {\n  display: flex;\n  flex-wrap: wrap;\n}\n.footer .container img {\n  width: 150px;\n  height: 50px;\n  object-fit: fill;\n}\nhtml[data-theme=light] .footer .container img {\n  filter: invert(1);\n}\n\n/*# sourceMappingURL=styles.module.css.map */\n"
  },
  {
    "path": "website/src/theme/Footer/styles.module.scss",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n/*\n.footerLogoLink {\n  opacity: 0.5;\n  transition: opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default);\n}\n\n.footerLogoLink:hover {\n  opacity: 1;\n}\n*/\n\n.footer {\n\n  .container {\n\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n\n    @media screen and (max-width: 1000px){\n      flex-direction: column;\n      .copyright{\n        margin-top: var(--spacing-md);\n      }\n    }\n\n    p{\n      margin: 0 var(--spacing-md) 0 0;\n    }\n\n    .sponsor{\n      display: flex;\n      align-items: center;\n      .sponsors{\n        display: flex;\n        flex-wrap: wrap;\n\n      }\n    }\n\n    img {\n      width: 150px;\n      height: 50px;\n\n      object-fit: fill;\n\n\n      html[data-theme=light] &{\n        filter: invert(1);\n      }\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "website/src/theme/Navbar/index.js",
    "content": "import React from 'react';\nimport Navbar from '@theme-original/Navbar';\n\nexport default function NavbarWrapper(props) {\n  return (\n    <>\n      <Navbar {...props} />\n    </>\n  );\n}\n"
  },
  {
    "path": "website/src/theme/Navbar/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/*\nHide toggle in small viewports\n */\n@media (max-width: 996px) {\n  .toggle {\n    display: none;\n  }\n}\n\n.navbarHideable {\n  transition: transform var(--ifm-transition-fast) ease;\n}\n\n.navbarHidden {\n  transform: translate3d(0, calc(-100% - 2px), 0);\n}\n\n.navbarSidebarToggle {\n  margin-right: 1rem;\n}\n\n.navbarSidebarMobile ul li a{\n  padding: 10px var(--spacing-sm);\n}\n"
  },
  {
    "path": "website/static/.nojekyll",
    "content": ""
  },
  {
    "path": "website/static/js/newsletter-popup.js",
    "content": "// eslint-disable-next-line max-len\n!function(c,h,i,m,p){m=c.createElement(h),p=c.getElementsByTagName(h)[0],m.async=1,m.src=i,p.parentNode.insertBefore(m,p)}(document,\"script\",\"https://chimpstatic.com/mcjs-connected/js/users/4ad018201643381a89d30000c/0e8ce2085fb209fed1b656a74.js\");\n"
  },
  {
    "path": "website/static/styles/default.css",
    "content": "a {\n  text-decoration: none;\n}\na:link {\n  color: #ffd000;\n}\na:visited {\n  color: #ffd000;\n}\na:hover {\n  color: #ffd000;\n  text-decoration: underline;\n}\na:active {\n  color: #ffd000;\n}\n\nbody {\n  font-family: \"Yanone Kaffeesatz\", sans-serif;\n  font-size: 24px;\n  line-height: 1.25;\n  text-align: justify;\n}\n\nheader {\n  margin: 5vh auto;\n  width: 75%;\n  max-width: 500px;\n}\nheader img {\n  max-width: 100%;\n}\n\nmain {\n  margin: 0 auto;\n  width: 75%;\n  max-width: 500px;\n  padding: 0 50px;\n}\n\n/*# sourceMappingURL=default.css.map */\n"
  },
  {
    "path": "website/static/styles/default.scss",
    "content": "// Colors\n$base-color: #ffd000;\n\na {\n\n  text-decoration: none;\n\n  &:link {\n    color: $base-color;\n  }\n\n  &:visited {\n    color: $base-color;\n  }\n\n  &:hover {\n    color: $base-color;\n    text-decoration: underline;\n  }\n\n  &:active {\n    color: $base-color;\n  }\n\n}\n\nbody {\n  font-family: 'Yanone Kaffeesatz', sans-serif;\n  font-size: 24px;\n  line-height: 1.25;\n  text-align: justify;\n}\n\nheader {\n\n  margin: 5vh auto;\n  width: 75%;\n  max-width: 500px;\n\n  img {\n    max-width: 100%;\n  }\n\n}\n\nmain {\n  margin: 0 auto;\n  width: 75%;\n  max-width: 500px;\n  padding: 0 50px;\n}\n"
  },
  {
    "path": "website/static/styles/jsdoc.css",
    "content": ".page-header, pre.code-toolbar > .toolbar:hover {\n  background-color: red;\n}\n"
  }
]