Full Code of cotejp/webmidi for AI

master 9ef427c60f7e cached
176 files
1.8 MB
484.4k tokens
735 symbols
1 requests
Download .txt
Showing preview only (1,869K chars total). Download the full file or copy to clipboard to get everything.
Repository: cotejp/webmidi
Branch: master
Commit: 9ef427c60f7e
Files: 176
Total size: 1.8 MB

Directory structure:
gitextract_ohmewh1n/

├── .babelrc
├── .editorconfig
├── .env.sample
├── .eslintrc.cjs
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── ask-a-question.md
│   │   ├── report-a-bug.md
│   │   └── suggest-a-new-feature.md
│   └── workflows/
│       └── codeql-analysis.yml
├── .gitignore
├── .nycrc
├── BANNER.txt
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── SECURITY.md
├── examples/
│   ├── README.md
│   ├── electron/
│   │   ├── README.md
│   │   └── basic-example/
│   │       ├── README.md
│   │       ├── index.html
│   │       ├── main.js
│   │       ├── package.json
│   │       ├── preload.js
│   │       └── renderer.js
│   ├── next.js/
│   │   ├── README.md
│   │   └── basic-example/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── package.json
│   │       └── pages/
│   │           └── index.js
│   ├── p5.js/
│   │   ├── README.md
│   │   ├── basic-example/
│   │   │   ├── index.html
│   │   │   ├── sketch.js
│   │   │   └── styles.css
│   │   └── querying-note-state/
│   │       ├── index.html
│   │       ├── sketch.js
│   │       └── styles.css
│   ├── quick-start/
│   │   └── index.html
│   ├── react/
│   │   ├── README.md
│   │   └── basic-example/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── package.json
│   │       ├── public/
│   │       │   ├── index.html
│   │       │   ├── manifest.json
│   │       │   └── robots.txt
│   │       └── src/
│   │           ├── App.css
│   │           ├── App.js
│   │           ├── App.test.js
│   │           ├── index.css
│   │           ├── index.js
│   │           ├── reportWebVitals.js
│   │           └── setupTests.js
│   └── typescript/
│       ├── README.md
│       └── basic-nodejs-example/
│           ├── index.ts
│           └── package.json
├── package.json
├── scripts/
│   ├── api-documentation/
│   │   ├── generate-html.js
│   │   ├── generate-markdown.js
│   │   └── templates/
│   │       ├── core/
│   │       │   ├── class.hbs
│   │       │   ├── constructor.hbs
│   │       │   ├── enumerations.hbs
│   │       │   ├── events.hbs
│   │       │   ├── methods.hbs
│   │       │   └── properties.hbs
│   │       └── helpers/
│   │           ├── ddata.js
│   │           ├── djip-helpers.js
│   │           └── state.js
│   ├── library/
│   │   ├── build.js
│   │   ├── rollup.config.cjs.js
│   │   ├── rollup.config.esm.js
│   │   └── rollup.config.iife.js
│   ├── sponsors/
│   │   └── retrieve-sponsors.js
│   ├── typescript-declarations/
│   │   ├── generate.js
│   │   └── generateOLD.js
│   └── website/
│       └── deploy.js
├── src/
│   ├── Enumerations.js
│   ├── Forwarder.js
│   ├── Input.js
│   ├── InputChannel.js
│   ├── Message.js
│   ├── Note.js
│   ├── Output.js
│   ├── OutputChannel.js
│   ├── Utilities.js
│   └── WebMidi.js
├── test/
│   ├── Enumerations.test.js
│   ├── Forwarder.test.js
│   ├── Input.test.js
│   ├── InputChannel.test.js
│   ├── Message.test.js
│   ├── Note.test.js
│   ├── Output.test.js
│   ├── OutputChannel.test.js
│   ├── Utilities.test.js
│   ├── WebMidi.test.js
│   └── support/
│       ├── JZZ.js
│       ├── Utils.cjs.js
│       └── Utils.iife.js
├── typescript/
│   └── webmidi.d.ts
└── website/
    ├── .gitignore
    ├── README.md
    ├── api/
    │   ├── classes/
    │   │   ├── Enumerations.md
    │   │   ├── EventEmitter.md
    │   │   ├── Forwarder.md
    │   │   ├── Input.md
    │   │   ├── InputChannel.md
    │   │   ├── Listener.md
    │   │   ├── Message.md
    │   │   ├── Note.md
    │   │   ├── Output.md
    │   │   ├── OutputChannel.md
    │   │   ├── Utilities.md
    │   │   ├── WebMidi.md
    │   │   └── _category_.json
    │   └── index.md
    ├── babel.config.js
    ├── blog/
    │   └── 2021-12-01/
    │       └── version-3-has-been-released.md
    ├── docs/
    │   ├── archives/
    │   │   ├── _category_.json
    │   │   ├── v1.md
    │   │   └── v2.md
    │   ├── getting-started/
    │   │   ├── _category_.json
    │   │   ├── basics.md
    │   │   ├── installation.md
    │   │   └── supported-environments.md
    │   ├── going-further/
    │   │   ├── _category_.json
    │   │   ├── electron.md
    │   │   ├── forwarding.md
    │   │   ├── middle-c.md
    │   │   ├── performance.md
    │   │   ├── sysex.md
    │   │   └── typescript.md
    │   ├── index.md
    │   ├── migration/
    │   │   ├── _category_.json
    │   │   └── migration.md
    │   └── roadmap/
    │       ├── _category_.json
    │       ├── under-evaluation.md
    │       ├── v3.md
    │       └── v4.md
    ├── docusaurus.config.js
    ├── package.json
    ├── sidebars.js
    ├── src/
    │   ├── components/
    │   │   ├── Button.js
    │   │   ├── Button.module.css
    │   │   ├── Button.module.scss
    │   │   ├── Column.js
    │   │   ├── Column.module.css
    │   │   ├── Column.module.scss
    │   │   ├── HomepageFeatures.js
    │   │   ├── HomepageFeatures.module.css
    │   │   ├── InformationBar.js
    │   │   ├── InformationBar.module.css
    │   │   └── InformationBar.module.scss
    │   ├── css/
    │   │   ├── custom.css
    │   │   ├── custom.scss
    │   │   ├── index.css
    │   │   └── index.scss
    │   ├── pages/
    │   │   ├── about/
    │   │   │   └── index.md
    │   │   ├── index.js
    │   │   ├── index.module.css
    │   │   ├── index.module.scss
    │   │   ├── research/
    │   │   │   └── index.md
    │   │   ├── showcase/
    │   │   │   └── index.md
    │   │   ├── sponsors/
    │   │   │   └── index.md
    │   │   └── tester/
    │   │       └── index.js
    │   └── theme/
    │       ├── CodeBlock/
    │       │   └── index.js
    │       ├── Footer/
    │       │   ├── index.js
    │       │   ├── styles.module.css
    │       │   └── styles.module.scss
    │       └── Navbar/
    │           ├── index.js
    │           └── styles.module.css
    └── static/
        ├── .nojekyll
        ├── js/
        │   └── newsletter-popup.js
        └── styles/
            ├── default.css
            ├── default.scss
            └── jsdoc.css

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": [
    [
      "@babel/preset-env", { "targets": {"node": "current"} }
    ]
  ],
  "env": {
    "test": {
      "plugins": [ "istanbul" ]
    }
  }
}


================================================
FILE: .editorconfig
================================================
# EditorConfig helps maintain consistent coding styles for multiple developers working on the same
# project across various editors and IDEs: https://editorconfig.org

root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
max_line_length = 100
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[COMMIT_EDITMSG]
max_line_length = 0


================================================
FILE: .env.sample
================================================
# This is an example file. To actually use it, you need to rename it ".env". The .env file is used
# by the dotenv module to set environment variables needed during development. The .env file MUST
# NEVER BE committed.

# Token used by the `release-it` module to create automatic GitHub releases.
GITHUB_TOKEN=XXXXX


================================================
FILE: .eslintrc.cjs
================================================
module.exports = {

  "env": {
    "amd": true,
    "browser": true,
    "mocha": true,
    "node": true,
    "es6": true
  },

  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },

  "globals": {
    "Promise": "readonly",
    "WebMidi": "readonly",
    "chai": "readonly",
    "sinon": "readonly",
    "expect": "readonly",
    "Note": "readonly",
    "isNative": "readonly",
    "config": "readonly"
  },

  "extends": [
    "eslint:recommended",
    "prettier",
    "plugin:react/recommended"
  ],

  // The idea here is to stick to the rules defined by Prettier (https://prettier.io/) and only make
  // exceptions in ESLint when absolutely necessary.
  "rules": {

    // Rules to align ESLint with Prettier (even though we are already using eslint-config-prettier)
    "indent": ["error", 2],
    "semi": ["error", "always"],
    "quote-props": ["error", "as-needed"],
    "quotes": ["error", "double", {"avoidEscape":  true, "allowTemplateLiterals": true}],

    // Rules that knowingly change the default Prettier behaviour
    "no-multi-spaces": ["error", { "ignoreEOLComments": true }],
    "linebreak-style": ["error", "unix"], // Force \n instead of Prettier's auto-detect behaviour
    "no-trailing-spaces": ["error", { "skipBlankLines": true, "ignoreComments": true }],
    "max-len": ["error", { "code": 100, "comments": 150 }], // Prettier's 80 is too small. Period.
    "no-console": ["error", { "allow": ["info", "warn", "error"] }], // Only some (unlike Prettier)

    // Other rules
    "no-prototype-builtins": "off",

    "react/prop-types": "off"

  },

  "settings": {
    "react": {
      "version": "detect"
    }
  }

};


================================================
FILE: .github/FUNDING.yml
================================================
# Up to 4 GitHub sponsors-enabled usernames e.g. [user1, user2]
github: [djipco]

# Single Patreon username
#patreon:

# Replace with a single Open Collective username
#open_collective:

# Replace with a single Ko-fi username
#ko_fi:

# Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#tidelift:

# Replace with a single Community Bridge project-name e.g., cloud-foundry
#community_bridge:

# Replace with a single Liberapay username
#liberapay:

# Replace with a single IssueHunt username
#issuehunt:

# Replace with a single Otechie username
#otechie:

# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
#custom:


================================================
FILE: .github/ISSUE_TEMPLATE/ask-a-question.md
================================================
---
name: Ask a question
about: This is to ask a usage, support or general question
title: ''
labels: ''
assignees: ''

---

**Questions should be submitted to the Forum**

All questions should be submitted to the **Questions & Support** section of the WebMidi.js Forum: 

https://webmidijs.org/forum/

This opens up the question to the community while reserving GitHub strictly for bugs and issues.

Thank you.


================================================
FILE: .github/ISSUE_TEMPLATE/report-a-bug.md
================================================
---
name: Report a bug
about: This is only to report a bug, issue or problem.
title: ''
labels: ''
assignees: ''

---

**Description**
Describe the problem and how to reproduce it. If appropriate, include code samples, screenshots, error messages, etc. 

**Environment:**
Specify the environment where you are witnessing the problem:
  - Library version and flavour (CJS, ESM or IIFE)
  - Runtime (browser or Node.js) and version
  - Language (JavaScript or TypeScript)
  - Operating system

**Details**
Add any other information, context or details that could help track down the problem.


================================================
FILE: .github/ISSUE_TEMPLATE/suggest-a-new-feature.md
================================================
---
name: Suggest a new feature
about: This is to suggest a new feature or improvement
title: ''
labels: ''
assignees: ''

---

**Enhancement proposals should be submitted in the Forum**

To submit a new feature or improvement request, please post it to the **Enhancement Proposals** section of the WebMidi.js Forum: 

https://webmidijs.org/forum/

This allows the feature request to be discussed with the community while reserving GitHub strictly for bugs and issues.

Thank you.


================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
  push:
    branches: [ master ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ master ]
  schedule:
    - cron: '32 5 * * 4'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'javascript' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://git.io/codeql-language-support

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v1
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        # queries: ./path/to/local/query, your-org/your-repo/queries@main

    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v1

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 https://git.io/JvXDl

    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
    #    and modify them (or add more) to build your code if your project
    #    uses a compiled language

    #- run: |
    #   make bootstrap
    #   make release

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v1


================================================
FILE: .gitignore
================================================
### System #########################################################################################
.DS_Store*
Icon?
._*
Thumbs.db
ehthumbs.db
Desktop.ini
.directory
*~
*.tgz

### Editors ########################################################################################
.idea

.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

*.sublime-project
*.sublime-workspace

### Dependency Directories #########################################################################
node_modules

### Logs ###########################################################################################
*.log

### Circle CI ######################################################################################
.circleci

### Test coverage ##################################################################################
.nyc_output
coverage

### Passwords, tokens and such #####################################################################
.credentials
.env*
!.env.sample

### Production build ###############################################################################
dist


================================================
FILE: .nycrc
================================================
{
  "report-dir": "./node_modules/nyc/.nyc_output",
  "temp-dir": "./node_modules/nyc/.coverage"
}


================================================
FILE: BANNER.txt
================================================
<%= pkg.webmidi.name %> v<%= pkg.version %>
<%= pkg.webmidi.tagline %>
<%= pkg.homepage %>
Build generated on <%= moment().format('MMMM Do, YYYY') %>.

© Copyright 2015-<%= moment().format('YYYY') %>, Jean-Philippe Côté.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.


================================================
FILE: CHANGELOG.md
================================================
# Changelog

Starting with version 3.x, all notable changes to WebMidi.js will be documented in this file. The 
format used is the one suggested by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [3.0.0]

### Added

- WebMidi.js now has builtin **Node.js** support thanks to the [jzz](https://www.npmjs.com/package/jzz)
node module by Jazz-Soft.

- WebMidi.js is now available in IIFE (Immediately Invoked Function Expression), CJS (CommonJS) and 
ESM (ECMAScript module) flavours (all with normal and minifed versions with sourcemaps).

- WebMidi.js now publishes a **TypeScript** definition file for CJS and ESM with all releases.

- WebMidi.js now explicitly checks for Jazz-Plugin support in environments with no native support 
for the Web MIDI API.

- The `WebMidi.enable()` method now returns a promise. The promise is fulfilled with the `WebMidi`
object. It still supports using a callback.

- `Input` and `Output` objects now emit `opened`, `closed` and `disconnected` events.

- Hundreds of unit tests have been added and test coverage is now available.

- There are new `InputChannel` and `OutputChannel` objects. They are used to communication with a 
  single channel of an input or output.

- All emitted events now have a `target` property referencing the object that triggered the event.

- The `sendNoteOn()` method has been added. It behaves the same way as `playNote()` does except it 
does not accept a `duration` option. It was added mostly for completeness' sake.

- The `sendNoteOff()` method has been added. It behaves the same way as `stopNote()` does. 
Actually, `stopNote()` is an alias to `sendNoteOff()`.

- All methods of the `Output` object that communication with a device are prefixed with "send". This 
  makes it easier to find the right method. Old method names are still usable but are deprecated.

- The `sendChannelAftertouch()`, `sendKeyAftertouch()` and `sendPitchbend()` method now have a 
`useRawValue` options allowing the assignment of value using an integer between 0 and 127 instead of
a float between 0 and 1.

- There is a new `Note` object that can be used in various places such as when calling `playNote()`
or `stopNote()`. It carries with it the note number, the duration (if any), the attack and release
information, etc.

- A `WebMidi.validation` property (defaults to `true`) can be used to disable all argument checking
and legacy support throughout the library (for performance). This property can also be in the 
options of `WebMidi.enable()`.

- The `send()` and `sendSysex()` methods of `Output` and `OutputChannel` can now officially use 
`Uint8Array` input (not supported on Node.js however).

- Licence has been changed to Apache 2.0

- An `octaveOffset` property has been added to `Input`, `InputChannel`, `Output` and 
`OutputChannel`. This means you can offset the octave globally, at the input/output level or at the
 channel level

- A `Message` object has been added. This allows easier routing of messages.

- Added support for RPN messages and improved NRPN parsing.

- A two-position array can now be passed to `sendControlChange()` to specify both MSB and LSB at 
once.

- The `InputChannel` object offers a `getNoteState()` method that reports if a note is currently 
playing or not. It also has a new `notesState` property which is an array holding the playing status
of all notes (0-127).

- It is now possible to add a forwarder to an `Input` that will forward MIDI messages to a specified
output. Also, the inbound messages can be filtered for forwarding by message type and channel. A new
`Forwarder` class has been added for that purpose.

- Added `WebMidi.version`

- The `WedMidi` object now has a `defaults` property where you can set system-wide defaults such as
the default `attack` and `release` velocity. More defaults to come!

### Changed

- [BREAKING CHANGE] Passing `undefined` as the `channel` value to `addListener()` no longer means
that all channels should be listening. This was a terrible design decision and it ends with version 
3.

- Documentation is now generated with [jsdoc](https://www.npmjs.com/package/jsdoc) instead of the 
outdated [yuidoc](https://www.npmjs.com/package/grunt-contrib-yuidoc).

- [BREAKING CHANGE] The `"controlchange"` event's `value` property is now a float between 0 and 1. 
Its `rawValue` property now contains the 7bit integer value (between 0 and 127).

- [BREAKING CHANGE] The `"nrpn"` event's `value` property is now a float between 0 and 1. 
Its `rawValue` property now contains the 16bit integer value (between 0 and 65535).

- Grunt has been replaced with NPM scripts for all build purposes.

- [BREAKING CHANGE] The `nrpnEventsEnabled` property has been moved from the `Input` class to the 
`InputChannel` class. Trying to access it will trigger a warning in the console.

- [BREAKING CHANGE] The `getCcNameByNumber()` method has been moved from the `Input` class to the 
`InputChannel` class and now returns `undefined` instead of `false` when no matching name is found.

- [BREAKING CHANGE] The `"tuningrequest"` event has been renamed `"tunerequest"`. 

- The event received by listeners registered on `Input` and `InputChannel` objects has been slightly 
changed. Its `data` property now contains a regular array (instead of a `Uint8Array`). Its `rawData` 
property now contains the `Uint8Array`.

- Several methods have been moved from the `WebMidi` object to the `Utilities` object. Using the old
methods will continue to work but will trigger a deprecation warning in the console.

- The `send()` method now accepts a `Message` object.

- If a device is disconnected and connected back, it will retain its state (such as listeners, 
etc.). This is particularly useful when the computer goes to sleep and is brought back online.

- Several conversion methods have been added to the new `Utilities` class such as 
`from7bitToFloat()`, `fromFloatTo7Bit()`, `fromMsbLsbToFloat()`, `fromFloatToMsbLsb()`, etc.

- All enumerations have been move to the `Enumerations` object (e.g. `MIDI_CHANNEL_MESSAGES`, 
`MIDI_CHANNEL_NUMBERS`, etc.)

### Deprecated

- The `velocity` option parameter has been renamed `attack`. There are new `rawAttack` and 
`rawRelease` parameters that should be used instead of setting `rawVelocity` to `true`.

- The `WebMidi.noteNameToNumber()` method was renamed and moved to `Utilities.toNoteNumber()`. The 
old method has been deprecated but will continue to work in v3.x.

- The `WebMidi.toMIDIChannels()` method was renamed and moved to `Utilities.sanitizeChannels()`. The
old method has been deprecated but will continue to work in v3.x.

- The name of the `Output.sendTuningRequest()` method was changed to `Output.sendTuneRequest()`. The
old name has been deprecated but will continue to work in v3.x.

- The `on()` method of the `Input` class has been deprecated. Use `addListener()` instead.

- The `InputChannel.nrpnEventsEnabled` property has been renamed to
`InputChannel.parameterNumberEventsEnabled`. The old property is deprecated but will be kept for 
backwards compatibility.

### Removed

- Support for Bower.


## [2.5.1] - 2019-08-25

Versions 2.5.x and earlier have not been tracked in this changelog.


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
info@webmidijs.org.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior,  harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

First off, **thank you** for considering to contribute to this project. You should know that there 
are many ways to contribute. You can write tutorials or blog posts, improve the documentation, 
submit bug reports or feature requests and write actual source code. All of these are very 
worthwhile contributions.

Following these guidelines helps to communicate that you respect the time of the developers managing 
this open source project. In return, they will reciprocate that respect in addressing your issue, 
assessing changes, and helping you finalize your pull requests.

## Submitting a Feature Requests

If you find yourself wishing for a feature, you are probably not alone. There are bound to be others 
out there with similar needs. Before submitting a feature request, first check if the 
[wiki](https://github.com/djipco/webmidi/wiki)'s enhancements section already lists that feature. 

If not, open an [issue](https://github.com/djipco/webmidi/issues) which describes the feature you 
would like to see, why you need it, and how it should work.

## Understanding How to Contribute

Contribution to this project is done via pull requests. This allows the owner and contributors to
properly review what gets merged into the project. 

**If this is your first pull request**, you can learn how to get started from a free series of 
tutorials called 
[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).

## Submitting a Pull Request

**WebMidi.js** is a relatively small library supported by an even smaller team. Therefore, the 
process for contributing is intended to be simple and friendly. 

However, to insure good quality, there are steps that you should go through when submitting a PR. 
Here are the usual steps:

1. Discuss the change(s) you wish to make by means of an 
[issue](https://github.com/djipco/webmidi/issues).
2. Unless the PR is for a minor improvement (typo, documentation, etc.), you should write and/or 
update unit tests and check your code against the tests (see below).
3. If appropriate, update the [jsdoc](http://usejsdoc.org/) comments. Keeping the documentation and
the API consistant is very important.
4. If appropriate, update the `README.md` file.

Please note that your code should adhere to the styles defined in `.eslintrc.js`. You can use 
`npm run lint` to make sure it does.

Finally, **do not** update the library's version number. Version numbering and releases will be 
handled by the owner. If the PR breaks backwards-compatibility, it must be communicated explicitely
to the owner. The versioning scheme follows the [SemVer](http://semver.org/) standard.

## Testing

WebMidi.js now has a proper test suite. The tests can be run on the command line without a need for
a browser (thanks to Tim Susa).

You can execute all tests, including code coverage, by running the following command in the 
terminal or on the command line:

``` 
npm run test-all
``` 

You can develop in *watch mode* with hot file reloading like so: 
``` 
npm run test -- -w
``` 

You can start a single test in this way:
``` 
npx mocha ./test/virtual-midi-test.js
``` 

You can develop a single test in *watch mode* like this:
``` 
npx mocha ./test/virtual-midi-test.js -- -w
``` 

If you simply want to view code coverage, you can do:
``` 
npm run test-coverage
``` 


================================================
FILE: LICENSE.txt
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.


================================================
FILE: README.md
================================================
![WebMidi.js Logo](https://webmidijs.org/img/webmidijs-logo-color-on-white.svg "WebMidi.js")

[![](https://data.jsdelivr.com/v1/package/npm/webmidi/badge)](https://www.jsdelivr.com/package/npm/webmidi)
[![npm](https://img.shields.io/npm/dm/webmidi)](https://www.npmjs.com/package/webmidi)
[![npm](https://img.shields.io/npm/dt/webmidi)](https://www.npmjs.com/package/webmidi)
[![](https://img.shields.io/github/stars/djipco/webmidi?style=social)](https://github.com/djipco/webmidi)
[![npm](https://img.shields.io/npm/l/webmidi)](https://www.npmjs.com/package/webmidi)

## Introduction

**WEBMIDI.js** makes it easy to interact with MIDI instruments directly from a web browser or from 
Node.js. It simplifies the control of physical or virtual MIDI instruments with user-friendly 
functions such as `playNote()`, `sendPitchBend()` or `sendControlChange()`. It also allows reacting 
to inbound MIDI messages by adding listeners for events such as `"noteon"`, `"pitchbend"` or 
`"programchange"`.

In short, the goal behind WEBMIDI.js is to get you started with your web-based MIDI project as quickly
and efficiently as possible.

## Getting Started

The [**official website**](https://webmidijs.org) site is the best place to get started. Over there,
you will find, amongst others, two key resources:

* [Documentation](https://webmidijs.org/docs/)
* [API Reference](https://webmidijs.org/api/)

To exchange with fellow users and myself, you can visit our [**Forum**](https://github.com/djipco/webmidi/discussions)
which is hosted on the GitHub Discussions platform: 

* [Forum](https://github.com/djipco/webmidi/discussions)

If you want to stay up-to-date, here are your best sources:

* [Newsletter](https://mailchi.mp/eeffe50651bd/webmidijs-newsletter)
* [Twitter](https://twitter.com/webmidijs)

## Sponsors

WEBMIDI.js is a passion project but it still takes quite a bit of time, effort and money to develop and 
maintain. That's why I would like to sincerely thank 👏 these sponsors for their support: 

[<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">

If you use the library and find it useful, please 💜 [**sponsor the project**](https://github.com/sponsors/djipco).

## Feature Request

If you would like to request a new feature, enhancement or API change, please first check that it is 
not [already planned](https://webmidijs.org/docs/future-versions/next). Then, discuss it in the 
[Enhancement Proposals](https://github.com/djipco/webmidi/discussions/categories/feature-requests) 
section of the forum.

## Citing this Software in Research

If you use this software for research or academic purposes, please cite the project in your 
references (or wherever appropriate). Here's an example of how to cite it 
([APA Style](https://apastyle.apa.org/)):

>Côté, J. P. (2021). WebMidi.js v3.0.0 [Computer Software]. Retrieved from 
https://github.com/djipco/webmidi

Cheers!

-- Jean-Philippe


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions


| Version       | Support            | Notes                                                         |
| ------------- | ------------------ | ------------------------------------------------------------- |
| 3.0.x (alpha) | :white_check_mark: | This is the current, officially-supported version.            |
| 2.5.x         | :white_check_mark: | Bug and security fixes only.                                  |
| 1.0.x         | :x:                | This version has reached end of life.                         |

## Reporting a Vulnerability

To report a security-related problem, **you should not file an issue on GitHub** as this might put 
users at risk. **Instead, send an email to jp@cote.cc**.


================================================
FILE: examples/README.md
================================================
# WEBMIDI.js Usage Examples

In this directory, you will find starter examples to help you use WEBMIDI.js in various contexts or 
for various purposes. 

## Browser Quick Start Example

  * [Quick Start](quick-start/)

## Frameworks

  * [Next.js](next.js/)
  * [p5.js](p5.js/)
  * [React](react/)

## Environments

  * [Electron](electron/)

## Languages

  * [TypeScript](typescript/)

## More Examples wanted!

To submit a new example, simply send a pull request and it will be quickly reviewed. Please create
a README.md file to provide information regarding how the example should be used.


================================================
FILE: examples/electron/README.md
================================================
# Using WEBMIDI.js with Electron

A collection of examples to use WEBMIDI.js inside an Electron application.

## Examples

* [**Basic Electron Example**](basic-example)

## Platform specific notes

The permission requests must be properly handled in the main process before initializing WEBMIDI:

```javascript
  mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => {
    if (permission === 'midi' || permission === 'midiSysex') {
      callback(true);
    } else {
      callback(false);
    }
  })

  mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
    if (permission === 'midi' || permission === 'midiSysex') {
      return true;
    }

    return false;
  });
```


================================================
FILE: examples/electron/basic-example/README.md
================================================
# Starter Template for Electron

This is a minimal Electron application based on the [Quick Start Guide](https://electronjs.org/docs/latest/tutorial/quick-start) within the Electron documentation.

## To Use

```bash
# Install dependencies
npm install

# Run the app
npm start
```


================================================
FILE: examples/electron/basic-example/index.html
================================================
<!DOCTYPE html>

<html>

  <head>

    <meta charset="UTF-8">
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
    >

    <title>WEBMIDI.js + Electron Demo</title>

  </head>

  <body>

    <h1>WEBMIDI.js + Electron Demo</h1>

    <p>
      Node.js <span id="node-version"></span>,
      Chromium <span id="chrome-version"></span>,
      Electron <span id="electron-version"></span>
    </p>

    <p>WEBMIDI.js <span id="webmidi-version"></span></p>
    <p>MIDI Inputs: <span id="webmidi-inputs"></span></p>
    <p>MIDI Outputs: <span id="webmidi-outputs"></span></p>

    <!-- You can also require other files to run in this process -->
    <script src="./renderer.js" type="module"></script>

  </body>

</html>


================================================
FILE: examples/electron/basic-example/main.js
================================================
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require("electron");
const path = require("path");

function createWindow () {

  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js")
    }
  });

  // Respond affirmatively to requests for MIDI and MIDI SysEx permissions
  mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => {
    if (permission === 'midi' || permission === 'midiSysex') {
      callback(true);
    } else {
      callback(false);
    }
  })

  // Respond affirmatively to checks for MIDI and MIDI SysEx permissions
  mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
    if (permission === 'midi' || permission === 'midiSysex') {
      return true;
    }

    return false;
  });

  // and load the index.html of the app.
  mainWindow.loadFile("index.html");

  // Open dev tools
  // mainWindow.webContents.openDevTools();

}

// This method will be called when Electron has finished initialization and is ready to create
// browser windows. Some APIs can only be used after this event occurs.
app.whenReady().then(() => {

  createWindow();

  app.on("activate", function () {
    // On macOS it's common to re-create a window in the app when the dock icon is clicked and there
    // are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });

});

// Quit when all windows are closed, except on macOS. There, it's common for applications and their
// menu bar to stay active until the user quits explicitly with Cmd + Q.
app.on("window-all-closed", function () {
  if (process.platform !== "darwin") app.quit();
});

// In this file you can include the rest of your app's specific main process code. You can also put
// them in separate files and require them here.


================================================
FILE: examples/electron/basic-example/package.json
================================================
{
  "name": "webmidijs-electron-demo",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "license": "CC0-1.0",
  "devDependencies": {
    "electron": "^39.8.5"
  },
  "dependencies": {
    "webmidi": "latest"
  }
}


================================================
FILE: examples/electron/basic-example/preload.js
================================================
// All of the Node.js APIs are available in the preload process. It has the same sandbox as a Chrome
// extension.
window.addEventListener("DOMContentLoaded", () => {

  const replaceText = (selector, text) => {
    const element = document.getElementById(selector);
    if (element) element.innerText = text;
  };

  for (const type of ["chrome", "node", "electron"]) {
    replaceText(`${type}-version`, process.versions[type]);
  }

});


================================================
FILE: examples/electron/basic-example/renderer.js
================================================
// This file is required by the index.html file and will be executed in the renderer process for
// that window. No Node.js APIs are available in this process because `nodeIntegration` is turned
// off. Use `preload.js` to selectively enable features needed in the rendering process.

import {WebMidi} from "./node_modules/webmidi/dist/esm/webmidi.esm.js";

WebMidi.enable()
  .then(onEnabled)
  .catch(err => console.warning(err));

function onEnabled() {

  document.getElementById("webmidi-version").innerHTML += `v${WebMidi.version}`;

  WebMidi.inputs.forEach(input => {
    document.getElementById("webmidi-inputs").innerHTML += `${input.name}, `;
  });

  WebMidi.outputs.forEach(output => {
    document.getElementById("webmidi-outputs").innerHTML += `${output.name}, `;
  });

}


================================================
FILE: examples/next.js/README.md
================================================
# Using WEBMIDI.js with Next.js

A collection of examples to use WEBMIDI.js with the Next.js framework.

## Examples

* [**Basic Next.js Example**](basic-example)


================================================
FILE: examples/next.js/basic-example/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local


================================================
FILE: examples/next.js/basic-example/README.md
================================================


# Starter Template for Next.js

This is a starter template for [Learn Next.js](https://nextjs.org/learn).

## To Use

```bash
# Install dependencies
npm install

# Run the app
npm run dev
```


================================================
FILE: examples/next.js/basic-example/package.json
================================================
{
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "^15.5.15",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "webmidi": "latest"
  }
}


================================================
FILE: examples/next.js/basic-example/pages/index.js
================================================
import Head from "next/head";

import {WebMidi} from "webmidi";
// const {WebMidi} = require("webmidi");

WebMidi.enable()
  .then(() => {

    WebMidi.inputs.forEach(input => {
      console.info("Detected input: ", input.name);
      input.addListener("noteon", e => console.info(e.type, e.note.identifier));
      input.addListener("noteoff", e => console.info(e.type, e.note.identifier));
    });

  })
  .catch(err => console.error(err));

export default function Home() {
  return (
    <div className="container">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1 className="title">
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>

        <p className="description">
          Get started by editing <code>pages/index.js</code>
        </p>

        <div className="grid">
          <a href="https://nextjs.org/docs" className="card">
            <h3>Documentation &rarr;</h3>
            <p>Find in-depth information about Next.js features and API.</p>
          </a>

          <a href="https://nextjs.org/learn" className="card">
            <h3>Learn &rarr;</h3>
            <p>Learn about Next.js in an interactive course with quizzes!</p>
          </a>

          <a
            href="https://github.com/vercel/next.js/tree/master/examples"
            className="card"
          >
            <h3>Examples &rarr;</h3>
            <p>Discover and deploy boilerplate example Next.js projects.</p>
          </a>

          <a
            href="https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
            className="card"
          >
            <h3>Deploy &rarr;</h3>
            <p>
              Instantly deploy your Next.js site to a public URL with Vercel.
            </p>
          </a>
        </div>
      </main>

      <footer>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <img src="/vercel.svg" alt="Vercel" className="logo" />
        </a>
      </footer>

      <style jsx>{`
        .container {
          min-height: 100vh;
          padding: 0 0.5rem;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
        }

        main {
          padding: 5rem 0;
          flex: 1;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
        }

        footer {
          width: 100%;
          height: 100px;
          border-top: 1px solid #eaeaea;
          display: flex;
          justify-content: center;
          align-items: center;
        }

        footer img {
          margin-left: 0.5rem;
        }

        footer a {
          display: flex;
          justify-content: center;
          align-items: center;
        }

        a {
          color: inherit;
          text-decoration: none;
        }

        .title a {
          color: #0070f3;
          text-decoration: none;
        }

        .title a:hover,
        .title a:focus,
        .title a:active {
          text-decoration: underline;
        }

        .title {
          margin: 0;
          line-height: 1.15;
          font-size: 4rem;
        }

        .title,
        .description {
          text-align: center;
        }

        .description {
          line-height: 1.5;
          font-size: 1.5rem;
        }

        code {
          background: #fafafa;
          border-radius: 5px;
          padding: 0.75rem;
          font-size: 1.1rem;
          font-family: Menlo, Monaco, Lucida Console, Liberation Mono,
            DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
        }

        .grid {
          display: flex;
          align-items: center;
          justify-content: center;
          flex-wrap: wrap;

          max-width: 800px;
          margin-top: 3rem;
        }

        .card {
          margin: 1rem;
          flex-basis: 45%;
          padding: 1.5rem;
          text-align: left;
          color: inherit;
          text-decoration: none;
          border: 1px solid #eaeaea;
          border-radius: 10px;
          transition: color 0.15s ease, border-color 0.15s ease;
        }

        .card:hover,
        .card:focus,
        .card:active {
          color: #0070f3;
          border-color: #0070f3;
        }

        .card h3 {
          margin: 0 0 1rem 0;
          font-size: 1.5rem;
        }

        .card p {
          margin: 0;
          font-size: 1.25rem;
          line-height: 1.5;
        }

        .logo {
          height: 1em;
        }

        @media (max-width: 600px) {
          .grid {
            width: 100%;
            flex-direction: column;
          }
        }
      `}</style>

      <style jsx global>{`
        html,
        body {
          padding: 0;
          margin: 0;
          font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
            Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
            sans-serif;
        }

        * {
          box-sizing: border-box;
        }
      `}</style>
    </div>
  )
}


================================================
FILE: examples/p5.js/README.md
================================================
# Using WEBMIDI.js with p5.js

A collection of examples to use WEBMIDI.js inside the p5.js framework.

## Examples

* [**Basic Example**](basic-example): drawing circles on canvas when **note on** MIDI event is 
received.

* [**Querying note state**](querying-note-state): draw keyboard keys in color when MIDI keys
presssed


================================================
FILE: examples/p5.js/basic-example/index.html
================================================
<!DOCTYPE html>

<html lang="en">

  <head>
    <meta charset="utf-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js"></script>
    <link rel="stylesheet" href="styles.css">
    <title>WebMidi.js + p5.js - Basic Example</title>
  </head>

  <body>
    <h1>WebMidi.js + p5.js - Basic Example</h1>
    <p>Randomly draw circles when a <strong>note on</strong> event is received on MIDI channel 1</p>
    <script src="sketch.js"></script>
  </body>

</html>


================================================
FILE: examples/p5.js/basic-example/sketch.js
================================================
function setup() {

  createCanvas(window.innerWidth, window.innerHeight);
  noStroke();

  // Enable WebMidi.js and trigger the onWebMidiEnabled() function when ready.
  WebMidi.enable()
    .then(onWebMidiEnabled)
    .catch(err => alert(err));

}

function draw() {

}

function onWebMidiEnabled() {

  // Check if at least one MIDI input is detected. If not, display warning and quit.
  if (WebMidi.inputs.length < 1) {
    alert("No MIDI inputs detected.");
    return;
  }

  // Add a listener on all the MIDI inputs that are detected
  WebMidi.inputs.forEach(input => {

    // When a "note on" is received on MIDI channel 1, generate a random color start
    input.channels[1].addListener("noteon", function() {
      fill(random(255), random(255), random(255));
      circle(random(width), random(height), 100);
    });

  });

}


================================================
FILE: examples/p5.js/basic-example/styles.css
================================================
html, body {
  width: 100vw;
  height: 100vh;
  margin: 0;
}

main {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}

h1, p {
  margin: 0;
  padding: 16px 16px 0 16px;
}



================================================
FILE: examples/p5.js/querying-note-state/index.html
================================================
<!DOCTYPE html>

<html lang="en">

  <head>
    <meta charset="utf-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js"></script>
    <link rel="stylesheet" href="styles.css">
    <title>WebMidi.js + p5.js - Querying Note State</title>
  </head>

  <body>
    <h1>WebMidi.js + p5.js - Querying Note State</h1>
    <p>Draw colored rectangles when specific notes are pressed.</p>
    <script src="sketch.js"></script>
  </body>

</html>


================================================
FILE: examples/p5.js/querying-note-state/sketch.js
================================================
let channel;

async function setup() {

  // Enable WebMidi.js
  await WebMidi.enable();

  // Display available inputs in console (use the name to retrieve it)
  console.log(WebMidi.inputs);
  const input = WebMidi.getInputByName("MPK mini 3");
  channel = input.channels[1];

  // Create canvas
  createCanvas(500, 200);

}

function draw() {

  // Check if WebMidi is enabled and channel has been assigned before moving on
  if (!channel) return;

  // Draw blank keys
  for (let i = 0; i < 8; i++) {

    // Default fill is white
    fill("white");

    // Each key has its own color. When it's pressed, we draw it in color (instead of white)
    if (i === 0 && channel.getNoteState("C4")) {
      fill("yellow");
    } else if (i === 1 && channel.getNoteState("D4")) {
      fill("red");
    } else if (i === 2 && channel.getNoteState("E4")) {
      fill("pink");
    } else if (i === 3 && channel.getNoteState("F4")) {
      fill("orange");
    } else if (i === 4 && channel.getNoteState("G4")) {
      fill("purple");
    } else if (i === 5 && channel.getNoteState("A4")) {
      fill("green");
    } else if (i === 6 && channel.getNoteState("B4")) {
      fill("turquoise");
    } else if (i === 7 && channel.getNoteState("C5")) {
      fill("blue");
    }

    // Draw the keys
    rect(85 + i * 40, 50, 30, 100);

  }

}


================================================
FILE: examples/p5.js/querying-note-state/styles.css
================================================
html, body {
  width: 100vw;
  height: 100vh;
  margin: 0;
}

h1, p {
  margin: 0;
  padding: 16px 16px 0 16px;
}



================================================
FILE: examples/quick-start/index.html
================================================
<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="UTF-8">
    <title>WebMidi.js Quick Start</title>

    <script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js"></script>

    <script type="module">

      // Enable WebMidi.js and trigger the onEnabled() function when ready.
      WebMidi
        .enable()
        .then(onEnabled)
        .catch(err => alert(err));

      function onEnabled() {

        if (WebMidi.inputs.length < 1) {
          document.body.innerHTML+= "No device detected.";
        } else {
          WebMidi.inputs.forEach((device, index) => {
            document.body.innerHTML+= `${index}: ${device.name} <br>`;
          });
        }

        const mySynth = WebMidi.inputs[0];
        // const mySynth = WebMidi.getInputByName("TYPE NAME HERE!")

        mySynth.channels[1].addListener("noteon", e => {
          document.body.innerHTML+= `${e.note.name} <br>`;
        });

      }

    </script>

  </head>

  <body>
    <h1>WebMidi.js Quick Start</h1>
  </body>

</html>


================================================
FILE: examples/react/README.md
================================================
# Using WEBMIDI.js with p5.js

A collection of examples to use WEBMIDI.js inside the React framework.

## Examples

* [**Basic Example**](basic-example)


================================================
FILE: examples/react/basic-example/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*


================================================
FILE: examples/react/basic-example/README.md
================================================
# Getting Started with Create React App

This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).

## Available Scripts

In the project directory, you can run:

### `yarn start`

Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.

The page will reload if you make edits.\
You will also see any lint errors in the console.

### `yarn test`

Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.

### `yarn build`

Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.

The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!

See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.

### `yarn eject`

**Note: this is a one-way operation. Once you `eject`, you can’t go back!**

If 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.

Instead, 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.

You 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.

## Learn More

You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).

To learn React, check out the [React documentation](https://reactjs.org/).

### Code Splitting

This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)

### Analyzing the Bundle Size

This 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)

### Making a Progressive Web App

This 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)

### Advanced Configuration

This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)

### Deployment

This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)

### `yarn build` fails to minify

This 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)


================================================
FILE: examples/react/basic-example/package.json
================================================
{
  "name": "basic-example",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.1",
    "@testing-library/react": "^12.1.2",
    "@testing-library/user-event": "^13.5.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "^5.0.0",
    "web-vitals": "^2.1.2",
    "webmidi": "latest"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}


================================================
FILE: examples/react/basic-example/public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>


================================================
FILE: examples/react/basic-example/public/manifest.json
================================================
{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}


================================================
FILE: examples/react/basic-example/public/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:


================================================
FILE: examples/react/basic-example/src/App.css
================================================
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}


================================================
FILE: examples/react/basic-example/src/App.js
================================================
import logo from './logo.svg';
import './App.css';
import {WebMidi} from "webmidi";

function App() {

  WebMidi.enable()
    .then(() => console.log(WebMidi.inputs))
    .catch(err => console.log(err));

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;


================================================
FILE: examples/react/basic-example/src/App.test.js
================================================
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});


================================================
FILE: examples/react/basic-example/src/index.css
================================================
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}


================================================
FILE: examples/react/basic-example/src/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();


================================================
FILE: examples/react/basic-example/src/reportWebVitals.js
================================================
const reportWebVitals = onPerfEntry => {
  if (onPerfEntry && onPerfEntry instanceof Function) {
    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(onPerfEntry);
      getFID(onPerfEntry);
      getFCP(onPerfEntry);
      getLCP(onPerfEntry);
      getTTFB(onPerfEntry);
    });
  }
};

export default reportWebVitals;


================================================
FILE: examples/react/basic-example/src/setupTests.js
================================================
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';


================================================
FILE: examples/typescript/README.md
================================================
# Using WEBMIDI.js in a TypeScript project

Version 3 of WEBMIDI.js officially supports TypeScript. Type declarations are available for the ESM
(ECMAScript modules) and CJS (CommonJS, a.k.a. Node.js modules) flavours in the `/dist` directory.

To use the example, `cd` into the example directory, install the necessary modules  with 
`npm install` and write some TypeScript code. You can generate the final JavaScript code with
`tsc myFile.ts`. This should yield a `myFile.js` which you can run with `node myFile.js`.

## Examples

* [**Basic Example**](basic-nodejs-example): listing input and output ports and reacting to 
  pressed keyboard keys.


================================================
FILE: examples/typescript/basic-nodejs-example/index.ts
================================================
import {WebMidi} from "webmidi";

async function start() {

  await WebMidi.enable();

  // List available inputs
  console.log("Available inputs: ");

  WebMidi.inputs.forEach(input => {
    console.log("\t" + input.name);
    input.addListener("noteon", e => console.log(e.note.identifier));
  });

  // List available outputs
  console.log("Available outputs: ");

  WebMidi.outputs.forEach(output => {
    console.log("\t" + output.name);
  });

}

start();


================================================
FILE: examples/typescript/basic-nodejs-example/package.json
================================================
{
  "name": "typescript-basic-nidejs-example",
  "version": "1.0.0",
  "main": "index.js",
  "devDependencies": {
    "typescript": "^4.5.4"
  },
  "dependencies": {
    "webmidi": "latest"
  }
}


================================================
FILE: package.json
================================================
{
  "name": "webmidi",
  "version": "3.1.16",
  "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.",
  "main": "./dist/cjs/webmidi.cjs.min.js",
  "types": "./dist/cjs/webmidi.cjs.d.ts",
  "exports": {
    ".": {
      "require": {
        "types": "./dist/cjs/webmidi.cjs.d.ts",
        "default": "./dist/cjs/webmidi.cjs.min.js"
      },
      "import": {
        "types": "./dist/esm/webmidi.esm.d.ts",
        "default": "./dist/esm/webmidi.esm.js"
      }
    }
  },
  "author": {
    "name": "Jean-Philippe Côté",
    "email": "webmidi@djip.co",
    "url": "https://github.com/djipco/"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/djipco/webmidi.git"
  },
  "bugs": {
    "url": "https://github.com/djipco/webmidi/issues"
  },
  "files": [
    "/dist"
  ],
  "keywords": [
    "midi",
    "message",
    "web",
    "browser",
    "front-end",
    "web midi api",
    "music",
    "djipco",
    "music",
    "protocol",
    "communication",
    "channel",
    "node",
    "instrument",
    "device",
    "note"
  ],
  "homepage": "https://webmidijs.org",
  "license": "Apache-2.0",
  "webmidi": {
    "name": "WEBMIDI.js",
    "tagline": "A JavaScript library to kickstart your MIDI projects"
  },
  "engines": {
    "node": ">=8.5"
  },
  "scripts": {
    "library:build-all": "npm run lint && npm run library:build-cjs && npm run library:build-esm && npm run library:build-iife",
    "library:build-cjs": "node scripts/library/build.js -t cjs && npm run typescript-declaration:generate -- -t cjs",
    "library:build-esm": "node scripts/library/build.js -t esm && npm run typescript-declaration:generate -- -t esm",
    "library:build-iife": "node scripts/library/build.js -t iife",
    "test-coverage:report-as-text": "nyc --reporter=text mocha",
    "test-coverage:report-as-html": "nyc --reporter=html mocha",
    "website:build": "npm run --prefix website build",
    "website:serve": "npm run --prefix website serve",
    "website:dev-server": "npm run --prefix website start",
    "website:generate+push-to-gh-pages": "npm run jsdoc:generate-markdown+push && npm run --prefix website build && node scripts/website/deploy.js",
    "jsdoc:generate-html+push": "node scripts/api-documentation/generate-html.js",
    "jsdoc:generate-markdown+push": "node scripts/api-documentation/generate-markdown.js",
    "typescript-declaration:generate": "node scripts/typescript-declarations/generate.js",
    "lint": "eslint ./src/*.js",
    "release": "dotenv release-it --",
    "release:alpha": "npm run release -- prepatch --preRelease=alpha  --npm.tag=next --github.preRelease",
    "sponsors:update": "node scripts/sponsors/retrieve-sponsors.js",
    "test:all": "mocha",
    "test:Input": "mocha test/Input.test.js",
    "test:InputChannel": "mocha test/InputChannel.test.js",
    "test:Note": "mocha test/Note.test.js",
    "test:Message": "mocha test/Message.test.js",
    "test:Enumerations": "mocha test/Enumerations.test.js",
    "test:Forwarder": "mocha test/Forwarder.test.js",
    "test:Output": "mocha test/Output.test.js",
    "test:OutputChannel": "mocha test/OutputChannel.test.js",
    "test:Utilities": "mocha test/Utilities.test.js",
    "test:WebMidi": "mocha test/WebMidi.test.js"
  },
  "dependencies": {
    "djipevents": "^2.0.7"
  },
  "devDependencies": {
    "@alexbinary/rimraf": "^1.0.2",
    "@babel/cli": "^7.13.0",
    "@babel/core": "^7.9.0",
    "@babel/plugin-proposal-class-properties": "^7.8.3",
    "@babel/polyfill": "^7.8.7",
    "@babel/preset-env": "^7.9.0",
    "@julusian/midi": "^3.1.0",
    "@octokit/graphql": "^4.8.0",
    "@rollup/plugin-babel": "^5.3.0",
    "@rollup/plugin-replace": "^5.0.2",
    "array-back": "^6.2.0",
    "babel-plugin-istanbul": "^6.0.0",
    "chai": "^4.3.3",
    "docusaurus": "^1.14.7",
    "dotenv-cli": "^5.1.0",
    "eslint": "^7.32.0",
    "eslint-config-prettier": "^7.2.0",
    "eslint-plugin-react": "^7.26.1",
    "foodoc": "0.0.9",
    "fs-extra": "^10.0.0",
    "ftp-deploy": "^2.3.7",
    "gh-pages": "^5.0.0",
    "handlebars": "^4.7.9",
    "jsdoc": "^3.6.7",
    "jsdoc-to-markdown": "^7.1.0",
    "markdown-it": "^12.3.2",
    "marked": "^4.0.10",
    "minimist": "^1.2.5",
    "mocha": "^10.0.0",
    "moment": "^2.29.4",
    "nyc": "^15.0.1",
    "object-get": "^2.1.1",
    "prepend-file": "^2.0.0",
    "reduce-flatten": "^3.0.1",
    "release-it": "^19.0.4",
    "replace-in-file": "^6.3.2",
    "rollup": "^3.30.0",
    "rollup-plugin-license": "^2.3.0",
    "rollup-plugin-strip-code": "^0.2.7",
    "rollup-plugin-terser": "^5.3.0",
    "rollup-plugin-version-injector": "^1.3.3",
    "semver": "^7.3.5",
    "simple-git": "^3.32.3",
    "sinon": "^9.0.1",
    "sinon-browser-only": "^1.12.1",
    "system-commands": "^1.1.7",
    "test-value": "^3.0.0",
    "util": "^0.12.4"
  },
  "release-it": {
    "git": {
      "commitMessage": "Release v${version}"
    },
    "github": {
      "release": true,
      "draft": false,
      "releaseName": "Release v${version}",
      "assets": [
        "webmidi-${version}.tgz"
      ]
    },
    "hooks": {
      "after:bump": [
        "npm run library:build-all"
      ],
      "before:git:release": [
        "npm pack"
      ],
      "after:release": "rimraf webmidi-${version}.tgz"
    }
  },
  "optionalDependencies": {
    "jzz": "^1.8.5"
  }
}


================================================
FILE: scripts/api-documentation/generate-html.js
================================================
// This script generates web-based API documentation files from jsdoc comments in the source code
// and commits them to the '/archives/api' directory of the 'gh-pages' branch. This makes them
// available at djipco.githib.io/webmidi/archives/api/v3

// Modules
const fs = require("fs-extra");
const fsPromises = require("fs").promises;
const git = require("simple-git")();
const pkg = require("../../package.json");
const moment = require("moment");
const path = require("path");
const os = require("os");
const rimraf = require("@alexbinary/rimraf");
const system = require("system-commands");

// Some paths
const CUSTOM_CSS = "../css/custom.css";
const TARGET_BRANCH = "gh-pages";
const TARGET_PATH = "./archives/api/v" + pkg.version.split(".")[0];

// Google Analytics configuration
const GA_CONFIG = {
  ua: "UA-162785934-1",
  domain: "https://djipco.github.io/webmidi"
};

// JSDoc configuration object to write as configuration file
const config = {

  tags: {
    allowUnknownTags: true
  },

  // The opts property is for command-line arguments passed directly in the config file
  opts: {
    template: "./node_modules/foodoc/template"
  },

  // Source files
  source: {
    include: [
      "./src/Enumerations.js",
      "./src/Forwarder.js",
      "./src/Input.js",
      "./src/InputChannel.js",
      "./src/Message.js",
      "./src/Note.js",
      "./src/Output.js",
      "./src/OutputChannel.js",
      "./src/Utilities.js",
      "./src/WebMidi.js",

      "./node_modules/djipevents/src/djipevents.js"
    ]
  },

  sourceType: "module",

  plugins: [
    "plugins/markdown"
  ],

  // Configurations for the Foodoc template
  templates: {

    systemName: `${pkg.webmidi.name} API`,
    systemSummary: pkg.webmidi.tagline,
    // systemLogo: LOGO_PATH,
    systemColor: "#ffcf09",

    copyright: `© <a href="${pkg.author.url}">${pkg.author.name}</a>, ` +
      `2015-${new Date().getFullYear()}. ` +
      `${pkg.webmidi.name} v${pkg.version} is released under the ${pkg.license} license.`,

    navMembers: [
      {kind: "class", title: "Classes", summary: "All documented classes."},
      {kind: "external", title: "Externals", summary: "All documented external members."},
      // {kind: "global", title: "Globals", summary: "All documented globals."},
      {kind: "mixin", title: "Mixins", summary: "All documented mixins."},
      {kind: "interface", title: "Interfaces", summary: "All documented interfaces."},
      {kind: "module", title: "Modules", summary: "All documented modules."},
      {kind: "namespace", title: "Namespaces", summary: "All documented namespaces."},
      {kind: "tutorial", title: "Tutorials", summary: "All available tutorials."}
    ],

    analytics: GA_CONFIG,

    stylesheets: [
      CUSTOM_CSS
    ],

    dateFormat: "MMMM Do YYYY @ H:mm:ss",
    sort: "longname, linenum, version, since",

    collapseSymbols: true

  }

};

function log(message) {
  console.info("\x1b[32m", message, "\x1b[0m");
}

async function execute() {

  // Temporary target folder
  const TMP_SAVE_PATH = await fsPromises.mkdtemp(path.join(os.tmpdir(), "webmidi-api-doc-"));
  const CONF_PATH = TMP_SAVE_PATH + "/.jsdoc.json";

  // Write temporary configuration file
  fs.writeFileSync(CONF_PATH, JSON.stringify(config));

  // Prepare jsdoc command and generate documentation
  const cmd = "./node_modules/.bin/jsdoc " +
    `--configure ${CONF_PATH} ` +
    `--destination ${TMP_SAVE_PATH}`;
  await system(cmd);
  log(`Documentation temporarily generated in "${TMP_SAVE_PATH}"`);

  // Remove temporary configuration file
  await rimraf(CONF_PATH);

  // Here, we remove the index.html page and replace it with the list_class.html file
  await fs.copy(
    TMP_SAVE_PATH + "/list_class.html",
    TMP_SAVE_PATH + "/index.html",
    {overwrite: true}
  );

  // Get current branch (so we can come back to it later)
  let results = await git.branch();
  const ORIGINAL_BRANCH = results.current;

  // Switch to target branch
  log(`Switching from '${ORIGINAL_BRANCH}' branch to '${TARGET_BRANCH}' branch`);
  await git.checkout(TARGET_BRANCH);

  // Move dir to final destination and commit
  await fs.move(TMP_SAVE_PATH, TARGET_PATH, {overwrite: true});
  await git.add([TARGET_PATH]);
  await git.commit("Updated on: " + moment().format(), [TARGET_PATH]);
  await git.push();
  log(`Changes committed to ${TARGET_PATH} folder of '${TARGET_BRANCH}' branch`);

  // Come back to original branch
  log(`Switching back to '${ORIGINAL_BRANCH}' branch`);
  await git.checkout(ORIGINAL_BRANCH);

}

// Execute and catch errors if any (in red)
execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m"));


================================================
FILE: scripts/api-documentation/generate-markdown.js
================================================
// Imports
const djipHelpers = require("./templates/helpers/djip-helpers.js");
const fs = require("fs-extra");
const git = require("simple-git")();
const Handlebars = require("handlebars");
const jsdoc2md = require("jsdoc-to-markdown");
const moment = require("moment");
const path = require("path");
const process = require("process");

// Paths
const ROOT_PATH = process.cwd();
const SOURCE_DIR = path.resolve(ROOT_PATH, "src");
const TARGET_PATH = path.join(process.cwd(), "website", "api", "classes");
const TEMPLATE_DIR = path.resolve(ROOT_PATH, "scripts/api-documentation/templates/");
const DJIPEVENTS = path.resolve(ROOT_PATH, "node_modules/djipevents/src/djipevents.js");

// Register some Handlebars helpers
Handlebars.registerHelper({
  eventName: djipHelpers.eventName,
  stripNewlines: djipHelpers.stripNewlines,
  inlineLinks: djipHelpers.inlineLinks,
  methodSignature: djipHelpers.methodSignature,
  eq: djipHelpers.eq,
  ne: djipHelpers.ne,
  lt: djipHelpers.lt,
  gt: djipHelpers.gt,
  lte: djipHelpers.lte,
  gte: djipHelpers.gte,
  and: djipHelpers.and,
  or: djipHelpers.or,
  curly: djipHelpers.curly,
  createEventAnchor: djipHelpers.createEventAnchor,
});

async function generate() {

  // Get source files list
  let files = await fs.readdir(SOURCE_DIR);
  files = files.map(file => path.resolve(SOURCE_DIR, file));
  files.push(DJIPEVENTS);

  // Compute JSON from JSDoc
  const data = jsdoc2md.getTemplateDataSync({files: files});

  // Build list of classes (explicitly adding Listener and EventEmitter)
  let classes = files.map(file => path.basename(file, ".js")).filter(file => file !== "djipevents");
  classes.push("Listener", "EventEmitter");

  // Parse each class file and save parsed output
  classes.forEach(filepath => {

    const basename = path.basename(filepath, ".js");
    const filtered = data.filter(x => x.memberof === basename || x.id === basename);

    // Save markdown files
    fs.writeFileSync(
      path.resolve(TARGET_PATH, `${basename}.md`),
      parseFile(filtered)
    );
    console.info(`Saved markdown file to ${basename}.md`);

  });

  // Commit generated files
  await git.add([TARGET_PATH]);
  await git.commit("Automatically generated on: " + moment().format(), [TARGET_PATH]);
  console.info(`Files in ${TARGET_PATH} committed to git`);
  await git.push();
  console.info(`Files pushed to remote`);

}

function parseFile(data) {

  let output = "";
  let hbs;
  let filtered;

  // Sort elements according to kind and then according to name
  const order = {class: 0, constructor: 1, function: 2, member: 3, event: 4, enum: 5, typedef: 6};
  data.sort((a, b) => {
    if (order[a.kind] === order[b.kind]) {
      return a.id.localeCompare(b.id);
    } else {
      return order[a.kind] < order[b.kind] ? -1 : 1;
    }
  });

  // Sort 'fires' if present
  data.forEach(element => {
    if (Array.isArray(element.fires)) element.fires.sort();
  });

  // Class
  filtered = data.filter(el => el.kind === "class")[0];
  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/class.hbs`), {encoding: "utf-8"});
  output += Handlebars.compile(hbs)(filtered);

  // Constructor
  filtered = data.filter(el => el.kind === "constructor")[0];
  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/constructor.hbs`), {encoding: "utf-8"});
  output += Handlebars.compile(hbs)(filtered);

  // Members
  filtered = data.filter(el => el.kind === "member" && el.access !== "private");
  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/properties.hbs`), {encoding: "utf-8"});
  output += Handlebars.compile(hbs)(filtered);

  // Methods
  filtered = data.filter(el => el.kind === "function" && el.access !== "private");
  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/methods.hbs`), {encoding: "utf-8"});
  output += Handlebars.compile(hbs)(filtered);

  // Events
  filtered = data.filter(el => el.kind === "event" && el.access !== "private");
  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/events.hbs`), {encoding: "utf-8"});
  output += Handlebars.compile(hbs)(filtered);

  // Enums
  filtered = data.filter(el => el.kind === "enum" && el.access !== "private");
  hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/enumerations.hbs`), {encoding: "utf-8"});
  output += Handlebars.compile(hbs)(filtered);

  // Strip out links to Listener class
  // output = output.replaceAll("[**Listener**](Listener)", "`Listener`");
  // output = output.replaceAll("[Listener](Listener)", "`Listener`");
  // output = output.replaceAll("[**arguments**](Listener#arguments)", "`arguments`");

  return output;

}

generate().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m"));


================================================
FILE: scripts/api-documentation/templates/core/class.hbs
================================================
{{!

  This template creates the 'class' portion of the output (at the top). It relies on the following
  helpers:

    - inlineLinks
    - eq
    - eventName

}}

{{! Class name }}
# {{name}}

{{! Class description }}
{{{inlineLinks description}}}

{{! Since }}
{{#if since}}
**Since**: {{since}}
{{/if}}

{{! Extends }}
{{#if augments}}
**Extends**: {{#each augments}}[`{{this}}`]({{this}}){{#unless @last}}, {{/unless}}{{/each}}
<!--**Extends**: {{#each augments}}{{#if (eq this "EventEmitter")}}{{this}}{{else if (eq this "Listener")}}{{this}}{{else}}[`{{this}}`]({{this}}){{#unless @last}}, {{/unless}}{{/if}}{{/each}}-->
{{/if}}

{{! Fires }}
{{#if fires}}
**Fires**: {{#each fires}}[`{{eventName this}}`](#event:{{eventName this}}){{#unless @last}}, {{/unless}}{{/each}}
{{/if}}


================================================
FILE: scripts/api-documentation/templates/core/constructor.hbs
================================================
{{!

  This template creates the 'constructor' portion of the output (below the class). It relies on the
  following helpers:

    - stripNewlines

}}
{{#if this}}

{{! Constructor name }}
### `Constructor`

{{! Description }}
{{{inlineLinks description}}}

{{! Parameters }}
{{#if params}}

  **Parameters**

  > `new {{this.longname}}({{methodSignature this}})`

  <div class="parameter-table-container">

  | Parameter    | Type         | Default      | Description  |
  | ------------ | ------------ | ------------ | ------------ |
  {{#each params}}
    |{{#if this.optional}}[{{/if}}**`{{this.name}}`**{{#if this.optional}}]{{/if}} | {{#each this.type.names}}{{this}}<br />{{/each}} |{{this.defaultvalue}}|{{{stripNewlines (inlineLinks this.description)}}}|
  {{/each}}

  </div>

{{/if}}

{{! Exceptions }}
{{#if this.exceptions}}
**Throws**:
{{#each this.exceptions}}
* {{#if this.type}}`{{this.type.names.[0]}}` : {{/if}}{{inlineLinks this.description}}
{{/each}}
{{/if}}

{{/if}}


================================================
FILE: scripts/api-documentation/templates/core/enumerations.hbs
================================================
{{!

  This template creates the 'enums' portion of the output . It relies on the following helpers:

    - inlineLinks
    - stripNewlines

}}
{{#if this}}
***

## Enums

{{#each this}}
{{! Name }}
### `.{{this.name}}` {{curly true}}#{{this.name}}{{curly}}
**Type**: {{this.type.names.[0]}}<br />
{{! Attributes }}
{{#if (eq this.scope "static")}}
**Attributes**: static
{{/if}}

{{! Description }}
{{{inlineLinks description}}}
{{/each}}

{{/if}}


================================================
FILE: scripts/api-documentation/templates/core/events.hbs
================================================
{{!

  This template creates the 'Events' portion of the output . It relies on the following helpers:

    - inlineLinks
    - stripNewlines

}}
{{#if this}}
***

## Events

{{#each this}}
{{! Name and anchor }}
### `{{this.name}}` {{curly true}}{{createEventAnchor this.name}}{{curly}}

<a id="event:{{this.name}}"></a>


{{! Description }}
{{{inlineLinks description}}}

{{! Since }}
{{#if since}}
**Since**: {{since}}
{{/if}}

{{! Properties }}
{{#if properties}}

**Event Properties**

| Property                 | Type                     | Description              |
| ------------------------ | ------------------------ | ------------------------ |
{{#each properties}}
  |**`{{this.name}}`** |{{this.type.names.[0]}}|{{{stripNewlines (inlineLinks this.description)}}}|
{{/each}}

{{/if}}

{{/each}}

{{/if}}


================================================
FILE: scripts/api-documentation/templates/core/methods.hbs
================================================
{{!

  This template creates the 'Events' portion of the output . It relies on the following helpers:

    - inlineLinks
    - stripNewlines

}}
{{#if this}}
***

## Methods

{{#each this}}

{{! Name }}
### `.{{this.name}}({{#if params}}...{{/if}})` {{curly true}}#{{this.name}}{{curly}}

{{! Since }}
{{#if this.since}}
**Since**: {{this.since}}<br />
{{/if}}
{{! Attributes }}
{{#if (eq this.async true)}}
**Attributes**: async
{{/if}}

{{! Description }}
{{{inlineLinks description}}}

{{! Parameters }}
{{#if params}}

  **Parameters**

  > Signature: `{{this.name}}({{methodSignature this}})`

  <div class="parameter-table-container">

  | Parameter    | Type(s)      | Default      | Description  |
  | ------------ | ------------ | ------------ | ------------ |
  {{#each params}}
    |{{#if this.optional}}[{{/if}}**`{{this.name}}`**{{#if this.optional}}]{{/if}} | {{#each this.type.names}}{{this}}<br />{{/each}} |{{this.defaultvalue}}|{{{stripNewlines (inlineLinks this.description)}}}|
  {{/each}}

  </div>

{{/if}}

{{! Returns }}
{{#if returns}}
**Return Value**

{{! Return value description }}
> Returns: {{#each returns~}}
{{#if type~}}
{{#each type.names~}}
`{{{this}}}`{{#unless @last}} or {{/unless}}
{{~/each}}<br />
{{/if~}}

{{~#if description}}

{{{inlineLinks description}}}
{{/if~}}
{{~/each}}
{{/if}}

{{! Attributes }}
{{#if (eq this.scope "static")}}

**Attributes**: static
{{/if}}

{{#if this.exceptions}}
**Throws**:
{{#each this.exceptions}}
  * {{#if this.type}}`{{this.type.names.[0]}}` : {{/if}}{{{inlineLinks this.description}}}
{{/each}}
{{/if}}

{{/each}}

{{/if}}


================================================
FILE: scripts/api-documentation/templates/core/properties.hbs
================================================
{{!

  This template creates the 'properties' portion of the output . It relies on the following helpers:

    - inlineLinks
    - stripNewlines

}}
{{#if this}}
***

## Properties

{{#each this}}
{{! Name }}
### `.{{this.name}}` {{curly true}}#{{this.name}}{{curly}}
{{! Since }}
{{#if this.since}}
**Since**: {{this.since}}<br />
{{/if}}
{{! Type }}
**Type**: {{this.type.names.[0]}}<br />
{{#if (or this.readonly this.nullable)}}
{{! Attributes }}
**Attributes**: {{#if this.readonly}}read-only{{/if}}{{#if this.nullable}}, nullable{{/if}}{{#if (eq this.scope "static")}}, static{{/if}}<br />
{{/if}}


{{! Description }}
{{{inlineLinks description}}}

{{! Properties }}
{{#if properties}}

  **Properties**

  | Property     | Type         | Description  |
  | ------------ | ------------ | ------------ |
  {{#each properties}}
    |**`{{this.name}}`** |{{this.type.names.[0]}}|{{{stripNewlines (inlineLinks this.description)}}}|
  {{/each}}

{{/if}}

{{/each}}

{{/if}}


================================================
FILE: scripts/api-documentation/templates/helpers/ddata.js
================================================
var arrayify = require('array-back');
var util = require('util');
var handlebars = require('handlebars');
var marked = require('marked');
var objectGet = require('object-get');
var where = require('test-value').where;
var flatten = require('reduce-flatten');
var state = require('./state.js');

/**
 * ddata is a collection of handlebars helpers for working with the documentation data output by
 * [jsdoc-parse](https://github.com/75lb/jsdoc-parse).
 *
 * @module
 * @example
 * ```js
 * var handlebars = require("handlebars")
 * var ddata = require("ddata")
 * var docs = require("./docs.json") // jsdoc-parse output
 *
 * handlebars.registerHelper(ddata)
 * var template =
 * "{{#module name='yeah-module'}}\
 * The author of the module is: {{author}}.\
 * {{/module}}"
 * console.log(handlebars.compile(template)(docs))
 * ```
 */

/* utility block helpers */
exports.link = link;
exports.returnSig2 = returnSig2;
exports.sig = sig;
exports.children = children;
exports.indexChildren = indexChildren;

/* helpers which return objects */
exports._link = _link;

/* helpers which return booleans */
exports.isClass = isClass;
exports.isClassMember = isClassMember;
exports.isConstructor = isConstructor;
exports.isFunction = isFunction;
exports.isConstant = isConstant;
exports.isEvent = isEvent;
exports.isEnum = isEnum;
exports.isTypedef = isTypedef;
exports.isCallback = isCallback;
exports.isModule = isModule;
exports.isMixin = isMixin;
exports.isExternal = isExternal;
exports.isPrivate = isPrivate;
exports.isProtected = isProtected;
exports.showMainIndex = showMainIndex;

/* helpers which return lists */
exports._orphans = _orphans;
exports._identifiers = _identifiers;
exports._children = _children;
exports._globals = _globals;
exports.descendants = descendants;

/* helpers which return single identifiers */
exports.exported = exported;
exports.parentObject = parentObject;
exports._identifier = _identifier;

/* helpers which return strings */
exports.anchorName = anchorName;
exports.md = md;
exports.md2 = md2;
exports.methodSig = methodSig;
exports.parseLink = parseLink;
exports.parentName = parentName;
exports.option = option;
exports.optionEquals = optionEquals;
exports.optionSet = optionSet;
exports.optionIsSet = optionIsSet;
exports.stripNewlines = stripNewlines;

/* helpers which keep state */
exports.headingDepth = headingDepth;
exports.depth = depth;
exports.depthIncrement = depthIncrement;
exports.depthDecrement = depthDecrement;
exports.indexDepth = indexDepth;
exports.indexDepthIncrement = indexDepthIncrement;
exports.indexDepthDecrement = indexDepthDecrement;

/**
 * omits externals without a description
 * @static
 */
function _globals (options) {
  options.hash.scope = 'global'
  return _identifiers(options).filter(function (identifier) {
    if (identifier.kind === 'external') {
      return identifier.description && identifier.description.length > 0
    } else {
      return true
    }
  })
}

/**
 * This helper is a duplicate of the handlebars `each` helper with the one exception that
 * `depthIncrement` is called on each iteration.
 * @category Block helper: selector
 */
function children (options) {
  var context = _children.call(this, options);
  var fn = options.fn;
  var inverse = options.inverse;
  var i = 0;
  var ret = '';
  var data;

  var contextPath

  if (options.data) {
    data = handlebars.createFrame(options.data)
  }

  for (var j = context.length; i < j; i++) {
    depthIncrement(options)
    if (data) {
      data.index = i
      data.first = (i === 0)
      data.last = (i === (context.length - 1))

      if (contextPath) {
        data.contextPath = contextPath + i
      }
    }
    ret = ret + fn(context[i], { data: data })
    depthDecrement(options)
  }

  if (i === 0) {
    ret = inverse(this)
  }

  return ret
}

/**
 * This helper is a duplicate of the handlebars `each` helper with the one exception that
 * `indexDepthIncrement` is called on each iteration.
 * @static
 * @category Block helper: selector
 */
function indexChildren (options) {
  var context = _children.call(this, options)
  var fn = options.fn
  var inverse = options.inverse
  var i = 0
  var ret = ''
  var data

  var contextPath

  if (options.data) {
    data = handlebars.createFrame(options.data)
  }

  for (var j = context.length; i < j; i++) {
    indexDepthIncrement(options)
    if (data) {
      data.index = i
      data.first = (i === 0)
      data.last = (i === (context.length - 1))

      if (contextPath) {
        data.contextPath = contextPath + i
      }
    }
    ret = ret + fn(context[i], { data: data })
    indexDepthDecrement(options)
  }

  if (i === 0) {
    ret = inverse(this)
  }

  return ret
}

/**
 * @param id {string} - the ID to link to, e.g. `external:XMLHttpRequest`, `GlobalClass#propOne`
 * etc.
 * @static
 * @category Block helper: util
 * @example
 * {{#link "module:someModule.property"~}}
 *   {{name}} {{!-- prints 'property' --}}
 *   {{url}}  {{!-- prints 'module-someModule-property' --}}
 * {{/link}}
 */
function link (longname, options) {
  return options.fn(_link(longname, options));
}

/**
 * e.g. namepaths `module:Something` or type expression `Array.<module:Something>`
 * @static
 * @param {string} - namepath or type expression
 * @param {object} - the handlebars helper options object
 */
function _link (input, options) {
  if (typeof input !== 'string') return null

  var linked, matches, namepath
  var output = {}

  /*
   test input for
   1. A type expression containing a namepath, e.g. Array.<module:Something>
   2. a namepath referencing an `id`
   3. a namepath referencing a `longname`
   */
  if ((matches = input.match(/.*?<(.*?)>/))) {
    namepath = matches[1]
  } else {
    namepath = input
  }

  options.hash = { id: namepath }
  linked = _identifier(options)
  if (!linked) {
    options.hash = { longname: namepath }
    linked = _identifier(options)
  }
  if (!linked) {
    output = { name: input, url: null }
  } else {
    output.name = input.replace(namepath, linked.name)
    if (isExternal.call(linked)) {
      if (linked.description) {
        output.url = '#' + anchorName.call(linked, options)
      } else {
        if (linked.see && linked.see.length) {
          var firstLink = parseLink(linked.see[0])[0]
          output.url = firstLink ? firstLink.url : linked.see[0]
        } else {
          output.url = null
        }
      }
    } else {
      output.url = '#' + anchorName.call(linked, options)
    }
  }
  return output
}

/**
 @static
 @returns {{symbol: string, types: array}}
 @category Block helper: util
 */
function returnSig2 (options) {
  if (!isConstructor.call(this)) {
    if (this.returns) {
      var typeNames = arrayify(this.returns).map(function (ret) {
        return ret.type && ret.type.names
      })
      typeNames = typeNames.filter(function (name) {
        return name
      })
      if (typeNames.length) {
        return options.fn({
          symbol: '⇒',
          types: typeNames.reduce(flatten, [])
        })
      } else {
        return options.fn({
          symbol: null,
          types: null
        })
      }
    } else if ((this.type || this.kind === 'namespace') && this.kind !== 'event') {
      if (this.kind === 'namespace') {
        return options.fn({
          symbol: ':',
          types: ['object']
        })
      } else {
        return options.fn({
          symbol: ':',
          types: this.type.names
        })
      }
    }
  }
}

/**
 @category Block helper: util
 */
function sig (options) {
  var data

  if (options.data) {
    data = handlebars.createFrame(options.data || {})
  }

  data.prefix = this.kind === 'constructor' ? 'new' : ''
  data.parent = null
  data.accessSymbol = null
  data.name = isEvent.call(this) ? '"' + this.name + '"' : this.name
  data.methodSign = null
  data.returnSymbol = null
  data.returnTypes = null
  data.suffix = this.isExported ? '⏏' : isPrivate.call(this) ? '℗' : ''
  data.depOpen = null
  data.depClose = null
  data.codeOpen = null
  data.codeClose = null

  var mSig = methodSig.call(this)
  if (isConstructor.call(this) || isFunction.call(this)) {
    data.methodSign = '(' + mSig + ')'
  } else if (isEvent.call(this)) {
    if (mSig) data.methodSign = '(' + mSig + ')'
  }

  if (!isEvent.call(this)) {
    data.parent = parentName.call(this, options)
    data.accessSymbol = (this.scope === 'static' || this.scope === 'instance') ? '.' : this.scope === 'inner' ? '~' : ''
  }

  if (!isConstructor.call(this)) {
    if (this.returns) {
      data.returnSymbol = '⇒'
      var typeNames = arrayify(this.returns)
        .map(function (ret) {
          return ret.type && ret.type.names
        })
        .filter(function (name) {
          return name
        })
      if (typeNames.length) {
        data.returnTypes = typeNames.reduce(flatten, [])
      }
    } else if ((this.type || this.kind === 'namespace') && this.kind !== 'event') {
      data.returnSymbol = ':'

      if (isEnum.call(this)) {
        data.returnTypes = ['enum']
      } else if (this.kind === 'namespace') {
        data.returnTypes = ['object']
      } else {
        data.returnTypes = this.type.names
      }
    } else if (this.chainable) {
      data.returnSymbol = '↩︎'
    } else if (this.augments) {
      data.returnSymbol = '⇐'
      data.returnTypes = [this.augments[0]]
    }
  }

  if (this.deprecated) {
    if (optionEquals('no-gfm', true, options) || options.hash['no-gfm']) {
      data.depOpen = '<del>'
      data.depClose = '</del>'
    } else {
      data.depOpen = '~~'
      data.depClose = '~~'
    }
  }

  if (option('name-format', options) && !isClass.call(this) && !isModule.call(this)) {
    data.codeOpen = '`'
    data.codeClose = '`'
  }

  return options.fn(this, { data: data })
}

/**
 @this {identifier}
 @returns {boolean}
 @alias module:ddata.isClass
 */
function isClass () { return this.kind === 'class' }

/**
 returns true if the parent of the current identifier is a class
 @returns {boolean}
 @static
 */
function isClassMember (options) {
  var parent = arrayify(options.data.root).find(where({ id: this.memberof }))
  if (parent) {
    return parent.kind === 'class'
  }
}
function isConstructor () { return this.kind === 'constructor' }
function isFunction () { return this.kind === 'function' }
function isConstant () { return this.kind === 'constant' }
/**
 returns true if this is an event
 @returns {boolean}
 @static
 */
function isEvent () { return this.kind === 'event' }
function isEnum () { return this.isEnum || this.kind === 'enum' }
function isExternal () { return this.kind === 'external' }
function isTypedef () {
  return this.kind === 'typedef' && this.type.names[0] !== 'function'
}
function isCallback () {
  return this.kind === 'typedef' && this.type.names[0] === 'function'
}
function isModule () { return this.kind === 'module' }
function isMixin () { return this.kind === 'mixin' }
function isPrivate () { return this.access === 'private' }
function isProtected () { return this.access === 'protected' }

/**
 True if there at least two top-level identifiers (i.e. globals or modules)
 @category returns boolean
 @returns {boolean}
 @static
 */
function showMainIndex (options) {
  return _orphans(options).length > 1
}

/**
 * Returns an array of the top-level elements which have no parents. Output only includes externals which have a description.
 * @returns {array}
 * @static
 * @category Returns list
 */
function _orphans (options) {
  options.hash.memberof = undefined
  return _identifiers(options).filter(function (identifier) {
    if (identifier.kind === 'external') {
      return identifier.description && identifier.description.length > 0
    } else {
      return true
    }
  })
}

/**
 * Returns an array of identifiers matching the query
 * @returns {array}
 * @static
 */
function _identifiers (options) {
  var query = {}

  for (var prop in options.hash) {
    if (/^-/.test(prop)) {
      query[prop.replace(/^-/, '!')] = options.hash[prop]
    } else if (/^_/.test(prop)) {
      query[prop.replace(/^_/, '')] = new RegExp(options.hash[prop])
    } else {
      query[prop] = options.hash[prop]
    }
  }
  return arrayify(options.data.root).filter(where(query)).filter(function (doclet) {
    return !doclet.ignore && (state.options.private ? true : doclet.access !== 'private')
  })
}

/**
 return the identifiers which are a `memberof` this one. Exclude externals without descriptions.
 @param [sortBy] {string} - "kind"
 @param [min] {number} - only returns if there are `min` children
 @this {identifier}
 @returns {identifier[]}
 @static
 */
function _children (options) {
  if (!this.id) return []
  var min = options.hash.min
  delete options.hash.min
  options.hash.memberof = this.id
  var output = _identifiers(options)
  output = output.filter(function (identifier) {
    if (identifier.kind === 'external') {
      return identifier.description && identifier.description.length > 0
    } else {
      return true
    }
  })
  if (output.length >= (min || 0)) return output
}

/**
 return a flat list containing all decendants
 @param [sortBy] {string} - "kind"
 @param [min] {number} - only returns if there are `min` children
 @this {identifier}
 @returns {identifier[]}
 @static
 */
function descendants (options) {
  var min = typeof options.hash.min !== 'undefined' ? options.hash.min : 2
  delete options.hash.min
  options.hash.memberof = this.id
  var output = []
  function iterate (childrenList) {
    if (childrenList.length) {
      childrenList.forEach(function (child) {
        output.push(child)
        iterate(_children.call(child, options))
      })
    }
  }
  iterate(_children.call(this, options))
  if (output.length >= (min || 0)) return output
}

/**
 returns the exported identifier of this module
 @this {identifier} - only works on a module
 @returns {identifier}
 @static
 */
function exported (options) {
  var exp = arrayify(options.data.root).find(where({ '!kind': 'module', id: this.id }))
  return exp || this
}

/**
 Returns an identifier matching the query
 @static
 */
function _identifier (options) {
  return _identifiers(options)[0]
}

/**
 Returns the parent
 @static
 */
function parentObject (options) {
  return arrayify(options.data.root).find(where({ id: this.memberof }))
}

/**
 returns a unique ID string suitable for use as an `href`.
 @this {identifier}
 @returns {string}
 @static
 @category Returns string
 @example
 ```js
 > ddata.anchorName.call({ id: "module:yeah--Yeah()" })
 'module_yeah--Yeah_new'
 ```
 */
function anchorName (options) {
  if (!this.id) throw new Error('[anchorName helper] cannot create a link without a id: ' + JSON.stringify(this))
  if (this.inherited) {
    options.hash.id = this.inherits
    var inherits = _identifier(options)
    if (inherits) {
      return anchorName.call(inherits, options)
    } else {
      return ''
    }
  }
  return util.format(
    '%s%s%s',
    this.isExported ? 'exp_' : '',
    this.kind === 'constructor' ? 'new_' : '',
    this.id
      .replace(/:/g, '_')
      .replace(/~/g, '..')
      .replace(/\(\)/g, '_new')
      .replace(/#/g, '+')
  )
}

/**
 converts the supplied text to markdown
 @static
 @category Returns string
 */
function md (string, options) {
  if (string) {
    var result = marked(string).replace('lang-js', 'language-javascript')
    return result
  }
}
function md2 (options) {
  return marked.inlineLexer(options.fn(this).toString(), [])
}

/**
 Returns the method signature, e.g. `(options, [onComplete])`
 @this {identifier}
 @returns {string}
 @category Returns string
 @static
 */
function methodSig () {
  return arrayify(this.params).filter(function (param) {
    return param.name && !/\./.test(param.name);
  }).map(function (param) {
    if (param.variable) {
      return param.optional ? "[..." + param.name + "]" : "..." + param.name;
    } else {
      return param.optional ? "[" + param.name + "]" : param.name;
    }
  }).join(", ");
}

/**
 * extracts url and caption data from @link tags
 * @param {string} - a string containing one or more {@link} tags
 * @returns {Array.<{original: string, caption: string, url: string}>}
 * @static
 */
function parseLink (text) {
  if (!text) return ''
  var results = []
  var matches = null
  var link1 = /{@link\s+([^\s}|]+?)\s*}/g // {@link someSymbol}
  var link2 = /\[([^\]]+?)\]{@link\s+([^\s}|]+?)\s*}/g // [caption here]{@link someSymbol}
  var link3 = /{@link\s+([^\s}|]+?)\s*\|([^}]+?)}/g // {@link someSymbol|caption here}
  var link4 = /{@link\s+([^\s}|]+?)\s+([^}|]+?)}/g // {@link someSymbol Caption Here}

  while ((matches = link4.exec(text)) !== null) {
    results.push({
      original: matches[0],
      caption: matches[2],
      url: matches[1]
    })
    text = text.replace(matches[0], ' '.repeat(matches[0].length))
  }

  while ((matches = link3.exec(text)) !== null) {
    results.push({
      original: matches[0],
      caption: matches[2],
      url: matches[1]
    })
    text = text.replace(matches[0], ' '.repeat(matches[0].length))
  }

  while ((matches = link2.exec(text)) !== null) {
    results.push({
      original: matches[0],
      caption: matches[1],
      url: matches[2]
    })
    text = text.replace(matches[0], ' '.repeat(matches[0].length))
  }

  while ((matches = link1.exec(text)) !== null) {
    results.push({
      original: matches[0],
      caption: matches[1],
      url: matches[1]
    })
    text = text.replace(matches[0], ' '.repeat(matches[0].length))
  }
  return results
}

/**
 returns the parent name, instantiated if necessary
 @this {identifier}
 @returns {string}
 @static
 */
function parentName (options) {
  function instantiate (input) {
    if (/^[A-Z]{3}/.test(input)) {
      return input.replace(/^([A-Z]+)([A-Z])/, function (str, p1, p2) {
        return p1.toLowerCase() + p2
      })
    } else {
      return input.charAt(0).toLowerCase() + input.slice(1)
    }
  }

  /* don't bother with a parentName for exported identifiers */
  if (this.isExported) return ''

  if (this.memberof && this.kind !== 'constructor') {
    var parent = arrayify(options.data.root).find(where({ id: this.memberof }))
    if (parent) {
      if (this.scope === 'instance') {
        var name = parent.typicalname || parent.name
        return instantiate(name)
      } else if (this.scope === 'static' && !(parent.kind === 'class' || parent.kind === 'constructor')) {
        return parent.typicalname || parent.name
      } else {
        return parent.name
      }
    } else {
      return this.memberof
    }
  }
}

/**
 returns a dmd option, e.g. "sort-by", "heading-depth" etc.
 @static
 */
function option (name, options) {
  return objectGet(options.data.root.options, name)
}

/**
 @static
 */
function optionEquals (name, value, options) {
  return options.data.root.options[name] === value
}

/**
 @static
 */
function optionSet (name, value, options) {
  options.data.root.options[name] = value
}

/**
 @static
 */
function optionIsSet (name, options) {
  return options.data.root.options[name] !== undefined
}

/**
 @static
 */
function stripNewlines (input) {
  if (input) return input.replace(/[\r\n]+/g, ' ')
}

/**
 @static
 */
function headingDepth (options) {
  return options.data.root.options._depth + (options.data.root.options['heading-depth'])
}

/**
 @static
 */
function depth (options) {
  return options.data.root.options._depth
}

/**
 @static
 */
function depthIncrement (options) {
  options.data.root.options._depth++
}

/**
 @static
 */
function depthDecrement (options) {
  options.data.root.options._depth--
}

/**
 @static
 */
function indexDepth (options) {
  return options.data.root.options._indexDepth
}

/**
 @static
 */
function indexDepthIncrement (options) {
  options.data.root.options._indexDepth++
}

/**
 @static
 */
function indexDepthDecrement (options) {
  options.data.root.options._indexDepth--
}


================================================
FILE: scripts/api-documentation/templates/helpers/djip-helpers.js
================================================
const ddata = require("./ddata.js");

/**
 * Strips newline characters (\n) from the input
 * @param input {string}
 * @returns {string}
 */
function stripNewlines (input) {
  if (input) return input.replace(/[\r\n]+/g, " ");
}
exports.stripNewlines = stripNewlines;

/**
 * Extract the event name from the jsdoc-reported name
 * @param input {string}
 * @returns {string}
 */
function eventName (input) {
  return input.split(":")[1];
}
exports.eventName = eventName;

/**
 * Replaces JSDoc {@link} tags with markdown links in the supplied text
 */
function inlineLinks (text, options) {

  if (text) {
    const links = ddata.parseLink(text);
    links.forEach(function (link) {
      const linked = ddata._link(link.url, options);
      if (link.caption === link.url) link.caption = linked.name;
      if (linked.url) link.url = linked.url;
      text = text.replace(link.original, `[${link.caption}](${link.url})`);
    });
  }

  return text;

}
exports.inlineLinks = inlineLinks;

function curly(object, open) {
  return open ? "{" : "}";
};
exports.curly = curly;

function eq(v1, v2) { return v1 === v2; }
exports.eq = eq;
function ne(v1, v2) { return v1 !== v2; }
exports.ne = ne;
function lt(v1, v2) {return v1 < v2; }
exports.lt = lt;
function gt(v1, v2) { return v1 > v2; }
exports.gt = gt;
function lte(v1, v2) { return v1 <= v2; }
exports.lte = lte;
function gte(v1, v2) { return v1 >= v2; }
exports.gte = gte;
function and() { return Array.prototype.every.call(arguments, Boolean); }
exports.and = and;
function or() { return Array.prototype.slice.call(arguments, 0, -1).some(Boolean); }
exports.or = or;

function methodSignature(context) {
  return ddata.methodSig.call(context);
}
exports.methodSignature = methodSignature;

function createEventAnchor(name) {
  return "#event-" + name.replace(":", "-");
}
exports.createEventAnchor = createEventAnchor;


================================================
FILE: scripts/api-documentation/templates/helpers/state.js
================================================
exports.templateData = []
exports.options = {}


================================================
FILE: scripts/library/build.js
================================================
// This script builds the bundled library files (both normal and minified with sourcemaps) and
// commits them to the 'dist' folder for later publishing. By default, CommonJS, ES Modules and IIFE
// versions are built.
//
// Calling this script with the -t argument allows building only of them. Options are: cjs, esm and
// iife.

// Modules
const system = require("system-commands");
const fs = require("fs");
const path = require("path");

// Parse arguments (default type is esm). Use -t as type (if valid)
let type = "esm";
const argv = require("minimist")(process.argv.slice(2));
if (["cjs", "esm", "iife"].includes(argv.t)) type = argv.t;

// Prepare general command
let cmd = `./node_modules/.bin/rollup ` +
  `--input src/WebMidi.js ` +
  `--format ${type} `;

// Minified version (with sourcemap)
let minified = cmd + ` --file dist/${type}/webmidi.${type}.min.js ` +
  `--sourcemap ` +
  `--config ${__dirname}/rollup.config.${type}.min.js`;

// Non-minified version
let normal = cmd + ` --file dist/${type}/webmidi.${type}.js ` +
  `--config ${__dirname}/rollup.config.${type}.js`;

async function execute(type) {

  // Write package.json file so proper versions are imported by Node
  if (type === "esm") {

    fs.writeFileSync(
      path.join(process.cwd(), "dist", "esm", "package.json"), '{"type": "module"}'
    );

    console.info(
      "\x1b[32m", // green font
      `Custom package.json file ("type": "module") saved to "dist/${type}/package.json"`,
      "\x1b[0m"   // reset font
    );

  } else if (type === "cjs") {

    fs.writeFileSync(
      path.join(process.cwd(), "dist", "cjs", "package.json"), '{"type": "commonjs"}'
    );

    console.info(
      "\x1b[32m", // green font
      `Custom package.json file ("type": "commonjs") saved to "dist/${type}/package.json"`,
      "\x1b[0m"   // reset font
    );

  }

  // Production build
  await system(minified);

  console.info(
    "\x1b[32m", // green font
    `The "${type}" minified build was saved to "dist/${type}/webmidi.${type}.min.js"`,
    "\x1b[0m"   // reset font
  );

  // Development build
  await system(normal);

  console.info(
    "\x1b[32m", // green font
    `The "${type}" non-minified build was saved to "dist/${type}/webmidi.${type}.js"`,
    "\x1b[0m"   // reset font
  );
}

// Execute and catch errors if any (in red)
execute(type).catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m"));



================================================
FILE: scripts/library/rollup.config.cjs.js
================================================
import babel from "@rollup/plugin-babel";
import stripCode from "rollup-plugin-strip-code";
import replace from "@rollup/plugin-replace";
const fs = require("fs");
const license = require("rollup-plugin-license");
const versionInjector = require("rollup-plugin-version-injector");

const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n";

export default {
  plugins: [
    versionInjector(),
    replace({__flavour__: "cjs"}),
    stripCode({
      start_comment: "START-ESM",
      end_comment: "END-ESM"
    }),
    babel(),
    license({banner: BANNER})
  ]
};


================================================
FILE: scripts/library/rollup.config.esm.js
================================================
import stripCode from "rollup-plugin-strip-code";
import replace from "@rollup/plugin-replace";

const fs = require("fs");
const license = require("rollup-plugin-license");
const versionInjector = require("rollup-plugin-version-injector");

const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n";

export default {

  plugins: [
    versionInjector(),
    replace({__flavour__: "esm"}),
    stripCode({
      start_comment: "START-CJS",
      end_comment: "END-CJS"
    }),
    license({
      banner: BANNER
    })
  ]

};


================================================
FILE: scripts/library/rollup.config.iife.js
================================================
import babel from "@rollup/plugin-babel";
import stripCode from "rollup-plugin-strip-code";
import replace from "@rollup/plugin-replace";

const fs = require("fs");
const license = require("rollup-plugin-license");
const versionInjector = require("rollup-plugin-version-injector");

const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n";

export default {

  output: {
    name: "window",       // WebMidi and Note will be added to window
    extend: true,         // important!
    exports: "named"
  },

  plugins: [
    versionInjector(),
    replace({__flavour__: "iife"}),
    stripCode({
      start_comment: "START-CJS",
      end_comment: "END-CJS"
    }),
    stripCode({
      start_comment: "START-ESM",
      end_comment: "END-ESM"
    }),
    babel(),
    license({
      banner: BANNER
    })
  ]

};


================================================
FILE: scripts/sponsors/retrieve-sponsors.js
================================================
const { graphql } = require("@octokit/graphql");
const { token } = require("../../.credentials/sponsors.js");
const replace = require("replace-in-file");
const path = require("path");

const TARGET = path.join(process.cwd(), "website", "src", "pages", "sponsors", "index.md");

async function getSponsors() {

  const query = `{

    user (login: "djipco") {
      sponsorshipsAsMaintainer(first: 100, includePrivate: true) {
        totalCount
        nodes {
          sponsorEntity {
          ... on User {
            login
            name
            avatarUrl
            url
            sponsorshipForViewerAsSponsorable {
                isOneTimePayment
                privacyLevel
                tier {
                  id
                  name
                  monthlyPriceInDollars
                }
              }
            }
          }
        }
      }
    }

  }`;

  const {user} = await graphql(
    query,
    { headers: { authorization: `token ${token}` } }
  );

  return user.sponsorshipsAsMaintainer.nodes;

}

getSponsors().then(async data => {

  // data.forEach(d => {
  //   console.log(d.sponsorEntity.login, d.sponsorEntity.sponsorshipForViewerAsSponsorable);
  //   // console.log(d);
  // });

  let output = "";

  data.sort((a, b) => {
    a = a.sponsorEntity.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars;
    b = b.sponsorEntity.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars;

    if (a > b) return -1;
    if (a < b) return -1;
    return 0;

  });

  data.forEach(entity => {

    const sponsor = entity.sponsorEntity;

    if (sponsor.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars < 6) {
      return;
    }

    if (sponsor.sponsorshipForViewerAsSponsorable.privacyLevel === "PUBLIC") {
      const name = sponsor.name || sponsor.login;
      output += `<a href="${sponsor.url}" title="${name}" class="user-icon">\n`;
      output += `\t<img src="${sponsor.avatarUrl}" `;
      output += `alt="${name}" width="100" height="100" />\n`;
      output += `</a>\n\n`;
    } else {
      output += `<a class="user-icon"><img src="/img/sponsors/user.png" `;
      output += `alt="Anonymous" width="100" height="100" /></a>\n\n`;
    }

  });

  const options = {
    files: TARGET,
    from: /<!-- SPONSOR START -->.*<!-- SPONSOR END -->/sgm,
    to: "<!-- SPONSOR START -->\n\n" + output + "<!-- SPONSOR END -->",
  };

  const results = await replace(options);

});


================================================
FILE: scripts/typescript-declarations/generate.js
================================================
// This script injects a few dynamic properties into the source TypeScript declaration file and
// copies it to the ./dist/esm and ./dist/cjs directories.

// Modules
const path = require("path");
const replace = require("replace-in-file");
const pkg = require("../../package.json");
const fs = require("fs-extra");

// Path to source declaration file
const SOURCE_FILE = path.join(__dirname, "../../typescript", "webmidi.d.ts");

// Output directory
const OUTPUT_DIR = "dist";

// Console arguments
const argv = require("minimist")(process.argv.slice(2));

// Get targets to save the file for
let targets = [];
let type = "all";
if (["cjs", "esm"].includes(argv.t)) type = argv.t;

if (type === "all" || type === "cjs")
  targets.push({path: "webmidi.cjs.d.ts", name: "cjs", type: "CommonJS"});

if (type === "all" || type === "esm")
  targets.push({path: "webmidi.esm.d.ts", name: "esm", type: "ES2020"});

async function execute() {

  targets.forEach(async target => {

    const FILE = path.join(OUTPUT_DIR, target.name, target.path);

    // Copy the file to the target directory
    await fs.copy(SOURCE_FILE, FILE, {overwrite: true});

    // Insert dynamic data
    replace.sync({
      files: FILE,
      from: ["{{LIBRARY}}", "{{VERSION}}", "{{HOMEPAGE}}", "{{AUTHOR_NAME}}", "{{AUTHOR_URL}}"],
      to: [pkg.webmidi.name, pkg.version, pkg.homepage, pkg.author.name, pkg.author.url]
    });

    log(`Saved ${target.type} TypeScript declaration file to '${FILE}'`);

  });

}

// Execute and catch errors if any (in red)
execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m"));

function log(message) {
  console.info("\x1b[32m", message, "\x1b[0m");
}


================================================
FILE: scripts/typescript-declarations/generateOLD.js
================================================
// This script generates TypeScript declaration files (.d.ts) of the library and saves them to the
// 'dist' directory for later publishing. By default, CommonJS and ES2020 versions are generated.
//
// Calling this script with the -t argument allows generating only one declaration file. Options
// are: cjs and esm.
//
// Calling this script with the -c argument allows you to commit and push the generated files.
// Options are true or false.

// Modules
const git = require("simple-git")();
const moment = require("moment");
const path = require("path");
const prependFile = require("prepend-file");
const replace = require("replace-in-file");
const system = require("system-commands");
const pkg = require("../../package.json");
const os = require("os");
const fsPromises = require("fs").promises;
const fs = require("fs-extra");

const OUT_DIR = "dist";

const WEB_MIDI_API_CLASSES = [
  "MIDIAccess",
  "MIDIConnectionEvent",
  "MIDIConnectionEventInit",
  "MIDIInput",
  "MIDIInputMap",
  "MIDIMessageEvent",
  "MIDIMessageEventInit",
  "MIDIOptions",
  "MIDIOutput",
  "MIDIOutputMap",
  "MIDIPort",
  "MIDIPortConnectionStatus",
  "MIDIPortDeviceState",
  "MIDIPortType"
];

const HEADER = `// Type definitions for ${pkg.webmidi.name} ${pkg.version}\n` +
  `// Project: ${pkg.homepage}\n` +
  `// Definitions by: ${pkg.author.name} <https://github.com/djipco/>\n` +
  `// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n\n`;

let targets = [];

let type = "all";
const argv = require("minimist")(process.argv.slice(2));
if (["cjs", "esm"].includes(argv.t)) type = argv.t;

const commit = argv.c === "true";

if (type === "all" || type === "cjs")
  targets.push({source: "webmidi.cjs.js", name: "cjs", type: "CommonJS"});

if (type === "all" || type === "esm")
  targets.push({source: "webmidi.esm.js", name: "esm", type: "ES2020"});

async function execute() {

  // Temp dir
  const TMP_DIR_PATH = await fsPromises.mkdtemp(path.join(os.tmpdir(), "webmidi-ts-"));

  targets.forEach(async target => {

    // Make a copy of the source file so we can substitute some elements before parsing
    const TMP_FILE_PATH = path.join(TMP_DIR_PATH, target.source);
    await fs.copy(
      path.join(OUT_DIR, target.name, target.source),
      TMP_FILE_PATH,
      {overwrite: true}
    );

    // Add TypeScript WebMidi namespace before native Web MIDI API objects
    WEB_MIDI_API_CLASSES.forEach(element => {

      const options = {
        files: TMP_FILE_PATH,
        from: new RegExp("{" + element + "}", "g"),
        to: () => `{WebMidi.${element}}`
      };

      replace.sync(options);

    });

    // Replace callback type by simply "EventEmitterCallback" (this is needed because tsc does not
    // support tilde character in types. See below...
    replace.sync({
      files: TMP_FILE_PATH,
      from: new RegExp("{EventEmitter~callback}", "g"),
      to: () => "{EventEmitterCallback}"
    });

    // Generate declaration file
    const cmd = "npx -p typescript tsc " + TMP_FILE_PATH +
      " --declaration --allowJs --emitDeclarationOnly" +
      " --module " + target.type +
      " --lib ES2020,DOM --outDir " + path.join(OUT_DIR, target.name) ;
    await system(cmd);

    // Path to current .d.ts file
    const file = path.join(OUT_DIR, target.name, target.source.replace(".js", ".d.ts"));

    // Remove readonly flag
    const options = {
      files: [file],
      from: /readonly /g,
      to: ""
    };
    await replace(options);





    // // Inject correct definitions for Input class
    // await replace({
    //   files: [file],
    //   from: /export class Input extends EventEmitter \{/,
    //   to: fs.readFileSync(
    //     path.join(__dirname, "injections", "Input.txt"), {encoding: "utf8", flag: "r"}
    //   )
    // });
    //






    // Prepend header for DefinitelyTyped
    await prependFile(file, HEADER);
    log("Saved " + target.type + " TypeScript declaration file to '" + file + "'");

    // // Inject EventEmitterCallback type declaration
    // fs.appendFileSync(
    //   file,
    //   `\n\n` +
    //   `// This is automatically injected to fix a bug with the TypeScript compiler\n` +
    //   `export type EventEmitterCallback = (...args: any[]) => void;\n`
    //   // dans Tone.js, ils utilisent:
    //   //`export type EventEmitterCallback<A extends any[] = any[]> = (...args: A) => void;`
    // );

    // Commit
    if (commit) {
      await git.add([file]);
      await git.commit("Generated by TypeScript compiler on " + moment().format());
      await git.push();
    }

  });

}

// Execute and catch errors if any (in red)
execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m"));

function log(message) {
  console.info("\x1b[32m", message, "\x1b[0m");
}


================================================
FILE: scripts/website/deploy.js
================================================
// This script copies the files generated by Docusaurus in /website/build to the root of the
// 'gh-pages' branch. This makes them available at djipco.github.io/webmidi

// Importation
const fs = require("fs-extra");
const fsPromises = require("fs").promises;
const git = require("simple-git")();
const moment = require("moment");
const os = require("os");
const path = require("path");
const rimraf = require("@alexbinary/rimraf");

// Configuration
const TARGET_BRANCH = "gh-pages";
const SOURCE_DIR = path.join(process.cwd(), "website", "build");

async function execute() {

  const TMP_DIR = await fsPromises.mkdtemp(path.join(os.tmpdir(), "webmidi-website-"));

  // Get list of files and directories inside the Docusaurus build directory
  let files = await fsPromises.readdir(SOURCE_DIR);

  // Get rid of .DS_Store and other unnecessary files
  files = files.filter(file => !file.startsWith("."));

  // Copy files to tmp directory
  for (const file of files) {
    const source = path.join(SOURCE_DIR, file);
    const target = path.join(TMP_DIR, file);
    await fs.copy(source, target, {overwrite: true});
  }
  log("Copied website files to temp dir: " + TMP_DIR);

  // Get current branch (so we can come back to it later)
  let results = await git.branch();
  const ORIGINAL_BRANCH = results.current;

  // Switch to target branch
  log(`Switching from '${ORIGINAL_BRANCH}' branch to '${TARGET_BRANCH}' branch`);
  await git.checkout(TARGET_BRANCH);

  // Copy content of tmp dir to final destination and commit
  for (const file of files) {
    const source = path.join(TMP_DIR, file);
    const target = path.join(process.cwd(), file);
    await fs.copy(source, target, {overwrite: true});
    await git.add([target]);
    await git.commit("Updated on: " + moment().format(), [target]);
  }

  await git.push();
  log(`Website files committed to '${TARGET_BRANCH}' branch and pushed to remote`);

  // Remove temp files
  rimraf(TMP_DIR);
  log(`Temp directory removed`);

  // Come back to original branch
  log(`Switching back to '${ORIGINAL_BRANCH}' branch`);
  await git.checkout(ORIGINAL_BRANCH);

}

function log(message) {
  console.info("\x1b[32m", message, "\x1b[0m");
}

// Execution
execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m"));


================================================
FILE: src/Enumerations.js
================================================
/**
 * The `Enumerations` class contains enumerations and arrays of elements used throughout the
 * library. All its properties are static and should be referenced using the class name. For
 * example: `Enumerations.CHANNEL_MESSAGES`.
 *
 * @license Apache-2.0
 * @since 3.0.0
 */
export class Enumerations {

  /**
   * @enum {Object.<string, number>}
   * @readonly
   * @deprecated since 3.1 (use Enumerations.CHANNEL_MESSAGES instead)
   * @private
   * @static
   */
  static get MIDI_CHANNEL_MESSAGES() {

    if (this.validation) {
      console.warn(
        "The MIDI_CHANNEL_MESSAGES enum has been deprecated. Use the " +
        "Enumerations.CHANNEL_MESSAGES enum instead."
      );
    }

    return Enumerations.CHANNEL_MESSAGES;

  }

  /**
   * Enumeration of all MIDI channel message names and their associated 4-bit numerical value:
   *
   * | Message Name        | Hexadecimal | Decimal |
   * |---------------------|-------------|---------|
   * | `noteoff`           | 0x8         | 8       |
   * | `noteon`            | 0x9         | 9       |
   * | `keyaftertouch`     | 0xA         | 10      |
   * | `controlchange`     | 0xB         | 11      |
   * | `programchange`     | 0xC         | 12      |
   * | `channelaftertouch` | 0xD         | 13      |
   * | `pitchbend`         | 0xE         | 14      |
   *
   * @enum {Object.<string, number>}
   * @readonly
   * @since 3.1
   * @static
   */
  static get CHANNEL_MESSAGES() {

    return {
      noteoff: 0x8,           // 8
      noteon: 0x9,            // 9
      keyaftertouch: 0xA,     // 10
      controlchange: 0xB,     // 11
      programchange: 0xC,     // 12
      channelaftertouch: 0xD, // 13
      pitchbend: 0xE          // 14
    };

  }

  /**
   * A simple array of the 16 valid MIDI channel numbers (`1` to `16`):
   *
   * @type {number[]}
   * @readonly
   * @since 3.1
   * @static
   */
  static get CHANNEL_NUMBERS() {
    return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
  }

  /**
   * @type {number[]}
   * @readonly
   * @deprecated since 3.1 (use Enumerations.CHANNEL_NUMBERS instead)
   * @private
   * @static
   */
  static get MIDI_CHANNEL_NUMBERS() {

    if (this.validation) {
      console.warn(
        "The MIDI_CHANNEL_NUMBERS array has been deprecated. Use the " +
        "Enumerations.CHANNEL_NUMBERS array instead."
      );
    }

    return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];

  }

  /**
   * Enumeration of all MIDI channel mode message names and their associated numerical value:
   *
   *
   * | Message Name          | Hexadecimal | Decimal |
   * |-----------------------|-------------|---------|
   * | `allsoundoff`         | 0x78        | 120     |
   * | `resetallcontrollers` | 0x79        | 121     |
   * | `localcontrol`        | 0x7A        | 122     |
   * | `allnotesoff`         | 0x7B        | 123     |
   * | `omnimodeoff`         | 0x7C        | 124     |
   * | `omnimodeon`          | 0x7D        | 125     |
   * | `monomodeon`          | 0x7E        | 126     |
   * | `polymodeon`          | 0x7F        | 127     |
   *
   * @enum {Object.<string, number>}
   * @readonly
   * @since 3.1
   * @static
   */
  static get CHANNEL_MODE_MESSAGES() {

    return {
      allsoundoff: 120,
      resetallcontrollers: 121,
      localcontrol: 122,
      allnotesoff: 123,
      omnimodeoff: 124,
      omnimodeon: 125,
      monomodeon: 126,
      polymodeon: 127
    };

  }

  /**
   * @enum {Object.<string, number>}
   * @deprecated since 3.1 (use Enumerations.CHANNEL_MODE_MESSAGES instead)
   * @private
   * @readonly
   * @static
   */
  static get MIDI_CHANNEL_MODE_MESSAGES() {

    if (this.validation) {
      console.warn(
        "The MIDI_CHANNEL_MODE_MESSAGES enum has been deprecated. Use the " +
        "Enumerations.CHANNEL_MODE_MESSAGES enum instead."
      );
    }

    return Enumerations.CHANNEL_MODE_MESSAGES;

  }

  /**
   * @enum {Object.<string, number>}
   * @readonly
   * @static
   * @private
   * @deprecated since version 3.0.26 (use `CONTROL_CHANGE_MESSAGES` instead)
   */
  static get MIDI_CONTROL_CHANGE_MESSAGES() {

    if (this.validation) {
      console.warn(
        "The MIDI_CONTROL_CHANGE_MESSAGES enum has been deprecated. Use the " +
        "Enumerations.CONTROL_CHANGE_MESSAGES array instead."
      );
    }

    return {

      bankselectcoarse: 0,
      modulationwheelcoarse: 1,
      breathcontrollercoarse: 2,
      controller3: 3,
      footcontrollercoarse: 4,
      portamentotimecoarse: 5,
      dataentrycoarse: 6,
      volumecoarse: 7,
      balancecoarse: 8,
      controller9: 9,
      pancoarse: 10,
      expressioncoarse: 11,
      effectcontrol1coarse: 12,
      effectcontrol2coarse: 13,
      controller14: 14,
      controller15: 15,
      generalpurposeslider1: 16,
      generalpurposeslider2: 17,
      generalpurposeslider3: 18,
      generalpurposeslider4: 19,
      controller20: 20,
      controller21: 21,
      controller22: 22,
      controller23: 23,
      controller24: 24,
      controller25: 25,
      controller26: 26,
      controller27: 27,
      controller28: 28,
      controller29: 29,
      controller30: 30,
      controller31: 31,
      bankselectfine: 32,
      modulationwheelfine: 33,
      breathcontrollerfine: 34,
      controller35: 35,
      footcontrollerfine: 36,
      portamentotimefine: 37,
      dataentryfine: 38,
      volumefine: 39,
      balancefine: 40,
      controller41: 41,
      panfine: 42,
      expressionfine: 43,
      effectcontrol1fine: 44,
      effectcontrol2fine: 45,
      controller46: 46,
      controller47: 47,
      controller48: 48,
      controller49: 49,
      controller50: 50,
      controller51: 51,
      controller52: 52,
      controller53: 53,
      controller54: 54,
      controller55: 55,
      controller56: 56,
      controller57: 57,
      controller58: 58,
      controller59: 59,
      controller60: 60,
      controller61: 61,
      controller62: 62,
      controller63: 63,
      holdpedal: 64,
      portamento: 65,
      sustenutopedal: 66,
      softpedal: 67,
      legatopedal: 68,
      hold2pedal: 69,
      soundvariation: 70,
      resonance: 71,
      soundreleasetime: 72,
      soundattacktime: 73,
      brightness: 74,
      soundcontrol6: 75,
      soundcontrol7: 76,
      soundcontrol8: 77,
      soundcontrol9: 78,
      soundcontrol10: 79,
      generalpurposebutton1: 80,
      generalpurposebutton2: 81,
      generalpurposebutton3: 82,
      generalpurposebutton4: 83,
      controller84: 84,
      controller85: 85,
      controller86: 86,
      controller87: 87,
      controller88: 88,
      controller89: 89,
      controller90: 90,
      reverblevel: 91,
      tremololevel: 92,
      choruslevel: 93,
      celestelevel: 94,
      phaserlevel: 95,
      databuttonincrement: 96,
      databuttondecrement: 97,
      nonregisteredparametercoarse: 98,
      nonregisteredparameterfine: 99,
      registeredparametercoarse: 100,
      registeredparameterfine: 101,
      controller102: 102,
      controller103: 103,
      controller104: 104,
      controller105: 105,
      controller106: 106,
      controller107: 107,
      controller108: 108,
      controller109: 109,
      controller110: 110,
      controller111: 111,
      controller112: 112,
      controller113: 113,
      controller114: 114,
      controller115: 115,
      controller116: 116,
      controller117: 117,
      controller118: 118,
      controller119: 119,
      allsoundoff: 120,
      resetallcontrollers: 121,
      localcontrol: 122,
      allnotesoff: 123,
      omnimodeoff: 124,
      omnimodeon: 125,
      monomodeon: 126,
      polymodeon: 127

    };

  }

  /**
   * An array of objects, ordered by control number, describing control change messages. Each object
   * in the array has 3 properties with some objects having a fourth one (`position`) :
   *
   *  * `number`: MIDI control number (0-127);
   *  * `name`: name of emitted event (eg: `bankselectcoarse`, `choruslevel`, etc) that can be
   *  listened to;
   *  * `description`: user-friendly description of the controller's purpose;
   *  * `position` (optional): whether this controller's value should be considered an `msb` or
   *  `lsb`
   *
   * Not all controllers have a predefined function. For those that don't, `name` is the word
   * "controller" followed by the number (e.g. `controller112`).
   *
   * | Event name                     | Control Number |
   * |--------------------------------|----------------|
   * | `bankselectcoarse`             | 0              |
   * | `modulationwheelcoarse`        | 1              |
   * | `breathcontrollercoarse`       | 2              |
   * | `controller3`                  | 3              |
   * | `footcontrollercoarse`         | 4              |
   * | `portamentotimecoarse`         | 5              |
   * | `dataentrycoarse`              | 6              |
   * | `volumecoarse`                 | 7              |
   * | `balancecoarse`                | 8              |
   * | `controller9`                  | 9              |
   * | `pancoarse`                    | 10             |
   * | `expressioncoarse`             | 11             |
   * | `effectcontrol1coarse`         | 12             |
   * | `effectcontrol2coarse`         | 13             |
   * | `controller14`                 | 14             |
   * | `controller15`                 | 15             |
   * | `generalpurposecontroller1`    | 16             |
   * | `generalpurposecontroller2`    | 17             |
   * | `generalpurposecontroller3`    | 18             |
   * | `generalpurposecontroller4`    | 19             |
   * | `controller20`                 | 20             |
   * | `controller21`                 | 21             |
   * | `controller22`                 | 22             |
   * | `controller23`                 | 23             |
   * | `controller24`                 | 24             |
   * | `controller25`                 | 25             |
   * | `controller26`                 | 26             |
   * | `controller27`                 | 27             |
   * | `controller28`                 | 28             |
   * | `controller29`                 | 29             |
   * | `controller30`                 | 30             |
   * | `controller31`                 | 31             |
   * | `bankselectfine`               | 32             |
   * | `modulationwheelfine`          | 33             |
   * | `breathcontrollerfine`         | 34             |
   * | `controller35`                 | 35             |
   * | `footcontrollerfine`           | 36             |
   * | `portamentotimefine`           | 37             |
   * | `dataentryfine`                | 38             |
   * | `channelvolumefine`            | 39             |
   * | `balancefine`                  | 40             |
   * | `controller41`                 | 41             |
   * | `panfine`                      | 42             |
   * | `expressionfine`               | 43             |
   * | `effectcontrol1fine`           | 44             |
   * | `effectcontrol2fine`           | 45             |
   * | `controller46`                 | 46             |
   * | `controller47`                 | 47             |
   * | `controller48`                 | 48             |
   * | `controller49`                 | 49             |
   * | `controller50`                 | 50             |
   * | `controller51`                 | 51             |
   * | `controller52`                 | 52             |
   * | `controller53`                 | 53             |
   * | `controller54`                 | 54             |
   * | `controller55`                 | 55             |
   * | `controller56`                 | 56             |
   * | `controller57`                 | 57             |
   * | `controller58`                 | 58             |
   * | `controller59`                 | 59             |
   * | `controller60`                 | 60             |
   * | `controller61`                 | 61             |
   * | `controller62`                 | 62             |
   * | `controller63`                 | 63             |
   * | `damperpedal`                  | 64             |
   * | `portamento`                   | 65             |
   * | `sostenuto`                    | 66             |
   * | `softpedal`                    | 67             |
   * | `legatopedal`                  | 68             |
   * | `hold2`                        | 69             |
   * | `soundvariation`               | 70             |
   * | `resonance`                    | 71             |
   * | `releasetime`                  | 72             |
   * | `attacktime`                   | 73             |
   * | `brightness`                   | 74             |
   * | `decaytime`                    | 75             |
   * | `vibratorate`                  | 76             |
   * | `vibratodepth`                 | 77             |
   * | `vibratodelay`                 | 78             |
   * | `controller79`                 | 79             |
   * | `generalpurposecontroller5`    | 80             |
   * | `generalpurposecontroller6`    | 81             |
   * | `generalpurposecontroller7`    | 82             |
   * | `generalpurposecontroller8`    | 83             |
   * | `portamentocontrol`            | 84             |
   * | `controller85`                 | 85             |
   * | `controller86`                 | 86             |
   * | `controller87`                 | 87             |
   * | `highresolutionvelocityprefix` | 88             |
   * | `controller89`                 | 89             |
   * | `controller90`                 | 90             |
   * | `effect1depth`                 | 91             |
   * | `effect2depth`                 | 92             |
   * | `effect3depth`                 | 93             |
   * | `effect4depth`                 | 94             |
   * | `effect5depth`                 | 95             |
   * | `dataincrement`                | 96             |
   * | `datadecrement`                | 97             |
   * | `nonregisteredparameterfine`   | 98             |
   * | `nonregisteredparametercoarse` | 99             |
   * | `nonregisteredparameterfine`   | 100            |
   * | `registeredparametercoarse`    | 101            |
   * | `controller102`                | 102            |
   * | `controller103`                | 103            |
   * | `controller104`                | 104            |
   * | `controller105`                | 105            |
   * | `controller106`                | 106            |
   * | `controller107`                | 107            |
   * | `controller108`                | 108            |
   * | `controller109`                | 109            |
   * | `controller110`                | 110            |
   * | `controller111`                | 111            |
   * | `controller112`                | 112            |
   * | `controller113`                | 113            |
   * | `controller114`                | 114            |
   * | `controller115`                | 115            |
   * | `controller116`                | 116            |
   * | `controller117`                | 117            |
   * | `controller118`                | 118            |
   * | `controller119`                | 119            |
   * | `allsoundoff`                  | 120            |
   * | `resetallcontrollers`          | 121            |
   * | `localcontrol`                 | 122            |
   * | `allnotesoff`                  | 123            |
   * | `omnimodeoff`                  | 124            |
   * | `omnimodeon`                   | 125            |
   * | `monomodeon`                   | 126            |
   * | `polymodeon`                   | 127            |
   *
   * @type {object[]}
   * @readonly
   * @static
   * @since 3.1
   */
  static get CONTROL_CHANGE_MESSAGES() {

    return [
      {
        number: 0,
        name: "bankselectcoarse",
        description: "Bank Select (Coarse)",
        position: "msb"
      },
      {
        number: 1,
        name: "modulationwheelcoarse",
        description: "Modulation Wheel (Coarse)",
        position: "msb"
      },
      {
        number: 2,
        name: "breathcontrollercoarse",
        description: "Breath Controller (Coarse)",
        position: "msb"
      },
      {
        number: 3,
        name: "controller3",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 4,
        name: "footcontrollercoarse",
        description: "Foot Controller (Coarse)",
        position: "msb"
      },
      {
        number: 5,
        name: "portamentotimecoarse",
        description: "Portamento Time (Coarse)",
        position: "msb"
      },
      {
        number: 6,
        name: "dataentrycoarse",
        description: "Data Entry (Coarse)",
        position: "msb"
      },
      {
        number: 7,
        name: "volumecoarse",
        description: "Channel Volume (Coarse)",
        position: "msb"
      },
      {
        number: 8,
        name: "balancecoarse",
        description: "Balance (Coarse)",
        position: "msb"
      },
      {
        number: 9,
        name: "controller9",
        description: "Controller 9 (Coarse)",
        position: "msb"
      },
      {
        number: 10,
        name: "pancoarse",
        description: "Pan (Coarse)",
        position: "msb"
      },
      {
        number: 11,
        name: "expressioncoarse",
        description: "Expression Controller (Coarse)",
        position: "msb"
      },
      {
        number: 12,
        name: "effectcontrol1coarse",
        description: "Effect Control 1 (Coarse)",
        position: "msb"
      },
      {
        number: 13,
        name: "effectcontrol2coarse",
        description: "Effect Control 2 (Coarse)",
        position: "msb"
      },
      {
        number: 14,
        name: "controller14",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 15,
        name: "controller15",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 16,
        name: "generalpurposecontroller1",
        description: "General Purpose Controller 1 (Coarse)",
        position: "msb"
      },
      {
        number: 17,
        name: "generalpurposecontroller2",
        description: "General Purpose Controller 2 (Coarse)",
        position: "msb"
      },
      {
        number: 18,
        name: "generalpurposecontroller3",
        description: "General Purpose Controller 3 (Coarse)",
        position: "msb"
      },
      {
        number: 19,
        name: "generalpurposecontroller4",
        description: "General Purpose Controller 4 (Coarse)",
        position: "msb"
      },
      {
        number: 20,
        name: "controller20",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 21,
        name: "controller21",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 22,
        name: "controller22",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 23,
        name: "controller23",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 24,
        name: "controller24",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 25,
        name: "controller25",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 26,
        name: "controller26",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 27,
        name: "controller27",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 28,
        name: "controller28",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 29,
        name: "controller29",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 30,
        name: "controller30",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 31,
        name: "controller31",
        description: "Undefined",
        position: "msb"
      },
      {
        number: 32,
        name: "bankselectfine",
        description: "Bank Select (Fine)",
        position: "lsb"
      },
      {
        number: 33,
        name: "modulationwheelfine",
        description: "Modulation Wheel (Fine)",
        position: "lsb"
      },
      {
        number: 34,
        name: "breathcontrollerfine",
        description: "Breath Controller (Fine)",
        position: "lsb"
      },
      {
        number: 35,
        name: "controller35",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 36,
        name: "footcontrollerfine",
        description: "Foot Controller (Fine)",
        position: "lsb"
      },
      {
        number: 37,
        name: "portamentotimefine",
        description: "Portamento Time (Fine)",
        position: "lsb"
      },
      {
        number: 38,
        name: "dataentryfine",
        description: "Data Entry (Fine)",
        position: "lsb"
      },
      {
        number: 39,
        name: "channelvolumefine",
        description: "Channel Volume (Fine)",
        position: "lsb"
      },
      {
        number: 40,
        name: "balancefine",
        description: "Balance (Fine)",
        position: "lsb"
      },
      {
        number: 41,
        name: "controller41",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 42,
        name: "panfine",
        description: "Pan (Fine)",
        position: "lsb"
      },
      {
        number: 43,
        name: "expressionfine",
        description: "Expression Controller (Fine)",
        position: "lsb"
      },
      {
        number: 44,
        name: "effectcontrol1fine",
        description: "Effect control 1 (Fine)",
        position: "lsb"
      },
      {
        number: 45,
        name: "effectcontrol2fine",
        description: "Effect control 2 (Fine)",
        position: "lsb"
      },
      {
        number: 46,
        name: "controller46",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 47,
        name: "controller47",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 48,
        name: "controller48",
        description: "General Purpose Controller 1 (Fine)",
        position: "lsb"
      },
      {
        number: 49,
        name: "controller49",
        description: "General Purpose Controller 2 (Fine)",
        position: "lsb"
      },
      {
        number: 50,
        name: "controller50",
        description: "General Purpose Controller 3 (Fine)",
        position: "lsb"
      },
      {
        number: 51,
        name: "controller51",
        description: "General Purpose Controller 4 (Fine)",
        position: "lsb"
      },
      {
        number: 52,
        name: "controller52",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 53,
        name: "controller53",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 54,
        name: "controller54",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 55,
        name: "controller55",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 56,
        name: "controller56",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 57,
        name: "controller57",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 58,
        name: "controller58",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 59,
        name: "controller59",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 60,
        name: "controller60",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 61,
        name: "controller61",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 62,
        name: "controller62",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 63,
        name: "controller63",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 64,
        name: "damperpedal",
        description: "Damper Pedal On/Off"
      },
      {
        number: 65,
        name: "portamento",
        description: "Portamento On/Off"
      },
      {
        number: 66,
        name: "sostenuto",
        description: "Sostenuto On/Off"
      },
      {
        number: 67,
        name: "softpedal",
        description: "Soft Pedal On/Off"
      },
      {
        number: 68,
        name: "legatopedal",
        description: "Legato Pedal On/Off"
      },
      {
        number: 69,
        name: "hold2",
        description: "Hold 2 On/Off"
      },
      {
        number: 70,
        name: "soundvariation",
        description: "Sound Variation",
        position: "lsb"
      },
      {
        number: 71,
        name: "resonance",
        description: "Resonance",
        position: "lsb"
      },
      {
        number: 72,
        name: "releasetime",
        description: "Release Time",
        position: "lsb"
      },
      {
        number: 73,
        name: "attacktime",
        description: "Attack Time",
        position: "lsb"
      },
      {
        number: 74,
        name: "brightness",
        description: "Brightness",
        position: "lsb"
      },
      {
        number: 75,
        name: "decaytime",
        description: "Decay Time",
        position: "lsb"
      },
      {
        number: 76,
        name: "vibratorate",
        description: "Vibrato Rate",
        position: "lsb"
      },
      {
        number: 77,
        name: "vibratodepth",
        description: "Vibrato Depth",
        position: "lsb"
      },
      {
        number: 78,
        name: "vibratodelay",
        description: "Vibrato Delay",
        position: "lsb"
      },
      {
        number: 79,
        name: "controller79",
        description: "Undefined",
        position: "lsb"
      },
      {
        number: 80,
        name: "generalpurposecontroller5",
        description: "General Purpose Controller 5",
        position: "lsb"
      },
      {
        number: 81,
        name: "generalpurposecontroller6",
        description: "General Purpose Controller 6",
        position: "lsb"
      },
      {
        number: 82,
        name: "generalpurposecontroller7",
        description: "General Purpose Controller 7",
        position: "lsb"
      },
      {
        number: 83,
        name: "generalpurposecontroller8",
        description: "General Purpose Controller 8",
        position: "lsb"
      },
      {
        number: 84,
        name: "portamentocontrol",
        description: "Portamento Control",
        position: "lsb"
      },
      {
        number: 85,
        name: "controller85",
        description: "Undefined"
      },
      {
        number: 86,
        name: "controller86",
        description: "Undefined"
      },
      {
        number: 87,
        name: "controller87",
        description: "Undefined"
      },
      {
        number: 88,
        name: "highresolutionvelocityprefix",
        description: "High Resolution Velocity Prefix",
        position: "lsb"
      },
      {
        number: 89,
        name: "controller89",
        description: "Undefined"
      },
      {
        number: 90,
        name: "controller90",
        description: "Undefined"
      },
      {
        number: 91,
        name: "effect1depth",
        description: "Effects 1 Depth (Reverb Send Level)"
      },
      {
        number: 92,
        name: "effect2depth",
        description: "Effects 2 Depth"
      },
      {
        number: 93,
        name: "effect3depth",
        description: "Effects 3 Depth (Chorus Send Level)"
      },
      {
        number: 94,
        name: "effect4depth",
        description: "Effects 4 Depth"
      },
      {
        number: 95,
        name: "effect5depth",
        description: "Effects 5 Depth"
      },
      {
        number: 96,
        name: "dataincrement",
        description: "Data Increment"
      },
      {
        number: 97,
        name: "datadecrement",
        description: "Data Decrement"
      },
      {
        number: 98,
        name: "nonregisteredparameterfine",
        description: "Non-Registered Parameter Number (Fine)",
        position: "lsb"
      },
      {
        number: 99,
        name: "nonregisteredparametercoarse",
        description: "Non-Registered Parameter Number (Coarse)",
        position: "msb"
      },
      {
        number: 100,
        name: "registeredparameterfine",
        description: "Registered Parameter Number (Fine)",
        position: "lsb"
      },
      {
        number: 101,
        name: "registeredparametercoarse",
        description: "Registered Parameter Number (Coarse)",
        position: "msb"
      },
      {
        number: 102,
        name: "controller102",
        description: "Undefined"
      },
      {
        number: 103,
        name: "controller103",
        description: "Undefined"
      },
      {
        number: 104,
        name: "controller104",
        description: "Undefined"
      },
      {
        number: 105,
        name: "controller105",
        description: "Undefined"
      },
      {
        number: 106,
        name: "controller106",
        description: "Undefined"
      },
      {
        number: 107,
        name: "controller107",
        description: "Undefined"
      },
      {
        number: 108,
        name: "controller108",
        description: "Undefined"
      },
      {
        number: 109,
        name: "controller109",
        description: "Undefined"
      },
      {
        number: 110,
        name: "controller110",
        description: "Undefined"
      },
      {
        number: 111,
        name: "controller111",
        description: "Undefined"
      },
      {
        number: 112,
        name: "controller112",
        description: "Undefined"
      },
      {
        number: 113,
        name: "controller113",
        description: "Undefined"
      },
      {
        number: 114,
        name: "controller114",
        description: "Undefined"
      },
      {
        number: 115,
        name: "controller115",
        description: "Undefined"
      },
      {
        number: 116,
        name: "controller116",
        description: "Undefined"
      },
      {
        number: 117,
        name: "controller117",
        description: "Undefined"
      },
      {
        number: 118,
        name: "controller118",
        description: "Undefined"
      },
      {
        number: 119,
        name: "controller119",
        description: "Undefined"
      },
      {
        number: 120,
        name: "allsoundoff",
        description: "All Sound Off"
      },
      {
        number: 121,
        name: "resetallcontrollers",
        description: "Reset All Controllers"
      },
      {
        number: 122,
        name: "localcontrol",
        description: "Local Control On/Off"
      },
      {
        number: 123,
        name: "allnotesoff",
        description: "All Notes Off"
      },
      {
        number: 124,
        name: "omnimodeoff",
        description: "Omni Mode Off"
      },
      {
        number: 125,
        name: "omnimodeon",
        description: "Omni Mode On"
      },
      {
        number: 126,
        name: "monomodeon",
        description: "Mono Mode On"
      },
      {
        number: 127,
        name: "polymodeon",
        description: "Poly Mode On"
      },
    ];

  }

  /**
   * Enumeration of all MIDI registered parameters and their associated pair of numerical values.
   * MIDI registered parameters extend the original list of control change messages. Currently,
   * there are only a limited number of them:
   *
   *
   * | Control Function             | [LSB, MSB]   |
   * |------------------------------|--------------|
   * | `pitchbendrange`             | [0x00, 0x00] |
   * | `channelfinetuning`          | [0x00, 0x01] |
   * | `channelcoarsetuning`        | [0x00, 0x02] |
   * | `tuningprogram`              | [0x00, 0x03] |
   * | `tuningbank`                 | [0x00, 0x04] |
   * | `modulationrange`            | [0x00, 0x05] |
   * | `azimuthangle`               | [0x3D, 0x00] |
   * | `elevationangle`             | [0x3D, 0x01] |
   * | `gain`                       | [0x3D, 0x02] |
   * | `distanceratio`              | [0x3D, 0x03] |
   * | `maximumdistance`            | [0x3D, 0x04] |
   * | `maximumdistancegain`        | [0x3D, 0x05] |
   * | `referencedistanceratio`     | [0x3D, 0x06] |
   * | `panspreadangle`             | [0x3D, 0x07] |
   * | `rollangle`                  | [0x3D, 0x08] |
   *
   * @enum {Object.<string, number[]>}
   * @readonly
   * @since 3.1
   * @static
   */
  static get REGISTERED_PARAMETERS() {

    return {
      pitchbendrange: [0x00, 0x00],
      channelfinetuning: [0x00, 0x01],
      channelcoarsetuning: [0x00, 0x02],
      tuningprogram: [0x00, 0x03],
      tuningbank: [0x00, 0x04],

      modulationrange: [0x00, 0x05],
      azimuthangle: [0x3D, 0x00],
      elevationangle: [0x3D, 0x01],
      gain: [0x3D, 0x02],
      distanceratio: [0x3D, 0x03],
      maximumdistance: [0x3D, 0x04],
      maximumdistancegain: [0x3D, 0x05],
      referencedistanceratio: [0x3D, 0x06],
      panspreadangle: [0x3D, 0x07],
      rollangle: [0x3D, 0x08]
    };

  }

  /**
   * @enum {Object.<string, number[]>}
   * @readonly
   * @deprecated since 3.1 (use Enumerations.REGISTERED_PARAMETERS instead)
   * @private
   * @static
   */
  static get MIDI_REGISTERED_PARAMETERS() {

    if (this.validation) {
      console.warn(
        "The MIDI_REGISTERED_PARAMETERS enum has been deprecated. Use the " +
        "Enumerations.REGISTERED_PARAMETERS enum instead."
      );
    }

    return Enumerations.MIDI_REGISTERED_PARAMETERS;

  }

  /**
   * Enumeration of all valid MIDI system messages and matching numerical values. This library also
   * uses two additional custom messages.
   *
   * **System Common Messages**
   *
   * | Function               | Hexadecimal | Decimal |
   * |------------------------|-------------|---------|
   * | `sysex`                | 0xF0        |  240    |
   * | `timecode`             | 0xF1        |  241    |
   * | `songposition`         | 0xF2        |  242    |
   * | `songselect`           | 0xF3        |  243    |
   * | `tunerequest`          | 0xF6        |  246    |
   * | `sysexend`             | 0xF7        |  247    |
   *
   * The `sysexend` message is never actually received. It simply ends a sysex stream.
   *
   * **System Real-Time Messages**
   *
   * | Function               | Hexadecimal | Decimal |
   * |------------------------|-------------|---------|
   * | `clock`                | 0xF8        |  248    |
   * | `start`                | 0xFA        |  250    |
   * | `continue`             | 0xFB        |  251    |
   * | `stop`                 | 0xFC        |  252    |
   * | `activesensing`        | 0xFE        |  254    |
   * | `reset`                | 0xFF        |  255    |
   *
   * Values 249 and 253 are relayed by the
   * [Web MIDI API](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API) but they do not
   * serve any specific purpose. The
   * [MIDI 1.0 spec](https://www.midi.org/specifications/item/table-1-summary-of-midi-message)
   * simply states that they are undefined/reserved.
   *
   * **Custom Messages**
   *
   * These two messages are mostly for internal use. They are not MIDI messages and cannot be sent
   * or forwarded.
   *
   * | Function               | Hexadecimal | Decimal |
   * |------------------------|-------------|---------|
   * | `midimessage`          |             |  0      |
   * | `unknownsystemmessage` |             |  -1     |
   *
   * @enum {Object.<string, number>}
   * @readonly
   * @since 3.1
   * @static
   */
  static get SYSTEM_MESSAGES() {

    return {

      // System common messages
      sysex: 0xF0,            // 240
      timecode: 0xF1,         // 241
      songposition: 0xF2,     // 242
      songselect: 0xF3,       // 243
      tunerequest: 0xF6,      // 246
      tuningrequest: 0xF6,    // for backwards-compatibility (deprecated in version 3.0)
      sysexend: 0xF7,         // 247 (never actually received - simply ends a sysex)

      // System real-time messages
      clock: 0xF8,            // 248
      start: 0xFA,            // 250
      continue: 0xFB,         // 251
      stop: 0xFC,             // 252
      activesensing: 0xFE,    // 254
      reset: 0xFF,            // 255

      // Custom WebMidi.js messages
      midimessage: 0,
      unknownsystemmessage: -1

    };

  }

  /**
   * @enum {Object.<string, number>}
   * @readonly
   * @deprecated since 3.1 (use Enumerations.SYSTEM_MESSAGES instead)
   * @private
   * @static
   */
  static get MIDI_SYSTEM_MESSAGES() {

    if (this.validation) {
      console.warn(
        "The MIDI_SYSTEM_MESSAGES enum has been deprecated. Use the " +
        "Enumerations.SYSTEM_MESSAGES enum instead."
      );
    }

    return Enumerations.SYSTEM_MESSAGES;

  }

  /**
   * Array of channel-specific event names that can be listened for. This includes channel mode
   * events and RPN/NRPN events.
   *
   * @type {string[]}
   * @readonly
   */
  static get CHANNEL_EVENTS() {

    return [

      // MIDI channel message events
      "noteoff",
      "controlchange",
      "noteon",
      "keyaftertouch",
      "programchange",
      "channelaftertouch",
      "pitchbend",

      // MIDI channel mode events
      "allnotesoff",
      "allsoundoff",
      "localcontrol",
      "monomode",
      "omnimode",
      "resetallcontrollers",

      // RPN/NRPN events
      "nrpn",
      "nrpn-dataentrycoarse",
      "nrpn-dataentryfine",
      "nrpn-dataincrement",
      "nrpn-datadecrement",
      "rpn",
      "rpn-dataentrycoarse",
      "rpn-dataentryfine",
      "rpn-dataincrement",
      "rpn-datadecrement",

      // Legacy (remove in v4)
      "nrpn-databuttonincrement",
      "nrpn-databuttondecrement",
      "rpn-databuttonincrement",
      "rpn-databuttondecrement",

    ];
  }

}


================================================
FILE: src/Forwarder.js
================================================
import {Enumerations} from "./Enumerations.js";
import {Output} from "./Output.js";
import {WebMidi} from "./WebMidi.js";

/**
 * The `Forwarder` class allows the forwarding of MIDI messages to predetermined outputs. When you
 * call its [`forward()`](#forward) method, it will send the specified [`Message`](Message) object
 * to all the outputs listed in its [`destinations`](#destinations) property.
 *
 * If specific channels or message types have been defined in the [`channels`](#channels) or
 * [`types`](#types) properties, only messages matching the channels/types will be forwarded.
 *
 * While it can be manually instantiated, you are more likely to come across a `Forwarder` object as
 * the return value of the [`Input.addForwarder()`](Input#addForwarder) method.
 *
 * @license Apache-2.0
 * @since 3.0.0
 */
export class Forwarder {

  /**
   * Creates a `Forwarder` object.
   *
   * @param {Output|Output[]} [destinations=\[\]] An [`Output`](Output) object, or an array of such
   * objects, to forward the message to.
   *
   * @param {object} [options={}]
   * @param {string|string[]} [options.types=(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).
   * @param {number|number[]} [options.channels=[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`).
   */
  constructor(destinations = [], options = {}) {

    /**
     * An array of [`Output`](Output) objects to forward the message to.
     * @type {Output[]}
     */
    this.destinations = [];

    /**
     * An array of message types (`"noteon"`, `"controlchange"`, etc.) that must be matched in order
     * for messages to be forwarded. By default, this array includes all
     * [`Enumerations.SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) and
     * [`Enumerations.CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES).
     * @type {string[]}
     */
    this.types = [
      ...Object.keys(Enumerations.SYSTEM_MESSAGES),
      ...Object.keys(Enumerations.CHANNEL_MESSAGES)
    ];

    /**
     * An array of MIDI channel numbers that the message must match in order to be forwarded. By
     * default, this array includes all MIDI channels (`1` to `16`).
     * @type {number[]}
     */
    this.channels = Enumerations.MIDI_CHANNEL_NUMBERS;

    /**
     * Indicates whether message forwarding is currently suspended or not in this forwarder.
     * @type {boolean}
     */
    this.suspended = false;

    // Make sure parameters are arrays
    if (!Array.isArray(destinations)) destinations = [destinations];
    if (options.types && !Array.isArray(options.types)) options.types = [options.types];
    if (options.channels && !Array.isArray(options.channels)) options.channels = [options.channels];

    if (WebMidi.validation) {

      // Validate destinations
      destinations.forEach(destination => {
        if ( !(destination instanceof Output) ) {
          throw new TypeError("Destinations must be of type 'Output'.");
        }
      });

      // Validate types
      if (options.types !== undefined) {

        options.types.forEach(type => {
          if (
            ! Enumerations.SYSTEM_MESSAGES.hasOwnProperty(type) &&
            ! Enumerations.CHANNEL_MESSAGES.hasOwnProperty(type)
          ) {
            throw new TypeError("Type must be a valid message type.");
          }
        });

      }

      // Validate channels
      if (options.channels !== undefined) {

        options.channels.forEach(channel => {
          if (! Enumerations.MIDI_CHANNEL_NUMBERS.includes(channel) ) {
            throw new TypeError("MIDI channel must be between 1 and 16.");
          }
        });

      }

    }

    this.destinations = destinations;
    if (options.types) this.types = options.types;
    if (options.channels) this.channels = options.channels;

  }

  /**
   * Sends the specified message to the forwarder's destination(s) if it matches the specified
   * type(s) and channel(s).
   *
   * @param {Message} message The [`Message`](Message) object to forward.
   */
  forward(message) {

    // Abort if forwarding is currently suspended
    if (this.suspended) return;

    // Abort if this message type should not be forwarded
    if (!this.types.includes(message.type)) return;

    // Abort if this channel should not be forwarded
    if (message.channel && !this.channels.includes(message.channel)) return;

    // Forward
    this.destinations.forEach(destination => {
      if (WebMidi.validation && !(destination instanceof Output)) return;
      destination.send(message);
    });

  }

}


================================================
FILE: src/Input.js
================================================
import {Enumerations} from "./Enumerations.js";
import {EventEmitter} from "../node_modules/djipevents/src/djipevents.js";
import {Forwarder} from "./Forwarder.js";
import {InputChannel} from "./InputChannel.js";
import {Message} from "./Message.js";
import {Utilities} from "./Utilities.js";
import {WebMidi} from "./WebMidi.js";

/**
 * The `Input` class represents a single MIDI input port. This object is automatically instantiated
 * by the library according to the host's MIDI subsystem and does not need to be directly
 * instantiated. Instead, you can access all `Input` objects by referring to the
 * [`WebMidi.inputs`](WebMidi#inputs) array. You can also retrieve inputs by using methods such as
 * [`WebMidi.getInputByName()`](WebMidi#getInputByName) and
 * [`WebMidi.getInputById()`](WebMidi#getInputById).
 *
 * Note that a single MIDI device may expose several inputs and/or outputs.
 *
 * **Important**: the `Input` class does not directly fire channel-specific MIDI messages
 * (such as [`noteon`](InputChannel#event:noteon) or
 * [`controlchange`](InputChannel#event:controlchange), etc.). The [`InputChannel`](InputChannel)
 * object does that. However, you can still use the
 * [`Input.addListener()`](#addListener) method to listen to channel-specific events on multiple
 * [`InputChannel`](InputChannel) objects at once.
 *
 * @fires Input#opened
 * @fires Input#disconnected
 * @fires Input#closed
 * @fires Input#midimessage
 *
 * @fires Input#sysex
 * @fires Input#timecode
 * @fires Input#songposition
 * @fires Input#songselect
 * @fires Input#tunerequest
 * @fires Input#clock
 * @fires Input#start
 * @fires Input#continue
 * @fires Input#stop
 * @fires Input#activesensing
 * @fires Input#reset
 *
 * @fires Input#unknownmidimessage
 *
 * @extends EventEmitter
 * @license Apache-2.0
 */
export class Input extends EventEmitter {

  /**
   * Creates an `Input` object.
   *
   * @param {MIDIInput} midiInput [`MIDIInput`](https://developer.mozilla.org/en-US/docs/Web/API/MIDIInput)
   * object as provided by the MIDI subsystem (Web MIDI API).
   */
  constructor(midiInput) {

    super();

    /**
     * Reference to the actual MIDIInput object
     * @private
     */
    this._midiInput = midiInput;

    /**
     * @type {number}
     * @private
     */
    this._octaveOffset = 0;

    /**
     * Array containing the 16 [`InputChannel`](InputChannel) objects available for this `Input`. The
     * channels are numbered 1 through 16.
     *
     * @type {InputChannel[]}
     */
    this.channels = [];
    for (let i = 1; i <= 16; i++) this.channels[i] = new InputChannel(this, i);

    /**
     * @type {Forwarder[]}
     * @private
     */
    this._forwarders = [];

    // Setup listeners
    this._midiInput.onstatechange = this._onStateChange.bind(this);
    this._midiInput.onmidimessage = this._onMidiMessage.bind(this);

  }

  /**
   * Destroys the `Input` by removing all listeners, emptying the [`channels`](#channels) array and
   * unlinking the MIDI subsystem. This is mostly for internal use.
   *
   * @returns {Promise<void>}
   */
  async destroy() {
    this.removeListener();
    this.channels.forEach(ch => ch.destroy());
    this.channels = [];
    this._forwarders = [];
    if (this._midiInput) {
      this._midiInput.onstatechange = null;
      this._midiInput.onmidimessage = null;
    }
    await this.close();
    this._midiInput = null;
  }

  /**
   * Executed when a `"statechange"` event occurs.
   *
   * @param e
   * @private
   */
  _onStateChange(e) {

    let event = {
      timestamp: WebMidi.time,
      target: this,
      port: this // for consistency
    };

    if (e.port.connection === "open") {

      /**
       * Event emitted when the `Input` has been opened by calling the [`open()`]{@link #open}
       * method.
       *
       * @event Input#opened
       * @type {object}
       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in
       * milliseconds since the navigation start of the document).
       * @property {string} type `opened`
       * @property {Input} target The object that dispatched the event.
       * @property {Input} port The `Input` that triggered the event.
       */
      event.type = "opened";
      this.emit("opened", event);

    } else if (e.port.connection === "closed" && e.port.state === "connected") {

      /**
       * Event emitted when the `Input` has been closed by calling the
       * [`close()`]{@link #close} method.
       *
       * @event Input#closed
       * @type {object}
       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in
       * milliseconds since the navigation start of the document).
       * @property {string} type `closed`
       * @property {Input} target The object that dispatched the event.
       * @property {Input} port The `Input` that triggered the event.
       */
      event.type = "closed";
      this.emit("closed", event);

    } else if (e.port.connection === "closed" && e.port.state === "disconnected") {

      /**
       * Event emitted when the `Input` becomes unavailable. This event is typically fired
       * when the MIDI device is unplugged.
       *
       * @event Input#disconnected
       * @type {object}
       * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in
       * milliseconds since the navigation start of the document).
       * @property {string} type `disconnected`
       * @property {Input} port Object with properties describing the {@link Input} that was
       * disconnected. This is not the actual `Input` as it is no longer available.
       * @property {Input} target The object that dispatched the event.
       */
      event.type = "disconnected";
      event.port = {
        connection: e.port.connection,
        id: e.port.id,
        manufacturer: e.port.manufacturer,
        name: e.port.name,
        state: e.port.state,
        type: e.port.type
      };
      this.emit("disconnected", event);

    } else if (e.port.connection === "pending" && e.port.state === "disconnected") {
      // I don't see the need to forward that...
    } else {
      console.warn("This statechange event was not caught: ", e.port.connection, e.port.state);
    }

  }

  /**
   * Executed when a `"midimessage"` event is received
   * @param e
   * @private
   */
  _onMidiMessage(e) {

    // Create Message object from MIDI data
    const message = new Message(e.data);

    /**
     * Event emitted when any MIDI message is received on an `Input`.
     *
     * @event Input#midimessage
     *
     * @type {object}
     *
     * @property {Input} port The `Input` that triggered the event.
     * @property {Input} target The object that dispatched the event.
     * @property {Message} message A [`Message`](Message) object containing information about the
     * incoming MIDI message.
     * @property {number} timestamp The moment (DOMHighResTimeStamp) when the event occurred (in
     * milliseconds since the navigation start of the document).
     * @property {string} type `midimessage`
     *
     * @since 2.1
     */
    const event = {
      port: this,
      target: this,
      message: message,
      timestamp: e.timeStamp,
      type: "midimessage",

      data: message.data,           // @deprecated (will be removed in v4)
      rawData: message.data,        // @deprecated (will be removed in v4)
      statusByte: message.data[0],  // @deprecated (will be removed in v4)
      dataBytes: message.dataBytes  // @deprecated (will be removed in v4)
    };

    this.emit("midimessage", event);

    // Messages are forwarded to InputChannel if they are channel messages or parsed locally for
    // system messages.
    if (message.isSystemMessage) {           // system messages
      this._parseEvent(event);
    } else if (message.isChannelMessage) {   // channel messages
      this.channels[message.channel]._processMidiMessageEvent(event);
    }

    // Forward message if forwarders have been defined
    this._forwarders.forEach(forwarder => forwarder.forward(message));

  }

  /**
   * @private
   */
  _parseEvent(e) {

    // Make a shallow copy of the incoming event so we can use it as the new event.
    const event = Object.assign({}, e);
    event.type = event.message.type || "unknownmidimessage";

    // Add custom property for 'songselect'
    if (event.type === "songselect") {
      event.song = e.data[1] + 1; // deprecated
      event.value = e.data[1];
      event.rawValue = event.value;
    }

    // Emit event
    this.emit(event.type, event);

  }

  /**
   * Opens the input for usage. This is usually unnecessary as the port is opened automatically when
   * WebMidi is enabled.
   *
   * @returns {Promise<Input>} The promise is fulfilled with the `Input` object.
   */
  async open() {

    // Explicitly opens the port for usage. This is not mandatory. When the port is not explicitly
    // opened, it is implicitly opened (asynchronously) when assigning a listener to the
    // `onmidimessage` property of the `MIDIInput`. We do it explicitly so that 'connected' events
    // are dispatched immediately and that we are ready to listen.
    try {
      await this._midiInput.open();
    } catch (err) {
      return Promise.reject(err);
    }

    return Promise.resolve(this);

  }

  /**
   * Closes the input. When an input is closed, it cannot be used to listen to MIDI messages until
   * the input is opened again by calling [`Input.open()`](Input#open).
   *
   * **Note**: if what you want to do is stop events from being dispatched, you should use
   * [`eventsSuspended`](#eventsSuspended) instead.
   *
   * @returns {Promise<Input>} The promise is fulfilled with the `Input` object
   */
  async close() {

    // We close the port. This triggers a statechange event which, in turn, will emit the 'closed'
    // event.
    if (!this._midiInput) return Promise.resolve(this);

    try {
      await this._midiInput.close();
    } catch (err) {
      return Promise.reject(err);
    }

    return Promise.resolve(this);

  }

  /**
   * @private
   * @deprecated since v3.0.0 (moved to 'Utilities' class)
   */
  getChannelModeByNumber() {
    if (WebMidi.validation) {
      console.warn(
        "The 'getChannelModeByNumber()' method has been moved to the 'Utilities' class."
      );
    }
  }

  /**
   * Adds an event listener that will trigger a function callback when the specified event is
   * dispatched. The event usually is **input-wide** but can also be **channel-specific**.
   *
   * Input-wide events do not target a specific MIDI channel so it makes sense to listen for them
   * at the `Input` level and not at the [`InputChannel`](InputChannel) level. Channel-specific
   * events target a specific channel. Usually, in this case, you would add the listener to the
   * [`InputChannel`](InputChannel) object. However, as a convenience, you can also listen to
   * channel-specific events directly on an `Input`. This allows you to react to a channel-specific
   * event no matter which channel it actually came through.
   *
   * When listening for an event, you simply need to specify the event name and the function to
   * execute:
   *
   * ```javascript
   * const listener = WebMidi.inputs[0].addListener("midimessage", e => {
   *   console.log(e);
   * });
   * ```
   *
   * Calling the function with an input-wide event (such as
   * [`"midimessage"`]{@link #event:midimessage}), will return the [`Listener`](Listener) object
   * that was created.
   *
   * If you call the function with a channel-specific event (such as
   * [`"noteon"`]{@link InputChannel#event:noteon}), it will return an array of all
   * [`Listener`](Listener) objects that were created (one for each channel):
   *
   * ```javascript
   * const listeners = WebMidi.inputs[0].addListener("noteon", someFunction);
   * ```
   *
   * You can also specify which channels you want to add the listener to:
   *
   * ```javascript
   * const listeners = WebMidi.inputs[0].addListener("noteon", someFunction, {channels: [1, 2, 3]});
   * ```
   *
   * In this case, `listeners` is an array containing 3 [`Listener`](Listener) objects. The order of
   * the listeners in the array follows the order the channels were specified in.
   *
   * Note that, when adding channel-specific listeners, it is the [`InputChannel`](InputChannel)
   * instance that actually gets a listener added and not the `Input` instance. You can check that
   * by calling [`InputChannel.hasListener()`](InputChannel#hasListener()).
   *
   * There are 8 families of events you can listen to:
   *
   * 1. **MIDI System Common** Events (input-wide)
   *
   *    * [`songposition`]{@link Input#event:songposition}
   *    * [`songselect`]{@link Input#event:songselect}
   *    * [`sysex`]{@link Input#event:sysex}
   *    * [`timecode`]{@link Input#event:timecode}
   *    * [`tunerequest`]{@link Input#event:tunerequest}
   *
   * 2. **MIDI System Real-Time** Events (input-wide)
   *
   *    * [`clock`]{@link Input#event:clock}
   *    * [`start`]{@link Input#event:start}
   *    * [`continue`]{@link Input#event:continue}
   *    * [`stop`]{@link Input#event:stop}
   *    * [`activesensing`]{@link Input#event:activesensing}
   *    * [`reset`]{@link Input#event:reset}
   *
   * 3. **State Change** Events (input-wide)
   *
   *    * [`opened`]{@link Input#event:opened}
   *    * [`closed`]{@link Input#event:closed}
   *    * [`disconnected`]{@link Input#event:disconnected}
   *
   * 4. **Catch-All** Events (input-wide)
   *
   *    * [`midimessage`]{@link Input#event:midimessage}
   *    * [`unknownmidimessage`]{@link Input#event:unknownmidimessage}
   *
   * 5. **Channel Voice** Events (channel-specific)
   *
   *    * [`channelaftertouch`]{@link InputChannel#event:channelaftertouch}
   *    * [`controlchange`]{@link InputChannel#event:controlchange}
   *      * [`controlchange-controller0`]{@link InputChannel#event:controlchange-controller0}
   *      * [`controlchange-controller1`]{@link InputChannel#event:controlchange-controller1}
   *      * [`controlchange-controller2`]{@link InputChannel#event:controlchange-controller2}
   *      * (...)
   *      * [`controlchange-controller127`]{@link InputChannel#event:controlchange-controller127}
   *    * [`keyaftertouch`]{@link InputChannel#event:keyaftertouch}
   *    * [`noteoff`]{@link InputChannel#event:noteoff}
   *    * [`noteon`]{@link InputChannel#event:noteon}
   *    * [`pitchbend`]{@link InputChannel#event:pitchbend}
   *    * [`programchange`]{@link InputChannel#event:programchange}
   *
   *    Note: you can listen for a specific control change message by using an event name like this:
   *    `controlchange-controller23`, `controlchange-controller99`, `controlchange-controller122`,
   *    etc.
   *
   * 6. **Channel Mode** Events (channel-specific)
   *
   *    * [`allnotesoff`]{@link InputChannel#event:allnotesoff}
   *    * [`allsoundoff`]{@link InputChannel#event:allsoundoff}
   *    * [`localcontrol`]{@link InputChannel#event:localcontrol}
   *    * [`monomode`]{@link InputChannel#event:monomode}
   *    * [`omnimode`]{@link InputChannel#event:omnimode}
   *    * [`resetallcontrollers`]{@link InputChannel#event:resetallcontrollers}
   *
   * 7. **NRPN** Events (channel-specific)
   *
   *    * [`nrpn`]{@link InputChannel#event:nrpn}
   *    * [`nrpn-dataentrycoarse`]{@link InputChannel#event:nrpn-dataentrycoarse}
   *    * [`nrpn-dataentryfine`]{@link InputChannel#event:nrpn-dataentryfine}
   *    * [`nrpn-dataincrement`]{@link InputChannel#event:nrpn-dataincrement}
   *    * [`nrpn-datadecrement`]{@link InputChannel#event:nrpn-datadecrement}
   *
   * 8. **RPN** Events (channel-specific)
   *
   *    * [`rpn`]{@link InputChannel#event:rpn}
   *    * [`rpn-dataentrycoarse`]{@link InputChannel#event:rpn-dataentrycoarse}
   *    * [`rpn-dataentryfine`]{@link InputChannel#event:rpn-dataentryfine}
   *    * [`rpn-dataincrement`]{@link InputChannel#event:rpn-dataincrement}
   *    * [`rpn-datadecrement`]{@link InputChannel#event:rpn-datadecrement}
   *
   * @param event {string | EventEmitter.ANY_EVENT} The type of the event.
   *
   * @param listener {function} 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).
   *
   * @param {object} [options={}]
   *
   * @param {array} [options.arguments] 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.
   *
   * @param {number|number[]} [options.channels=[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.
   *
   * @param {object} [options.context=this] The value of `this` in the callback function.
   *
   * @param {number} [options.duration=Infinity] The number of milliseconds before the listener
   * automatically expires.
   *
   * @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning
   * of the listeners array and thus be triggered before others.
   *
   * @param {number} [options.remaining=Infinity] The number of times after which the callback
   * should automatically be removed.
   *
   * @returns {Listener|Listener[]} If the event is input-wide, a single [`Listener`](Listener)
   * object is returned. If the event is channel-specific, an array of all the
   * [`Listener`](Listener) objects is returned (one for each channel).
   */
  addListener(event, listener, options = {}) {

    if (WebMidi.validation) {

      // Legacy compatibility
      if (typeof options === "function") {
        let channels = (listener != undefined) ? [].concat(listener) : undefined; // clone
        listener = options;
        options = {channels: channels};
      }

    }

    // Check if the event is channel-specific or input-wide
    if (Enumerations.CHANNEL_EVENTS.includes(event)) {

      // If no channel defined, use all.
      if (options.channels === undefined) options.channels = Enumerations.MIDI_CHANNEL_NUMBERS;

      let listeners = [];

      Utilities.sanitizeChannels(options.channels).forEach(ch => {
        listeners.push(this.channels[ch].addListener(event, listener, options));
      });

      return listeners;

    } else {

      return super.addListener(event, listener, options);

    }

  }

  /**
   * Adds a one-time event listener that will trigger a function callback when the specified event
   * happens. The event can be **channel-bound** or **input-wide**. Channel-bound events are
   * dispatched by [`InputChannel`]{@link InputChannel} ob
Download .txt
gitextract_ohmewh1n/

├── .babelrc
├── .editorconfig
├── .env.sample
├── .eslintrc.cjs
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── ask-a-question.md
│   │   ├── report-a-bug.md
│   │   └── suggest-a-new-feature.md
│   └── workflows/
│       └── codeql-analysis.yml
├── .gitignore
├── .nycrc
├── BANNER.txt
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── SECURITY.md
├── examples/
│   ├── README.md
│   ├── electron/
│   │   ├── README.md
│   │   └── basic-example/
│   │       ├── README.md
│   │       ├── index.html
│   │       ├── main.js
│   │       ├── package.json
│   │       ├── preload.js
│   │       └── renderer.js
│   ├── next.js/
│   │   ├── README.md
│   │   └── basic-example/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── package.json
│   │       └── pages/
│   │           └── index.js
│   ├── p5.js/
│   │   ├── README.md
│   │   ├── basic-example/
│   │   │   ├── index.html
│   │   │   ├── sketch.js
│   │   │   └── styles.css
│   │   └── querying-note-state/
│   │       ├── index.html
│   │       ├── sketch.js
│   │       └── styles.css
│   ├── quick-start/
│   │   └── index.html
│   ├── react/
│   │   ├── README.md
│   │   └── basic-example/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── package.json
│   │       ├── public/
│   │       │   ├── index.html
│   │       │   ├── manifest.json
│   │       │   └── robots.txt
│   │       └── src/
│   │           ├── App.css
│   │           ├── App.js
│   │           ├── App.test.js
│   │           ├── index.css
│   │           ├── index.js
│   │           ├── reportWebVitals.js
│   │           └── setupTests.js
│   └── typescript/
│       ├── README.md
│       └── basic-nodejs-example/
│           ├── index.ts
│           └── package.json
├── package.json
├── scripts/
│   ├── api-documentation/
│   │   ├── generate-html.js
│   │   ├── generate-markdown.js
│   │   └── templates/
│   │       ├── core/
│   │       │   ├── class.hbs
│   │       │   ├── constructor.hbs
│   │       │   ├── enumerations.hbs
│   │       │   ├── events.hbs
│   │       │   ├── methods.hbs
│   │       │   └── properties.hbs
│   │       └── helpers/
│   │           ├── ddata.js
│   │           ├── djip-helpers.js
│   │           └── state.js
│   ├── library/
│   │   ├── build.js
│   │   ├── rollup.config.cjs.js
│   │   ├── rollup.config.esm.js
│   │   └── rollup.config.iife.js
│   ├── sponsors/
│   │   └── retrieve-sponsors.js
│   ├── typescript-declarations/
│   │   ├── generate.js
│   │   └── generateOLD.js
│   └── website/
│       └── deploy.js
├── src/
│   ├── Enumerations.js
│   ├── Forwarder.js
│   ├── Input.js
│   ├── InputChannel.js
│   ├── Message.js
│   ├── Note.js
│   ├── Output.js
│   ├── OutputChannel.js
│   ├── Utilities.js
│   └── WebMidi.js
├── test/
│   ├── Enumerations.test.js
│   ├── Forwarder.test.js
│   ├── Input.test.js
│   ├── InputChannel.test.js
│   ├── Message.test.js
│   ├── Note.test.js
│   ├── Output.test.js
│   ├── OutputChannel.test.js
│   ├── Utilities.test.js
│   ├── WebMidi.test.js
│   └── support/
│       ├── JZZ.js
│       ├── Utils.cjs.js
│       └── Utils.iife.js
├── typescript/
│   └── webmidi.d.ts
└── website/
    ├── .gitignore
    ├── README.md
    ├── api/
    │   ├── classes/
    │   │   ├── Enumerations.md
    │   │   ├── EventEmitter.md
    │   │   ├── Forwarder.md
    │   │   ├── Input.md
    │   │   ├── InputChannel.md
    │   │   ├── Listener.md
    │   │   ├── Message.md
    │   │   ├── Note.md
    │   │   ├── Output.md
    │   │   ├── OutputChannel.md
    │   │   ├── Utilities.md
    │   │   ├── WebMidi.md
    │   │   └── _category_.json
    │   └── index.md
    ├── babel.config.js
    ├── blog/
    │   └── 2021-12-01/
    │       └── version-3-has-been-released.md
    ├── docs/
    │   ├── archives/
    │   │   ├── _category_.json
    │   │   ├── v1.md
    │   │   └── v2.md
    │   ├── getting-started/
    │   │   ├── _category_.json
    │   │   ├── basics.md
    │   │   ├── installation.md
    │   │   └── supported-environments.md
    │   ├── going-further/
    │   │   ├── _category_.json
    │   │   ├── electron.md
    │   │   ├── forwarding.md
    │   │   ├── middle-c.md
    │   │   ├── performance.md
    │   │   ├── sysex.md
    │   │   └── typescript.md
    │   ├── index.md
    │   ├── migration/
    │   │   ├── _category_.json
    │   │   └── migration.md
    │   └── roadmap/
    │       ├── _category_.json
    │       ├── under-evaluation.md
    │       ├── v3.md
    │       └── v4.md
    ├── docusaurus.config.js
    ├── package.json
    ├── sidebars.js
    ├── src/
    │   ├── components/
    │   │   ├── Button.js
    │   │   ├── Button.module.css
    │   │   ├── Button.module.scss
    │   │   ├── Column.js
    │   │   ├── Column.module.css
    │   │   ├── Column.module.scss
    │   │   ├── HomepageFeatures.js
    │   │   ├── HomepageFeatures.module.css
    │   │   ├── InformationBar.js
    │   │   ├── InformationBar.module.css
    │   │   └── InformationBar.module.scss
    │   ├── css/
    │   │   ├── custom.css
    │   │   ├── custom.scss
    │   │   ├── index.css
    │   │   └── index.scss
    │   ├── pages/
    │   │   ├── about/
    │   │   │   └── index.md
    │   │   ├── index.js
    │   │   ├── index.module.css
    │   │   ├── index.module.scss
    │   │   ├── research/
    │   │   │   └── index.md
    │   │   ├── showcase/
    │   │   │   └── index.md
    │   │   ├── sponsors/
    │   │   │   └── index.md
    │   │   └── tester/
    │   │       └── index.js
    │   └── theme/
    │       ├── CodeBlock/
    │       │   └── index.js
    │       ├── Footer/
    │       │   ├── index.js
    │       │   ├── styles.module.css
    │       │   └── styles.module.scss
    │       └── Navbar/
    │           ├── index.js
    │           └── styles.module.css
    └── static/
        ├── .nojekyll
        ├── js/
        │   └── newsletter-popup.js
        └── styles/
            ├── default.css
            ├── default.scss
            └── jsdoc.css
Download .txt
SYMBOL INDEX (735 symbols across 50 files)

FILE: examples/electron/basic-example/main.js
  function createWindow (line 5) | function createWindow () {

FILE: examples/electron/basic-example/renderer.js
  function onEnabled (line 11) | function onEnabled() {

FILE: examples/next.js/basic-example/pages/index.js
  function Home (line 18) | function Home() {

FILE: examples/p5.js/basic-example/sketch.js
  function setup (line 1) | function setup() {
  function draw (line 13) | function draw() {
  function onWebMidiEnabled (line 17) | function onWebMidiEnabled() {

FILE: examples/p5.js/querying-note-state/sketch.js
  function setup (line 3) | async function setup() {
  function draw (line 18) | function draw() {

FILE: examples/react/basic-example/src/App.js
  function App (line 5) | function App() {

FILE: examples/typescript/basic-nodejs-example/index.ts
  function start (line 3) | async function start() {

FILE: scripts/api-documentation/generate-html.js
  constant CUSTOM_CSS (line 17) | const CUSTOM_CSS = "../css/custom.css";
  constant TARGET_BRANCH (line 18) | const TARGET_BRANCH = "gh-pages";
  constant TARGET_PATH (line 19) | const TARGET_PATH = "./archives/api/v" + pkg.version.split(".")[0];
  constant GA_CONFIG (line 22) | const GA_CONFIG = {
  function log (line 101) | function log(message) {
  function execute (line 105) | async function execute() {

FILE: scripts/api-documentation/generate-markdown.js
  constant ROOT_PATH (line 12) | const ROOT_PATH = process.cwd();
  constant SOURCE_DIR (line 13) | const SOURCE_DIR = path.resolve(ROOT_PATH, "src");
  constant TARGET_PATH (line 14) | const TARGET_PATH = path.join(process.cwd(), "website", "api", "classes");
  constant TEMPLATE_DIR (line 15) | const TEMPLATE_DIR = path.resolve(ROOT_PATH, "scripts/api-documentation/...
  constant DJIPEVENTS (line 16) | const DJIPEVENTS = path.resolve(ROOT_PATH, "node_modules/djipevents/src/...
  function generate (line 36) | async function generate() {
  function parseFile (line 74) | function parseFile(data) {

FILE: scripts/api-documentation/templates/helpers/ddata.js
  function _globals (line 95) | function _globals (options) {
  function children (line 111) | function children (options) {
  function indexChildren (line 153) | function indexChildren (options) {
  function link (line 200) | function link (longname, options) {
  function _link (line 210) | function _link (input, options) {
  function returnSig2 (line 261) | function returnSig2 (options) {
  function sig (line 300) | function sig (options) {
  function isClass (line 386) | function isClass () { return this.kind === 'class' }
  function isClassMember (line 393) | function isClassMember (options) {
  function isConstructor (line 399) | function isConstructor () { return this.kind === 'constructor' }
  function isFunction (line 400) | function isFunction () { return this.kind === 'function' }
  function isConstant (line 401) | function isConstant () { return this.kind === 'constant' }
  function isEvent (line 407) | function isEvent () { return this.kind === 'event' }
  function isEnum (line 408) | function isEnum () { return this.isEnum || this.kind === 'enum' }
  function isExternal (line 409) | function isExternal () { return this.kind === 'external' }
  function isTypedef (line 410) | function isTypedef () {
  function isCallback (line 413) | function isCallback () {
  function isModule (line 416) | function isModule () { return this.kind === 'module' }
  function isMixin (line 417) | function isMixin () { return this.kind === 'mixin' }
  function isPrivate (line 418) | function isPrivate () { return this.access === 'private' }
  function isProtected (line 419) | function isProtected () { return this.access === 'protected' }
  function showMainIndex (line 427) | function showMainIndex (options) {
  function _orphans (line 437) | function _orphans (options) {
  function _identifiers (line 453) | function _identifiers (options) {
  function _children (line 478) | function _children (options) {
  function descendants (line 502) | function descendants (options) {
  function exported (line 525) | function exported (options) {
  function _identifier (line 534) | function _identifier (options) {
  function parentObject (line 542) | function parentObject (options) {
  function anchorName (line 558) | function anchorName (options) {
  function md (line 586) | function md (string, options) {
  function md2 (line 592) | function md2 (options) {
  function methodSig (line 603) | function methodSig () {
  function parseLink (line 621) | function parseLink (text) {
  function parentName (line 674) | function parentName (options) {
  function option (line 709) | function option (name, options) {
  function optionEquals (line 716) | function optionEquals (name, value, options) {
  function optionSet (line 723) | function optionSet (name, value, options) {
  function optionIsSet (line 730) | function optionIsSet (name, options) {
  function stripNewlines (line 737) | function stripNewlines (input) {
  function headingDepth (line 744) | function headingDepth (options) {
  function depth (line 751) | function depth (options) {
  function depthIncrement (line 758) | function depthIncrement (options) {
  function depthDecrement (line 765) | function depthDecrement (options) {
  function indexDepth (line 772) | function indexDepth (options) {
  function indexDepthIncrement (line 779) | function indexDepthIncrement (options) {
  function indexDepthDecrement (line 786) | function indexDepthDecrement (options) {

FILE: scripts/api-documentation/templates/helpers/djip-helpers.js
  function stripNewlines (line 8) | function stripNewlines (input) {
  function eventName (line 18) | function eventName (input) {
  function inlineLinks (line 26) | function inlineLinks (text, options) {
  function curly (line 43) | function curly(object, open) {
  function eq (line 48) | function eq(v1, v2) { return v1 === v2; }
  function ne (line 50) | function ne(v1, v2) { return v1 !== v2; }
  function lt (line 52) | function lt(v1, v2) {return v1 < v2; }
  function gt (line 54) | function gt(v1, v2) { return v1 > v2; }
  function lte (line 56) | function lte(v1, v2) { return v1 <= v2; }
  function gte (line 58) | function gte(v1, v2) { return v1 >= v2; }
  function and (line 60) | function and() { return Array.prototype.every.call(arguments, Boolean); }
  function or (line 62) | function or() { return Array.prototype.slice.call(arguments, 0, -1).some...
  function methodSignature (line 65) | function methodSignature(context) {
  function createEventAnchor (line 70) | function createEventAnchor(name) {

FILE: scripts/library/build.js
  function execute (line 32) | async function execute(type) {

FILE: scripts/library/rollup.config.cjs.js
  constant BANNER (line 8) | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") ...

FILE: scripts/library/rollup.config.esm.js
  constant BANNER (line 8) | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") ...

FILE: scripts/library/rollup.config.iife.js
  constant BANNER (line 9) | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") ...

FILE: scripts/sponsors/retrieve-sponsors.js
  constant TARGET (line 6) | const TARGET = path.join(process.cwd(), "website", "src", "pages", "spon...
  function getSponsors (line 8) | async function getSponsors() {

FILE: scripts/typescript-declarations/generate.js
  constant SOURCE_FILE (line 11) | const SOURCE_FILE = path.join(__dirname, "../../typescript", "webmidi.d....
  constant OUTPUT_DIR (line 14) | const OUTPUT_DIR = "dist";
  function execute (line 30) | async function execute() {
  function log (line 55) | function log(message) {

FILE: scripts/typescript-declarations/generateOLD.js
  constant OUT_DIR (line 22) | const OUT_DIR = "dist";
  constant WEB_MIDI_API_CLASSES (line 24) | const WEB_MIDI_API_CLASSES = [
  constant HEADER (line 41) | const HEADER = `// Type definitions for ${pkg.webmidi.name} ${pkg.versio...
  function execute (line 60) | async function execute() {
  function log (line 161) | function log(message) {

FILE: scripts/website/deploy.js
  constant TARGET_BRANCH (line 14) | const TARGET_BRANCH = "gh-pages";
  constant SOURCE_DIR (line 15) | const SOURCE_DIR = path.join(process.cwd(), "website", "build");
  function execute (line 17) | async function execute() {
  function log (line 65) | function log(message) {

FILE: src/Enumerations.js
  class Enumerations (line 9) | class Enumerations {
    method MIDI_CHANNEL_MESSAGES (line 18) | static get MIDI_CHANNEL_MESSAGES() {
    method CHANNEL_MESSAGES (line 49) | static get CHANNEL_MESSAGES() {
    method CHANNEL_NUMBERS (line 71) | static get CHANNEL_NUMBERS() {
    method MIDI_CHANNEL_NUMBERS (line 82) | static get MIDI_CHANNEL_NUMBERS() {
    method CHANNEL_MODE_MESSAGES (line 115) | static get CHANNEL_MODE_MESSAGES() {
    method MIDI_CHANNEL_MODE_MESSAGES (line 137) | static get MIDI_CHANNEL_MODE_MESSAGES() {
    method MIDI_CONTROL_CHANGE_MESSAGES (line 157) | static get MIDI_CONTROL_CHANGE_MESSAGES() {
    method CONTROL_CHANGE_MESSAGES (line 451) | static get CONTROL_CHANGE_MESSAGES() {
    method REGISTERED_PARAMETERS (line 1211) | static get REGISTERED_PARAMETERS() {
    method MIDI_REGISTERED_PARAMETERS (line 1241) | static get MIDI_REGISTERED_PARAMETERS() {
    method SYSTEM_MESSAGES (line 1303) | static get SYSTEM_MESSAGES() {
    method MIDI_SYSTEM_MESSAGES (line 1339) | static get MIDI_SYSTEM_MESSAGES() {
    method CHANNEL_EVENTS (line 1359) | static get CHANNEL_EVENTS() {

FILE: src/Forwarder.js
  class Forwarder (line 19) | class Forwarder {
    method constructor (line 38) | constructor(destinations = [], options = {}) {
    method forward (line 124) | forward(message) {

FILE: src/Input.js
  class Input (line 48) | class Input extends EventEmitter {
    method constructor (line 56) | constructor(midiInput) {
    method destroy (line 99) | async destroy() {
    method _onStateChange (line 118) | _onStateChange(e) {
    method _onMidiMessage (line 199) | _onMidiMessage(e) {
    method _parseEvent (line 252) | _parseEvent(e) {
    method open (line 276) | async open() {
    method close (line 301) | async close() {
    method getChannelModeByNumber (line 321) | getChannelModeByNumber() {
    method addListener (line 480) | addListener(event, listener, options = {}) {
    method addOneTimeListener (line 651) | addOneTimeListener(event, listener, options = {}) {
    method on (line 662) | on(event, channel, listener, options) {
    method hasListener (line 684) | hasListener(event, listener, options = {}) {
    method removeListener (line 737) | removeListener(event, listener, options = {}) {
    method addForwarder (line 796) | addForwarder(output, options = {}) {
    method removeForwarder (line 818) | removeForwarder(forwarder) {
    method hasForwarder (line 830) | hasForwarder(forwarder) {
    method name (line 840) | get name() {
    method id (line 852) | get id() {
    method connection (line 862) | get connection() {
    method manufacturer (line 872) | get manufacturer() {
    method octaveOffset (line 890) | get octaveOffset() {
    method octaveOffset (line 893) | set octaveOffset(value) {
    method state (line 910) | get state() {
    method type (line 920) | get type() {
    method nrpnEventsEnabled (line 929) | get nrpnEventsEnabled() {

FILE: src/InputChannel.js
  class InputChannel (line 119) | class InputChannel extends EventEmitter {
    method constructor (line 127) | constructor(input, number) {
    method destroy (line 190) | destroy() {
    method _processMidiMessageEvent (line 204) | _processMidiMessageEvent(e) {
    method _parseEventForStandardMessages (line 239) | _parseEventForStandardMessages(e) {
    method _parseChannelModeMessage (line 2310) | _parseChannelModeMessage(e) {
    method _parseEventForParameterNumber (line 2459) | _parseEventForParameterNumber(event) {
    method _isRpnOrNrpnController (line 2544) | _isRpnOrNrpnController(controller) {
    method _dispatchParameterNumberEvent (line 2560) | _dispatchParameterNumberEvent(type, paramMsb, paramLsb, e) {
    method getChannelModeByNumber (line 2874) | getChannelModeByNumber(number) {
    method getCcNameByNumber (line 2891) | getCcNameByNumber(number) {
    method getNoteState (line 2918) | getNoteState(note) {
    method octaveOffset (line 2948) | get octaveOffset() {
    method octaveOffset (line 2951) | set octaveOffset(value) {
    method input (line 2967) | get input() {
    method number (line 2976) | get number() {
    method nrpnEventsEnabled (line 2987) | get nrpnEventsEnabled() {
    method nrpnEventsEnabled (line 2990) | set nrpnEventsEnabled(value) {

FILE: src/Message.js
  class Message (line 11) | class Message {
    method constructor (line 20) | constructor(data) {

FILE: src/Note.js
  class Note (line 28) | class Note {
    method constructor (line 67) | constructor(value, options = {}) {
    method identifier (line 97) | get identifier() {
    method identifier (line 100) | set identifier(value) {
    method name (line 120) | get name() {
    method name (line 123) | set name(value) {
    method accidental (line 141) | get accidental() {
    method accidental (line 144) | set accidental(value) {
    method octave (line 160) | get octave() {
    method octave (line 163) | set octave(value) {
    method duration (line 181) | get duration() {
    method duration (line 184) | set duration(value) {
    method attack (line 202) | get attack() {
    method attack (line 205) | set attack(value) {
    method release (line 223) | get release() {
    method release (line 226) | set release(value) {
    method rawAttack (line 244) | get rawAttack() {
    method rawAttack (line 247) | set rawAttack(value) {
    method rawRelease (line 256) | get rawRelease() {
    method rawRelease (line 259) | set rawRelease(value) {
    method number (line 271) | get number() {
    method getOffsetNumber (line 284) | getOffsetNumber(octaveOffset = 0, semitoneOffset = 0) {

FILE: src/Output.js
  class Output (line 27) | class Output extends EventEmitter {
    method constructor (line 35) | constructor(midiOutput) {
    method destroy (line 70) | async destroy() {
    method _onStateChange (line 82) | _onStateChange(e) {
    method open (line 166) | async open() {
    method close (line 188) | async close() {
    method send (line 233) | send(message, options = {time: 0}, legacy = 0) {
    method sendSysex (line 385) | sendSysex(identification, data= [], options = {}) {
    method clear (line 416) | clear() {
    method sendTimecodeQuarterFrame (line 455) | sendTimecodeQuarterFrame(value, options = {}) {
    method sendSongPosition (line 496) | sendSongPosition(value = 0, options = {}) {
    method sendSongSelect (line 539) | sendSongSelect(value = 0, options = {}) {
    method sendTuneRequest (line 580) | sendTuneRequest(options = {}) {
    method sendClock (line 607) | sendClock(options = {}) {
    method sendStart (line 635) | sendStart(options = {}) {
    method sendContinue (line 663) | sendContinue(options = {}) {
    method sendStop (line 690) | sendStop(options = {}) {
    method sendActiveSensing (line 718) | sendActiveSensing(options = {}) {
    method sendReset (line 745) | sendReset(options = {}) {
    method sendTuningRequest (line 760) | sendTuningRequest(options = {}) {
    method sendKeyAftertouch (line 809) | sendKeyAftertouch(note, pressure, options = {}) {
    method sendControlChange (line 927) | sendControlChange(controller, value, options = {}, legacy = {}) {
    method sendPitchBendRange (line 985) | sendPitchBendRange(semitones= 0, cents = 0, options = {}) {
    method setPitchBendRange (line 1002) | setPitchBendRange(semitones = 0, cents = 0, channel = "all", options =...
    method sendRpnValue (line 1072) | sendRpnValue(parameter, data, options = {}) {
    method setRegisteredParameter (line 1088) | setRegisteredParameter(parameter, data = [], channel = "all", options ...
    method sendChannelAftertouch (line 1133) | sendChannelAftertouch(pressure, options = {}, legacy = {}) {
    method sendPitchBend (line 1196) | sendPitchBend(value, options = {}, legacy = {}) {
    method sendProgramChange (line 1246) | sendProgramChange(program = 0, options = {}, legacy = {}) {
    method sendModulationRange (line 1301) | sendModulationRange(semitones, cents, options = {}) {
    method setModulationRange (line 1317) | setModulationRange(semitones = 0, cents = 0, channel = "all", options ...
    method sendMasterTuning (line 1366) | sendMasterTuning(value, options = {}) {
    method setMasterTuning (line 1382) | setMasterTuning(value, channel = {}, options = {}) {
    method sendTuningProgram (line 1425) | sendTuningProgram(value, options = {}) {
    method setTuningProgram (line 1441) | setTuningProgram(value, channel = "all", options = {}) {
    method sendTuningBank (line 1484) | sendTuningBank(value= 0, options = {}) {
    method setTuningBank (line 1500) | setTuningBank(parameter, channel = "all", options = {}) {
    method sendChannelMode (line 1565) | sendChannelMode(command, value = 0, options = {}, legacy = {}) {
    method sendAllSoundOff (line 1611) | sendAllSoundOff(options = {}) {
    method sendAllNotesOff (line 1646) | sendAllNotesOff(options = {}) {
    method sendResetAllControllers (line 1678) | sendResetAllControllers(options = {}, legacy = {}) {
    method sendPolyphonicMode (line 1727) | sendPolyphonicMode(mode, options = {}, legacy = {}) {
    method sendLocalControl (line 1777) | sendLocalControl(state, options = {}, legacy = {}) {
    method sendOmniMode (line 1831) | sendOmniMode(state, options = {}, legacy = {}) {
    method sendNrpnValue (line 1912) | sendNrpnValue(parameter, data, options = {}) {
    method setNonRegisteredParameter (line 1928) | setNonRegisteredParameter(parameter, data = [], channel = "all", optio...
    method sendRpnIncrement (line 1985) | sendRpnIncrement(parameter, options = {}) {
    method incrementRegisteredParameter (line 2001) | incrementRegisteredParameter(parameter, channel = "all", options = {}) {
    method sendRpnDecrement (line 2060) | sendRpnDecrement(parameter, options = {}) {
    method decrementRegisteredParameter (line 2076) | decrementRegisteredParameter(parameter, channel = "all", options = {}) {
    method sendNoteOff (line 2135) | sendNoteOff(note, options= {}, legacy = {}) {
    method stopNote (line 2201) | stopNote(note, options) {
    method playNote (line 2277) | playNote(note, options = {}, legacy = {}) {
    method sendNoteOn (line 2355) | sendNoteOn(note, options = {}, legacy = {}) {
    method name (line 2386) | get name() {
    method id (line 2398) | get id() {
    method connection (line 2408) | get connection() {
    method manufacturer (line 2418) | get manufacturer() {
    method state (line 2428) | get state() {
    method type (line 2438) | get type() {
    method octaveOffset (line 2453) | get octaveOffset() {
    method octaveOffset (line 2456) | set octaveOffset(value) {

FILE: src/OutputChannel.js
  class OutputChannel (line 22) | class OutputChannel extends EventEmitter {
    method constructor (line 30) | constructor(output, number) {
    method destroy (line 61) | destroy() {
    method send (line 102) | send(message, options = {time: 0}) {
    method sendKeyAftertouch (line 145) | sendKeyAftertouch(target, pressure, options = {}) {
    method sendControlChange (line 307) | sendControlChange(controller, value, options = {}) {
    method _selectNonRegisteredParameter (line 376) | _selectNonRegisteredParameter(parameter, options = {}) {
    method _deselectRegisteredParameter (line 414) | _deselectRegisteredParameter(options = {}) {
    method _deselectNonRegisteredParameter (line 436) | _deselectNonRegisteredParameter(options = {}) {
    method _selectRegisteredParameter (line 461) | _selectRegisteredParameter(parameter, options = {}) {
    method _setCurrentParameter (line 484) | _setCurrentParameter(data, options = {}) {
    method sendRpnDecrement (line 549) | sendRpnDecrement(parameter, options = {}) {
    method sendRpnIncrement (line 620) | sendRpnIncrement(parameter, options = {}) {
    method playNote (line 718) | playNote(note, options = {}) {
    method sendNoteOff (line 788) | sendNoteOff(note, options = {}) {
    method stopNote (line 878) | stopNote(note, options = {}) {
    method sendNoteOn (line 929) | sendNoteOn(note, options = {}) {
    method sendChannelMode (line 1019) | sendChannelMode(command, value = 0, options = {}) {
    method sendOmniMode (line 1077) | sendOmniMode(state, options = {}) {
    method sendChannelAftertouch (line 1113) | sendChannelAftertouch(pressure, options = {}) {
    method sendMasterTuning (line 1176) | sendMasterTuning(value, options = {}) {
    method sendModulationRange (line 1229) | sendModulationRange(semitones, cents, options = {}) {
    method sendNrpnValue (line 1301) | sendNrpnValue(nrpn, data, options = {}) {
    method sendPitchBend (line 1366) | sendPitchBend(value, options = {}) {
    method sendPitchBendRange (line 1456) | sendPitchBendRange(semitones, cents, options = {}) {
    method sendProgramChange (line 1498) | sendProgramChange(program, options = {}) {
    method sendRpnValue (line 1571) | sendRpnValue(rpn, data, options = {}) {
    method sendTuningBank (line 1625) | sendTuningBank(value, options = {}) {
    method sendTuningProgram (line 1660) | sendTuningProgram(value, options = {}) {
    method sendLocalControl (line 1695) | sendLocalControl(state, options = {}) {
    method sendAllNotesOff (line 1720) | sendAllNotesOff(options = {}) {
    method sendAllSoundOff (line 1740) | sendAllSoundOff(options = {}) {
    method sendResetAllControllers (line 1760) | sendResetAllControllers(options = {}) {
    method sendPolyphonicMode (line 1783) | sendPolyphonicMode(mode, options = {}) {
    method octaveOffset (line 1804) | get octaveOffset() {
    method octaveOffset (line 1807) | set octaveOffset(value) {
    method output (line 1823) | get output() {
    method number (line 1832) | get number() {

FILE: src/Utilities.js
  class Utilities (line 12) | class Utilities {
    method toNoteNumber (line 41) | static toNoteNumber(identifier, octaveOffset = 0) {
    method getNoteDetails (line 87) | static getNoteDetails(value) {
    method sanitizeChannels (line 126) | static sanitizeChannels(channel) {
    method toTimestamp (line 174) | static toTimestamp(time) {
    method guessNoteNumber (line 210) | static guessNoteNumber(input, octaveOffset) {
    method toNoteIdentifier (line 249) | static toNoteIdentifier(number, octaveOffset) {
    method buildNote (line 304) | static buildNote(input, options= {}) {
    method buildNoteArray (line 369) | static buildNoteArray(notes, options = {}) {
    method from7bitToFloat (line 394) | static from7bitToFloat(value) {
    method fromFloatTo7Bit (line 412) | static fromFloatTo7Bit(value) {
    method fromMsbLsbToFloat (line 426) | static fromMsbLsbToFloat(msb, lsb = 0) {
    method fromFloatToMsbLsb (line 444) | static fromFloatToMsbLsb(value) {
    method offsetNumber (line 472) | static offsetNumber(number, octaveOffset = 0, semitoneOffset = 0) {
    method getPropertyByValue (line 495) | static getPropertyByValue(object, value) {
    method getCcNameByNumber (line 511) | static getCcNameByNumber(number) {
    method getCcNumberByName (line 532) | static getCcNumberByName(name) {
    method getChannelModeByNumber (line 552) | static getChannelModeByNumber(number) {
    method isNode (line 575) | static get isNode() {
    method isBrowser (line 585) | static get isBrowser() {

FILE: src/WebMidi.js
  class WebMidi (line 71) | class WebMidi extends EventEmitter {
    method constructor (line 77) | constructor() {
    method enable (line 247) | async enable(options = {}, legacy = false) {
    method disable (line 410) | async disable() {
    method getInputById (line 466) | getInputById(id, options = {disconnected: false}) {
    method getInputByName (line 508) | getInputByName(name, options = {disconnected: false}) {
    method getOutputByName (line 545) | getOutputByName(name, options = {disconnected: false}) {
    method getOutputById (line 585) | getOutputById(id, options = {disconnected: false}) {
    method noteNameToNumber (line 614) | noteNameToNumber(name) {
    method getOctave (line 628) | getOctave(number) {
    method sanitizeChannels (line 647) | sanitizeChannels(channel) {
    method toMIDIChannels (line 661) | toMIDIChannels(channel) {
    method guessNoteNumber (line 677) | guessNoteNumber(input) {
    method getValidNoteArray (line 693) | getValidNoteArray(notes, options = {}) {
    method convertToTimestamp (line 706) | convertToTimestamp(time) {
    method _destroyInputsAndOutputs (line 722) | async _destroyInputsAndOutputs() {
    method _onInterfaceStateChange (line 739) | _onInterfaceStateChange(e) {
    method _updateInputsAndOutputs (line 843) | async _updateInputsAndOutputs() {
    method _updateInputs (line 855) | async _updateInputs() {
    method _updateOutputs (line 901) | async _updateOutputs() {
    method enabled (line 973) | get enabled() {
    method inputs (line 983) | get inputs() {
    method isNode (line 991) | get isNode() {
    method isBrowser (line 1005) | get isBrowser() {
    method octaveOffset (line 1031) | get octaveOffset() {
    method octaveOffset (line 1034) | set octaveOffset(value) {
    method outputs (line 1051) | get outputs() {
    method supported (line 1067) | get supported() {
    method sysexEnabled (line 1078) | get sysexEnabled() {
    method time (line 1096) | get time() {
    method version (line 1106) | get version() {
    method flavour (line 1121) | get flavour() {
    method CHANNEL_EVENTS (line 1129) | get CHANNEL_EVENTS() {
    method MIDI_SYSTEM_MESSAGES (line 1142) | get MIDI_SYSTEM_MESSAGES() {
    method MIDI_CHANNEL_MODE_MESSAGES (line 1159) | get MIDI_CHANNEL_MODE_MESSAGES() {
    method MIDI_CONTROL_CHANGE_MESSAGES (line 1176) | get MIDI_CONTROL_CHANGE_MESSAGES() {
    method MIDI_REGISTERED_PARAMETER (line 1193) | get MIDI_REGISTERED_PARAMETER() {
    method NOTES (line 1210) | get NOTES() {

FILE: test/Forwarder.test.js
  constant VIRTUAL_OUTPUT (line 7) | let VIRTUAL_OUTPUT = new midi.Input();
  constant VIRTUAL_OUTPUT_NAME (line 8) | let VIRTUAL_OUTPUT_NAME = "Virtual Output";
  constant WEBMIDI_OUTPUT (line 9) | let WEBMIDI_OUTPUT;
  function assert (line 106) | function assert(deltaTime, message) {
  function assert (line 133) | function assert(deltaTime, message) {
  function assert (line 164) | function assert(deltaTime, message) {
  function assert (line 198) | function assert(deltaTime, message) {

FILE: test/Input.test.js
  constant VIRTUAL_INPUT (line 8) | let VIRTUAL_INPUT = new midi.Output();
  constant VIRTUAL_INPUT_NAME (line 9) | let VIRTUAL_INPUT_NAME = "Virtual Input";
  constant WEBMIDI_INPUT (line 10) | let WEBMIDI_INPUT;
  constant VIRTUAL_OUTPUT (line 14) | let VIRTUAL_OUTPUT = new midi.Input();
  constant VIRTUAL_OUTPUT_NAME (line 15) | let VIRTUAL_OUTPUT_NAME = "Virtual Output";
  constant WEBMIDI_OUTPUT (line 16) | let WEBMIDI_OUTPUT;
  function assert (line 61) | function assert(e) {
  function assert (line 91) | function assert(e) {
  function assert (line 122) | function assert(e) {
  function assert (line 155) | function assert(e) {
  function assert (line 189) | function assert(e) {
  function assert (line 221) | function assert(e) {
  function assert (line 252) | function assert(e) {
  function assert (line 387) | function assert(event) {
  function assert (line 405) | function assert(event) {
  function assert (line 865) | function assert(deltaTime, message) {
  function assert (line 889) | function assert(deltaTime, message) {
  function assert (line 916) | function assert(deltaTime, message) {
  function assert (line 941) | function assert(deltaTime, message) {
  function assert (line 976) | function assert(deltaTime, message) {

FILE: test/InputChannel.test.js
  constant VIRTUAL_INPUT (line 8) | let VIRTUAL_INPUT = {
  constant WEBMIDI_INPUT (line 13) | let WEBMIDI_INPUT;
  function assert (line 65) | function assert(e) {
  function assert (line 93) | function assert(e) {
  function assert (line 127) | function assert(e) {
  function assert (line 162) | function assert(e) {
  function assert (line 187) | function assert(e) {
  function assert (line 217) | function assert(e) {
  function assert (line 241) | function assert(e) {
  function assert (line 272) | function assert(e) {
  function assert (line 296) | function assert(e) {
  function assert (line 329) | function assert(e, index) {
  function assert (line 369) | function assert(e, index) {
  function assert (line 397) | function assert(e) {
  function assert (line 420) | function assert(e) {
  function assert (line 443) | function assert(e) {
  function assert (line 466) | function assert(e) {
  function assert (line 487) | function assert(e) {
  function assert (line 510) | function assert(e) {
  function assert (line 541) | function assert(e) {
  function assert (line 563) | function assert(e) {
  function assert (line 595) | function assert(e) {
  function assert1 (line 635) | function assert1(e) {
  function assert2 (line 639) | function assert2(e) {
  function assert1 (line 670) | function assert1(e) {
  function assert2 (line 674) | function assert2(e) {
  function assert1 (line 705) | function assert1(e) {
  function assert2 (line 709) | function assert2(e) {
  function assert1 (line 740) | function assert1(e) {
  function assert2 (line 744) | function assert2(e) {
  function assert1 (line 779) | function assert1(e) {
  function assert2 (line 783) | function assert2(e) {
  function assert1 (line 815) | function assert1(e) {
  function assert2 (line 820) | function assert2(e) {
  function assert1 (line 853) | function assert1(e) {
  function assert2 (line 858) | function assert2(e) {
  function assert1 (line 891) | function assert1(e) {
  function assert2 (line 896) | function assert2(e) {
  function assert1 (line 929) | function assert1(e) {
  function assert2 (line 934) | function assert2(e) {
  function assert (line 970) | function assert(e) {
  function assert1 (line 1002) | function assert1(e) {
  function assert1 (line 1031) | function assert1(e) {
  function assert1 (line 1061) | function assert1(e) {
  function assert1 (line 1092) | function assert1(e) {
  function assert (line 1200) | function assert(value) {
  function assert (line 1246) | function assert() {
  function assert (line 1293) | function assert() {

FILE: test/Note.test.js
  function assert (line 50) | function assert(value) {
  function assert (line 108) | function assert(note) {
  function assert (line 150) | function assert(triplet) {
  function assert (line 181) | function assert(value) {
  function assert (line 207) | function assert(value) {
  function assert (line 232) | function assert(value) {
  function assert (line 263) | function assert(value) {
  function assert (line 298) | function assert(value) {
  function assert (line 321) | function assert(value) {
  function assert (line 341) | function assert(pair) {
  function assert (line 361) | function assert(pair) {
  function assert (line 396) | function assert(value) {
  function assert (line 424) | function assert(value) {
  function assert (line 446) | function assert(item) {
  function assert (line 473) | function assert(item) {
  function assert (line 502) | function assert(value) {
  function assert (line 524) | function assert(item) {
  function assert (line 551) | function assert(item) {
  function assert (line 583) | function assert(value) {
  function assert (line 615) | function assert(value) {
  function assert (line 642) | function assert(value) {
  function assert (line 669) | function assert(value) {

FILE: test/Output.test.js
  constant VIRTUAL_OUTPUT (line 8) | let VIRTUAL_OUTPUT = new midi.Input();
  constant VIRTUAL_OUTPUT_NAME (line 9) | let VIRTUAL_OUTPUT_NAME = "Virtual Output";
  constant WEBMIDI_OUTPUT (line 10) | let WEBMIDI_OUTPUT;
  function assert (line 224) | function assert(deltaTime, message) {
  function assert (line 259) | function assert(deltaTime, message) {
  function assert (line 287) | function assert(deltaTime, message) {
  function assert (line 314) | function assert(deltaTime, message) {
  function assert (line 395) | function assert(value){
  function assert (line 429) | function assert(value){
  function assert (line 453) | function assert(value){
  function assert (line 477) | function assert(value){
  function assert (line 498) | function assert(value){
  function assert (line 521) | function assert(value){
  function assert (line 554) | function assert(deltaTime, message) {
  function assert (line 572) | function assert(deltaTime, message) {
  function assert (line 591) | function assert(deltaTime, message) {
  function assert (line 610) | function assert(deltaTime, message) {
  function assert (line 642) | function assert(deltaTime, message) {
  function assert (line 683) | function assert(deltaTime, message) {
  function assert (line 713) | function assert() {
  function assert (line 732) | function assert() {
  function assert (line 753) | function assert() {
  function assert (line 773) | function assert() {
  function assert (line 903) | function assert(deltaTime, message) {

FILE: test/OutputChannel.test.js
  constant VIRTUAL_OUTPUT (line 8) | let VIRTUAL_OUTPUT = new midi.Input();
  constant VIRTUAL_OUTPUT_NAME (line 9) | let VIRTUAL_OUTPUT_NAME = "Virtual Output";
  constant WEBMIDI_OUTPUT (line 10) | let WEBMIDI_OUTPUT;
  function assert (line 48) | function assert(value) {
  function assert (line 71) | function assert(value) {
  function assert (line 120) | function assert(deltaTime, message) {
  function assert (line 175) | function assert(deltaTime, message) {
  function assert (line 287) | function assert(value) {
  function assert (line 308) | function assert(value) {
  function assert (line 357) | function assert(deltaTime, message) {
  function assert (line 412) | function assert(deltaTime, message) {
  function assert (line 607) | function assert(deltaTime, message) {
  function assert (line 635) | function assert(deltaTime, message) {
  function assert (line 663) | function assert(deltaTime, message) {
  function assert (line 688) | function assert(deltaTime, message) {
  function assert (line 728) | function assert(deltaTime, message) {
  function assert (line 772) | function assert(value){
  function assert (line 796) | function assert(value){
  function assert (line 814) | function assert(deltaTime, message) {
  function assert (line 836) | function assert(deltaTime, message) {
  function assert (line 859) | function assert(value){
  function assert (line 896) | function assert(deltaTime, message) {
  function assert (line 925) | function assert() {
  function assert (line 945) | function assert() {
  function assert (line 968) | function assert(deltaTime, message) {
  function assert (line 1002) | function assert(deltaTime, message) {
  function assert (line 1033) | function assert(value) {
  function assert (line 1057) | function assert(value) {
  function assert (line 1086) | function assert(deltaTime, message) {
  function assert (line 1111) | function assert(deltaTime, message) {
  function assert (line 1212) | function assert(deltaTime, message) {
  function assert (line 1271) | function assert(deltaTime, message) {
  function assert (line 1297) | function assert(value) {
  function assert (line 1317) | function assert(value) {
  function assert (line 1338) | function assert(value) {
  function assert (line 1368) | function assert(deltaTime, message) {
  function assert (line 1393) | function assert(deltaTime, message) {
  function assert (line 1422) | function assert(deltaTime, message) {
  function assert (line 1450) | function assert(deltaTime, message) {
  function assert (line 1471) | function assert(deltaTime, message) {
  function assert (line 1637) | function assert(deltaTime, message) {
  function assert (line 1662) | function assert(deltaTime, message) {
  function assert (line 1691) | function assert(deltaTime, message) {
  function assert (line 1719) | function assert(deltaTime, message) {
  function assert (line 1740) | function assert(deltaTime, message) {
  function assert (line 1909) | function assert(deltaTime, message) {
  function assert (line 1936) | function assert(deltaTime, message) {
  function assert (line 1964) | function assert(value) {
  function assert (line 1988) | function assert(value) {
  function assert (line 2016) | function assert(deltaTime, message) {
  function assert (line 2049) | function assert(deltaTime, message) {
  function assert (line 2082) | function assert(deltaTime, message) {
  function assert (line 2123) | function assert(deltaTime, message) {
  function assert (line 2158) | function assert(value) {
  function assert (line 2182) | function assert(value) {
  function assert (line 2238) | function assert(deltaTime, message) {
  function assert (line 2285) | function assert(value) {
  function assert (line 2324) | function assert(deltaTime, message) {
  function assert (line 2388) | function assert(deltaTime, message) {
  function assert (line 2417) | function assert(semitone) {
  function assert (line 2438) | function assert(value) {
  function assert (line 2532) | function assert(deltaTime, message) {
  function assert (line 2567) | function assert(deltaTime, message) {
  function assert (line 2598) | function assert(value) {
  function assert (line 2623) | function assert(value) {
  function assert (line 2685) | function assert(deltaTime, message) {
  function assert (line 2719) | function assert(deltaTime, message) {
  function assert (line 2757) | function assert(deltaTime, message) {
  function assert (line 2783) | function assert(value) {
  function assert (line 2812) | function assert(value) {
  function assert (line 2870) | function assert(deltaTime, message) {
  function assert (line 2899) | function assert(value) {
  function assert (line 2922) | function assert(value) {
  function assert (line 2978) | function assert(deltaTime, message) {
  function assert (line 2996) | function assert(deltaTime, message) {
  function assert (line 3026) | function assert(deltaTime, message) {
  function assert (line 3050) | function assert(value) {
  function assert (line 3145) | function assert(deltaTime, message) {
  function assert (line 3181) | function assert(deltaTime, message) {
  function assert (line 3216) | function assert(deltaTime, message) {
  function assert (line 3247) | function assert(value) {
  function assert (line 3272) | function assert(value) {
  function assert (line 3333) | function assert(deltaTime, message) {
  function assert (line 3363) | function assert(value) {
  function assert (line 3416) | function assert(deltaTime, message) {
  function assert (line 3445) | function assert(value) {
  function assert (line 3534) | function assert(deltaTime, message) {
  function assert (line 3576) | function assert(deltaTime, message) {

FILE: test/Utilities.test.js
  function assert (line 22) | function assert(pair) {
  function assert (line 53) | function assert(value) {
  function assert (line 75) | function assert(item) {
  function assert (line 100) | function assert(item) {
  function assert (line 128) | function assert(item) {
  function assert (line 153) | function assert(item) {
  function assert (line 182) | function assert(item) {
  function assert (line 299) | function assert(value) {
  function assert (line 314) | function assert(value) {
  function assert (line 337) | function assert(item) {
  function assert (line 357) | function assert(item) {
  function assert (line 384) | function assert(value) {
  function assert (line 408) | function assert(item) {
  function assert (line 437) | function assert(value) {
  function assert (line 465) | function assert(value) {
  function assert (line 494) | function assert(item) {
  function assert (line 519) | function assert(item) {
  function assert (line 544) | function assert(item) {
  function assert (line 571) | function assert(item) {
  function assert (line 595) | function assert(item) {
  function assert (line 630) | function assert(item) {
  function assert (line 657) | function assert(value) {
  function assert (line 680) | function assert(value) {
  function assert (line 700) | function assert(pair) {
  function assert (line 722) | function assert(pair) {
  function assert (line 782) | function assert(item) {
  function assert (line 826) | function assert(item) {
  function assert (line 860) | function assert(item) {
  function assert (line 1028) | function assert(value) {
  function assert (line 1068) | function assert(item) {
  function assert (line 1093) | function assert(item) {
  function assert (line 1118) | function assert(item) {
  function assert (line 1143) | function assert(item) {
  function assert (line 1174) | function assert(item) {
  function assert (line 1196) | function assert(item) {
  function assert (line 1223) | function assert(item) {
  function assert (line 1245) | function assert(item) {

FILE: test/WebMidi.test.js
  constant VIRTUAL_INPUT_NAME (line 8) | const VIRTUAL_INPUT_NAME = "Virtual Input";
  constant VIRTUAL_INPUT (line 9) | const VIRTUAL_INPUT = new midi.Output(VIRTUAL_INPUT_NAME);
  constant VIRTUAL_OUTPUT_NAME (line 10) | const VIRTUAL_OUTPUT_NAME = "Virtual Output";
  constant VIRTUAL_OUTPUT (line 11) | const VIRTUAL_OUTPUT = new midi.Input(VIRTUAL_OUTPUT_NAME);

FILE: test/support/JZZ.js
  function c (line 1) | function c(){(this._orig=this)._ready=!1,this._queue=[],this._log=[]}
  function p (line 1) | function p(t,e){this._bad?e instanceof Function&&e.apply(this,[new Error...
  function l (line 1) | function l(t,e){this._bad?t._crash(this._err()):setTimeout(function(){t....
  function d (line 1) | function d(t){this._bad&&t._break(this._err()),t._resume()}
  function _ (line 1) | function _(t,n,r){t[r]=function(){var t=arguments,e=n._image();return th...
  function m (line 1) | function m(t){this._bad||(t instanceof Function?t.apply(this):console.lo...
  function g (line 1) | function g(t){this._bad&&(t instanceof Function?t.apply(this):console.lo...
  function v (line 1) | function v(t){this._bad?t._crash(this._err()):(this._break("Closed"),t._...
  function y (line 1) | function y(t){if(t.length){var e=t.shift();if(t.length){var n=this;this....
  function C (line 1) | function C(t,e){for(var n=0;n<t.length;n++)if(t[n]===e)return;t.push(e)}
  function b (line 1) | function b(t,e){for(var n=0;n<t.length;n++)if(t[n]===e)return void t.spl...
  function w (line 1) | function w(){c.apply(this)}
  function S (line 1) | function S(t,e,n){if(void 0===e)return S(t,[],[]);if(t instanceof Object...
  function t (line 1) | function t(){}
  function t (line 1) | function t(){}
  function x (line 1) | function x(){var t,e;for(M._info.engine=K._type,M._info.version=K._versi...
  function P (line 1) | function P(){this._bad||K._refresh(this)}
  function B (line 1) | function B(t,e){var n,r;t instanceof Function&&(t=t(e)),t instanceof Arr...
  function T (line 1) | function T(t,e){var n;n=e instanceof RegExp?"Port matching "+e+" not fou...
  function F (line 1) | function F(t,e){if(this._bad)t._crash(this._err());else{var n=B(e,O);if(...
  function q (line 1) | function q(t,e){if(this._bad)t._crash(this._err());else{var n=B(e,I);if(...
  function D (line 1) | function D(t,e){this._bad?t._crash():(t._slip(J,[e]),t._resume())}
  function L (line 1) | function L(){c.apply(this),this._handles=[],this._outs=[]}
  function z (line 1) | function z(t){this._bad||this._receive(t)}
  function j (line 1) | function j(t){this._emit(t)}
  function k (line 1) | function k(t){t instanceof Function?C(this._orig._handles,t):C(this._ori...
  function N (line 1) | function N(t){void 0===t?(this._orig._handles=[],this._orig._outs=[]):t ...
  function R (line 1) | function R(t,e){this._orig._mpe||(this._orig._mpe=new Nt),this._orig._mp...
  function U (line 1) | function U(t){if(t!=parseInt(t)||t<0||15<t)throw RangeError("Bad channel...
  function G (line 1) | function G(t,e){L.apply(this),this._port=t._orig,this._chan=e,_(this,thi...
  function Z (line 1) | function Z(t,e,n){L.apply(this),this._port=t._orig,this._master=e,this._...
  function V (line 1) | function V(){c.apply(this),this._handles=[],_(this,M,"refresh"),_(this,M...
  function J (line 1) | function J(t){t instanceof Function&&(this._orig._handles.length||K._wat...
  function W (line 1) | function W(t){void 0===t?this._orig._handles=[]:b(this._orig._handles,t)...
  function X (line 1) | function X(){"undefined"!=typeof module&&module.exports?function(t){K._t...
  function Y (line 1) | function Y(){var t=document.createElement("div");t.style.visibility="hid...
  function tt (line 1) | function tt(){if(Q){var e=this;return Q.call(H,{}).then(function(t){ut(t...
  function et (line 1) | function et(){if(Q){var e=this;return Q.call(H,{sysex:!0}).then(function...
  function nt (line 1) | function nt(){var n,r,i=this;this._pause(),document.addEventListener("ja...
  function rt (line 1) | function rt(){this._pause();var t=this;f(function(){t._crash()})}
  function it (line 1) | function it(t){for(var e=[],n=function(t){var e=["node","extension","plu...
  function ot (line 1) | function ot(){K._type="none",K._version=s,K._sysex=!0,K._outs=[],K._ins=...
  function st (line 1) | function st(){var t;function s(){for(var t=0;t<this.clients.length;t++)t...
  function ut (line 1) | function ut(t,e){var n;function o(){for(var t=0;t<this.clients.length;t+...
  function at (line 1) | function at(t){return M||function(t){Rt(),(M=new w)._options=t,M._push(y...
  function ht (line 1) | function ht(){var t=this instanceof ht?this:t=new ht;return ht.prototype...
  function ft (line 1) | function ft(){29.97==this.type&&!this.second&&this.frame<2&&this.minute%...
  function ct (line 1) | function ct(t){return[[24,25,29.97,30][t[7]>>1&3],(1&t[7])<<4|t[6],t[5]<...
  function pt (line 1) | function pt(t){for(var e,n=[],r=0;r<t.length;r++)n[r]=(e=t[r])<10?"0"+e:...
  function lt (line 1) | function lt(t){var e,n=this instanceof lt?this:n=new lt;if(t instanceof ...
  function mt (line 1) | function mt(t){throw RangeError("Bad MIDI value: "+t)}
  function gt (line 1) | function gt(t){return U(t),parseInt(t)}
  function vt (line 1) | function vt(t,e){return(t!=parseInt(t)||t<0||127<t)&&mt(void 0===e?t:e),...
  function yt (line 1) | function yt(t,e){return(t!=parseInt(t)||t<0||255<t)&&mt(void 0===e?t:e),...
  function Ct (line 1) | function Ct(t){return(t!=parseInt(t)||t<0||16383<t)&&mt(t),127&parseInt(t)}
  function bt (line 1) | function bt(t){return(t!=parseInt(t)||t<0||16383<t)&&mt(t),parseInt(t)>>7}
  function Mt (line 1) | function Mt(t,e){var n=new lt;return n.ff=yt(t),n.dd=void 0===e?"":funct...
  function It (line 1) | function It(t,e,n){t.prototype[e]=function(){return this.send(n.apply(0,...
  function At (line 1) | function At(t,e,n){t.prototype[e]=function(){return this.send(n.apply(0,...
  function Et (line 1) | function Et(t,e){lt[t]=function(){return new lt(e.apply(0,arguments))}}
  function xt (line 1) | function xt(t,e){lt[t]=function(){return e.apply(0,arguments)}}
  function Pt (line 1) | function Pt(t,r){Et(t,r),Z.prototype[t]=function(){var t,e=Array.prototy...
  function Bt (line 1) | function Bt(t,e){for(n in St)St.hasOwnProperty(n)&&It(t,n,St[n]);for(n i...
  function qt (line 1) | function qt(t){for(var e=[],n=0;n<t.length;n++)e[n]=t.charCodeAt(n);retu...
  function Dt (line 1) | function Dt(t){return t instanceof Array?function(t){for(var e="",n=0;n<...
  function Lt (line 1) | function Lt(t){return(t<16?"0":"")+t.toString(16)}
  function zt (line 1) | function zt(t){for(var e=[],n=0;n<t.length;n++)e[n]=Lt(t[n]);return e.jo...
  function jt (line 1) | function jt(t){return t.length?": "+zt(qt(t)):""}
  function kt (line 1) | function kt(t){return t.length?": "+function(t){for(var e="",n=0;n<t.len...
  function Nt (line 1) | function Nt(){var t=this instanceof Nt?this:t=new Nt;return t.reset(),ar...
  function Rt (line 1) | function Rt(){if(!Tt&&"undefined"!=typeof window){var t=window.AudioCont...
  function Wt (line 1) | function Wt(t,e,n){this.name=t,this.message=e,this.code=n}
  function Ht (line 1) | function Ht(t,e){this.bubbles=!1,this.cancelBubble=!1,this.cancelable=!1...
  function Qt (line 1) | function Qt(t,e){this.bubbles=!1,this.cancelBubble=!1,this.cancelable=!1...
  function Kt (line 1) | function Kt(t,e){t&&(t.onstatechange&&t.onstatechange(new Ht(t,t)),e.ons...
  function $t (line 1) | function $t(n,r){var i=this,o=!1,e=null,s=null;this.type="input",this.id...
  function Xt (line 1) | function Xt(t){for(var e,n;t.length;){for(e=0;e<t.length&&!(t[e]==parseI...
  function Yt (line 1) | function Yt(t,e,n,r){var i=this;this.id=t,this.name=e,this.man=n,this.ve...
  function te (line 1) | function te(t){return 128<=t&&t<=191||224<=t&&t<=239||242==t?2:192<=t&&t...
  function ne (line 1) | function ne(r,i){var o=this,s=!1,e=null;this.type="output",this.id=i.id,...
  function re (line 1) | function re(t,e,n,r){this.id=t,this.name=e,this.man=n,this.ver=r,this.co...
  function ie (line 1) | function ie(r){this.has=function(t){return r.hasOwnProperty(t)&&r[t].con...
  function oe (line 1) | function oe(e,n){this.get=function(t){if(Vt.hasOwnProperty(t)&&Vt[t].con...
  function se (line 1) | function se(e,n){this.get=function(t){if(Zt.hasOwnProperty(t)&&Zt[t].con...
  function ue (line 1) | function ue(t){var e,n,r,i;for(e=0;e<t.inputs.added.length;e++)r=t.input...
  function ae (line 1) | function ae(t){var e,n,r=null;this.sysexEnabled=t,this.inputs=new oe(thi...

FILE: test/support/Utils.iife.js
  function isNative (line 1) | function isNative(fn) {

FILE: typescript/webmidi.d.ts
  type Navigator (line 11) | interface Navigator {
  type MIDIOptions (line 21) | interface MIDIOptions {
  type MIDIInputMap (line 33) | type MIDIInputMap = Map<string, MIDIInput>;
  type MIDIOutputMap (line 39) | type MIDIOutputMap = Map<string, MIDIOutput>;
  type MIDIAccess (line 41) | interface MIDIAccess extends EventTarget {
  type MIDIPortType (line 76) | type MIDIPortType = 'input' | 'output';
  type MIDIPortDeviceState (line 78) | type MIDIPortDeviceState = 'disconnected' | 'connected';
  type MIDIPortConnectionState (line 80) | type MIDIPortConnectionState = 'open' | 'closed' | 'pending';
  type MIDIPort (line 82) | interface MIDIPort extends EventTarget {
  type MIDIInput (line 164) | interface MIDIInput extends MIDIPort {
  type MIDIOutput (line 185) | interface MIDIOutput extends MIDIPort {
  type MIDIMessageEvent (line 206) | interface MIDIMessageEvent extends Event {
  type MIDIMessageEventInit (line 218) | interface MIDIMessageEventInit extends EventInit {
  type MIDIConnectionEvent (line 230) | interface MIDIConnectionEvent extends Event {
  type MIDIConnectionEventInit (line 237) | interface MIDIConnectionEventInit extends EventInit {
  class EventEmitter (line 253) | class EventEmitter {
  class Listener (line 545) | class Listener {
  class Enumerations (line 638) | class Enumerations {
  class Forwarder (line 996) | class Forwarder {
  class Input (line 1097) | class Input extends EventEmitter {
  class InputChannel (line 1747) | class InputChannel extends EventEmitter {
  class Message (line 2070) | class Message {
  class Note (line 2212) | class Note {
  class Output (line 2385) | class Output extends EventEmitter {
  class OutputChannel (line 4127) | class OutputChannel extends EventEmitter {
  class Utilities (line 5168) | class Utilities {
  class WebMidi (line 5547) | class WebMidi extends EventEmitter {
  type EventEmitterCallback (line 6036) | type EventEmitterCallback = (...args: any[]) => void;
  type Event (line 6052) | interface Event {
  type ErrorEvent (line 6070) | interface ErrorEvent extends Event {
  type PortEvent (line 6100) | interface PortEvent extends Event {
  type MessageEvent (line 6125) | interface MessageEvent extends PortEvent {
  type ControlChangeMessageEvent (line 6160) | interface ControlChangeMessageEvent extends MessageEvent {
  type NoteMessageEvent (line 6188) | interface NoteMessageEvent extends MessageEvent {
  type ParameterNumberMessageEvent (line 6225) | interface ParameterNumberMessageEvent extends MessageEvent {
  type InputChannelEventMap (line 6236) | interface InputChannelEventMap {
  type PortEventMap (line 6408) | interface PortEventMap {
  type InputEventMap (line 6417) | interface InputEventMap extends PortEventMap {
  type WebMidiEventMap (line 6602) | interface WebMidiEventMap {

FILE: website/docusaurus.config.js
  constant BASE_URL (line 4) | const BASE_URL = "/";

FILE: website/src/components/Button.js
  function Button (line 13) | function Button(props) {

FILE: website/src/components/Column.js
  function Column (line 4) | function Column({children, type,}) {

FILE: website/src/components/HomepageFeatures.js
  function Feature (line 38) | function Feature({Svg, title, description}) {
  function HomepageFeatures (line 52) | function HomepageFeatures() {

FILE: website/src/components/InformationBar.js
  function InformationBar (line 4) | function InformationBar({children, type,}) {

FILE: website/src/pages/index.js
  function HomepageHero (line 9) | function HomepageHero() {
  function Presentation (line 35) | function Presentation() {
  function Home (line 68) | function Home() {

FILE: website/src/pages/tester/index.js
  function Tester (line 17) | function Tester() {

FILE: website/src/theme/CodeBlock/index.js
  function CodeBlockWrapper (line 4) | function CodeBlockWrapper(props) {

FILE: website/src/theme/Footer/index.js
  function Footer (line 14) | function Footer() {

FILE: website/src/theme/Navbar/index.js
  function NavbarWrapper (line 4) | function NavbarWrapper(props) {
Condensed preview — 176 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,909K chars).
[
  {
    "path": ".babelrc",
    "chars": 165,
    "preview": "{\n  \"presets\": [\n    [\n      \"@babel/preset-env\", { \"targets\": {\"node\": \"current\"} }\n    ]\n  ],\n  \"env\": {\n    \"test\": {"
  },
  {
    "path": ".editorconfig",
    "chars": 416,
    "preview": "# EditorConfig helps maintain consistent coding styles for multiple developers working on the same\n# project across vari"
  },
  {
    "path": ".env.sample",
    "chars": 316,
    "preview": "# This is an example file. To actually use it, you need to rename it \".env\". The .env file is used\n# by the dotenv modul"
  },
  {
    "path": ".eslintrc.cjs",
    "chars": 1678,
    "preview": "module.exports = {\n\n  \"env\": {\n    \"amd\": true,\n    \"browser\": true,\n    \"mocha\": true,\n    \"node\": true,\n    \"es6\": tru"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 662,
    "preview": "# Up to 4 GitHub sponsors-enabled usernames e.g. [user1, user2]\ngithub: [djipco]\n\n# Single Patreon username\n#patreon:\n\n#"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/ask-a-question.md",
    "chars": 412,
    "preview": "---\nname: Ask a question\nabout: This is to ask a usage, support or general question\ntitle: ''\nlabels: ''\nassignees: ''\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/report-a-bug.md",
    "chars": 590,
    "preview": "---\nname: Report a bug\nabout: This is only to report a bug, issue or problem.\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n*"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/suggest-a-new-feature.md",
    "chars": 481,
    "preview": "---\nname: Suggest a new feature\nabout: This is to suggest a new feature or improvement\ntitle: ''\nlabels: ''\nassignees: '"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 2330,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".gitignore",
    "chars": 1122,
    "preview": "### System #########################################################################################\n.DS_Store*\nIcon?\n._"
  },
  {
    "path": ".nycrc",
    "chars": 99,
    "preview": "{\n  \"report-dir\": \"./node_modules/nyc/.nyc_output\",\n  \"temp-dir\": \"./node_modules/nyc/.coverage\"\n}\n"
  },
  {
    "path": "BANNER.txt",
    "chars": 744,
    "preview": "<%= pkg.webmidi.name %> v<%= pkg.version %>\n<%= pkg.webmidi.tagline %>\n<%= pkg.homepage %>\nBuild generated on <%= moment"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7202,
    "preview": "# Changelog\n\nStarting with version 3.x, all notable changes to WebMidi.js will be documented in this file. The \nformat u"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5220,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3403,
    "preview": "# Contributing\n\nFirst off, **thank you** for considering to contribute to this project. You should know that there \nare "
  },
  {
    "path": "LICENSE.txt",
    "chars": 10141,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 3533,
    "preview": "![WebMidi.js Logo](https://webmidijs.org/img/webmidijs-logo-color-on-white.svg \"WebMidi.js\")\n\n[![](https://data.jsdelivr"
  },
  {
    "path": "SECURITY.md",
    "chars": 746,
    "preview": "# Security Policy\n\n## Supported Versions\n\n\n| Version       | Support            | Notes                                 "
  },
  {
    "path": "examples/README.md",
    "chars": 595,
    "preview": "# WEBMIDI.js Usage Examples\n\nIn this directory, you will find starter examples to help you use WEBMIDI.js in various con"
  },
  {
    "path": "examples/electron/README.md",
    "chars": 780,
    "preview": "# Using WEBMIDI.js with Electron\n\nA collection of examples to use WEBMIDI.js inside an Electron application.\n\n## Example"
  },
  {
    "path": "examples/electron/basic-example/README.md",
    "chars": 281,
    "preview": "# Starter Template for Electron\n\nThis is a minimal Electron application based on the [Quick Start Guide](https://electro"
  },
  {
    "path": "examples/electron/basic-example/index.html",
    "chars": 806,
    "preview": "<!DOCTYPE html>\n\n<html>\n\n  <head>\n\n    <meta charset=\"UTF-8\">\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n     "
  },
  {
    "path": "examples/electron/basic-example/main.js",
    "chars": 2017,
    "preview": "// Modules to control application life and create native browser window\nconst {app, BrowserWindow} = require(\"electron\")"
  },
  {
    "path": "examples/electron/basic-example/package.json",
    "chars": 307,
    "preview": "{\n  \"name\": \"webmidijs-electron-demo\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A minimal Electron application\",\n  \"main\""
  },
  {
    "path": "examples/electron/basic-example/preload.js",
    "chars": 440,
    "preview": "// All of the Node.js APIs are available in the preload process. It has the same sandbox as a Chrome\n// extension.\nwindo"
  },
  {
    "path": "examples/electron/basic-example/renderer.js",
    "chars": 788,
    "preview": "// This file is required by the index.html file and will be executed in the renderer process for\n// that window. No Node"
  },
  {
    "path": "examples/next.js/README.md",
    "chars": 163,
    "preview": "# 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* "
  },
  {
    "path": "examples/next.js/basic-example/.gitignore",
    "chars": 362,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "examples/next.js/basic-example/README.md",
    "chars": 194,
    "preview": "\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"
  },
  {
    "path": "examples/next.js/basic-example/package.json",
    "chars": 243,
    "preview": "{\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \""
  },
  {
    "path": "examples/next.js/basic-example/pages/index.js",
    "chars": 5406,
    "preview": "import Head from \"next/head\";\n\nimport {WebMidi} from \"webmidi\";\n// const {WebMidi} = require(\"webmidi\");\n\nWebMidi.enable"
  },
  {
    "path": "examples/p5.js/README.md",
    "chars": 325,
    "preview": "# 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* [*"
  },
  {
    "path": "examples/p5.js/basic-example/index.html",
    "chars": 582,
    "preview": "<!DOCTYPE html>\n\n<html lang=\"en\">\n\n  <head>\n    <meta charset=\"utf-8\" />\n    <script src=\"https://cdnjs.cloudflare.com/a"
  },
  {
    "path": "examples/p5.js/basic-example/sketch.js",
    "chars": 839,
    "preview": "function setup() {\n\n  createCanvas(window.innerWidth, window.innerHeight);\n  noStroke();\n\n  // Enable WebMidi.js and tri"
  },
  {
    "path": "examples/p5.js/basic-example/styles.css",
    "chars": 201,
    "preview": "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:"
  },
  {
    "path": "examples/p5.js/querying-note-state/index.html",
    "chars": 561,
    "preview": "<!DOCTYPE html>\n\n<html lang=\"en\">\n\n  <head>\n    <meta charset=\"utf-8\" />\n    <script src=\"https://cdnjs.cloudflare.com/a"
  },
  {
    "path": "examples/p5.js/querying-note-state/sketch.js",
    "chars": 1331,
    "preview": "let channel;\n\nasync function setup() {\n\n  // Enable WebMidi.js\n  await WebMidi.enable();\n\n  // Display available inputs "
  },
  {
    "path": "examples/p5.js/querying-note-state/styles.css",
    "chars": 115,
    "preview": "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",
    "chars": 1049,
    "preview": "<!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    <"
  },
  {
    "path": "examples/react/README.md",
    "chars": 153,
    "preview": "# Using WEBMIDI.js with p5.js\n\nA collection of examples to use WEBMIDI.js inside the React framework.\n\n## Examples\n\n* [*"
  },
  {
    "path": "examples/react/basic-example/.gitignore",
    "chars": 310,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "examples/react/basic-example/README.md",
    "chars": 3348,
    "preview": "# Getting Started with Create React App\n\nThis project was bootstrapped with [Create React App](https://github.com/facebo"
  },
  {
    "path": "examples/react/basic-example/package.json",
    "chars": 842,
    "preview": "{\n  \"name\": \"basic-example\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@testing-library/jest-dom"
  },
  {
    "path": "examples/react/basic-example/public/index.html",
    "chars": 1721,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.i"
  },
  {
    "path": "examples/react/basic-example/public/manifest.json",
    "chars": 492,
    "preview": "{\n  \"short_name\": \"React App\",\n  \"name\": \"Create React App Sample\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n     "
  },
  {
    "path": "examples/react/basic-example/public/robots.txt",
    "chars": 67,
    "preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "examples/react/basic-example/src/App.css",
    "chars": 564,
    "preview": ".App {\n  text-align: center;\n}\n\n.App-logo {\n  height: 40vmin;\n  pointer-events: none;\n}\n\n@media (prefers-reduced-motion:"
  },
  {
    "path": "examples/react/basic-example/src/App.js",
    "chars": 664,
    "preview": "import logo from './logo.svg';\nimport './App.css';\nimport {WebMidi} from \"webmidi\";\n\nfunction App() {\n\n  WebMidi.enable("
  },
  {
    "path": "examples/react/basic-example/src/App.test.js",
    "chars": 246,
    "preview": "import { render, screen } from '@testing-library/react';\nimport App from './App';\n\ntest('renders learn react link', () ="
  },
  {
    "path": "examples/react/basic-example/src/index.css",
    "chars": 366,
    "preview": "body {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n    'Ubuntu', 'Can"
  },
  {
    "path": "examples/react/basic-example/src/index.js",
    "chars": 500,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport repor"
  },
  {
    "path": "examples/react/basic-example/src/reportWebVitals.js",
    "chars": 362,
    "preview": "const reportWebVitals = onPerfEntry => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vitals'"
  },
  {
    "path": "examples/react/basic-example/src/setupTests.js",
    "chars": 241,
    "preview": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).to"
  },
  {
    "path": "examples/typescript/README.md",
    "chars": 650,
    "preview": "# Using WEBMIDI.js in a TypeScript project\n\nVersion 3 of WEBMIDI.js officially supports TypeScript. Type declarations ar"
  },
  {
    "path": "examples/typescript/basic-nodejs-example/index.ts",
    "chars": 462,
    "preview": "import {WebMidi} from \"webmidi\";\n\nasync function start() {\n\n  await WebMidi.enable();\n\n  // List available inputs\n  cons"
  },
  {
    "path": "examples/typescript/basic-nodejs-example/package.json",
    "chars": 196,
    "preview": "{\n  \"name\": \"typescript-basic-nidejs-example\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"devDependencies\": {\n    \"t"
  },
  {
    "path": "package.json",
    "chars": 5665,
    "preview": "{\n  \"name\": \"webmidi\",\n  \"version\": \"3.1.16\",\n  \"description\": \"WEBMIDI.js makes it easy to talk to MIDI instruments fro"
  },
  {
    "path": "scripts/api-documentation/generate-html.js",
    "chars": 4673,
    "preview": "// This script generates web-based API documentation files from jsdoc comments in the source code\n// and commits them to"
  },
  {
    "path": "scripts/api-documentation/generate-markdown.js",
    "chars": 4678,
    "preview": "// Imports\nconst djipHelpers = require(\"./templates/helpers/djip-helpers.js\");\nconst fs = require(\"fs-extra\");\nconst git"
  },
  {
    "path": "scripts/api-documentation/templates/core/class.hbs",
    "chars": 786,
    "preview": "{{!\n\n  This template creates the 'class' portion of the output (at the top). It relies on the following\n  helpers:\n\n    "
  },
  {
    "path": "scripts/api-documentation/templates/core/constructor.hbs",
    "chars": 990,
    "preview": "{{!\n\n  This template creates the 'constructor' portion of the output (below the class). It relies on the\n  following hel"
  },
  {
    "path": "scripts/api-documentation/templates/core/enumerations.hbs",
    "chars": 449,
    "preview": "{{!\n\n  This template creates the 'enums' portion of the output . It relies on the following helpers:\n\n    - inlineLinks\n"
  },
  {
    "path": "scripts/api-documentation/templates/core/events.hbs",
    "chars": 816,
    "preview": "{{!\n\n  This template creates the 'Events' portion of the output . It relies on the following helpers:\n\n    - inlineLinks"
  },
  {
    "path": "scripts/api-documentation/templates/core/methods.hbs",
    "chars": 1605,
    "preview": "{{!\n\n  This template creates the 'Events' portion of the output . It relies on the following helpers:\n\n    - inlineLinks"
  },
  {
    "path": "scripts/api-documentation/templates/core/properties.hbs",
    "chars": 976,
    "preview": "{{!\n\n  This template creates the 'properties' portion of the output . It relies on the following helpers:\n\n    - inlineL"
  },
  {
    "path": "scripts/api-documentation/templates/helpers/ddata.js",
    "chars": 19895,
    "preview": "var arrayify = require('array-back');\nvar util = require('util');\nvar handlebars = require('handlebars');\nvar marked = r"
  },
  {
    "path": "scripts/api-documentation/templates/helpers/djip-helpers.js",
    "chars": 1872,
    "preview": "const ddata = require(\"./ddata.js\");\n\n/**\n * Strips newline characters (\\n) from the input\n * @param input {string}\n * @"
  },
  {
    "path": "scripts/api-documentation/templates/helpers/state.js",
    "chars": 47,
    "preview": "exports.templateData = []\nexports.options = {}\n"
  },
  {
    "path": "scripts/library/build.js",
    "chars": 2418,
    "preview": "// This script builds the bundled library files (both normal and minified with sourcemaps) and\n// commits them to the 'd"
  },
  {
    "path": "scripts/library/rollup.config.cjs.js",
    "chars": 592,
    "preview": "import babel from \"@rollup/plugin-babel\";\nimport stripCode from \"rollup-plugin-strip-code\";\nimport replace from \"@rollup"
  },
  {
    "path": "scripts/library/rollup.config.esm.js",
    "chars": 552,
    "preview": "import stripCode from \"rollup-plugin-strip-code\";\nimport replace from \"@rollup/plugin-replace\";\n\nconst fs = require(\"fs\""
  },
  {
    "path": "scripts/library/rollup.config.iife.js",
    "chars": 844,
    "preview": "import babel from \"@rollup/plugin-babel\";\nimport stripCode from \"rollup-plugin-strip-code\";\nimport replace from \"@rollup"
  },
  {
    "path": "scripts/sponsors/retrieve-sponsors.js",
    "chars": 2456,
    "preview": "const { graphql } = require(\"@octokit/graphql\");\nconst { token } = require(\"../../.credentials/sponsors.js\");\nconst repl"
  },
  {
    "path": "scripts/typescript-declarations/generate.js",
    "chars": 1691,
    "preview": "// This script injects a few dynamic properties into the source TypeScript declaration file and\n// copies it to the ./di"
  },
  {
    "path": "scripts/typescript-declarations/generateOLD.js",
    "chars": 4781,
    "preview": "// This script generates TypeScript declaration files (.d.ts) of the library and saves them to the\n// 'dist' directory f"
  },
  {
    "path": "scripts/website/deploy.js",
    "chars": 2293,
    "preview": "// This script copies the files generated by Docusaurus in /website/build to the root of the\n// 'gh-pages' branch. This "
  },
  {
    "path": "src/Enumerations.js",
    "chars": 38743,
    "preview": "/**\n * The `Enumerations` class contains enumerations and arrays of elements used throughout the\n * library. All its pro"
  },
  {
    "path": "src/Forwarder.js",
    "chars": 5055,
    "preview": "import {Enumerations} from \"./Enumerations.js\";\nimport {Output} from \"./Output.js\";\nimport {WebMidi} from \"./WebMidi.js\""
  },
  {
    "path": "src/Input.js",
    "chars": 42403,
    "preview": "import {Enumerations} from \"./Enumerations.js\";\nimport {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js"
  },
  {
    "path": "src/InputChannel.js",
    "chars": 142698,
    "preview": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {WebMidi} from \"./WebMidi.js\";\nimport "
  },
  {
    "path": "src/Message.js",
    "chars": 5239,
    "preview": "import {Utilities} from \"./Utilities.js\";\nimport {Enumerations} from \"./Enumerations.js\";\n\n/**\n * The `Message` class re"
  },
  {
    "path": "src/Note.js",
    "chars": 9080,
    "preview": "import {WebMidi} from \"./WebMidi.js\";\nimport {Utilities} from \"./Utilities.js\";\n\n/**\n * The `Note` class represents a si"
  },
  {
    "path": "src/Output.js",
    "chars": 99773,
    "preview": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {OutputChannel} from \"./OutputChannel."
  },
  {
    "path": "src/OutputChannel.js",
    "chars": 76649,
    "preview": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {WebMidi} from \"./WebMidi.js\";\nimport "
  },
  {
    "path": "src/Utilities.js",
    "chars": 21002,
    "preview": "import {Note} from \"./Note.js\";\nimport {WebMidi} from \"./WebMidi.js\";\nimport {Enumerations} from \"./Enumerations.js\";\n\n/"
  },
  {
    "path": "src/WebMidi.js",
    "chars": 40489,
    "preview": "import {EventEmitter} from \"../node_modules/djipevents/src/djipevents.js\";\nimport {Input} from \"./Input.js\";\nimport {Out"
  },
  {
    "path": "test/Enumerations.test.js",
    "chars": 1161,
    "preview": "const expect = require(\"chai\").expect;\nconst {Enumerations} = require(\"../dist/cjs/webmidi.cjs.js\");\n\n// VERIFIED\ndescri"
  },
  {
    "path": "test/Forwarder.test.js",
    "chars": 5175,
    "preview": "const expect = require(\"chai\").expect;\nconst {Message, WebMidi, Note, Forwarder} = require(\"../dist/cjs/webmidi.cjs.js\")"
  },
  {
    "path": "test/Input.test.js",
    "chars": 26837,
    "preview": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst sinon = require(\"sinon\");\nconst {We"
  },
  {
    "path": "test/InputChannel.test.js",
    "chars": 35182,
    "preview": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst {WebMidi, Utilities, Enumerations, "
  },
  {
    "path": "test/Message.test.js",
    "chars": 3829,
    "preview": "const expect = require(\"chai\").expect;\nconst {Message, Enumerations} = require(\"../dist/cjs/webmidi.cjs.js\");\n\ndescribe("
  },
  {
    "path": "test/Note.test.js",
    "chars": 14354,
    "preview": "const expect = require(\"chai\").expect;\nconst {WebMidi, Note, Utilities} = require(\"../dist/cjs/webmidi.cjs.js\");\n\ndescri"
  },
  {
    "path": "test/Output.test.js",
    "chars": 66382,
    "preview": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst sinon = require(\"sinon\");\nconst {We"
  },
  {
    "path": "test/OutputChannel.test.js",
    "chars": 90081,
    "preview": "const expect = require(\"chai\").expect;\nconst midi = require(\"@julusian/midi\");\nconst sinon = require(\"sinon\");\nconst {We"
  },
  {
    "path": "test/Utilities.test.js",
    "chars": 28860,
    "preview": "const expect = require(\"chai\").expect;\nconst {Utilities, WebMidi, Note, Enumerations} = require(\"../dist/cjs/webmidi.cjs"
  },
  {
    "path": "test/WebMidi.test.js",
    "chars": 22167,
    "preview": "const expect = require(\"chai\").expect;\nconst {WebMidi, Utilities} = require(\"../dist/cjs/webmidi.cjs.js\");\nconst midi = "
  },
  {
    "path": "test/support/JZZ.js",
    "chars": 57011,
    "preview": "!function(t,e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof def"
  },
  {
    "path": "test/support/Utils.cjs.js",
    "chars": 139,
    "preview": "const UtilsCjs = {\n\n  isNative: function(fn) {\n    return (/\\{\\s*\\[native code\\]\\s*\\}/).test(\"\" + fn);\n  }\n\n};\n\nmodule.e"
  },
  {
    "path": "test/support/Utils.iife.js",
    "chars": 81,
    "preview": "function isNative(fn) {\n  return (/\\{\\s*\\[native code\\]\\s*\\}/).test(\"\" + fn);\n}\n\n"
  },
  {
    "path": "typescript/webmidi.d.ts",
    "chars": 295913,
    "preview": "// Type definitions for {{LIBRARY}} v{{VERSION}}\n// Project: {{HOMEPAGE}}\n// Definitions by: {{AUTHOR_NAME}} <{{AUTHOR_U"
  },
  {
    "path": "website/.gitignore",
    "chars": 233,
    "preview": "# 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.lo"
  },
  {
    "path": "website/README.md",
    "chars": 743,
    "preview": "# Website\n\nThis website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.\n\n## In"
  },
  {
    "path": "website/api/classes/Enumerations.md",
    "chars": 12690,
    "preview": "\n# Enumerations\n\nThe `Enumerations` class contains enumerations and arrays of elements used throughout the\nlibrary. All "
  },
  {
    "path": "website/api/classes/EventEmitter.md",
    "chars": 14763,
    "preview": "\n# EventEmitter\n\nThe `EventEmitter` class provides methods to implement the _observable_ design pattern. This\npattern al"
  },
  {
    "path": "website/api/classes/Forwarder.md",
    "chars": 3288,
    "preview": "\n# Forwarder\n\nThe `Forwarder` class allows the forwarding of MIDI messages to predetermined outputs. When you\ncall its ["
  },
  {
    "path": "website/api/classes/Input.md",
    "chars": 43660,
    "preview": "\n# Input\n\nThe `Input` class represents a single MIDI input port. This object is automatically instantiated\nby the librar"
  },
  {
    "path": "website/api/classes/InputChannel.md",
    "chars": 147090,
    "preview": "\n# InputChannel\n\nThe `InputChannel` class represents a single MIDI input channel (1-16) from a single input\ndevice. This"
  },
  {
    "path": "website/api/classes/Listener.md",
    "chars": 2753,
    "preview": "\n# Listener\n\nThe `Listener` class represents a single event listener object. Such objects keep all relevant\ncontextual i"
  },
  {
    "path": "website/api/classes/Message.md",
    "chars": 3649,
    "preview": "\n# Message\n\nThe `Message` class represents a single MIDI message. It has several properties that make it\neasy to make se"
  },
  {
    "path": "website/api/classes/Note.md",
    "chars": 5879,
    "preview": "\n# Note\n\nThe `Note` class represents a single musical note such as `\"D3\"`, `\"G#4\"`, `\"F-1\"`, `\"Gb7\"`, etc.\n\n`Note` objec"
  },
  {
    "path": "website/api/classes/Output.md",
    "chars": 98042,
    "preview": "\n# Output\n\nThe `Output` class represents a single MIDI output port (not to be confused with a MIDI channel).\nA port is m"
  },
  {
    "path": "website/api/classes/OutputChannel.md",
    "chars": 70599,
    "preview": "\n# OutputChannel\n\nThe `OutputChannel` class represents a single output MIDI channel. `OutputChannel` objects are\nprovide"
  },
  {
    "path": "website/api/classes/Utilities.md",
    "chars": 18655,
    "preview": "\n# Utilities\n\nThe `Utilities` class contains general-purpose utility methods. All methods are static and\nshould be calle"
  },
  {
    "path": "website/api/classes/WebMidi.md",
    "chars": 33429,
    "preview": "\n# WebMidi\n\nThe `WebMidi` object makes it easier to work with the low-level Web MIDI API. Basically, it\nsimplifies sendi"
  },
  {
    "path": "website/api/classes/_category_.json",
    "chars": 74,
    "preview": "{\n  \"label\": \"Classes & Objects\",\n  \"position\": 2,\n  \"collapsed\": false\n}\n"
  },
  {
    "path": "website/api/index.md",
    "chars": 1628,
    "preview": "---\nsidebar_position: 1\ntitle: API Documentation\nslug: /\n---\n\n# API Documentation\n\n## Core Classes\n\nThese classes are th"
  },
  {
    "path": "website/babel.config.js",
    "chars": 89,
    "preview": "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",
    "chars": 3997,
    "preview": "---\ntitle: WEBMIDI.js v3 is available now!\ndescription: Version 3 of WEBMIDI.js, the library that lets you interact with"
  },
  {
    "path": "website/docs/archives/_category_.json",
    "chars": 53,
    "preview": "{\n  \"label\": \"Previous Versions\",\n  \"position\": 40\n}\n"
  },
  {
    "path": "website/docs/archives/v1.md",
    "chars": 348,
    "preview": "---\nsidebar_position: 2\nslug: /archives/v1\nsidebar_label: Version 1.0.0-beta.15\n---\n\n# Documentation for v1.0.0-beta.15 "
  },
  {
    "path": "website/docs/archives/v2.md",
    "chars": 14913,
    "preview": "---\nsidebar_position: 1\nslug: /archives/v2\nsidebar_label: Version 2.5.3\n---\n\n# Documentation for v2.5.3\n\n:::caution\n\nVer"
  },
  {
    "path": "website/docs/getting-started/_category_.json",
    "chars": 51,
    "preview": "{\n  \"label\": \"Getting Started\",\n  \"position\": 10\n}\n"
  },
  {
    "path": "website/docs/getting-started/basics.md",
    "chars": 11232,
    "preview": "---\nsidebar_position: 3\n---\n\n# Basics\n\n## Enabling the Library \n\nThe first step to get started is to enable the library."
  },
  {
    "path": "website/docs/getting-started/installation.md",
    "chars": 3884,
    "preview": "---\nsidebar_position: 2\n---\n\n# Installation\n\n## Distribution Flavours\n\nTo cater to various needs, WEBMIDI.js is distribu"
  },
  {
    "path": "website/docs/getting-started/supported-environments.md",
    "chars": 1662,
    "preview": "---\nsidebar_position: 1\nslug: /getting-started\n---\n\n# Supported Environments\n\nStarting with version 3, the library works"
  },
  {
    "path": "website/docs/going-further/_category_.json",
    "chars": 49,
    "preview": "{\n  \"label\": \"Going Further\",\n  \"position\": 20\n}\n"
  },
  {
    "path": "website/docs/going-further/electron.md",
    "chars": 709,
    "preview": "---\nsidebar_position: 5\ntitle: Electron\n---\n\n# Electron\n\nWEBMIDI.js works fine inside [Electron](https://www.electronjs."
  },
  {
    "path": "website/docs/going-further/forwarding.md",
    "chars": 300,
    "preview": "---\nsidebar_position: 3\ntitle: Forwarding\n---\n\n# Forwarding Messages\n\nStarting with version 3, it is now possible to for"
  },
  {
    "path": "website/docs/going-further/middle-c.md",
    "chars": 2446,
    "preview": "---\nsidebar_position: 1\n---\n\n# Middle C & Octave Offset\n\n## Default Value for Middle C\n\nThe general MIDI 1.0 specificati"
  },
  {
    "path": "website/docs/going-further/performance.md",
    "chars": 119,
    "preview": "---\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",
    "chars": 78,
    "preview": "---\nsidebar_position: 3\ntitle: Sysex\n---\n\n# System Exclusive Messages (sysex)\n"
  },
  {
    "path": "website/docs/going-further/typescript.md",
    "chars": 444,
    "preview": "---\nsidebar_position: 4\ntitle: TypeScript\n---\n\n# TypeScript\n\nTypeScript is supported in version 3+. However, it has not "
  },
  {
    "path": "website/docs/index.md",
    "chars": 3909,
    "preview": "---\nsidebar_position: 1\nslug: /\n---\n\n# Quick Start For v3.x\n\n**You want to get started as quickly as possible?** This gu"
  },
  {
    "path": "website/docs/migration/_category_.json",
    "chars": 45,
    "preview": "{\n  \"label\": \"Migration\",\n  \"position\": 30\n}\n"
  },
  {
    "path": "website/docs/migration/migration.md",
    "chars": 6594,
    "preview": "---\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 feedba"
  },
  {
    "path": "website/docs/roadmap/_category_.json",
    "chars": 43,
    "preview": "{\n  \"label\": \"Roadmap\",\n  \"position\": 50\n}\n"
  },
  {
    "path": "website/docs/roadmap/under-evaluation.md",
    "chars": 5076,
    "preview": "---\nsidebar_label: Under Evaluation\nsidebar_position: 2\n---\n\n# Potential Enhancements To Evaluate\n\nThe analysis has not "
  },
  {
    "path": "website/docs/roadmap/v3.md",
    "chars": 5644,
    "preview": "---\nsidebar_label: Version 3\nsidebar_position: 1\n---\n\n# Roadmap for version 3.x\n\n## Enhancements Still Remaining\n\n* Publ"
  },
  {
    "path": "website/docs/roadmap/v4.md",
    "chars": 520,
    "preview": "---\nsidebar_label: Version 4\nsidebar_position: 2\n---\n\n# Features Planned for Version 4\n\nThe full analysis has not starte"
  },
  {
    "path": "website/docusaurus.config.js",
    "chars": 6393,
    "preview": "const lightCodeTheme = require(\"prism-react-renderer/themes/github\");\nconst darkCodeTheme = require(\"prism-react-rendere"
  },
  {
    "path": "website/package.json",
    "chars": 1324,
    "preview": "{\n  \"name\": \"docusaurus\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"s"
  },
  {
    "path": "website/sidebars.js",
    "chars": 613,
    "preview": "/**\n * Creating a sidebar enables you to:\n - create an ordered group of docs\n - render a sidebar for each doc of that gr"
  },
  {
    "path": "website/src/components/Button.js",
    "chars": 729,
    "preview": "import React from \"react\";\nimport styles from \"./Button.module.scss\";\n\n/*export default function Button({children, type,"
  },
  {
    "path": "website/src/components/Button.module.css",
    "chars": 1857,
    "preview": ".Button {\n  background-color: var(--color-accent);\n  width: fit-content;\n  height: fit-content;\n  border-radius: 10px;\n "
  },
  {
    "path": "website/src/components/Button.module.scss",
    "chars": 1695,
    "preview": "$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);\n.Button {\n  background-color: var(--color-accent);\n  width: f"
  },
  {
    "path": "website/src/components/Column.js",
    "chars": 322,
    "preview": "import React from \"react\";\nimport styles from \"./Column.module.scss\";\n\nexport default function Column({children, type,})"
  },
  {
    "path": "website/src/components/Column.module.css",
    "chars": 311,
    "preview": ".Column {\n  display: grid;\n}\n\n.col-2 {\n  grid-template-columns: 48% 48%;\n  justify-content: space-between;\n  align-items"
  },
  {
    "path": "website/src/components/Column.module.scss",
    "chars": 251,
    "preview": ".Column{\n  display: grid;\n}\n\n.col-2 {\n  grid-template-columns: 48% 48%;\n  justify-content: space-between;\n  align-items:"
  },
  {
    "path": "website/src/components/HomepageFeatures.js",
    "chars": 1793,
    "preview": "import React from \"react\";\nimport clsx from \"clsx\";\nimport styles from \"./HomepageFeatures.module.css\";\n\nconst FeatureLi"
  },
  {
    "path": "website/src/components/HomepageFeatures.module.css",
    "chars": 191,
    "preview": "/* stylelint-disable docusaurus/copyright-header */\n\n.features {\n  display: flex;\n  align-items: center;\n  padding: 2rem"
  },
  {
    "path": "website/src/components/InformationBar.js",
    "chars": 430,
    "preview": "import React from \"react\";\nimport styles from \"./InformationBar.module.scss\";\n\nexport default function InformationBar({c"
  },
  {
    "path": "website/src/components/InformationBar.module.css",
    "chars": 283,
    "preview": ".InformationBar {\n  background-color: var(--color-bg-secondary);\n  padding: var(--spacing-xl) 0;\n  color: var(--color-wh"
  },
  {
    "path": "website/src/components/InformationBar.module.scss",
    "chars": 236,
    "preview": ".InformationBar{\n    background-color: var(--color-bg-secondary);\n    padding: var(--spacing-xl) 0;\n    color: var(--col"
  },
  {
    "path": "website/src/css/custom.css",
    "chars": 6302,
    "preview": "/* stylelint-disable docusaurus/copyright-header */\n@import url(\"https://fonts.googleapis.com/css2?family=Exo:ital,wght@"
  },
  {
    "path": "website/src/css/custom.scss",
    "chars": 7045,
    "preview": "/* stylelint-disable docusaurus/copyright-header */\n\n// Global custom stylesheet for Docusaurus. The 'classic' template "
  },
  {
    "path": "website/src/css/index.css",
    "chars": 2301,
    "preview": ".hero {\n  padding: var(--spacing-lg) 0;\n  min-height: 60vh;\n  color: var(--color-text-primary);\n  display: flex;\n  align"
  },
  {
    "path": "website/src/css/index.scss",
    "chars": 2196,
    "preview": ".hero{\n  padding: var(--spacing-lg) 0;\n  min-height: 60vh;\n  color: var(--color-text-primary);\n  display: flex;\n  align-"
  },
  {
    "path": "website/src/pages/about/index.md",
    "chars": 1639,
    "preview": "# 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](h"
  },
  {
    "path": "website/src/pages/index.js",
    "chars": 2671,
    "preview": "import React from \"react\";\nimport useBaseUrl from '@docusaurus/useBaseUrl';\nimport Layout from \"@theme/Layout\";\nimport u"
  },
  {
    "path": "website/src/pages/index.module.css",
    "chars": 2480,
    "preview": "/* stylelint-disable docusaurus/copyright-header */\n/**\n * CSS files with the .module.css suffix will be treated as CSS "
  },
  {
    "path": "website/src/pages/index.module.scss",
    "chars": 2450,
    "preview": "/* stylelint-disable docusaurus/copyright-header */\n\n/**\n * CSS files with the .module.css suffix will be treated as CSS"
  },
  {
    "path": "website/src/pages/research/index.md",
    "chars": 3493,
    "preview": "# Academic Research\n\nI invite all academics and researchers to show their support for this project by properly citing it"
  },
  {
    "path": "website/src/pages/showcase/index.md",
    "chars": 6948,
    "preview": "---\ntoc_max_heading_level: 2\n---\n\n# Showcase\n\nWebMidi.js is being used by amazing people to create awesome projects. Her"
  },
  {
    "path": "website/src/pages/sponsors/index.md",
    "chars": 1343,
    "preview": "# Sponsors\n\n:::tip Consider a sponsorship\n\nPlease help me grow and nurture WEBMIDI.js by ❤️ [sponsoring](https://github."
  },
  {
    "path": "website/src/pages/tester/index.js",
    "chars": 861,
    "preview": "import React from \"react\";\nimport Layout from \"@theme/Layout\";\nimport {Helmet} from \"react-helmet\";\n\n\n\n// import useBase"
  },
  {
    "path": "website/src/theme/CodeBlock/index.js",
    "chars": 193,
    "preview": "import React from 'react';\nimport CodeBlock from '@theme-original/CodeBlock';\n\nexport default function CodeBlockWrapper("
  },
  {
    "path": "website/src/theme/Footer/index.js",
    "chars": 1796,
    "preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
  },
  {
    "path": "website/src/theme/Footer/styles.module.css",
    "chars": 1033,
    "preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
  },
  {
    "path": "website/src/theme/Footer/styles.module.scss",
    "chars": 967,
    "preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
  },
  {
    "path": "website/src/theme/Navbar/index.js",
    "chars": 181,
    "preview": "import React from 'react';\nimport Navbar from '@theme-original/Navbar';\n\nexport default function NavbarWrapper(props) {\n"
  },
  {
    "path": "website/src/theme/Navbar/styles.module.css",
    "chars": 557,
    "preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
  },
  {
    "path": "website/static/.nojekyll",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "website/static/js/newsletter-popup.js",
    "chars": 284,
    "preview": "// 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"
  },
  {
    "path": "website/static/styles/default.css",
    "chars": 526,
    "preview": "a {\n  text-decoration: none;\n}\na:link {\n  color: #ffd000;\n}\na:visited {\n  color: #ffd000;\n}\na:hover {\n  color: #ffd000;\n"
  },
  {
    "path": "website/static/styles/default.scss",
    "chars": 568,
    "preview": "// Colors\n$base-color: #ffd000;\n\na {\n\n  text-decoration: none;\n\n  &:link {\n    color: $base-color;\n  }\n\n  &:visited {\n  "
  },
  {
    "path": "website/static/styles/jsdoc.css",
    "chars": 77,
    "preview": ".page-header, pre.code-toolbar > .toolbar:hover {\n  background-color: red;\n}\n"
  }
]

About this extraction

This page contains the full source code of the cotejp/webmidi GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 176 files (1.8 MB), approximately 484.4k tokens, and a symbol index with 735 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!