├── .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-lock.json │ │ ├── package.json │ │ ├── preload.js │ │ └── renderer.js ├── next.js │ ├── README.md │ └── basic-example │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── pages │ │ └── index.js │ │ └── public │ │ ├── favicon.ico │ │ └── vercel.svg ├── 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-lock.json │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── reportWebVitals.js │ │ └── setupTests.js └── typescript │ ├── README.md │ └── basic-nodejs-example │ ├── index.ts │ ├── package-lock.json │ └── package.json ├── package-lock.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.cjs.min.js │ ├── rollup.config.esm.js │ ├── rollup.config.esm.min.js │ ├── rollup.config.iife.js │ └── rollup.config.iife.min.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 │ └── webmidi.js-is-available-now.png ├── 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-lock.json ├── package.json ├── sidebars.js ├── src ├── components │ ├── Button.js │ ├── Button.module.css │ ├── Button.module.css.map │ ├── Button.module.scss │ ├── Column.js │ ├── Column.module.css │ ├── Column.module.css.map │ ├── Column.module.scss │ ├── HomepageFeatures.js │ ├── HomepageFeatures.module.css │ ├── InformationBar.js │ ├── InformationBar.module.css │ ├── InformationBar.module.css.map │ └── InformationBar.module.scss ├── css │ ├── custom.css │ ├── custom.css.map │ ├── custom.scss │ ├── index.css │ ├── index.css.map │ └── index.scss ├── pages │ ├── about │ │ └── index.md │ ├── index.js │ ├── index.module.css │ ├── index.module.css.map │ ├── 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.css.map │ └── styles.module.scss │ └── Navbar │ ├── index.js │ └── styles.module.css └── static ├── .nojekyll ├── img ├── blog │ ├── 2021-12-01 │ │ └── webmidijs-is-out.png │ └── jean-philippe_cote.jpg ├── docusaurus.png ├── favicon.ico ├── front-page │ ├── presentation-illustration-keyboard.svg │ ├── webmidi-demonstration-vertical.svg │ └── webmidi-demonstration.svg ├── logo.svg ├── og-card.png ├── person.png ├── sponsors │ ├── edouard-montpetit-logo.svg │ └── user.png ├── tutorial │ ├── docsVersionDropdown.png │ └── localeDropdown.png ├── undraw_docusaurus_mountain.svg ├── undraw_docusaurus_react.svg ├── undraw_docusaurus_tree.svg ├── webmidijs-logo-color-on-white.svg ├── webmidijs-logo-dark.svg ├── webmidijs-logo-light.svg ├── webmidijs-logo-small.png ├── webmidijs3-logo-1178x406.png ├── webmidijs3-logo-1280x640.png └── webmidijs3-logo-40x40.png ├── js └── newsletter-popup.js └── styles ├── default.css ├── default.css.map ├── default.scss └── jsdoc.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", { "targets": {"node": "current"} } 5 | ] 6 | ], 7 | "env": { 8 | "test": { 9 | "plugins": [ "istanbul" ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps maintain consistent coding styles for multiple developers working on the same 2 | # project across various editors and IDEs: https://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | indent_style = space 11 | indent_size = 2 12 | max_line_length = 100 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [COMMIT_EDITMSG] 19 | max_line_length = 0 20 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # This is an example file. To actually use it, you need to rename it ".env". The .env file is used 2 | # by the dotenv module to set environment variables needed during development. The .env file MUST 3 | # NEVER BE committed. 4 | 5 | # Token used by the `release-it` module to create automatic GitHub releases. 6 | GITHUB_TOKEN=XXXXX 7 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | "env": { 4 | "amd": true, 5 | "browser": true, 6 | "mocha": true, 7 | "node": true, 8 | "es6": true 9 | }, 10 | 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | 16 | "globals": { 17 | "Promise": "readonly", 18 | "WebMidi": "readonly", 19 | "chai": "readonly", 20 | "sinon": "readonly", 21 | "expect": "readonly", 22 | "Note": "readonly", 23 | "isNative": "readonly", 24 | "config": "readonly" 25 | }, 26 | 27 | "extends": [ 28 | "eslint:recommended", 29 | "prettier", 30 | "plugin:react/recommended" 31 | ], 32 | 33 | // The idea here is to stick to the rules defined by Prettier (https://prettier.io/) and only make 34 | // exceptions in ESLint when absolutely necessary. 35 | "rules": { 36 | 37 | // Rules to align ESLint with Prettier (even though we are already using eslint-config-prettier) 38 | "indent": ["error", 2], 39 | "semi": ["error", "always"], 40 | "quote-props": ["error", "as-needed"], 41 | "quotes": ["error", "double", {"avoidEscape": true, "allowTemplateLiterals": true}], 42 | 43 | // Rules that knowingly change the default Prettier behaviour 44 | "no-multi-spaces": ["error", { "ignoreEOLComments": true }], 45 | "linebreak-style": ["error", "unix"], // Force \n instead of Prettier's auto-detect behaviour 46 | "no-trailing-spaces": ["error", { "skipBlankLines": true, "ignoreComments": true }], 47 | "max-len": ["error", { "code": 100, "comments": 150 }], // Prettier's 80 is too small. Period. 48 | "no-console": ["error", { "allow": ["info", "warn", "error"] }], // Only some (unlike Prettier) 49 | 50 | // Other rules 51 | "no-prototype-builtins": "off", 52 | 53 | "react/prop-types": "off" 54 | 55 | }, 56 | 57 | "settings": { 58 | "react": { 59 | "version": "detect" 60 | } 61 | } 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Up to 4 GitHub sponsors-enabled usernames e.g. [user1, user2] 2 | github: [djipco] 3 | 4 | # Single Patreon username 5 | #patreon: 6 | 7 | # Replace with a single Open Collective username 8 | #open_collective: 9 | 10 | # Replace with a single Ko-fi username 11 | #ko_fi: 12 | 13 | # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 14 | #tidelift: 15 | 16 | # Replace with a single Community Bridge project-name e.g., cloud-foundry 17 | #community_bridge: 18 | 19 | # Replace with a single Liberapay username 20 | #liberapay: 21 | 22 | # Replace with a single IssueHunt username 23 | #issuehunt: 24 | 25 | # Replace with a single Otechie username 26 | #otechie: 27 | 28 | # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 29 | #custom: 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-a-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: This is to ask a usage, support or general question 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Questions should be submitted to the Forum** 11 | 12 | All questions should be submitted to the **Questions & Support** section of the WebMidi.js Forum: 13 | 14 | https://webmidijs.org/forum/ 15 | 16 | This opens up the question to the community while reserving GitHub strictly for bugs and issues. 17 | 18 | Thank you. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report-a-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a bug 3 | about: This is only to report a bug, issue or problem. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description** 11 | Describe the problem and how to reproduce it. If appropriate, include code samples, screenshots, error messages, etc. 12 | 13 | **Environment:** 14 | Specify the environment where you are witnessing the problem: 15 | - Library version and flavour (CJS, ESM or IIFE) 16 | - Runtime (browser or Node.js) and version 17 | - Language (JavaScript or TypeScript) 18 | - Operating system 19 | 20 | **Details** 21 | Add any other information, context or details that could help track down the problem. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggest-a-new-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Suggest a new feature 3 | about: This is to suggest a new feature or improvement 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Enhancement proposals should be submitted in the Forum** 11 | 12 | To submit a new feature or improvement request, please post it to the **Enhancement Proposals** section of the WebMidi.js Forum: 13 | 14 | https://webmidijs.org/forum/ 15 | 16 | This allows the feature request to be discussed with the community while reserving GitHub strictly for bugs and issues. 17 | 18 | Thank you. 19 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '32 5 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### System ######################################################################################### 2 | .DS_Store* 3 | Icon? 4 | ._* 5 | Thumbs.db 6 | ehthumbs.db 7 | Desktop.ini 8 | .directory 9 | *~ 10 | *.tgz 11 | 12 | ### Editors ######################################################################################## 13 | .idea 14 | 15 | .vscode/* 16 | !.vscode/settings.json 17 | !.vscode/tasks.json 18 | !.vscode/launch.json 19 | !.vscode/extensions.json 20 | 21 | *.sublime-project 22 | *.sublime-workspace 23 | 24 | ### Dependency Directories ######################################################################### 25 | node_modules 26 | 27 | ### Logs ########################################################################################### 28 | *.log 29 | 30 | ### Circle CI ###################################################################################### 31 | .circleci 32 | 33 | ### Test coverage ################################################################################## 34 | .nyc_output 35 | coverage 36 | 37 | ### Passwords, tokens and such ##################################################################### 38 | .credentials 39 | .env* 40 | !.env.sample 41 | 42 | ### Production build ############################################################################### 43 | dist 44 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "report-dir": "./node_modules/nyc/.nyc_output", 3 | "temp-dir": "./node_modules/nyc/.coverage" 4 | } 5 | -------------------------------------------------------------------------------- /BANNER.txt: -------------------------------------------------------------------------------- 1 | <%= pkg.webmidi.name %> v<%= pkg.version %> 2 | <%= pkg.webmidi.tagline %> 3 | <%= pkg.homepage %> 4 | Build generated on <%= moment().format('MMMM Do, YYYY') %>. 5 | 6 | © Copyright 2015-<%= moment().format('YYYY') %>, Jean-Philippe Côté. 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 9 | in compliance with the License. You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software distributed under the License 14 | is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 15 | or implied. See the License for the specific language governing permissions and limitations under 16 | the License. 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First off, **thank you** for considering to contribute to this project. You should know that there 4 | are many ways to contribute. You can write tutorials or blog posts, improve the documentation, 5 | submit bug reports or feature requests and write actual source code. All of these are very 6 | worthwhile contributions. 7 | 8 | Following these guidelines helps to communicate that you respect the time of the developers managing 9 | this open source project. In return, they will reciprocate that respect in addressing your issue, 10 | assessing changes, and helping you finalize your pull requests. 11 | 12 | ## Submitting a Feature Requests 13 | 14 | If you find yourself wishing for a feature, you are probably not alone. There are bound to be others 15 | out there with similar needs. Before submitting a feature request, first check if the 16 | [wiki](https://github.com/djipco/webmidi/wiki)'s enhancements section already lists that feature. 17 | 18 | If not, open an [issue](https://github.com/djipco/webmidi/issues) which describes the feature you 19 | would like to see, why you need it, and how it should work. 20 | 21 | ## Understanding How to Contribute 22 | 23 | Contribution to this project is done via pull requests. This allows the owner and contributors to 24 | properly review what gets merged into the project. 25 | 26 | **If this is your first pull request**, you can learn how to get started from a free series of 27 | tutorials called 28 | [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 29 | 30 | ## Submitting a Pull Request 31 | 32 | **WebMidi.js** is a relatively small library supported by an even smaller team. Therefore, the 33 | process for contributing is intended to be simple and friendly. 34 | 35 | However, to insure good quality, there are steps that you should go through when submitting a PR. 36 | Here are the usual steps: 37 | 38 | 1. Discuss the change(s) you wish to make by means of an 39 | [issue](https://github.com/djipco/webmidi/issues). 40 | 2. Unless the PR is for a minor improvement (typo, documentation, etc.), you should write and/or 41 | update unit tests and check your code against the tests (see below). 42 | 3. If appropriate, update the [jsdoc](http://usejsdoc.org/) comments. Keeping the documentation and 43 | the API consistant is very important. 44 | 4. If appropriate, update the `README.md` file. 45 | 46 | Please note that your code should adhere to the styles defined in `.eslintrc.js`. You can use 47 | `npm run lint` to make sure it does. 48 | 49 | Finally, **do not** update the library's version number. Version numbering and releases will be 50 | handled by the owner. If the PR breaks backwards-compatibility, it must be communicated explicitely 51 | to the owner. The versioning scheme follows the [SemVer](http://semver.org/) standard. 52 | 53 | ## Testing 54 | 55 | WebMidi.js now has a proper test suite. The tests can be run on the command line without a need for 56 | a browser (thanks to Tim Susa). 57 | 58 | You can execute all tests, including code coverage, by running the following command in the 59 | terminal or on the command line: 60 | 61 | ``` 62 | npm run test-all 63 | ``` 64 | 65 | You can develop in *watch mode* with hot file reloading like so: 66 | ``` 67 | npm run test -- -w 68 | ``` 69 | 70 | You can start a single test in this way: 71 | ``` 72 | npx mocha ./test/virtual-midi-test.js 73 | ``` 74 | 75 | You can develop a single test in *watch mode* like this: 76 | ``` 77 | npx mocha ./test/virtual-midi-test.js -- -w 78 | ``` 79 | 80 | If you simply want to view code coverage, you can do: 81 | ``` 82 | npm run test-coverage 83 | ``` 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![WebMidi.js Logo](https://webmidijs.org/img/webmidijs-logo-color-on-white.svg "WebMidi.js") 2 | 3 | [![](https://data.jsdelivr.com/v1/package/npm/webmidi/badge)](https://www.jsdelivr.com/package/npm/webmidi) 4 | [![npm](https://img.shields.io/npm/dm/webmidi)](https://www.npmjs.com/package/webmidi) 5 | [![npm](https://img.shields.io/npm/dt/webmidi)](https://www.npmjs.com/package/webmidi) 6 | [![](https://img.shields.io/github/stars/djipco/webmidi?style=social)](https://github.com/djipco/webmidi) 7 | [![npm](https://img.shields.io/npm/l/webmidi)](https://www.npmjs.com/package/webmidi) 8 | 9 | ## Introduction 10 | 11 | **WEBMIDI.js** makes it easy to interact with MIDI instruments directly from a web browser or from 12 | Node.js. It simplifies the control of physical or virtual MIDI instruments with user-friendly 13 | functions such as `playNote()`, `sendPitchBend()` or `sendControlChange()`. It also allows reacting 14 | to inbound MIDI messages by adding listeners for events such as `"noteon"`, `"pitchbend"` or 15 | `"programchange"`. 16 | 17 | In short, the goal behind WEBMIDI.js is to get you started with your web-based MIDI project as quickly 18 | and efficiently as possible. 19 | 20 | ## Getting Started 21 | 22 | The [**official website**](https://webmidijs.org) site is the best place to get started. Over there, 23 | you will find, amongst others, two key resources: 24 | 25 | * [Documentation](https://webmidijs.org/docs/) 26 | * [API Reference](https://webmidijs.org/api/) 27 | 28 | To exchange with fellow users and myself, you can visit our [**Forum**](https://github.com/djipco/webmidi/discussions) 29 | which is hosted on the GitHub Discussions platform: 30 | 31 | * [Forum](https://github.com/djipco/webmidi/discussions) 32 | 33 | If you want to stay up-to-date, here are your best sources: 34 | 35 | * [Newsletter](https://mailchi.mp/eeffe50651bd/webmidijs-newsletter) 36 | * [Twitter](https://twitter.com/webmidijs) 37 | 38 | ## Sponsors 39 | 40 | WEBMIDI.js is a passion project but it still takes quite a bit of time, effort and money to develop and 41 | maintain. That's why I would like to sincerely thank 👏 these sponsors for their support: 42 | 43 | [](https://github.com/awatterott "@awatterott")   [](https://github.com/rubendax "@rubendax")   Anonymous Sponsor   [](https://github.com/philmillman "@philmillman")   Anonymous Sponsor   Anonymous Sponsor 44 | 45 | If you use the library and find it useful, please 💜 [**sponsor the project**](https://github.com/sponsors/djipco). 46 | 47 | ## Feature Request 48 | 49 | If you would like to request a new feature, enhancement or API change, please first check that it is 50 | not [already planned](https://webmidijs.org/docs/future-versions/next). Then, discuss it in the 51 | [Enhancement Proposals](https://github.com/djipco/webmidi/discussions/categories/feature-requests) 52 | section of the forum. 53 | 54 | ## Citing this Software in Research 55 | 56 | If you use this software for research or academic purposes, please cite the project in your 57 | references (or wherever appropriate). Here's an example of how to cite it 58 | ([APA Style](https://apastyle.apa.org/)): 59 | 60 | >Côté, J. P. (2021). WebMidi.js v3.0.0 [Computer Software]. Retrieved from 61 | https://github.com/djipco/webmidi 62 | 63 | Cheers! 64 | 65 | -- Jean-Philippe 66 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | 6 | | Version | Support | Notes | 7 | | ------------- | ------------------ | ------------------------------------------------------------- | 8 | | 3.0.x (alpha) | :white_check_mark: | This is the current, officially-supported version. | 9 | | 2.5.x | :white_check_mark: | Bug and security fixes only. | 10 | | 1.0.x | :x: | This version has reached end of life. | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | To report a security-related problem, **you should not file an issue on GitHub** as this might put 15 | users at risk. **Instead, send an email to jp@cote.cc**. 16 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # WEBMIDI.js Usage Examples 2 | 3 | In this directory, you will find starter examples to help you use WEBMIDI.js in various contexts or 4 | for various purposes. 5 | 6 | ## Browser Quick Start Example 7 | 8 | * [Quick Start](quick-start/) 9 | 10 | ## Frameworks 11 | 12 | * [Next.js](next.js/) 13 | * [p5.js](p5.js/) 14 | * [React](react/) 15 | 16 | ## Environments 17 | 18 | * [Electron](electron/) 19 | 20 | ## Languages 21 | 22 | * [TypeScript](typescript/) 23 | 24 | ## More Examples wanted! 25 | 26 | To submit a new example, simply send a pull request and it will be quickly reviewed. Please create 27 | a README.md file to provide information regarding how the example should be used. 28 | -------------------------------------------------------------------------------- /examples/electron/README.md: -------------------------------------------------------------------------------- 1 | # Using WEBMIDI.js with Electron 2 | 3 | A collection of examples to use WEBMIDI.js inside an Electron application. 4 | 5 | ## Examples 6 | 7 | * [**Basic Electron Example**](basic-example) 8 | 9 | ## Platform specific notes 10 | 11 | The permission requests must be properly handled in the main process before initializing WEBMIDI: 12 | 13 | ```javascript 14 | mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => { 15 | if (permission === 'midi' || permission === 'midiSysex') { 16 | callback(true); 17 | } else { 18 | callback(false); 19 | } 20 | }) 21 | 22 | mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => { 23 | if (permission === 'midi' || permission === 'midiSysex') { 24 | return true; 25 | } 26 | 27 | return false; 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /examples/electron/basic-example/README.md: -------------------------------------------------------------------------------- 1 | # Starter Template for Electron 2 | 3 | This is a minimal Electron application based on the [Quick Start Guide](https://electronjs.org/docs/latest/tutorial/quick-start) within the Electron documentation. 4 | 5 | ## To Use 6 | 7 | ```bash 8 | # Install dependencies 9 | npm install 10 | 11 | # Run the app 12 | npm start 13 | ``` 14 | -------------------------------------------------------------------------------- /examples/electron/basic-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | WEBMIDI.js + Electron Demo 14 | 15 | 16 | 17 | 18 | 19 |

WEBMIDI.js + Electron Demo

20 | 21 |

22 | Node.js , 23 | Chromium , 24 | Electron 25 |

26 | 27 |

WEBMIDI.js

28 |

MIDI Inputs:

29 |

MIDI Outputs:

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/electron/basic-example/main.js: -------------------------------------------------------------------------------- 1 | // Modules to control application life and create native browser window 2 | const {app, BrowserWindow} = require("electron"); 3 | const path = require("path"); 4 | 5 | function createWindow () { 6 | 7 | // Create the browser window. 8 | const mainWindow = new BrowserWindow({ 9 | width: 800, 10 | height: 600, 11 | webPreferences: { 12 | preload: path.join(__dirname, "preload.js") 13 | } 14 | }); 15 | 16 | // Respond affirmatively to requests for MIDI and MIDI SysEx permissions 17 | mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => { 18 | if (permission === 'midi' || permission === 'midiSysex') { 19 | callback(true); 20 | } else { 21 | callback(false); 22 | } 23 | }) 24 | 25 | // Respond affirmatively to checks for MIDI and MIDI SysEx permissions 26 | mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => { 27 | if (permission === 'midi' || permission === 'midiSysex') { 28 | return true; 29 | } 30 | 31 | return false; 32 | }); 33 | 34 | // and load the index.html of the app. 35 | mainWindow.loadFile("index.html"); 36 | 37 | // Open dev tools 38 | // mainWindow.webContents.openDevTools(); 39 | 40 | } 41 | 42 | // This method will be called when Electron has finished initialization and is ready to create 43 | // browser windows. Some APIs can only be used after this event occurs. 44 | app.whenReady().then(() => { 45 | 46 | createWindow(); 47 | 48 | app.on("activate", function () { 49 | // On macOS it's common to re-create a window in the app when the dock icon is clicked and there 50 | // are no other windows open. 51 | if (BrowserWindow.getAllWindows().length === 0) createWindow(); 52 | }); 53 | 54 | }); 55 | 56 | // Quit when all windows are closed, except on macOS. There, it's common for applications and their 57 | // menu bar to stay active until the user quits explicitly with Cmd + Q. 58 | app.on("window-all-closed", function () { 59 | if (process.platform !== "darwin") app.quit(); 60 | }); 61 | 62 | // In this file you can include the rest of your app's specific main process code. You can also put 63 | // them in separate files and require them here. 64 | -------------------------------------------------------------------------------- /examples/electron/basic-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webmidijs-electron-demo", 3 | "version": "1.0.0", 4 | "description": "A minimal Electron application", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "license": "CC0-1.0", 10 | "devDependencies": { 11 | "electron": "^22.3.25" 12 | }, 13 | "dependencies": { 14 | "webmidi": "latest" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/electron/basic-example/preload.js: -------------------------------------------------------------------------------- 1 | // All of the Node.js APIs are available in the preload process. It has the same sandbox as a Chrome 2 | // extension. 3 | window.addEventListener("DOMContentLoaded", () => { 4 | 5 | const replaceText = (selector, text) => { 6 | const element = document.getElementById(selector); 7 | if (element) element.innerText = text; 8 | }; 9 | 10 | for (const type of ["chrome", "node", "electron"]) { 11 | replaceText(`${type}-version`, process.versions[type]); 12 | } 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /examples/electron/basic-example/renderer.js: -------------------------------------------------------------------------------- 1 | // This file is required by the index.html file and will be executed in the renderer process for 2 | // that window. No Node.js APIs are available in this process because `nodeIntegration` is turned 3 | // off. Use `preload.js` to selectively enable features needed in the rendering process. 4 | 5 | import {WebMidi} from "./node_modules/webmidi/dist/esm/webmidi.esm.js"; 6 | 7 | WebMidi.enable() 8 | .then(onEnabled) 9 | .catch(err => console.warning(err)); 10 | 11 | function onEnabled() { 12 | 13 | document.getElementById("webmidi-version").innerHTML += `v${WebMidi.version}`; 14 | 15 | WebMidi.inputs.forEach(input => { 16 | document.getElementById("webmidi-inputs").innerHTML += `${input.name}, `; 17 | }); 18 | 19 | WebMidi.outputs.forEach(output => { 20 | document.getElementById("webmidi-outputs").innerHTML += `${output.name}, `; 21 | }); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /examples/next.js/README.md: -------------------------------------------------------------------------------- 1 | # Using WEBMIDI.js with Next.js 2 | 3 | A collection of examples to use WEBMIDI.js with the Next.js framework. 4 | 5 | ## Examples 6 | 7 | * [**Basic Next.js Example**](basic-example) 8 | -------------------------------------------------------------------------------- /examples/next.js/basic-example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /examples/next.js/basic-example/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Starter Template for Next.js 4 | 5 | This is a starter template for [Learn Next.js](https://nextjs.org/learn). 6 | 7 | ## To Use 8 | 9 | ```bash 10 | # Install dependencies 11 | npm install 12 | 13 | # Run the app 14 | npm run dev 15 | ``` 16 | -------------------------------------------------------------------------------- /examples/next.js/basic-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "next": "^14.2.26", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "webmidi": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/next.js/basic-example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djipco/webmidi/f812407b67104ec0d641aa6ed838bca1777ab146/examples/next.js/basic-example/public/favicon.ico -------------------------------------------------------------------------------- /examples/next.js/basic-example/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /examples/p5.js/README.md: -------------------------------------------------------------------------------- 1 | # Using WEBMIDI.js with p5.js 2 | 3 | A collection of examples to use WEBMIDI.js inside the p5.js framework. 4 | 5 | ## Examples 6 | 7 | * [**Basic Example**](basic-example): drawing circles on canvas when **note on** MIDI event is 8 | received. 9 | 10 | * [**Querying note state**](querying-note-state): draw keyboard keys in color when MIDI keys 11 | presssed 12 | -------------------------------------------------------------------------------- /examples/p5.js/basic-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | WebMidi.js + p5.js - Basic Example 11 | 12 | 13 | 14 |

WebMidi.js + p5.js - Basic Example

15 |

Randomly draw circles when a note on event is received on MIDI channel 1

16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/p5.js/basic-example/sketch.js: -------------------------------------------------------------------------------- 1 | function setup() { 2 | 3 | createCanvas(window.innerWidth, window.innerHeight); 4 | noStroke(); 5 | 6 | // Enable WebMidi.js and trigger the onWebMidiEnabled() function when ready. 7 | WebMidi.enable() 8 | .then(onWebMidiEnabled) 9 | .catch(err => alert(err)); 10 | 11 | } 12 | 13 | function draw() { 14 | 15 | } 16 | 17 | function onWebMidiEnabled() { 18 | 19 | // Check if at least one MIDI input is detected. If not, display warning and quit. 20 | if (WebMidi.inputs.length < 1) { 21 | alert("No MIDI inputs detected."); 22 | return; 23 | } 24 | 25 | // Add a listener on all the MIDI inputs that are detected 26 | WebMidi.inputs.forEach(input => { 27 | 28 | // When a "note on" is received on MIDI channel 1, generate a random color start 29 | input.channels[1].addListener("noteon", function() { 30 | fill(random(255), random(255), random(255)); 31 | circle(random(width), random(height), 100); 32 | }); 33 | 34 | }); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /examples/p5.js/basic-example/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100vw; 3 | height: 100vh; 4 | margin: 0; 5 | } 6 | 7 | main { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | width: 100vw; 12 | height: 100vh; 13 | } 14 | 15 | h1, p { 16 | margin: 0; 17 | padding: 16px 16px 0 16px; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /examples/p5.js/querying-note-state/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | WebMidi.js + p5.js - Querying Note State 11 | 12 | 13 | 14 |

WebMidi.js + p5.js - Querying Note State

15 |

Draw colored rectangles when specific notes are pressed.

16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/p5.js/querying-note-state/sketch.js: -------------------------------------------------------------------------------- 1 | let channel; 2 | 3 | async function setup() { 4 | 5 | // Enable WebMidi.js 6 | await WebMidi.enable(); 7 | 8 | // Display available inputs in console (use the name to retrieve it) 9 | console.log(WebMidi.inputs); 10 | const input = WebMidi.getInputByName("MPK mini 3"); 11 | channel = input.channels[1]; 12 | 13 | // Create canvas 14 | createCanvas(500, 200); 15 | 16 | } 17 | 18 | function draw() { 19 | 20 | // Check if WebMidi is enabled and channel has been assigned before moving on 21 | if (!channel) return; 22 | 23 | // Draw blank keys 24 | for (let i = 0; i < 8; i++) { 25 | 26 | // Default fill is white 27 | fill("white"); 28 | 29 | // Each key has its own color. When it's pressed, we draw it in color (instead of white) 30 | if (i === 0 && channel.getNoteState("C4")) { 31 | fill("yellow"); 32 | } else if (i === 1 && channel.getNoteState("D4")) { 33 | fill("red"); 34 | } else if (i === 2 && channel.getNoteState("E4")) { 35 | fill("pink"); 36 | } else if (i === 3 && channel.getNoteState("F4")) { 37 | fill("orange"); 38 | } else if (i === 4 && channel.getNoteState("G4")) { 39 | fill("purple"); 40 | } else if (i === 5 && channel.getNoteState("A4")) { 41 | fill("green"); 42 | } else if (i === 6 && channel.getNoteState("B4")) { 43 | fill("turquoise"); 44 | } else if (i === 7 && channel.getNoteState("C5")) { 45 | fill("blue"); 46 | } 47 | 48 | // Draw the keys 49 | rect(85 + i * 40, 50, 30, 100); 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /examples/p5.js/querying-note-state/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100vw; 3 | height: 100vh; 4 | margin: 0; 5 | } 6 | 7 | h1, p { 8 | margin: 0; 9 | padding: 16px 16px 0 16px; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /examples/quick-start/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | WebMidi.js Quick Start 9 | 10 | 11 | 12 | 40 | 41 | 42 | 43 | 44 |

WebMidi.js Quick Start

45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/react/README.md: -------------------------------------------------------------------------------- 1 | # Using WEBMIDI.js with p5.js 2 | 3 | A collection of examples to use WEBMIDI.js inside the React framework. 4 | 5 | ## Examples 6 | 7 | * [**Basic Example**](basic-example) 8 | -------------------------------------------------------------------------------- /examples/react/basic-example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/react/basic-example/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | 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) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | 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) 55 | 56 | ### Making a Progressive Web App 57 | 58 | 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) 59 | 60 | ### Advanced Configuration 61 | 62 | 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) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | 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) 71 | -------------------------------------------------------------------------------- /examples/react/basic-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.1", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-scripts": "^5.0.0", 12 | "web-vitals": "^2.1.2", 13 | "webmidi": "latest" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/react/basic-example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djipco/webmidi/f812407b67104ec0d641aa6ed838bca1777ab146/examples/react/basic-example/public/favicon.ico -------------------------------------------------------------------------------- /examples/react/basic-example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/react/basic-example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djipco/webmidi/f812407b67104ec0d641aa6ed838bca1777ab146/examples/react/basic-example/public/logo192.png -------------------------------------------------------------------------------- /examples/react/basic-example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djipco/webmidi/f812407b67104ec0d641aa6ed838bca1777ab146/examples/react/basic-example/public/logo512.png -------------------------------------------------------------------------------- /examples/react/basic-example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/react/basic-example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from './logo.svg'; 2 | import './App.css'; 3 | import {WebMidi} from "webmidi"; 4 | 5 | function App() { 6 | 7 | WebMidi.enable() 8 | .then(() => console.log(WebMidi.inputs)) 9 | .catch(err => console.log(err)); 10 | 11 | return ( 12 |
13 |
14 | logo 15 |

16 | Edit src/App.js and save to reload. 17 |

18 | 24 | Learn React 25 | 26 |
27 |
28 | ); 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /examples/react/basic-example/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /examples/typescript/README.md: -------------------------------------------------------------------------------- 1 | # Using WEBMIDI.js in a TypeScript project 2 | 3 | Version 3 of WEBMIDI.js officially supports TypeScript. Type declarations are available for the ESM 4 | (ECMAScript modules) and CJS (CommonJS, a.k.a. Node.js modules) flavours in the `/dist` directory. 5 | 6 | To use the example, `cd` into the example directory, install the necessary modules with 7 | `npm install` and write some TypeScript code. You can generate the final JavaScript code with 8 | `tsc myFile.ts`. This should yield a `myFile.js` which you can run with `node myFile.js`. 9 | 10 | ## Examples 11 | 12 | * [**Basic Example**](basic-nodejs-example): listing input and output ports and reacting to 13 | pressed keyboard keys. 14 | -------------------------------------------------------------------------------- /examples/typescript/basic-nodejs-example/index.ts: -------------------------------------------------------------------------------- 1 | import {WebMidi} from "webmidi"; 2 | 3 | async function start() { 4 | 5 | await WebMidi.enable(); 6 | 7 | // List available inputs 8 | console.log("Available inputs: "); 9 | 10 | WebMidi.inputs.forEach(input => { 11 | console.log("\t" + input.name); 12 | input.addListener("noteon", e => console.log(e.note.identifier)); 13 | }); 14 | 15 | // List available outputs 16 | console.log("Available outputs: "); 17 | 18 | WebMidi.outputs.forEach(output => { 19 | console.log("\t" + output.name); 20 | }); 21 | 22 | } 23 | 24 | start(); 25 | -------------------------------------------------------------------------------- /examples/typescript/basic-nodejs-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-basic-nidejs-example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "devDependencies": { 6 | "typescript": "^4.5.4" 7 | }, 8 | "dependencies": { 9 | "webmidi": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/api-documentation/generate-html.js: -------------------------------------------------------------------------------- 1 | // This script generates web-based API documentation files from jsdoc comments in the source code 2 | // and commits them to the '/archives/api' directory of the 'gh-pages' branch. This makes them 3 | // available at djipco.githib.io/webmidi/archives/api/v3 4 | 5 | // Modules 6 | const fs = require("fs-extra"); 7 | const fsPromises = require("fs").promises; 8 | const git = require("simple-git")(); 9 | const pkg = require("../../package.json"); 10 | const moment = require("moment"); 11 | const path = require("path"); 12 | const os = require("os"); 13 | const rimraf = require("@alexbinary/rimraf"); 14 | const system = require("system-commands"); 15 | 16 | // Some paths 17 | const CUSTOM_CSS = "../css/custom.css"; 18 | const TARGET_BRANCH = "gh-pages"; 19 | const TARGET_PATH = "./archives/api/v" + pkg.version.split(".")[0]; 20 | 21 | // Google Analytics configuration 22 | const GA_CONFIG = { 23 | ua: "UA-162785934-1", 24 | domain: "https://djipco.github.io/webmidi" 25 | }; 26 | 27 | // JSDoc configuration object to write as configuration file 28 | const config = { 29 | 30 | tags: { 31 | allowUnknownTags: true 32 | }, 33 | 34 | // The opts property is for command-line arguments passed directly in the config file 35 | opts: { 36 | template: "./node_modules/foodoc/template" 37 | }, 38 | 39 | // Source files 40 | source: { 41 | include: [ 42 | "./src/Enumerations.js", 43 | "./src/Forwarder.js", 44 | "./src/Input.js", 45 | "./src/InputChannel.js", 46 | "./src/Message.js", 47 | "./src/Note.js", 48 | "./src/Output.js", 49 | "./src/OutputChannel.js", 50 | "./src/Utilities.js", 51 | "./src/WebMidi.js", 52 | 53 | "./node_modules/djipevents/src/djipevents.js" 54 | ] 55 | }, 56 | 57 | sourceType: "module", 58 | 59 | plugins: [ 60 | "plugins/markdown" 61 | ], 62 | 63 | // Configurations for the Foodoc template 64 | templates: { 65 | 66 | systemName: `${pkg.webmidi.name} API`, 67 | systemSummary: pkg.webmidi.tagline, 68 | // systemLogo: LOGO_PATH, 69 | systemColor: "#ffcf09", 70 | 71 | copyright: `© ${pkg.author.name}, ` + 72 | `2015-${new Date().getFullYear()}. ` + 73 | `${pkg.webmidi.name} v${pkg.version} is released under the ${pkg.license} license.`, 74 | 75 | navMembers: [ 76 | {kind: "class", title: "Classes", summary: "All documented classes."}, 77 | {kind: "external", title: "Externals", summary: "All documented external members."}, 78 | // {kind: "global", title: "Globals", summary: "All documented globals."}, 79 | {kind: "mixin", title: "Mixins", summary: "All documented mixins."}, 80 | {kind: "interface", title: "Interfaces", summary: "All documented interfaces."}, 81 | {kind: "module", title: "Modules", summary: "All documented modules."}, 82 | {kind: "namespace", title: "Namespaces", summary: "All documented namespaces."}, 83 | {kind: "tutorial", title: "Tutorials", summary: "All available tutorials."} 84 | ], 85 | 86 | analytics: GA_CONFIG, 87 | 88 | stylesheets: [ 89 | CUSTOM_CSS 90 | ], 91 | 92 | dateFormat: "MMMM Do YYYY @ H:mm:ss", 93 | sort: "longname, linenum, version, since", 94 | 95 | collapseSymbols: true 96 | 97 | } 98 | 99 | }; 100 | 101 | function log(message) { 102 | console.info("\x1b[32m", message, "\x1b[0m"); 103 | } 104 | 105 | async function execute() { 106 | 107 | // Temporary target folder 108 | const TMP_SAVE_PATH = await fsPromises.mkdtemp(path.join(os.tmpdir(), "webmidi-api-doc-")); 109 | const CONF_PATH = TMP_SAVE_PATH + "/.jsdoc.json"; 110 | 111 | // Write temporary configuration file 112 | fs.writeFileSync(CONF_PATH, JSON.stringify(config)); 113 | 114 | // Prepare jsdoc command and generate documentation 115 | const cmd = "./node_modules/.bin/jsdoc " + 116 | `--configure ${CONF_PATH} ` + 117 | `--destination ${TMP_SAVE_PATH}`; 118 | await system(cmd); 119 | log(`Documentation temporarily generated in "${TMP_SAVE_PATH}"`); 120 | 121 | // Remove temporary configuration file 122 | await rimraf(CONF_PATH); 123 | 124 | // Here, we remove the index.html page and replace it with the list_class.html file 125 | await fs.copy( 126 | TMP_SAVE_PATH + "/list_class.html", 127 | TMP_SAVE_PATH + "/index.html", 128 | {overwrite: true} 129 | ); 130 | 131 | // Get current branch (so we can come back to it later) 132 | let results = await git.branch(); 133 | const ORIGINAL_BRANCH = results.current; 134 | 135 | // Switch to target branch 136 | log(`Switching from '${ORIGINAL_BRANCH}' branch to '${TARGET_BRANCH}' branch`); 137 | await git.checkout(TARGET_BRANCH); 138 | 139 | // Move dir to final destination and commit 140 | await fs.move(TMP_SAVE_PATH, TARGET_PATH, {overwrite: true}); 141 | await git.add([TARGET_PATH]); 142 | await git.commit("Updated on: " + moment().format(), [TARGET_PATH]); 143 | await git.push(); 144 | log(`Changes committed to ${TARGET_PATH} folder of '${TARGET_BRANCH}' branch`); 145 | 146 | // Come back to original branch 147 | log(`Switching back to '${ORIGINAL_BRANCH}' branch`); 148 | await git.checkout(ORIGINAL_BRANCH); 149 | 150 | } 151 | 152 | // Execute and catch errors if any (in red) 153 | execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m")); 154 | -------------------------------------------------------------------------------- /scripts/api-documentation/generate-markdown.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const djipHelpers = require("./templates/helpers/djip-helpers.js"); 3 | const fs = require("fs-extra"); 4 | const git = require("simple-git")(); 5 | const Handlebars = require("handlebars"); 6 | const jsdoc2md = require("jsdoc-to-markdown"); 7 | const moment = require("moment"); 8 | const path = require("path"); 9 | const process = require("process"); 10 | 11 | // Paths 12 | const ROOT_PATH = process.cwd(); 13 | const SOURCE_DIR = path.resolve(ROOT_PATH, "src"); 14 | const TARGET_PATH = path.join(process.cwd(), "website", "api", "classes"); 15 | const TEMPLATE_DIR = path.resolve(ROOT_PATH, "scripts/api-documentation/templates/"); 16 | const DJIPEVENTS = path.resolve(ROOT_PATH, "node_modules/djipevents/src/djipevents.js"); 17 | 18 | // Register some Handlebars helpers 19 | Handlebars.registerHelper({ 20 | eventName: djipHelpers.eventName, 21 | stripNewlines: djipHelpers.stripNewlines, 22 | inlineLinks: djipHelpers.inlineLinks, 23 | methodSignature: djipHelpers.methodSignature, 24 | eq: djipHelpers.eq, 25 | ne: djipHelpers.ne, 26 | lt: djipHelpers.lt, 27 | gt: djipHelpers.gt, 28 | lte: djipHelpers.lte, 29 | gte: djipHelpers.gte, 30 | and: djipHelpers.and, 31 | or: djipHelpers.or, 32 | curly: djipHelpers.curly, 33 | createEventAnchor: djipHelpers.createEventAnchor, 34 | }); 35 | 36 | async function generate() { 37 | 38 | // Get source files list 39 | let files = await fs.readdir(SOURCE_DIR); 40 | files = files.map(file => path.resolve(SOURCE_DIR, file)); 41 | files.push(DJIPEVENTS); 42 | 43 | // Compute JSON from JSDoc 44 | const data = jsdoc2md.getTemplateDataSync({files: files}); 45 | 46 | // Build list of classes (explicitly adding Listener and EventEmitter) 47 | let classes = files.map(file => path.basename(file, ".js")).filter(file => file !== "djipevents"); 48 | classes.push("Listener", "EventEmitter"); 49 | 50 | // Parse each class file and save parsed output 51 | classes.forEach(filepath => { 52 | 53 | const basename = path.basename(filepath, ".js"); 54 | const filtered = data.filter(x => x.memberof === basename || x.id === basename); 55 | 56 | // Save markdown files 57 | fs.writeFileSync( 58 | path.resolve(TARGET_PATH, `${basename}.md`), 59 | parseFile(filtered) 60 | ); 61 | console.info(`Saved markdown file to ${basename}.md`); 62 | 63 | }); 64 | 65 | // Commit generated files 66 | await git.add([TARGET_PATH]); 67 | await git.commit("Automatically generated on: " + moment().format(), [TARGET_PATH]); 68 | console.info(`Files in ${TARGET_PATH} committed to git`); 69 | await git.push(); 70 | console.info(`Files pushed to remote`); 71 | 72 | } 73 | 74 | function parseFile(data) { 75 | 76 | let output = ""; 77 | let hbs; 78 | let filtered; 79 | 80 | // Sort elements according to kind and then according to name 81 | const order = {class: 0, constructor: 1, function: 2, member: 3, event: 4, enum: 5, typedef: 6}; 82 | data.sort((a, b) => { 83 | if (order[a.kind] === order[b.kind]) { 84 | return a.id.localeCompare(b.id); 85 | } else { 86 | return order[a.kind] < order[b.kind] ? -1 : 1; 87 | } 88 | }); 89 | 90 | // Sort 'fires' if present 91 | data.forEach(element => { 92 | if (Array.isArray(element.fires)) element.fires.sort(); 93 | }); 94 | 95 | // Class 96 | filtered = data.filter(el => el.kind === "class")[0]; 97 | hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/class.hbs`), {encoding: "utf-8"}); 98 | output += Handlebars.compile(hbs)(filtered); 99 | 100 | // Constructor 101 | filtered = data.filter(el => el.kind === "constructor")[0]; 102 | hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/constructor.hbs`), {encoding: "utf-8"}); 103 | output += Handlebars.compile(hbs)(filtered); 104 | 105 | // Members 106 | filtered = data.filter(el => el.kind === "member" && el.access !== "private"); 107 | hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/properties.hbs`), {encoding: "utf-8"}); 108 | output += Handlebars.compile(hbs)(filtered); 109 | 110 | // Methods 111 | filtered = data.filter(el => el.kind === "function" && el.access !== "private"); 112 | hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/methods.hbs`), {encoding: "utf-8"}); 113 | output += Handlebars.compile(hbs)(filtered); 114 | 115 | // Events 116 | filtered = data.filter(el => el.kind === "event" && el.access !== "private"); 117 | hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/events.hbs`), {encoding: "utf-8"}); 118 | output += Handlebars.compile(hbs)(filtered); 119 | 120 | // Enums 121 | filtered = data.filter(el => el.kind === "enum" && el.access !== "private"); 122 | hbs = fs.readFileSync(path.resolve(TEMPLATE_DIR, `core/enumerations.hbs`), {encoding: "utf-8"}); 123 | output += Handlebars.compile(hbs)(filtered); 124 | 125 | // Strip out links to Listener class 126 | // output = output.replaceAll("[**Listener**](Listener)", "`Listener`"); 127 | // output = output.replaceAll("[Listener](Listener)", "`Listener`"); 128 | // output = output.replaceAll("[**arguments**](Listener#arguments)", "`arguments`"); 129 | 130 | return output; 131 | 132 | } 133 | 134 | generate().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m")); 135 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/core/class.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | 3 | This template creates the 'class' portion of the output (at the top). It relies on the following 4 | helpers: 5 | 6 | - inlineLinks 7 | - eq 8 | - eventName 9 | 10 | }} 11 | 12 | {{! Class name }} 13 | # {{name}} 14 | 15 | {{! Class description }} 16 | {{{inlineLinks description}}} 17 | 18 | {{! Since }} 19 | {{#if since}} 20 | **Since**: {{since}} 21 | {{/if}} 22 | 23 | {{! Extends }} 24 | {{#if augments}} 25 | **Extends**: {{#each augments}}[`{{this}}`]({{this}}){{#unless @last}}, {{/unless}}{{/each}} 26 | 27 | {{/if}} 28 | 29 | {{! Fires }} 30 | {{#if fires}} 31 | **Fires**: {{#each fires}}[`{{eventName this}}`](#event:{{eventName this}}){{#unless @last}}, {{/unless}}{{/each}} 32 | {{/if}} 33 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/core/constructor.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | 3 | This template creates the 'constructor' portion of the output (below the class). It relies on the 4 | following helpers: 5 | 6 | - stripNewlines 7 | 8 | }} 9 | {{#if this}} 10 | 11 | {{! Constructor name }} 12 | ### `Constructor` 13 | 14 | {{! Description }} 15 | {{{inlineLinks description}}} 16 | 17 | {{! Parameters }} 18 | {{#if params}} 19 | 20 | **Parameters** 21 | 22 | > `new {{this.longname}}({{methodSignature this}})` 23 | 24 |
25 | 26 | | Parameter | Type | Default | Description | 27 | | ------------ | ------------ | ------------ | ------------ | 28 | {{#each params}} 29 | |{{#if this.optional}}[{{/if}}**`{{this.name}}`**{{#if this.optional}}]{{/if}} | {{#each this.type.names}}{{this}}
{{/each}} |{{this.defaultvalue}}|{{{stripNewlines (inlineLinks this.description)}}}| 30 | {{/each}} 31 | 32 |
33 | 34 | {{/if}} 35 | 36 | {{! Exceptions }} 37 | {{#if this.exceptions}} 38 | **Throws**: 39 | {{#each this.exceptions}} 40 | * {{#if this.type}}`{{this.type.names.[0]}}` : {{/if}}{{inlineLinks this.description}} 41 | {{/each}} 42 | {{/if}} 43 | 44 | {{/if}} 45 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/core/enumerations.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | 3 | This template creates the 'enums' portion of the output . It relies on the following helpers: 4 | 5 | - inlineLinks 6 | - stripNewlines 7 | 8 | }} 9 | {{#if this}} 10 | *** 11 | 12 | ## Enums 13 | 14 | {{#each this}} 15 | {{! Name }} 16 | ### `.{{this.name}}` {{curly true}}#{{this.name}}{{curly}} 17 | **Type**: {{this.type.names.[0]}}
18 | {{! Attributes }} 19 | {{#if (eq this.scope "static")}} 20 | **Attributes**: static 21 | {{/if}} 22 | 23 | {{! Description }} 24 | {{{inlineLinks description}}} 25 | {{/each}} 26 | 27 | {{/if}} 28 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/core/events.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | 3 | This template creates the 'Events' portion of the output . It relies on the following helpers: 4 | 5 | - inlineLinks 6 | - stripNewlines 7 | 8 | }} 9 | {{#if this}} 10 | *** 11 | 12 | ## Events 13 | 14 | {{#each this}} 15 | {{! Name and anchor }} 16 | ### `{{this.name}}` {{curly true}}{{createEventAnchor this.name}}{{curly}} 17 | 18 | 19 | 20 | 21 | {{! Description }} 22 | {{{inlineLinks description}}} 23 | 24 | {{! Since }} 25 | {{#if since}} 26 | **Since**: {{since}} 27 | {{/if}} 28 | 29 | {{! Properties }} 30 | {{#if properties}} 31 | 32 | **Event Properties** 33 | 34 | | Property | Type | Description | 35 | | ------------------------ | ------------------------ | ------------------------ | 36 | {{#each properties}} 37 | |**`{{this.name}}`** |{{this.type.names.[0]}}|{{{stripNewlines (inlineLinks this.description)}}}| 38 | {{/each}} 39 | 40 | {{/if}} 41 | 42 | {{/each}} 43 | 44 | {{/if}} 45 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/core/methods.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | 3 | This template creates the 'Events' portion of the output . It relies on the following helpers: 4 | 5 | - inlineLinks 6 | - stripNewlines 7 | 8 | }} 9 | {{#if this}} 10 | *** 11 | 12 | ## Methods 13 | 14 | {{#each this}} 15 | 16 | {{! Name }} 17 | ### `.{{this.name}}({{#if params}}...{{/if}})` {{curly true}}#{{this.name}}{{curly}} 18 | 19 | {{! Since }} 20 | {{#if this.since}} 21 | **Since**: {{this.since}}
22 | {{/if}} 23 | {{! Attributes }} 24 | {{#if (eq this.async true)}} 25 | **Attributes**: async 26 | {{/if}} 27 | 28 | {{! Description }} 29 | {{{inlineLinks description}}} 30 | 31 | {{! Parameters }} 32 | {{#if params}} 33 | 34 | **Parameters** 35 | 36 | > Signature: `{{this.name}}({{methodSignature this}})` 37 | 38 |
39 | 40 | | Parameter | Type(s) | Default | Description | 41 | | ------------ | ------------ | ------------ | ------------ | 42 | {{#each params}} 43 | |{{#if this.optional}}[{{/if}}**`{{this.name}}`**{{#if this.optional}}]{{/if}} | {{#each this.type.names}}{{this}}
{{/each}} |{{this.defaultvalue}}|{{{stripNewlines (inlineLinks this.description)}}}| 44 | {{/each}} 45 | 46 |
47 | 48 | {{/if}} 49 | 50 | {{! Returns }} 51 | {{#if returns}} 52 | **Return Value** 53 | 54 | {{! Return value description }} 55 | > Returns: {{#each returns~}} 56 | {{#if type~}} 57 | {{#each type.names~}} 58 | `{{{this}}}`{{#unless @last}} or {{/unless}} 59 | {{~/each}}
60 | {{/if~}} 61 | 62 | {{~#if description}} 63 | 64 | {{{inlineLinks description}}} 65 | {{/if~}} 66 | {{~/each}} 67 | {{/if}} 68 | 69 | {{! Attributes }} 70 | {{#if (eq this.scope "static")}} 71 | 72 | **Attributes**: static 73 | {{/if}} 74 | 75 | {{#if this.exceptions}} 76 | **Throws**: 77 | {{#each this.exceptions}} 78 | * {{#if this.type}}`{{this.type.names.[0]}}` : {{/if}}{{{inlineLinks this.description}}} 79 | {{/each}} 80 | {{/if}} 81 | 82 | {{/each}} 83 | 84 | {{/if}} 85 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/core/properties.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | 3 | This template creates the 'properties' portion of the output . It relies on the following helpers: 4 | 5 | - inlineLinks 6 | - stripNewlines 7 | 8 | }} 9 | {{#if this}} 10 | *** 11 | 12 | ## Properties 13 | 14 | {{#each this}} 15 | {{! Name }} 16 | ### `.{{this.name}}` {{curly true}}#{{this.name}}{{curly}} 17 | {{! Since }} 18 | {{#if this.since}} 19 | **Since**: {{this.since}}
20 | {{/if}} 21 | {{! Type }} 22 | **Type**: {{this.type.names.[0]}}
23 | {{#if (or this.readonly this.nullable)}} 24 | {{! Attributes }} 25 | **Attributes**: {{#if this.readonly}}read-only{{/if}}{{#if this.nullable}}, nullable{{/if}}{{#if (eq this.scope "static")}}, static{{/if}}
26 | {{/if}} 27 | 28 | 29 | {{! Description }} 30 | {{{inlineLinks description}}} 31 | 32 | {{! Properties }} 33 | {{#if properties}} 34 | 35 | **Properties** 36 | 37 | | Property | Type | Description | 38 | | ------------ | ------------ | ------------ | 39 | {{#each properties}} 40 | |**`{{this.name}}`** |{{this.type.names.[0]}}|{{{stripNewlines (inlineLinks this.description)}}}| 41 | {{/each}} 42 | 43 | {{/if}} 44 | 45 | {{/each}} 46 | 47 | {{/if}} 48 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/helpers/djip-helpers.js: -------------------------------------------------------------------------------- 1 | const ddata = require("./ddata.js"); 2 | 3 | /** 4 | * Strips newline characters (\n) from the input 5 | * @param input {string} 6 | * @returns {string} 7 | */ 8 | function stripNewlines (input) { 9 | if (input) return input.replace(/[\r\n]+/g, " "); 10 | } 11 | exports.stripNewlines = stripNewlines; 12 | 13 | /** 14 | * Extract the event name from the jsdoc-reported name 15 | * @param input {string} 16 | * @returns {string} 17 | */ 18 | function eventName (input) { 19 | return input.split(":")[1]; 20 | } 21 | exports.eventName = eventName; 22 | 23 | /** 24 | * Replaces JSDoc {@link} tags with markdown links in the supplied text 25 | */ 26 | function inlineLinks (text, options) { 27 | 28 | if (text) { 29 | const links = ddata.parseLink(text); 30 | links.forEach(function (link) { 31 | const linked = ddata._link(link.url, options); 32 | if (link.caption === link.url) link.caption = linked.name; 33 | if (linked.url) link.url = linked.url; 34 | text = text.replace(link.original, `[${link.caption}](${link.url})`); 35 | }); 36 | } 37 | 38 | return text; 39 | 40 | } 41 | exports.inlineLinks = inlineLinks; 42 | 43 | function curly(object, open) { 44 | return open ? "{" : "}"; 45 | }; 46 | exports.curly = curly; 47 | 48 | function eq(v1, v2) { return v1 === v2; } 49 | exports.eq = eq; 50 | function ne(v1, v2) { return v1 !== v2; } 51 | exports.ne = ne; 52 | function lt(v1, v2) {return v1 < v2; } 53 | exports.lt = lt; 54 | function gt(v1, v2) { return v1 > v2; } 55 | exports.gt = gt; 56 | function lte(v1, v2) { return v1 <= v2; } 57 | exports.lte = lte; 58 | function gte(v1, v2) { return v1 >= v2; } 59 | exports.gte = gte; 60 | function and() { return Array.prototype.every.call(arguments, Boolean); } 61 | exports.and = and; 62 | function or() { return Array.prototype.slice.call(arguments, 0, -1).some(Boolean); } 63 | exports.or = or; 64 | 65 | function methodSignature(context) { 66 | return ddata.methodSig.call(context); 67 | } 68 | exports.methodSignature = methodSignature; 69 | 70 | function createEventAnchor(name) { 71 | return "#event-" + name.replace(":", "-"); 72 | } 73 | exports.createEventAnchor = createEventAnchor; 74 | -------------------------------------------------------------------------------- /scripts/api-documentation/templates/helpers/state.js: -------------------------------------------------------------------------------- 1 | exports.templateData = [] 2 | exports.options = {} 3 | -------------------------------------------------------------------------------- /scripts/library/build.js: -------------------------------------------------------------------------------- 1 | // This script builds the bundled library files (both normal and minified with sourcemaps) and 2 | // commits them to the 'dist' folder for later publishing. By default, CommonJS, ES Modules and IIFE 3 | // versions are built. 4 | // 5 | // Calling this script with the -t argument allows building only of them. Options are: cjs, esm and 6 | // iife. 7 | 8 | // Modules 9 | const system = require("system-commands"); 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | 13 | // Parse arguments (default type is esm). Use -t as type (if valid) 14 | let type = "esm"; 15 | const argv = require("minimist")(process.argv.slice(2)); 16 | if (["cjs", "esm", "iife"].includes(argv.t)) type = argv.t; 17 | 18 | // Prepare general command 19 | let cmd = `./node_modules/.bin/rollup ` + 20 | `--input src/WebMidi.js ` + 21 | `--format ${type} `; 22 | 23 | // Minified version (with sourcemap) 24 | let minified = cmd + ` --file dist/${type}/webmidi.${type}.min.js ` + 25 | `--sourcemap ` + 26 | `--config ${__dirname}/rollup.config.${type}.min.js`; 27 | 28 | // Non-minified version 29 | let normal = cmd + ` --file dist/${type}/webmidi.${type}.js ` + 30 | `--config ${__dirname}/rollup.config.${type}.js`; 31 | 32 | async function execute(type) { 33 | 34 | // Write package.json file so proper versions are imported by Node 35 | if (type === "esm") { 36 | 37 | fs.writeFileSync( 38 | path.join(process.cwd(), "dist", "esm", "package.json"), '{"type": "module"}' 39 | ); 40 | 41 | console.info( 42 | "\x1b[32m", // green font 43 | `Custom package.json file ("type": "module") saved to "dist/${type}/package.json"`, 44 | "\x1b[0m" // reset font 45 | ); 46 | 47 | } else if (type === "cjs") { 48 | 49 | fs.writeFileSync( 50 | path.join(process.cwd(), "dist", "cjs", "package.json"), '{"type": "commonjs"}' 51 | ); 52 | 53 | console.info( 54 | "\x1b[32m", // green font 55 | `Custom package.json file ("type": "commonjs") saved to "dist/${type}/package.json"`, 56 | "\x1b[0m" // reset font 57 | ); 58 | 59 | } 60 | 61 | // Production build 62 | await system(minified); 63 | 64 | console.info( 65 | "\x1b[32m", // green font 66 | `The "${type}" minified build was saved to "dist/${type}/webmidi.${type}.min.js"`, 67 | "\x1b[0m" // reset font 68 | ); 69 | 70 | // Development build 71 | await system(normal); 72 | 73 | console.info( 74 | "\x1b[32m", // green font 75 | `The "${type}" non-minified build was saved to "dist/${type}/webmidi.${type}.js"`, 76 | "\x1b[0m" // reset font 77 | ); 78 | } 79 | 80 | // Execute and catch errors if any (in red) 81 | execute(type).catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m")); 82 | 83 | -------------------------------------------------------------------------------- /scripts/library/rollup.config.cjs.js: -------------------------------------------------------------------------------- 1 | import babel from "@rollup/plugin-babel"; 2 | import stripCode from "rollup-plugin-strip-code"; 3 | import replace from "@rollup/plugin-replace"; 4 | const fs = require("fs"); 5 | const license = require("rollup-plugin-license"); 6 | const versionInjector = require("rollup-plugin-version-injector"); 7 | 8 | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n"; 9 | 10 | export default { 11 | plugins: [ 12 | versionInjector(), 13 | replace({__flavour__: "cjs"}), 14 | stripCode({ 15 | start_comment: "START-ESM", 16 | end_comment: "END-ESM" 17 | }), 18 | babel(), 19 | license({banner: BANNER}) 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /scripts/library/rollup.config.cjs.min.js: -------------------------------------------------------------------------------- 1 | import babel from "@rollup/plugin-babel"; 2 | import replace from "@rollup/plugin-replace"; 3 | const fs = require("fs"); 4 | const license = require("rollup-plugin-license"); 5 | import {terser} from "rollup-plugin-terser"; 6 | import stripCode from "rollup-plugin-strip-code"; 7 | const versionInjector = require("rollup-plugin-version-injector"); 8 | 9 | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n"; 10 | 11 | export default { 12 | plugins: [ 13 | versionInjector(), 14 | replace({__flavour__: "cjs"}), 15 | stripCode({ 16 | start_comment: "START-ESM", 17 | end_comment: "END-ESM" 18 | }), 19 | babel(), 20 | terser(), 21 | license({banner: BANNER}), 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /scripts/library/rollup.config.esm.js: -------------------------------------------------------------------------------- 1 | import stripCode from "rollup-plugin-strip-code"; 2 | import replace from "@rollup/plugin-replace"; 3 | 4 | const fs = require("fs"); 5 | const license = require("rollup-plugin-license"); 6 | const versionInjector = require("rollup-plugin-version-injector"); 7 | 8 | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n"; 9 | 10 | export default { 11 | 12 | plugins: [ 13 | versionInjector(), 14 | replace({__flavour__: "esm"}), 15 | stripCode({ 16 | start_comment: "START-CJS", 17 | end_comment: "END-CJS" 18 | }), 19 | license({ 20 | banner: BANNER 21 | }) 22 | ] 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /scripts/library/rollup.config.esm.min.js: -------------------------------------------------------------------------------- 1 | import stripCode from "rollup-plugin-strip-code"; 2 | import replace from "@rollup/plugin-replace"; 3 | 4 | const fs = require("fs"); 5 | const license = require("rollup-plugin-license"); 6 | import { terser } from "rollup-plugin-terser"; 7 | const versionInjector = require("rollup-plugin-version-injector"); 8 | 9 | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n"; 10 | 11 | export default { 12 | 13 | plugins: [ 14 | versionInjector(), 15 | replace({__flavour__: "cjs"}), 16 | stripCode({ 17 | start_comment: "START-CJS", 18 | end_comment: "END-CJS" 19 | }), 20 | terser(), 21 | license({banner: BANNER}), 22 | ] 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /scripts/library/rollup.config.iife.js: -------------------------------------------------------------------------------- 1 | import babel from "@rollup/plugin-babel"; 2 | import stripCode from "rollup-plugin-strip-code"; 3 | import replace from "@rollup/plugin-replace"; 4 | 5 | const fs = require("fs"); 6 | const license = require("rollup-plugin-license"); 7 | const versionInjector = require("rollup-plugin-version-injector"); 8 | 9 | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n"; 10 | 11 | export default { 12 | 13 | output: { 14 | name: "window", // WebMidi and Note will be added to window 15 | extend: true, // important! 16 | exports: "named" 17 | }, 18 | 19 | plugins: [ 20 | versionInjector(), 21 | replace({__flavour__: "iife"}), 22 | stripCode({ 23 | start_comment: "START-CJS", 24 | end_comment: "END-CJS" 25 | }), 26 | stripCode({ 27 | start_comment: "START-ESM", 28 | end_comment: "END-ESM" 29 | }), 30 | babel(), 31 | license({ 32 | banner: BANNER 33 | }) 34 | ] 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /scripts/library/rollup.config.iife.min.js: -------------------------------------------------------------------------------- 1 | import babel from "@rollup/plugin-babel"; 2 | import stripCode from "rollup-plugin-strip-code"; 3 | import replace from "@rollup/plugin-replace"; 4 | import {terser} from "rollup-plugin-terser"; 5 | 6 | const fs = require("fs"); 7 | const license = require("rollup-plugin-license"); 8 | const versionInjector = require("rollup-plugin-version-injector"); 9 | 10 | const BANNER = fs.readFileSync(__dirname + "/../../BANNER.txt", "utf8") + "\n\n\n"; 11 | 12 | export default { 13 | 14 | output: { 15 | name: "window", // WebMidi, Note and others will be added to window 16 | extend: true, // important! 17 | exports: "named" 18 | }, 19 | 20 | plugins: [ 21 | versionInjector(), 22 | replace({__flavour__: "cjs"}), 23 | stripCode({ 24 | start_comment: "START-CJS", 25 | end_comment: "END-CJS" 26 | }), 27 | stripCode({ 28 | start_comment: "START-ESM", 29 | end_comment: "END-ESM" 30 | }), 31 | babel(), 32 | terser(), 33 | license({banner: BANNER}) 34 | ] 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /scripts/sponsors/retrieve-sponsors.js: -------------------------------------------------------------------------------- 1 | const { graphql } = require("@octokit/graphql"); 2 | const { token } = require("../../.credentials/sponsors.js"); 3 | const replace = require("replace-in-file"); 4 | const path = require("path"); 5 | 6 | const TARGET = path.join(process.cwd(), "website", "src", "pages", "sponsors", "index.md"); 7 | 8 | async function getSponsors() { 9 | 10 | const query = `{ 11 | 12 | user (login: "djipco") { 13 | sponsorshipsAsMaintainer(first: 100, includePrivate: true) { 14 | totalCount 15 | nodes { 16 | sponsorEntity { 17 | ... on User { 18 | login 19 | name 20 | avatarUrl 21 | url 22 | sponsorshipForViewerAsSponsorable { 23 | isOneTimePayment 24 | privacyLevel 25 | tier { 26 | id 27 | name 28 | monthlyPriceInDollars 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | }`; 38 | 39 | const {user} = await graphql( 40 | query, 41 | { headers: { authorization: `token ${token}` } } 42 | ); 43 | 44 | return user.sponsorshipsAsMaintainer.nodes; 45 | 46 | } 47 | 48 | getSponsors().then(async data => { 49 | 50 | // data.forEach(d => { 51 | // console.log(d.sponsorEntity.login, d.sponsorEntity.sponsorshipForViewerAsSponsorable); 52 | // // console.log(d); 53 | // }); 54 | 55 | let output = ""; 56 | 57 | data.sort((a, b) => { 58 | a = a.sponsorEntity.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars; 59 | b = b.sponsorEntity.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars; 60 | 61 | if (a > b) return -1; 62 | if (a < b) return -1; 63 | return 0; 64 | 65 | }); 66 | 67 | data.forEach(entity => { 68 | 69 | const sponsor = entity.sponsorEntity; 70 | 71 | if (sponsor.sponsorshipForViewerAsSponsorable.tier.monthlyPriceInDollars < 6) { 72 | return; 73 | } 74 | 75 | if (sponsor.sponsorshipForViewerAsSponsorable.privacyLevel === "PUBLIC") { 76 | const name = sponsor.name || sponsor.login; 77 | output += `\n`; 78 | output += `\t\n`; 80 | output += `\n\n`; 81 | } else { 82 | output += `\n\n`; 84 | } 85 | 86 | }); 87 | 88 | const options = { 89 | files: TARGET, 90 | from: /.*/sgm, 91 | to: "\n\n" + output + "", 92 | }; 93 | 94 | const results = await replace(options); 95 | 96 | }); 97 | -------------------------------------------------------------------------------- /scripts/typescript-declarations/generate.js: -------------------------------------------------------------------------------- 1 | // This script injects a few dynamic properties into the source TypeScript declaration file and 2 | // copies it to the ./dist/esm and ./dist/cjs directories. 3 | 4 | // Modules 5 | const path = require("path"); 6 | const replace = require("replace-in-file"); 7 | const pkg = require("../../package.json"); 8 | const fs = require("fs-extra"); 9 | 10 | // Path to source declaration file 11 | const SOURCE_FILE = path.join(__dirname, "../../typescript", "webmidi.d.ts"); 12 | 13 | // Output directory 14 | const OUTPUT_DIR = "dist"; 15 | 16 | // Console arguments 17 | const argv = require("minimist")(process.argv.slice(2)); 18 | 19 | // Get targets to save the file for 20 | let targets = []; 21 | let type = "all"; 22 | if (["cjs", "esm"].includes(argv.t)) type = argv.t; 23 | 24 | if (type === "all" || type === "cjs") 25 | targets.push({path: "webmidi.cjs.d.ts", name: "cjs", type: "CommonJS"}); 26 | 27 | if (type === "all" || type === "esm") 28 | targets.push({path: "webmidi.esm.d.ts", name: "esm", type: "ES2020"}); 29 | 30 | async function execute() { 31 | 32 | targets.forEach(async target => { 33 | 34 | const FILE = path.join(OUTPUT_DIR, target.name, target.path); 35 | 36 | // Copy the file to the target directory 37 | await fs.copy(SOURCE_FILE, FILE, {overwrite: true}); 38 | 39 | // Insert dynamic data 40 | replace.sync({ 41 | files: FILE, 42 | from: ["{{LIBRARY}}", "{{VERSION}}", "{{HOMEPAGE}}", "{{AUTHOR_NAME}}", "{{AUTHOR_URL}}"], 43 | to: [pkg.webmidi.name, pkg.version, pkg.homepage, pkg.author.name, pkg.author.url] 44 | }); 45 | 46 | log(`Saved ${target.type} TypeScript declaration file to '${FILE}'`); 47 | 48 | }); 49 | 50 | } 51 | 52 | // Execute and catch errors if any (in red) 53 | execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m")); 54 | 55 | function log(message) { 56 | console.info("\x1b[32m", message, "\x1b[0m"); 57 | } 58 | -------------------------------------------------------------------------------- /scripts/typescript-declarations/generateOLD.js: -------------------------------------------------------------------------------- 1 | // This script generates TypeScript declaration files (.d.ts) of the library and saves them to the 2 | // 'dist' directory for later publishing. By default, CommonJS and ES2020 versions are generated. 3 | // 4 | // Calling this script with the -t argument allows generating only one declaration file. Options 5 | // are: cjs and esm. 6 | // 7 | // Calling this script with the -c argument allows you to commit and push the generated files. 8 | // Options are true or false. 9 | 10 | // Modules 11 | const git = require("simple-git")(); 12 | const moment = require("moment"); 13 | const path = require("path"); 14 | const prependFile = require("prepend-file"); 15 | const replace = require("replace-in-file"); 16 | const system = require("system-commands"); 17 | const pkg = require("../../package.json"); 18 | const os = require("os"); 19 | const fsPromises = require("fs").promises; 20 | const fs = require("fs-extra"); 21 | 22 | const OUT_DIR = "dist"; 23 | 24 | const WEB_MIDI_API_CLASSES = [ 25 | "MIDIAccess", 26 | "MIDIConnectionEvent", 27 | "MIDIConnectionEventInit", 28 | "MIDIInput", 29 | "MIDIInputMap", 30 | "MIDIMessageEvent", 31 | "MIDIMessageEventInit", 32 | "MIDIOptions", 33 | "MIDIOutput", 34 | "MIDIOutputMap", 35 | "MIDIPort", 36 | "MIDIPortConnectionStatus", 37 | "MIDIPortDeviceState", 38 | "MIDIPortType" 39 | ]; 40 | 41 | const HEADER = `// Type definitions for ${pkg.webmidi.name} ${pkg.version}\n` + 42 | `// Project: ${pkg.homepage}\n` + 43 | `// Definitions by: ${pkg.author.name} \n` + 44 | `// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n\n`; 45 | 46 | let targets = []; 47 | 48 | let type = "all"; 49 | const argv = require("minimist")(process.argv.slice(2)); 50 | if (["cjs", "esm"].includes(argv.t)) type = argv.t; 51 | 52 | const commit = argv.c === "true"; 53 | 54 | if (type === "all" || type === "cjs") 55 | targets.push({source: "webmidi.cjs.js", name: "cjs", type: "CommonJS"}); 56 | 57 | if (type === "all" || type === "esm") 58 | targets.push({source: "webmidi.esm.js", name: "esm", type: "ES2020"}); 59 | 60 | async function execute() { 61 | 62 | // Temp dir 63 | const TMP_DIR_PATH = await fsPromises.mkdtemp(path.join(os.tmpdir(), "webmidi-ts-")); 64 | 65 | targets.forEach(async target => { 66 | 67 | // Make a copy of the source file so we can substitute some elements before parsing 68 | const TMP_FILE_PATH = path.join(TMP_DIR_PATH, target.source); 69 | await fs.copy( 70 | path.join(OUT_DIR, target.name, target.source), 71 | TMP_FILE_PATH, 72 | {overwrite: true} 73 | ); 74 | 75 | // Add TypeScript WebMidi namespace before native Web MIDI API objects 76 | WEB_MIDI_API_CLASSES.forEach(element => { 77 | 78 | const options = { 79 | files: TMP_FILE_PATH, 80 | from: new RegExp("{" + element + "}", "g"), 81 | to: () => `{WebMidi.${element}}` 82 | }; 83 | 84 | replace.sync(options); 85 | 86 | }); 87 | 88 | // Replace callback type by simply "EventEmitterCallback" (this is needed because tsc does not 89 | // support tilde character in types. See below... 90 | replace.sync({ 91 | files: TMP_FILE_PATH, 92 | from: new RegExp("{EventEmitter~callback}", "g"), 93 | to: () => "{EventEmitterCallback}" 94 | }); 95 | 96 | // Generate declaration file 97 | const cmd = "npx -p typescript tsc " + TMP_FILE_PATH + 98 | " --declaration --allowJs --emitDeclarationOnly" + 99 | " --module " + target.type + 100 | " --lib ES2020,DOM --outDir " + path.join(OUT_DIR, target.name) ; 101 | await system(cmd); 102 | 103 | // Path to current .d.ts file 104 | const file = path.join(OUT_DIR, target.name, target.source.replace(".js", ".d.ts")); 105 | 106 | // Remove readonly flag 107 | const options = { 108 | files: [file], 109 | from: /readonly /g, 110 | to: "" 111 | }; 112 | await replace(options); 113 | 114 | 115 | 116 | 117 | 118 | // // Inject correct definitions for Input class 119 | // await replace({ 120 | // files: [file], 121 | // from: /export class Input extends EventEmitter \{/, 122 | // to: fs.readFileSync( 123 | // path.join(__dirname, "injections", "Input.txt"), {encoding: "utf8", flag: "r"} 124 | // ) 125 | // }); 126 | // 127 | 128 | 129 | 130 | 131 | 132 | 133 | // Prepend header for DefinitelyTyped 134 | await prependFile(file, HEADER); 135 | log("Saved " + target.type + " TypeScript declaration file to '" + file + "'"); 136 | 137 | // // Inject EventEmitterCallback type declaration 138 | // fs.appendFileSync( 139 | // file, 140 | // `\n\n` + 141 | // `// This is automatically injected to fix a bug with the TypeScript compiler\n` + 142 | // `export type EventEmitterCallback = (...args: any[]) => void;\n` 143 | // // dans Tone.js, ils utilisent: 144 | // //`export type EventEmitterCallback = (...args: A) => void;` 145 | // ); 146 | 147 | // Commit 148 | if (commit) { 149 | await git.add([file]); 150 | await git.commit("Generated by TypeScript compiler on " + moment().format()); 151 | await git.push(); 152 | } 153 | 154 | }); 155 | 156 | } 157 | 158 | // Execute and catch errors if any (in red) 159 | execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m")); 160 | 161 | function log(message) { 162 | console.info("\x1b[32m", message, "\x1b[0m"); 163 | } 164 | -------------------------------------------------------------------------------- /scripts/website/deploy.js: -------------------------------------------------------------------------------- 1 | // This script copies the files generated by Docusaurus in /website/build to the root of the 2 | // 'gh-pages' branch. This makes them available at djipco.github.io/webmidi 3 | 4 | // Importation 5 | const fs = require("fs-extra"); 6 | const fsPromises = require("fs").promises; 7 | const git = require("simple-git")(); 8 | const moment = require("moment"); 9 | const os = require("os"); 10 | const path = require("path"); 11 | const rimraf = require("@alexbinary/rimraf"); 12 | 13 | // Configuration 14 | const TARGET_BRANCH = "gh-pages"; 15 | const SOURCE_DIR = path.join(process.cwd(), "website", "build"); 16 | 17 | async function execute() { 18 | 19 | const TMP_DIR = await fsPromises.mkdtemp(path.join(os.tmpdir(), "webmidi-website-")); 20 | 21 | // Get list of files and directories inside the Docusaurus build directory 22 | let files = await fsPromises.readdir(SOURCE_DIR); 23 | 24 | // Get rid of .DS_Store and other unnecessary files 25 | files = files.filter(file => !file.startsWith(".")); 26 | 27 | // Copy files to tmp directory 28 | for (const file of files) { 29 | const source = path.join(SOURCE_DIR, file); 30 | const target = path.join(TMP_DIR, file); 31 | await fs.copy(source, target, {overwrite: true}); 32 | } 33 | log("Copied website files to temp dir: " + TMP_DIR); 34 | 35 | // Get current branch (so we can come back to it later) 36 | let results = await git.branch(); 37 | const ORIGINAL_BRANCH = results.current; 38 | 39 | // Switch to target branch 40 | log(`Switching from '${ORIGINAL_BRANCH}' branch to '${TARGET_BRANCH}' branch`); 41 | await git.checkout(TARGET_BRANCH); 42 | 43 | // Copy content of tmp dir to final destination and commit 44 | for (const file of files) { 45 | const source = path.join(TMP_DIR, file); 46 | const target = path.join(process.cwd(), file); 47 | await fs.copy(source, target, {overwrite: true}); 48 | await git.add([target]); 49 | await git.commit("Updated on: " + moment().format(), [target]); 50 | } 51 | 52 | await git.push(); 53 | log(`Website files committed to '${TARGET_BRANCH}' branch and pushed to remote`); 54 | 55 | // Remove temp files 56 | rimraf(TMP_DIR); 57 | log(`Temp directory removed`); 58 | 59 | // Come back to original branch 60 | log(`Switching back to '${ORIGINAL_BRANCH}' branch`); 61 | await git.checkout(ORIGINAL_BRANCH); 62 | 63 | } 64 | 65 | function log(message) { 66 | console.info("\x1b[32m", message, "\x1b[0m"); 67 | } 68 | 69 | // Execution 70 | execute().catch(error => console.error("\x1b[31m", "Error: " + error, "\x1b[0m")); 71 | -------------------------------------------------------------------------------- /src/Forwarder.js: -------------------------------------------------------------------------------- 1 | import {Enumerations} from "./Enumerations.js"; 2 | import {Output} from "./Output.js"; 3 | import {WebMidi} from "./WebMidi.js"; 4 | 5 | /** 6 | * The `Forwarder` class allows the forwarding of MIDI messages to predetermined outputs. When you 7 | * call its [`forward()`](#forward) method, it will send the specified [`Message`](Message) object 8 | * to all the outputs listed in its [`destinations`](#destinations) property. 9 | * 10 | * If specific channels or message types have been defined in the [`channels`](#channels) or 11 | * [`types`](#types) properties, only messages matching the channels/types will be forwarded. 12 | * 13 | * While it can be manually instantiated, you are more likely to come across a `Forwarder` object as 14 | * the return value of the [`Input.addForwarder()`](Input#addForwarder) method. 15 | * 16 | * @license Apache-2.0 17 | * @since 3.0.0 18 | */ 19 | export class Forwarder { 20 | 21 | /** 22 | * Creates a `Forwarder` object. 23 | * 24 | * @param {Output|Output[]} [destinations=\[\]] An [`Output`](Output) object, or an array of such 25 | * objects, to forward the message to. 26 | * 27 | * @param {object} [options={}] 28 | * @param {string|string[]} [options.types=(all messages)] A MIDI message type or an array of such 29 | * types (`"noteon"`, `"controlchange"`, etc.), that the specified message must match in order to 30 | * be forwarded. If this option is not specified, all types of messages will be forwarded. Valid 31 | * messages are the ones found in either 32 | * [`SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) 33 | * or [`CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES). 34 | * @param {number|number[]} [options.channels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]] 35 | * A MIDI channel number or an array of channel numbers that the message must match in order to be 36 | * forwarded. By default all MIDI channels are included (`1` to `16`). 37 | */ 38 | constructor(destinations = [], options = {}) { 39 | 40 | /** 41 | * An array of [`Output`](Output) objects to forward the message to. 42 | * @type {Output[]} 43 | */ 44 | this.destinations = []; 45 | 46 | /** 47 | * An array of message types (`"noteon"`, `"controlchange"`, etc.) that must be matched in order 48 | * for messages to be forwarded. By default, this array includes all 49 | * [`Enumerations.SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) and 50 | * [`Enumerations.CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES). 51 | * @type {string[]} 52 | */ 53 | this.types = [ 54 | ...Object.keys(Enumerations.SYSTEM_MESSAGES), 55 | ...Object.keys(Enumerations.CHANNEL_MESSAGES) 56 | ]; 57 | 58 | /** 59 | * An array of MIDI channel numbers that the message must match in order to be forwarded. By 60 | * default, this array includes all MIDI channels (`1` to `16`). 61 | * @type {number[]} 62 | */ 63 | this.channels = Enumerations.MIDI_CHANNEL_NUMBERS; 64 | 65 | /** 66 | * Indicates whether message forwarding is currently suspended or not in this forwarder. 67 | * @type {boolean} 68 | */ 69 | this.suspended = false; 70 | 71 | // Make sure parameters are arrays 72 | if (!Array.isArray(destinations)) destinations = [destinations]; 73 | if (options.types && !Array.isArray(options.types)) options.types = [options.types]; 74 | if (options.channels && !Array.isArray(options.channels)) options.channels = [options.channels]; 75 | 76 | if (WebMidi.validation) { 77 | 78 | // Validate destinations 79 | destinations.forEach(destination => { 80 | if ( !(destination instanceof Output) ) { 81 | throw new TypeError("Destinations must be of type 'Output'."); 82 | } 83 | }); 84 | 85 | // Validate types 86 | if (options.types !== undefined) { 87 | 88 | options.types.forEach(type => { 89 | if ( 90 | ! Enumerations.SYSTEM_MESSAGES.hasOwnProperty(type) && 91 | ! Enumerations.CHANNEL_MESSAGES.hasOwnProperty(type) 92 | ) { 93 | throw new TypeError("Type must be a valid message type."); 94 | } 95 | }); 96 | 97 | } 98 | 99 | // Validate channels 100 | if (options.channels !== undefined) { 101 | 102 | options.channels.forEach(channel => { 103 | if (! Enumerations.MIDI_CHANNEL_NUMBERS.includes(channel) ) { 104 | throw new TypeError("MIDI channel must be between 1 and 16."); 105 | } 106 | }); 107 | 108 | } 109 | 110 | } 111 | 112 | this.destinations = destinations; 113 | if (options.types) this.types = options.types; 114 | if (options.channels) this.channels = options.channels; 115 | 116 | } 117 | 118 | /** 119 | * Sends the specified message to the forwarder's destination(s) if it matches the specified 120 | * type(s) and channel(s). 121 | * 122 | * @param {Message} message The [`Message`](Message) object to forward. 123 | */ 124 | forward(message) { 125 | 126 | // Abort if forwarding is currently suspended 127 | if (this.suspended) return; 128 | 129 | // Abort if this message type should not be forwarded 130 | if (!this.types.includes(message.type)) return; 131 | 132 | // Abort if this channel should not be forwarded 133 | if (message.channel && !this.channels.includes(message.channel)) return; 134 | 135 | // Forward 136 | this.destinations.forEach(destination => { 137 | if (WebMidi.validation && !(destination instanceof Output)) return; 138 | destination.send(message); 139 | }); 140 | 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /test/Enumerations.test.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; 2 | const {Enumerations} = require("../dist/cjs/webmidi.cjs.js"); 3 | 4 | // VERIFIED 5 | describe("Enumerations Object", function() { 6 | 7 | describe("get CHANNEL_MESSAGES()", function() { 8 | it("should return an object with valid properties", function() { 9 | expect(Enumerations.CHANNEL_MESSAGES.pitchbend).to.equal(0xE); 10 | }); 11 | }); 12 | 13 | describe("get MIDI_REGISTERED_PARAMETER()", function() { 14 | it("should return an object with valid properties", function() { 15 | expect(Enumerations.REGISTERED_PARAMETERS.rollangle[0]).to.equal(0x3D); 16 | expect(Enumerations.REGISTERED_PARAMETERS.rollangle[1]).to.equal(0x08); 17 | }); 18 | }); 19 | 20 | describe("get CONTROL_CHANGE_MESSAGES()", function() { 21 | it("should return an an array with valid properties", function() { 22 | expect(Enumerations.CONTROL_CHANGE_MESSAGES[73].name).to.equal("attacktime"); 23 | }); 24 | }); 25 | 26 | // Legacy (remove in v4) 27 | describe("get MIDI_CONTROL_CHANGE_MESSAGES()", function() { 28 | it("should return an object with valid properties", function() { 29 | expect(Enumerations.MIDI_CONTROL_CHANGE_MESSAGES.registeredparameterfine).to.equal(101); 30 | }); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /test/Message.test.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; 2 | const {Message, Enumerations} = require("../dist/cjs/webmidi.cjs.js"); 3 | 4 | describe("Message Object", function() { 5 | 6 | describe("constructor()", function() { 7 | 8 | it("should correctly set the type of message for system messages", function() { 9 | 10 | // Arrange 11 | let data = new Uint8Array(3); 12 | 13 | const items = [ 14 | {type: "sysex", status: 240}, 15 | {type: "timecode", status: 241}, 16 | {type: "songposition", status: 242}, 17 | {type: "songselect", status: 243}, 18 | {type: "tunerequest", status: 246}, 19 | {type: "sysexend", status: 247}, 20 | {type: "clock", status: 248}, 21 | {type: "start", status: 250}, 22 | {type: "continue", status: 251}, 23 | {type: "stop", status: 252}, 24 | {type: "activesensing", status: 254}, 25 | {type: "reset", status: 255} 26 | ]; 27 | 28 | // Act 29 | items.forEach(item => { 30 | data[0] = item.status; 31 | const message = new Message(data); 32 | expect(message.isSystemMessage).to.be.true; 33 | expect(message.isChannelMessage).to.be.false; 34 | expect(message.type).to.equal(item.type); 35 | }); 36 | 37 | }); 38 | 39 | it("should correctly set properties for channel messages", function() { 40 | 41 | // Arrange 42 | const items = [ 43 | {data: [0x8, 0, 127], type: "noteoff"}, 44 | {data: [0x9, 32, 96], type: "noteon"}, 45 | {data: [0xA, 64, 64], type: "keyaftertouch"}, 46 | {data: [0xB, 96, 32], type: "controlchange"}, 47 | {data: [0xC, 0], type: "programchange"}, 48 | {data: [0xD, 64], type: "channelaftertouch"}, 49 | {data: [0xE, 32, 64], type: "pitchbend"} 50 | ]; 51 | const channel = 10; 52 | 53 | // Act 54 | items.forEach(item => { 55 | item.data[0] = (item.data[0] << 4) + channel - 1; 56 | const message = new Message(item.data); 57 | expect(message.isSystemMessage).to.be.false; 58 | expect(message.isChannelMessage).to.be.true; 59 | expect(message.type).to.equal(item.type); 60 | expect(message.rawData).to.equal(item.data); 61 | }); 62 | 63 | }); 64 | 65 | it("should correctly set properties for sysex with basic manufacturer code", function() { 66 | 67 | // Arrange 68 | let data = new Uint8Array(6); 69 | data[0] = Enumerations.MIDI_SYSTEM_MESSAGES.sysex; // sysex 70 | data[1] = 0x42; // Korg 71 | data[2] = 1; // Some data 72 | data[3] = 2; // Some data 73 | data[4] = 3; // Some data 74 | data[5] = Enumerations.MIDI_SYSTEM_MESSAGES.sysexend; // sysex end 75 | 76 | // Act 77 | const message = new Message(data); 78 | 79 | // Assert 80 | expect(message.manufacturerId).to.have.ordered.members([data[1]]); 81 | expect(message.dataBytes).to.have.ordered.members([data[2], data[3], data[4]]); 82 | 83 | }); 84 | 85 | it("should correctly set properties for sysex with extended manufacturer code", function() { 86 | 87 | // Arrange 88 | let data = new Uint8Array(8); 89 | data[0] = Enumerations.MIDI_SYSTEM_MESSAGES.sysex; // sysex 90 | data[1] = 0; // MOTU (byte 1) 91 | data[2] = 0; // MOTU (byte 2) 92 | data[3] = 0x3B; // MOTU (byte 3) 93 | data[4] = 1; // Some data 94 | data[5] = 2; // Some data 95 | data[6] = 3; // Some data 96 | data[7] = Enumerations.MIDI_SYSTEM_MESSAGES.sysexend; // sysex end 97 | 98 | // Act 99 | const message = new Message(data); 100 | 101 | // Assert 102 | expect(message.manufacturerId).to.have.ordered.members([data[1], data[2], data[3]]); 103 | expect(message.dataBytes).to.have.ordered.members([data[4], data[5], data[6]]); 104 | 105 | }); 106 | 107 | }); 108 | 109 | }); 110 | 111 | 112 | -------------------------------------------------------------------------------- /test/support/Utils.cjs.js: -------------------------------------------------------------------------------- 1 | const UtilsCjs = { 2 | 3 | isNative: function(fn) { 4 | return (/\{\s*\[native code\]\s*\}/).test("" + fn); 5 | } 6 | 7 | }; 8 | 9 | module.exports = UtilsCjs; 10 | -------------------------------------------------------------------------------- /test/support/Utils.iife.js: -------------------------------------------------------------------------------- 1 | function isNative(fn) { 2 | return (/\{\s*\[native code\]\s*\}/).test("" + fn); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /website/api/classes/Forwarder.md: -------------------------------------------------------------------------------- 1 | 2 | # Forwarder 3 | 4 | The `Forwarder` class allows the forwarding of MIDI messages to predetermined outputs. When you 5 | call its [`forward()`](#forward) method, it will send the specified [`Message`](Message) object 6 | to all the outputs listed in its [`destinations`](#destinations) property. 7 | 8 | If specific channels or message types have been defined in the [`channels`](#channels) or 9 | [`types`](#types) properties, only messages matching the channels/types will be forwarded. 10 | 11 | While it can be manually instantiated, you are more likely to come across a `Forwarder` object as 12 | the return value of the [`Input.addForwarder()`](Input#addForwarder) method. 13 | 14 | **Since**: 3.0.0 15 | 16 | 17 | 18 | ### `Constructor` 19 | 20 | Creates a `Forwarder` object. 21 | 22 | 23 | **Parameters** 24 | 25 | > `new Forwarder([destinations], [options])` 26 | 27 |
28 | 29 | | Parameter | Type | Default | Description | 30 | | ------------ | ------------ | ------------ | ------------ | 31 | |[**`destinations`**] | Output
Array.<Output>
|\[\]|An [`Output`](Output) object, or an array of such objects, to forward the message to.| 32 | |[**`options`**] | object
|{}|| 33 | |[**`options.types`**] | string
Array.<string>
|(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).| 34 | |[**`options.channels`**] | number
Array.<number>
|[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`).| 35 | 36 |
37 | 38 | 39 | 40 | *** 41 | 42 | ## Properties 43 | 44 | ### `.channels` {#channels} 45 | **Type**: Array.<number>
46 | 47 | 48 | An array of MIDI channel numbers that the message must match in order to be forwarded. By 49 | default, this array includes all MIDI channels (`1` to `16`). 50 | 51 | 52 | ### `.destinations` {#destinations} 53 | **Type**: Array.<Output>
54 | 55 | 56 | An array of [`Output`](Output) objects to forward the message to. 57 | 58 | 59 | ### `.suspended` {#suspended} 60 | **Type**: boolean
61 | 62 | 63 | Indicates whether message forwarding is currently suspended or not in this forwarder. 64 | 65 | 66 | ### `.types` {#types} 67 | **Type**: Array.<string>
68 | 69 | 70 | An array of message types (`"noteon"`, `"controlchange"`, etc.) that must be matched in order 71 | for messages to be forwarded. By default, this array includes all 72 | [`Enumerations.SYSTEM_MESSAGES`](Enumerations#SYSTEM_MESSAGES) and 73 | [`Enumerations.CHANNEL_MESSAGES`](Enumerations#CHANNEL_MESSAGES). 74 | 75 | 76 | 77 | *** 78 | 79 | ## Methods 80 | 81 | 82 | ### `.forward(...)` {#forward} 83 | 84 | 85 | Sends the specified message to the forwarder's destination(s) if it matches the specified 86 | type(s) and channel(s). 87 | 88 | 89 | **Parameters** 90 | 91 | > Signature: `forward(message)` 92 | 93 |
94 | 95 | | Parameter | Type(s) | Default | Description | 96 | | ------------ | ------------ | ------------ | ------------ | 97 | |**`message`** | Message
||The [`Message`](Message) object to forward.| 98 | 99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /website/api/classes/Listener.md: -------------------------------------------------------------------------------- 1 | 2 | # Listener 3 | 4 | The `Listener` class represents a single event listener object. Such objects keep all relevant 5 | contextual information such as the event being listened to, the object the listener was attached 6 | to, the callback function and so on. 7 | 8 | 9 | 10 | 11 | ### `Constructor` 12 | 13 | Creates a new `Listener` object 14 | 15 | 16 | **Parameters** 17 | 18 | > `new Listener(event, target, callback, [options])` 19 | 20 |
21 | 22 | | Parameter | Type | Default | Description | 23 | | ------------ | ------------ | ------------ | ------------ | 24 | |**`event`** | string
Symbol
||The event being listened to| 25 | |**`target`** | EventEmitter
||The [`EventEmitter`](EventEmitter) object that the listener is attached to.| 26 | |**`callback`** | EventEmitter~callback
||The function to call when the listener is triggered| 27 | |[**`options`**] | Object
|{}|| 28 | |[**`options.context`**] | Object
|target|The context to invoke the listener in (a.k.a. the value of `this` inside the callback function).| 29 | |[**`options.remaining`**] | number
|Infinity|The remaining number of times after which the callback should automatically be removed.| 30 | |[**`options.arguments`**] | array
||An array of arguments that will be passed separately to the callback function upon execution. The array is stored in the [`arguments`](#arguments) property and can be retrieved or modified as desired.| 31 | 32 |
33 | 34 | 35 | **Throws**: 36 | * `TypeError` : The `event` parameter must be a string or 37 | [`EventEmitter.ANY_EVENT`](EventEmitter#ANY_EVENT). 38 | * `ReferenceError` : The `target` parameter is mandatory. 39 | * `TypeError` : The `callback` must be a function. 40 | 41 | *** 42 | 43 | ## Properties 44 | 45 | ### `.arguments` {#arguments} 46 | **Type**: array
47 | 48 | 49 | An array of arguments to pass to the callback function upon execution. 50 | 51 | 52 | ### `.callback` {#callback} 53 | **Type**: function
54 | 55 | 56 | The callback function to execute. 57 | 58 | 59 | ### `.context` {#context} 60 | **Type**: Object
61 | 62 | 63 | The context to execute the callback function in (a.k.a. the value of `this` inside the 64 | callback function) 65 | 66 | 67 | ### `.count` {#count} 68 | **Type**: number
69 | 70 | 71 | The number of times the listener function was executed. 72 | 73 | 74 | ### `.event` {#event} 75 | **Type**: string
76 | 77 | 78 | The event name. 79 | 80 | 81 | ### `.remaining` {#remaining} 82 | **Type**: number
83 | 84 | 85 | The remaining number of times after which the callback should automatically be removed. 86 | 87 | 88 | ### `.suspended` {#suspended} 89 | **Type**: boolean
90 | 91 | 92 | Whether this listener is currently suspended or not. 93 | 94 | 95 | ### `.target` {#target} 96 | **Type**: EventEmitter
97 | 98 | 99 | The object that the event is attached to (or that emitted the event). 100 | 101 | 102 | 103 | *** 104 | 105 | ## Methods 106 | 107 | 108 | ### `.remove()` {#remove} 109 | 110 | 111 | Removes the listener from its target. 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /website/api/classes/Message.md: -------------------------------------------------------------------------------- 1 | 2 | # Message 3 | 4 | The `Message` class represents a single MIDI message. It has several properties that make it 5 | easy to make sense of the binary data it contains. 6 | 7 | **Since**: 3.0.0 8 | 9 | 10 | 11 | ### `Constructor` 12 | 13 | Creates a new `Message` object from raw MIDI data. 14 | 15 | 16 | **Parameters** 17 | 18 | > `new Message(data)` 19 | 20 |
21 | 22 | | Parameter | Type | Default | Description | 23 | | ------------ | ------------ | ------------ | ------------ | 24 | |**`data`** | Uint8Array
||The raw data of the MIDI message as a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) of integers between `0` and `255`.| 25 | 26 |
27 | 28 | 29 | 30 | *** 31 | 32 | ## Properties 33 | 34 | ### `.channel` {#channel} 35 | **Type**: number
36 | **Attributes**: read-only
37 | 38 | 39 | The MIDI channel number (`1` - `16`) that the message is targeting. This is only for 40 | channel-specific messages. For system messages, this will be left `undefined`. 41 | 42 | 43 | ### `.command` {#command} 44 | **Type**: number
45 | **Attributes**: read-only
46 | 47 | 48 | An integer identifying the MIDI command. For channel-specific messages, the value is 4-bit 49 | and will be between `8` and `14`. For system messages, the value will be between `240` and 50 | `255`. 51 | 52 | 53 | ### `.data` {#data} 54 | **Type**: Array.<number>
55 | **Attributes**: read-only
56 | 57 | 58 | An array containing all the bytes of the MIDI message. Each byte is an integer between `0` 59 | and `255`. 60 | 61 | 62 | ### `.dataBytes` {#dataBytes} 63 | **Type**: Array.<number>
64 | **Attributes**: read-only
65 | 66 | 67 | An array of the the data byte(s) of the MIDI message (as opposed to the status byte). When 68 | the message is a system exclusive message (sysex), `dataBytes` explicitly excludes the 69 | manufacturer ID and the sysex end byte so only the actual data is included. 70 | 71 | 72 | ### `.isChannelMessage` {#isChannelMessage} 73 | **Type**: boolean
74 | **Attributes**: read-only
75 | 76 | 77 | A boolean indicating whether the MIDI message is a channel-specific message. 78 | 79 | 80 | ### `.isSystemMessage` {#isSystemMessage} 81 | **Type**: boolean
82 | **Attributes**: read-only
83 | 84 | 85 | A boolean indicating whether the MIDI message is a system message (not specific to a 86 | channel). 87 | 88 | 89 | ### `.manufacturerId` {#manufacturerId} 90 | **Type**: Array.<number>
91 | **Attributes**: read-only
92 | 93 | 94 | When the message is a system exclusive message (sysex), this property contains an array with 95 | either 1 or 3 entries that identify the manufacturer targeted by the message. 96 | 97 | To know how to translate these entries into manufacturer names, check out the official list: 98 | https://www.midi.org/specifications-old/item/manufacturer-id-numbers 99 | 100 | 101 | ### `.rawData` {#rawData} 102 | **Type**: Uint8Array
103 | **Attributes**: read-only
104 | 105 | 106 | A 107 | [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) 108 | containing the bytes of the MIDI message. Each byte is an integer between `0` and `255`. 109 | 110 | 111 | ### `.rawDataBytes` {#rawDataBytes} 112 | **Type**: Uint8Array
113 | **Attributes**: read-only
114 | 115 | 116 | A 117 | [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) 118 | of the data byte(s) of the MIDI message. When the message is a system exclusive message 119 | (sysex), `rawDataBytes` explicitly excludes the manufacturer ID and the sysex end byte so 120 | only the actual data is included. 121 | 122 | 123 | ### `.statusByte` {#statusByte} 124 | **Type**: number
125 | **Attributes**: read-only
126 | 127 | 128 | The MIDI status byte of the message as an integer between `0` and `255`. 129 | 130 | 131 | ### `.type` {#type} 132 | **Type**: string
133 | **Attributes**: read-only
134 | 135 | 136 | The type of message as a string (`"noteon"`, `"controlchange"`, `"sysex"`, etc.) 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /website/api/classes/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Classes & Objects", 3 | "position": 2, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /website/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: API Documentation 4 | slug: / 5 | --- 6 | 7 | # API Documentation 8 | 9 | ## Core Classes 10 | 11 | These classes are the ones developers are most likely to be dealing with while working on their MIDI 12 | projects. Note that all these classes are pre-instantiated within WEBMIDI.js. 13 | 14 | * [**WebMidi**](./classes/WebMidi.md) 15 | * [**Input**](./classes/Input.md) 16 | * [**InputChannel**](./classes/InputChannel.md) 17 | * [**Output**](./classes/Output.md) 18 | * [**OutputChannel**](./classes/OutputChannel.md) 19 | * [**Message**](./classes/Message.md) 20 | 21 | The exception are the [`Note`](./classes/Note.md) class which you can instantiate when you need 22 | to store a musical note and the [`Forwarder`](./classes/Forwarder.md) class used to forward 23 | messages from an input to an output: 24 | 25 | * [**Note**](./classes/Note.md) 26 | * [**Forwarder**](./classes/Forwarder.md) 27 | 28 | ## Support Classes 29 | 30 | These classes are mostly for internal use, but you might find them useful in some contexts. The 31 | [`Enumerations`](./classes/Enumerations.md) class contains static enums of MIDI messages, 32 | registered parameters, etc. The [`Utilities`](./classes/Utilities.md) class contains various 33 | static methods. 34 | 35 | * [**Enumerations**](./classes/Enumerations.md) 36 | * [**Utilities**](./classes/Utilities.md) 37 | 38 | ## DjipEvents Classes 39 | 40 | The `EventEmitter` and `Listener` classes from the 41 | [DjipEvents](https://github.com/djipco/djipevents) module are extended by various WEBMIDI.js 42 | classes. So, in the interest of completeness, we include their full documentation here and 43 | cross-reference it with the core classes 44 | 45 | * [**EventEmitter**](./classes/EventEmitter.md) 46 | * [**Listener**](./classes/Listener.md) 47 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve("@docusaurus/core/lib/babel/preset")], 3 | }; 4 | -------------------------------------------------------------------------------- /website/blog/2021-12-01/version-3-has-been-released.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: WEBMIDI.js v3 is available now! 3 | description: Version 3 of WEBMIDI.js, the library that lets you interact with your MIDI instruments and devices, is now available. It features Node.js and TypeScript support, various new objects (Message, Note, etc.) and a completely rewritten engine. 4 | authors: 5 | - name: Jean-Philippe Côté 6 | title: Creator of WEBMIDI.js 7 | url: /about 8 | image_url: /img/blog/jean-philippe_cote.jpg 9 | hide_table_of_contents: false 10 | keywords: [web midi api, music, instrument, midi, javascript] 11 | image: /img/blog/2021-12-01/webmidijs-is-out.png 12 | --- 13 | 14 | After a lot of work and testing, I am happy to announce today that version 3 of the go-to MIDI 15 | library for JavaScript has been released! You can [try it out](https://webmidijs.org/docs) right 16 | now! 17 | 18 | 19 | 20 | ![](webmidi.js-is-available-now.png) 21 | 22 | ### About WEBMIDI.js 23 | 24 | [**WEBMIDI.js**](https://webmidijs.org) exists to make it easier for developers to use the 25 | [Web MIDI API](https://webaudio.github.io/web-midi-api/). The Web MIDI API is a really exciting 26 | addition to the web platform allowing a web page to interact directly with MIDI musical instruments 27 | and devices. 28 | 29 | While great, many developers will find the API to be too low-level for their needs. Having to 30 | perform binary arithmetic or needing to constantly refer to the 300-page MIDI spec is no fun (trust 31 | me on this!). So, the goal for [**WEBMIDI.js**](https://webmidijs.org) is to get developers and 32 | musicians started with their web-based MIDI projects as efficiently as possible. 33 | 34 | As of today, [**WEBMIDI.js**](https://webmidijs.org) generates over **744K hits a month on 35 | [jsDelivr](https://www.jsdelivr.com/package/npm/webmidi)**. It is **downloaded over 4.4K times a 36 | month on [NPM](https://www.npmjs.com/package/webmidi)** and has been **starred by over 37 | [1000 developers](https://github.com/djipco/webmidi/stargazers)** on GitHub. Not too bad for a niche 38 | library that grew out of a personal passion project. 😀 39 | 40 | ### About the New Version 3 41 | 42 | Version 3 has been rewritten from scratch to make it both future-proof and backwards-compatible. It 43 | uses a modern development paradigm and now has its own dedicated website at 44 | [**webmidijs.org**](https://webmidijs.org). The library offers numerous new features such as: 45 | 46 | * Long-awaited **support for Node.js** (thanks to the [jzz](https://www.npmjs.com/package/jzz) 47 | module by Jazz-Soft). The exact same code can be used in supported browsers and in Node.js. 48 | 49 | * Distribution in **3 flavours**: **ESM** (ECMAScript module for modern browsers), **CJS** (CommonJS 50 | module for Node.js) and **IIFE** (Immediately Invoked Function Expression for legacy browsers and 51 | _ad hoc_ usage). 52 | 53 | * **TypeScript Support**. Every new release includes a TypeScript definition file for CJS and ESM in 54 | the `dist` directory. 55 | 56 | * **New `InputChannel` and `OutputChannel`** objects. You can now work with a single MIDI channel if 57 | that's appropriate for your needs. 58 | 59 | * **New `Note` object**. Makes it easier to work with notes and pass them around from one method to 60 | the next. 61 | 62 | * **New `Message` object** that allows easier routing of MIDI messages, including the ability to 63 | automatically **forward inbound MIDI messages** to one, or more, outputs (much like the good ol' 64 | physical THRU port). 65 | 66 | * Improved support for **system exclusive** (sysex) messages. 67 | 68 | * **Support for promises** while preserving legacy callback support. 69 | 70 | * Improved **support for RPN/NRPN messages**. 71 | 72 | * Addition of **hundreds of unit tests** to make sure the library remains stable at all times. 73 | 74 | * and lots more... 75 | 76 | ### Try it out! 77 | 78 | The [documentation section](https://webmidijs.org/docs) of the new website has all the information 79 | to get you started. If you need help, you can exchange with fellow users and myself using the 80 | [GitHub Discussions](https://github.com/djipco/webmidi/discussions) platform. 81 | 82 | If you use the library and find it useful, please think about 83 | [sponsoring](https://github.com/sponsors/djipco) 💜 the project. 84 | 85 | Cheers! 86 | 87 | Jean-Philippe 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /website/blog/2021-12-01/webmidi.js-is-available-now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djipco/webmidi/f812407b67104ec0d641aa6ed838bca1777ab146/website/blog/2021-12-01/webmidi.js-is-available-now.png -------------------------------------------------------------------------------- /website/docs/archives/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Previous Versions", 3 | "position": 40 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/archives/v1.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | slug: /archives/v1 4 | sidebar_label: Version 1.0.0-beta.15 5 | --- 6 | 7 | # Documentation for v1.0.0-beta.15 8 | 9 | :::caution 10 | 11 | There is no documentation per se for version 1.0.0-beta.15, However, you can still consult an 12 | archived copy of the full 13 | [API Reference](https://djipco.github.io/webmidi/archives/api/v1/classes/WebMidi.html). 14 | 15 | ::: 16 | -------------------------------------------------------------------------------- /website/docs/getting-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting Started", 3 | "position": 10 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Installation 6 | 7 | ## Distribution Flavours 8 | 9 | To cater to various needs, WEBMIDI.js is distributed in 3 different flavours: 10 | 11 | * **Immediately Invoked Function Expression** (IIFE): This version adds its objects directly to the 12 | global namespace. This is the legacy approach which is often easier for beginners. 13 | 14 | * **ES6 Module** (ESM): This is the modern approach which allows you to `import` the objects as 15 | needed (works in newer versions of browsers and Node.js). 16 | 17 | * **CommonJS Module** (CJS): this is the flavour traditionnally used by Node.js and often with 18 | bundling tools such as WebPack. 19 | 20 | All 3 flavours come in full and minified versions with sourcemap. 21 | 22 | 23 | ## Retrieving the Library 24 | 25 | Depending on your needs and environment, you can retrieve and install **WEBMIDI.js** in a variety of 26 | different ways. Let's look at all of them. 27 | 28 | ## Linking From CDN 29 | 30 | The fastest way to get started is to link the library directly from the 31 | [jsDelivr](https://www.jsdelivr.com/package/npm/webmidi) CDN (Content Delivery Network). Just add a 32 | ` 36 | ``` 37 | 38 | You can retrieve different versions and flavours of the library by modifying the URL. For example, 39 | to grab a different flavour replace `/dist/iife/webmidi.iife.js` by one of these: 40 | 41 | * `/dist/cjs/webmidi.cjs.js` 42 | * `/dist/cjs/webmidi.cjs.min.js` 43 | * `/dist/esm/webmidi.esm.js` 44 | * `/dist/esm/webmidi.esm.min.js` 45 | * `/dist/iife/webmidi.iife.js` 46 | * `/dist/iife/webmidi.iife.min.js` 47 | 48 | If you want more control over versions and flavours, check out the 49 | [jsDelivr examples](https://www.jsdelivr.com/features). 50 | 51 | ## Installing Manually 52 | 53 | Obviously, you can also install the library the old-fashioned way by manually 54 | [downloading it](https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.min.js) and 55 | placing it somewhere in your project. Link to it from your HTML page using a ` 79 | ``` 80 | * ### CommonJS 81 | 82 | Using **CommonJS** is the traditional approach for Node.js. 83 | 84 | ```javascript 85 | const {WebMidi} = require("webmidi"); 86 | ``` 87 | * ### ES Module 88 | 89 | This is the modern approach using the 90 | [ECMAScript module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) format. 91 | You can use it in browsers (see [compatibility](https://caniuse.com/es6-module-dynamic-import)) 92 | and in Node.js v12+. **Going forward, this is the favoured approach.** 93 | 94 | **Browsers:** 95 | 96 | ```javascript 97 | import {WebMidi} from "./node_modules/webmidi/dist/esm/webmidi.esm.min.js"; 98 | ``` 99 | 100 | **Node.js:** 101 | 102 | ```javascript 103 | import {WebMidi} from "webmidi"; 104 | ``` 105 | 106 | 107 | 108 | :::caution 109 | 110 | ## Insecure Origins 111 | 112 | Starting with version 77, 113 | [Chrome deprecated Web MIDI usage on insecure origins](https://www.chromestatus.com/feature/5138066234671104). 114 | This means that, going forward, any page using the library will need to be hosted on a secure 115 | origin: 116 | 117 | * `https://` 118 | * `localhost:` 119 | * `file:///` 120 | 121 | Also, the user will need to explicitely authorize usage via a prompt (no matter if system exclusive 122 | messages are used or not). 123 | 124 | ::: 125 | -------------------------------------------------------------------------------- /website/docs/getting-started/supported-environments.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | slug: /getting-started 4 | --- 5 | 6 | # Supported Environments 7 | 8 | Starting with version 3, the library works in both the browser and Node.js. Let's quickly look at 9 | the specificities of both these environments. 10 | 11 | ## Browser Support 12 | 13 | The library works in all browsers that natively [support](https://caniuse.com/midi) the 14 | [Web MIDI API](https://webaudio.github.io/web-midi-api/). Currently, the following major browsers 15 | have native support: 16 | 17 | * Edge v79+ 18 | * Chrome 43+ 19 | * Opera 30+ 20 | * Firefox 108+ 21 | 22 | It is also possible to use this library in other browsers if you install 23 | [Jazz-Plugin](https://jazz-soft.net/download/Jazz-Plugin/) v1.4+. This combination provides 24 | support for the following additional web browsers: 25 | 26 | * Safari 27 | * Internet Explorer 28 | 29 | Note that, in 2020, [Apple has announced](https://webkit.org/tracking-prevention/) that they would 30 | not natively support the Web MIDI API (and a host of other APIs) in Safari because of fingerprinting 31 | concerns. 32 | 33 | ## Node.js Support 34 | 35 | Version 3.0 of WEBMIDI.js introduced full Node.js support. Nothing special needs to be done, it 36 | should just work in the following environments (with Node.js 8.5+): 37 | 38 | * GNU/Linux 39 | * macOS 40 | * Windows 41 | * Raspberry Pi 42 | 43 | Support for the Node.js environment has been made possible in large part by the good folks of 44 | [Jazz-Soft](https://jazz-soft.net/) via their [JZZ](https://www.npmjs.com/package/jzz) module. 45 | 46 | ## TypeScript Support 47 | 48 | Starting with version 3, TypeScript is officially supported. You will find the TypeScript definition 49 | file in these locations in side tje library's folder: 50 | 51 | * `/dist/cjs/webmidi.cjs.d.ts` (Node.js module) 52 | * `/dist/esm/webmidi.esm.d.ts` (ECMAScript module) 53 | -------------------------------------------------------------------------------- /website/docs/going-further/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Going Further", 3 | "position": 20 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/going-further/electron.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | title: Electron 4 | --- 5 | 6 | # Electron 7 | 8 | WEBMIDI.js works fine inside [Electron](https://www.electronjs.org/) but you must make sure to 9 | properly handle the permission request and permission check handlers from within the main process: 10 | 11 | ```javascript 12 | mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback, details) => { 13 | if (permission === 'midi' || permission === 'midiSysex') { 14 | callback(true); 15 | } else { 16 | callback(false); 17 | } 18 | }) 19 | 20 | mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => { 21 | if (permission === 'midi' || permission === 'midiSysex') { 22 | return true; 23 | } 24 | return false; 25 | }); 26 | ``` 27 | -------------------------------------------------------------------------------- /website/docs/going-further/forwarding.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: Forwarding 4 | --- 5 | 6 | # Forwarding Messages 7 | 8 | Starting with version 3, it is now possible to forward messages from an 9 | [`Input`](/api/classes/Input) to an [`Output`](/api/classes/Output). This is done by using the 10 | `forward` method of the [`Input`](/api/classes/Input) object. 11 | -------------------------------------------------------------------------------- /website/docs/going-further/middle-c.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Middle C & Octave Offset 6 | 7 | ## Default Value for Middle C 8 | 9 | The general MIDI 1.0 specification does not explicitly define a pitch for middle C but it does 10 | consider middle C to be note number 60. The **MIDI Tuning Standard** states that note number 69 11 | should be tuned at 440Hz by default, which would make middle C (60) to be C4. However, different 12 | manufacturers have assigned middle C to various octaves/pitches (usually C3, C4 or C5). 13 | 14 | In accordance with the **MIDI Tuning Standard** and the 15 | [**scientific pitch notation**](https://en.wikipedia.org/wiki/Scientific_pitch_notation), WEBMIDI.js 16 | considers middle C (261.626 Hz) to be C4 by default. 17 | 18 | ## Offsetting Middle C 19 | 20 | You can offset the reported note name and octave by using the `octaveOffset` property of various 21 | objects. This will make it easier to interface with devices that do not place middle C at C4. 22 | 23 | ### Inbound Note Example 24 | 25 | If your external MIDI keyboard sends C3 and WEBMIDI.js reports it as C4, it is because your keyboard 26 | places middle C one octave lower than WEBMIDI.js does. To account for that, simply set 27 | [`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset) to `-1`. This way, when your keyboard 28 | sends C3, WEBMIDI.js will also report it as C3. 29 | 30 | In both cases the actual note number (60) remains the same. It is just being reported differently. 31 | 32 | ### Outbound Note Example 33 | 34 | If you are sending F#4 to an external device and that device thinks it's receiving F#5, it means 35 | that the external device places middle C one octave higher. In this case, set 36 | [`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset) to `1` to account for the difference. 37 | 38 | ## Offsetting Granularity 39 | 40 | For most scenarios, setting the global [`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset) 41 | is enough. However, the `octaveOffset` property is available for several objects to allow for better 42 | granularity: 43 | 44 | * [Input](/api/classes/Input) 45 | * [InputChannel](/api/classes/InputChannel) 46 | * [Output](/api/classes/Output) 47 | * [OutputChannel](/api/classes/OutputChannel) 48 | * [Note](/api/classes/Note) 49 | * [WebMidi](/api/classes/WebMidi) 50 | 51 | If you define `octaveOffset` on several objects, their value will be added. For example, if you 52 | set [`WebMidi.octaveOffset`](/api/classes/WebMidi#octaveOffset) to `-1` and set `octaveOffset` on a 53 | specific channel to `1`, the resulting offset on that channel will be `0` (-1 + 1) while the offset 54 | on other channels will be `1`. 55 | -------------------------------------------------------------------------------- /website/docs/going-further/performance.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Performance 6 | 7 | ## Targeting Channels 8 | 9 | To complete. 10 | 11 | ## Disabling Validation 12 | 13 | To complete. 14 | -------------------------------------------------------------------------------- /website/docs/going-further/sysex.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: Sysex 4 | --- 5 | 6 | # System Exclusive Messages (sysex) 7 | -------------------------------------------------------------------------------- /website/docs/going-further/typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: TypeScript 4 | --- 5 | 6 | # TypeScript 7 | 8 | TypeScript is supported in version 3+. However, it has not yet been tested extensively and some 9 | minor issues may remain. 10 | 11 | For instance, some types have been defined as `any` for lack of a better option. One such example 12 | has been described in 13 | [issue 229](https://github.com/djipco/webmidi/issues/229#issuecomment-1039036353). If you are a 14 | TypeScript expert, perhaps you can help. 15 | -------------------------------------------------------------------------------- /website/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | slug: / 4 | --- 5 | 6 | # Quick Start For v3.x 7 | 8 | **You want to get started as quickly as possible?** This guide will let you establish a connection 9 | with your MIDI instrument in less than 5 minutes. 10 | 11 | :::info 12 | 13 | Documentation for [version 2.5.x](https://webmidijs.org/archives/api/v2/) and 14 | [version 1.0.0](http://webmidijs.org/archives/api/v1/classes/WebMidi.html) is also available. 15 | 16 | ::: 17 | 18 | ## Step 1 - Create the HTML page 19 | 20 | :::tip 21 | 22 | Hint: You can **go even faster** by copying the 23 | [code](https://github.com/djipco/webmidi/blob/develop/examples/quick-start/index.html) from 24 | our GitHub repo. 25 | 26 | ::: 27 | 28 | Create an HTML document and link to the library: 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | 36 | 37 | WebMidi.js Quick Start 38 | 39 | 40 | 41 | 42 |

WebMidi.js Quick Start

43 | 44 | 45 | 46 | ``` 47 | 48 | ## Step 2 - Add a script 49 | 50 | Add the following ` 78 | ``` 79 | ## Step 3 - Connect your device 80 | 81 | 🎹 Connect an input MIDI device (synth, drum machine, controller, etc.) and load the HTML page in a 82 | [compatible browser](/docs/getting-started#browser-support). You will be 83 | prompted to authorize the MIDI connection. 84 | 85 | After authorization, the page should detect the connected MIDI devices and display their name. 86 | 87 | :::info 88 | 89 | If nothing shows up, first make sure your MIDI device is detected at the operating system level. 90 | 91 | ::: 92 | 93 | ## Step 4 - Listen for MIDI messages 94 | 95 | In the `onEnabled()` function, we first retrieve the input device we want to work with and store it 96 | in the `mySynth` variable. You can retrieve it by number or by name (as you wish). 97 | 98 | Then we use the `addListener()` method on MIDI channel 1 of the input device to add a 99 | callback function that will be called each time a **noteon** event is received on that MIDI channel. 100 | 101 | ```javascript 102 | function onEnabled() { 103 | 104 | if (WebMidi.inputs.length < 1) { 105 | document.body.innerHTML+= "No device detected."; 106 | } else { 107 | WebMidi.inputs.forEach((device, index) => { 108 | document.body.innerHTML+= `${index}: ${device.name}
`; 109 | }); 110 | } 111 | 112 | const mySynth = WebMidi.inputs[0]; 113 | // const mySynth = WebMidi.getInputByName("TYPE NAME HERE!") 114 | 115 | mySynth.channels[1].addListener("noteon", e => { 116 | document.body.innerHTML+= `${e.note.name}
`; 117 | }); 118 | 119 | } 120 | ``` 121 | Alternatively, if you wish to listen for notes from several channels at once, you can add the 122 | listener directly on the input device itself: 123 | 124 | ```javascript 125 | // Listen to 'note on' events on channels 1, 2 and 3 of the first input MIDI device 126 | WebMidi.inputs[0].addListener("noteon", e => { 127 | document.body.innerHTML+= `${e.note.name}
`; 128 | }, {channels: [1, 2, 3]}); 129 | ``` 130 | 131 | ## Step 5 - Have fun! 132 | 133 | **That's it!** To go further, please take some time to check out the 134 | [Getting Started](getting-started) section. It covers important topics such as installation 135 | options, compatibility, security, etc. 136 | 137 | :::tip 138 | 139 | If you ever need further help, you can also head over to the 140 | [GitHub Discussions](https://github.com/djipco/webmidi/discussions) page and ask all the questions 141 | you want! 142 | 143 | ::: 144 | -------------------------------------------------------------------------------- /website/docs/migration/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Migration", 3 | "position": 30 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/roadmap/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Roadmap", 3 | "position": 50 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/roadmap/under-evaluation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: Under Evaluation 3 | sidebar_position: 2 4 | --- 5 | 6 | # Potential Enhancements To Evaluate 7 | 8 | The analysis has not started yet. We will wait after the official launch of version 3. We do have a 9 | lot of ideas and suggestions in store. Depending on whether these features break the API or not, 10 | they may make it into version 3.x or be deployed in v4. 11 | 12 | :::info 13 | 14 | If you have suggestions, please post them for discussion to the 15 | [Feature Request](https://github.com/djipco/webmidi/discussions/categories/feature-requests) 16 | category of our GitHub Discussions. 17 | 18 | ::: 19 | 20 | ## Ideas & Suggestions to Evaluate 21 | 22 | If you feel any of these ideas should be given priority, plese explain why in the 23 | [Feature Request](https://github.com/djipco/webmidi/discussions/categories/feature-requests) 24 | category of our GitHub Discussions so I can properly triage them. 25 | 26 | * Add support for Web BLE MIDI ([browser implementation](https://github.com/skratchdot/ble-midi), 27 | [Node implementation](https://github.com/natcl/ble-midi)) 28 | 29 | * Explore compatibility with WebMidiLink. Could we create an output that points to a WebMidiLinked 30 | device? 31 | 32 | * Could we allow `WebMidi.time` to be reset? (see 33 | [discussion #213](https://github.com/djipco/webmidi/discussions/213)) 34 | 35 | * Add throttling or delay option to `sendSysex` (see discussion 36 | [#235](https://github.com/djipco/webmidi/discussions/235)). 37 | 38 | * Calculate BPM from clock messages 39 | ([Discussion #177](https://github.com/djipco/webmidi/discussions/177)) 40 | 41 | * Allow the first argument of `output.playNote( )` to be ‘0:0’ as ‘A0’, ‘7:3’ as ‘E:3’ and so on. 42 | 43 | * Add a "mute" option for inputs/outputs 44 | 45 | * Include the ability to add MIDI event listeners at the WebMidi.js level 46 | ([Issue #138](https://github.com/djipco/webmidi/issues/138)) 47 | 48 | * Emit events on `send()` so outbound MIDI messages can be listened for 49 | ([Discussion #171](https://github.com/djipco/webmidi/discussions/171)) 50 | 51 | * Add a `stopAllNotes()` method 52 | 53 | * Calculate time values and make them directly available for `songposition` and `timecode` message 54 | 55 | * Make Istanbul (nyc) break down the coverage stats by test file. 56 | 57 | * Add the ability to send grouped messages for CC events (and potentially others) 58 | 59 | * Add expliocit support for 60 | [MIDI Polyphonic Expressions](https://www.midi.org/midi-articles/midi-polyphonic-expression-mpe). 61 | 62 | * Add explicit support for 63 | [Universal System Exclusive Messages](https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages) 64 | 65 | * This would include a `sendIdentityRequest()` method to the output object (perhaps with a 66 | `getIdentity()` companion method that waits for the response) ([Issue #117](https://github.com/djipco/webmidi/issues/117)) 67 | 68 | * This could also include the capability to query device for make/model (similar to 69 | [jzz-midi-gear](https://www.npmjs.com/package/jzz-midi-gear)) 70 | 71 | * Implement show control protocol subset 72 | 73 | * Add ability to inject Jazz-Plugin code for browsers with no native Web MIDI API support. 74 | 75 | * Add the option to create sysex plugins for various devices 76 | [forum thread](https://webmidijs.org/forum/discussion/comment/97#Comment_97) 77 | 78 | * Add 79 | [issue and PR templates](https://help.github.com/en/github/building-a-strong-community/about-issue-and-pull-request-templates) 80 | 81 | * Add continuous integration tool 82 | 83 | * Add ability to read/write MIDI files 84 | 85 | * Solid timing, midi clock, sync, transport functionality 86 | 87 | * Helper functions that help to deal with sysex checksum from specific manufacturer (Roland, 88 | checksum, etc.) 89 | 90 | * Add explicit support for Sample Dump Format (see discussion on 91 | [forum](https://webmidijs.org/forum/discussion/30/has-there-been-any-work-on-sample-dump-standard)) 92 | 93 | * Allow third-party developers to develop modules that facilitate encoding and decoding of 94 | device-specific sysex messages (see [forum discussion](https://webmidijs.org/forum/discussion/37/)) 95 | 96 | * Add timing capabilities such as syncing with Tone.js or being able to schedule events using 97 | musical notes. 98 | 99 | * Add the ability to export a MIDI file (perhaps with another lib such as 100 | [MidiWriterJS](https://www.npmjs.com/package/midi-writer-js) or 101 | [Jazz-Soft](https://jazz-soft.net/demo/WriteMidiFile.html) 102 | 103 | * SMF Support 104 | 105 | * Check if something specific needs to be done to support Electron 106 | ([this discussion](https://www.electronjs.org/docs/api/session#sessetpermissionrequesthandlerhandler)). 107 | 108 | * Evaluate whether if would be worth it to switch from the `midi` module to the `web-midi-test` 109 | module for unit tests (discussion [here](https://github.com/djipco/webmidi/discussions/223)). 110 | 111 | ## Enhancements Put On Hold For Now 112 | 113 | * Consider usage of 114 | [pipelining operator](https://github.com/tc39/proposal-pipeline-operator/blob/master/README.md#introduction) 115 | for patching webmidi function calls to a sequence 116 | 117 | * Consider using [middleware](https://github.com/unbug/js-middleware) approach for making the app 118 | pluggable 119 | 120 | * Investigate the possibility to add a `Device` object that would group inputs and outputs for a 121 | single device (see [discussion #280](https://github.com/djipco/webmidi/discussions/280) for 122 | details) 123 | 124 | * Piano roll 125 | -------------------------------------------------------------------------------- /website/docs/roadmap/v4.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: Version 4 3 | sidebar_position: 2 4 | --- 5 | 6 | # Features Planned for Version 4 7 | 8 | The full analysis has not started yet. We do have a lot of [ideas](/docs/roadmap/under-evaluation) 9 | in store. Depending on whether these features break the API or not, they may make it into version 10 | 3.x or be deployed in v4. 11 | 12 | :::info 13 | 14 | If you have suggestions, please post them for discussion to the 15 | [Feature Request](https://github.com/djipco/webmidi/discussions/categories/feature-requests) 16 | category of our GitHub Discussions. 17 | 18 | ::: 19 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docusaurus", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^3.0.0", 18 | "@docusaurus/plugin-client-redirects": "^3.0.0", 19 | "@docusaurus/plugin-content-blog": "^3.0.0", 20 | "@docusaurus/plugin-content-docs": "^3.0.0", 21 | "@docusaurus/preset-classic": "^3.0.0", 22 | "@docusaurus/theme-search-algolia": "^3.0.0", 23 | "@mdx-js/react": "^1.6.21", 24 | "@svgr/webpack": "^6.3.1", 25 | "clsx": "^1.1.1", 26 | "docusaurus-plugin-sass": "^0.2.5", 27 | "file-loader": "^6.2.0", 28 | "prism-react-renderer": "^1.2.1", 29 | "react": "^17.0.1", 30 | "react-dom": "^17.0.1", 31 | "react-helmet": "^6.1.0", 32 | "sass": "^1.43.4", 33 | "typescript": "^4.4.2", 34 | "url-loader": "^4.1.1" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.5%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | module.exports = { 13 | // By default, Docusaurus generates a sidebar from the docs folder structure 14 | tutorialSidebar: [{type: "autogenerated", dirName: "."}], 15 | 16 | // But you can create a sidebar manually 17 | /* 18 | tutorialSidebar: [ 19 | { 20 | type: 'category', 21 | label: 'Tutorial', 22 | items: ['hello'], 23 | }, 24 | ], 25 | */ 26 | }; 27 | -------------------------------------------------------------------------------- /website/src/components/Button.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./Button.module.scss"; 3 | 4 | /*export default function Button({children, type, href, target}) { 5 | return ( 6 |
7 | {children} 8 |
9 |
10 | ); 11 | }*/ 12 | 13 | export default function Button(props) { 14 | const component = "Button"; 15 | const{ 16 | children, 17 | href, 18 | type, 19 | target, 20 | } = props; 21 | return ( 22 |
31 | {children} 32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /website/src/components/Button.module.css: -------------------------------------------------------------------------------- 1 | .Button { 2 | background-color: var(--color-accent); 3 | width: fit-content; 4 | height: fit-content; 5 | border-radius: 10px; 6 | position: relative; 7 | overflow: hidden; 8 | text-align: center; 9 | } 10 | .Button a { 11 | display: block; 12 | font: bold 1.56rem var(--font-secondary), sans-serif; 13 | padding: var(--spacing-sm) 1.5em; 14 | color: var(--color-text-primary); 15 | text-decoration: none; 16 | position: relative; 17 | z-index: 2; 18 | } 19 | .Button .buttonBg { 20 | width: 100%; 21 | height: 100%; 22 | position: absolute; 23 | } 24 | .Button.button-bg-full { 25 | border: solid 4px var(--color-accent); 26 | } 27 | .Button.button-bg-full .buttonBg { 28 | background-color: var(--color-accent-lighter); 29 | z-index: 1; 30 | top: 0; 31 | left: -100%; 32 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 33 | } 34 | .Button.button-bg-full:hover { 35 | border: solid 4px var(--color-accent-lighter); 36 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 37 | } 38 | .Button.button-bg-full:hover .buttonBg { 39 | left: 0; 40 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 41 | } 42 | .Button.button-bg-empty { 43 | background-color: rgba(0, 0, 0, 0); 44 | border: solid 4px var(--color-accent); 45 | } 46 | .Button.button-bg-empty a { 47 | color: var(--color-accent); 48 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 49 | } 50 | .Button.button-bg-empty .buttonBg { 51 | background-color: var(--color-accent-lighter); 52 | z-index: 1; 53 | top: 0; 54 | left: -100%; 55 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 56 | } 57 | .Button.button-bg-empty:hover { 58 | border: solid 4px var(--color-accent-lighter); 59 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 60 | } 61 | .Button.button-bg-empty:hover a { 62 | color: var(--color-white); 63 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 64 | } 65 | .Button.button-bg-empty:hover .buttonBg { 66 | left: 0; 67 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 68 | } 69 | 70 | /*# sourceMappingURL=Button.module.css.map */ 71 | -------------------------------------------------------------------------------- /website/src/components/Button.module.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["Button.module.scss"],"names":[],"mappings":"AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EAEA;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;;AACA;EACE;EAEA;EACA;EACA;EACA;;AAEF;EACE;EACA;;AACA;EACE;EACA;;AAIN;EACE;EACA;;AACA;EACE;EACA;;AAEF;EACE;EAEA;EACA;EACA;EACA;;AAEF;EACE;EACA;;AACA;EACE;EACA;;AAEF;EACE;EACA","file":"Button.module.css"} -------------------------------------------------------------------------------- /website/src/components/Button.module.scss: -------------------------------------------------------------------------------- 1 | $ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86); 2 | .Button { 3 | background-color: var(--color-accent); 4 | width: fit-content; 5 | height: fit-content; 6 | border-radius: 10px; 7 | position: relative; 8 | overflow: hidden; 9 | text-align: center; 10 | 11 | a { 12 | display: block; 13 | font: bold 1.56rem var(--font-secondary), sans-serif; 14 | padding: var(--spacing-sm) 1.5em; 15 | color: var(--color-text-primary); 16 | text-decoration: none; 17 | 18 | position: relative; 19 | z-index: 2; 20 | } 21 | .buttonBg { 22 | width: 100%; 23 | height: 100%; 24 | position: absolute; 25 | } 26 | &.button-bg-full { 27 | border: solid 4px var(--color-accent); 28 | .buttonBg { 29 | background-color: var(--color-accent-lighter); 30 | 31 | z-index: 1; 32 | top: 0; 33 | left: -100%; 34 | transition: all 0.3s $ease-in-out-circ; 35 | } 36 | &:hover { 37 | border: solid 4px var(--color-accent-lighter); 38 | transition: all 0.3s $ease-in-out-circ; 39 | .buttonBg { 40 | left: 0; 41 | transition: all 0.3s $ease-in-out-circ; 42 | } 43 | } 44 | } 45 | &.button-bg-empty { 46 | background-color: rgba(0, 0, 0, 0%); 47 | border: solid 4px var(--color-accent); 48 | a { 49 | color: var(--color-accent); 50 | transition: all 0.3s $ease-in-out-circ; 51 | } 52 | .buttonBg { 53 | background-color: var(--color-accent-lighter); 54 | 55 | z-index: 1; 56 | top: 0; 57 | left: -100%; 58 | transition: all 0.3s $ease-in-out-circ; 59 | } 60 | &:hover { 61 | border: solid 4px var(--color-accent-lighter); 62 | transition: all 0.3s $ease-in-out-circ; 63 | a { 64 | color: var(--color-white); 65 | transition: all 0.3s $ease-in-out-circ; 66 | } 67 | .buttonBg { 68 | left: 0; 69 | transition: all 0.3s $ease-in-out-circ; 70 | } 71 | } 72 | } 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /website/src/components/Column.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./Column.module.scss"; 3 | 4 | export default function Column({children, type,}) { 5 | const component = "Column"; 6 | return ( 7 |
12 | {children} 13 |
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /website/src/components/Column.module.css: -------------------------------------------------------------------------------- 1 | .Column { 2 | display: grid; 3 | } 4 | 5 | .col-2 { 6 | grid-template-columns: 48% 48%; 7 | justify-content: space-between; 8 | align-items: center; 9 | } 10 | @media screen and (max-width: 1000px) { 11 | .col-2 { 12 | display: grid; 13 | grid-template-columns: 1fr; 14 | text-align: center; 15 | } 16 | } 17 | 18 | /*# sourceMappingURL=Column.module.css.map */ 19 | -------------------------------------------------------------------------------- /website/src/components/Column.module.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["Column.module.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EALF;IAMI;IACA;IACA","file":"Column.module.css"} -------------------------------------------------------------------------------- /website/src/components/Column.module.scss: -------------------------------------------------------------------------------- 1 | .Column{ 2 | display: grid; 3 | } 4 | 5 | .col-2 { 6 | grid-template-columns: 48% 48%; 7 | justify-content: space-between; 8 | align-items: center; 9 | 10 | @media screen and(max-width: 1000px){ 11 | display: grid; 12 | grid-template-columns: 1fr; 13 | text-align: center; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import styles from "./HomepageFeatures.module.css"; 4 | 5 | const FeatureList = [ 6 | // { 7 | // title: 'Easy to Use', 8 | // Svg: require('../../static/img/undraw_docusaurus_mountain.svg').default, 9 | // description: ( 10 | // <> 11 | // Docusaurus was designed from the ground up to be easily installed and 12 | // used to get your website up and running quickly. 13 | // 14 | // ), 15 | // }, 16 | // { 17 | // title: 'Focus on What Matters', 18 | // Svg: require('../../static/img/undraw_docusaurus_tree.svg').default, 19 | // description: ( 20 | // <> 21 | // Docusaurus lets you focus on your docs, and we'll do the chores. Go 22 | // ahead and move your docs into the docs directory. 23 | // 24 | // ), 25 | // }, 26 | // { 27 | // title: 'Powered by React', 28 | // Svg: require('../../static/img/undraw_docusaurus_react.svg').default, 29 | // description: ( 30 | // <> 31 | // Extend or customize your website layout by reusing React. Docusaurus can 32 | // be extended while reusing the same header and footer. 33 | // 34 | // ), 35 | // }, 36 | ]; 37 | 38 | function Feature({Svg, title, description}) { 39 | return ( 40 |
41 |
42 | 43 |
44 |
45 |

{title}

46 |

{description}

47 |
48 |
49 | ); 50 | } 51 | 52 | export default function HomepageFeatures() { 53 | return ( 54 |
55 |
56 |
57 | {FeatureList.map((props, idx) => ( 58 | 59 | ))} 60 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | .features { 4 | display: flex; 5 | align-items: center; 6 | padding: 2rem 0; 7 | width: 100%; 8 | } 9 | 10 | .featureSvg { 11 | height: 200px; 12 | width: 200px; 13 | } 14 | -------------------------------------------------------------------------------- /website/src/components/InformationBar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./InformationBar.module.scss"; 3 | 4 | export default function InformationBar({children, type,}) { 5 | const component = "InformationBar"; 6 | return ( 7 |
12 |
13 |

14 | {children} 15 |

16 |
17 |
18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/components/InformationBar.module.css: -------------------------------------------------------------------------------- 1 | .InformationBar { 2 | background-color: var(--color-bg-secondary); 3 | padding: var(--spacing-xl) 0; 4 | color: var(--color-white); 5 | text-align: center; 6 | } 7 | .InformationBar p { 8 | font-size: 1.87rem; 9 | line-height: 120%; 10 | margin: 0; 11 | } 12 | 13 | /*# sourceMappingURL=InformationBar.module.css.map */ 14 | -------------------------------------------------------------------------------- /website/src/components/InformationBar.module.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["InformationBar.module.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;EACF;;AACE;EACE;EACA;EACA","file":"InformationBar.module.css"} -------------------------------------------------------------------------------- /website/src/components/InformationBar.module.scss: -------------------------------------------------------------------------------- 1 | .InformationBar{ 2 | background-color: var(--color-bg-secondary); 3 | padding: var(--spacing-xl) 0; 4 | color: var(--color-white); 5 | text-align: center; 6 | p { 7 | font-size: 1.87rem; 8 | line-height: 120%; 9 | margin:0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /website/src/css/custom.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["custom.scss"],"names":[],"mappings":"AAAA;AAMQ;AAGR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EAGA;EACA;EAGA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EAEA;EAGA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAvDF;IAwDI;;;;AAIF;EACE;EACA;EAEA;EACA;EACA;;;AASJ;EACE;EACA;EACA;EACA;;;AAIF;EAEE;;AAEA;EACE;;;AAKJ;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EAEE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAIF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAMF;EACE;;;AAIF;EACE;;;AAKF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAMA;EAEE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAKN;AAAA;AAAA;AAIA;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAIJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;;AAKJ;EACE;;;AAIF;EACE;EACA;EACA","file":"custom.css"} -------------------------------------------------------------------------------- /website/src/css/index.css: -------------------------------------------------------------------------------- 1 | .hero { 2 | padding: var(--spacing-lg) 0; 3 | min-height: 60vh; 4 | color: var(--color-text-primary); 5 | display: flex; 6 | align-items: center; 7 | background: var(--color-bg-primary); 8 | text-align: center; 9 | } 10 | @media screen and (max-width: 1000px) { 11 | .hero { 12 | min-height: 40vh; 13 | padding-top: 0; 14 | } 15 | } 16 | .hero .logo { 17 | max-width: 600px; 18 | height: 300px; 19 | background: center url("/img/webmidijs-logo-dark.svg") no-repeat; 20 | margin: auto; 21 | } 22 | @media screen and (max-width: 500px) { 23 | .hero .logo { 24 | height: 10em; 25 | margin: var(--spacing-md) 0; 26 | } 27 | } 28 | html[data-theme=dark] .hero .logo { 29 | background: center url("/img/webmidijs-logo-light.svg") no-repeat; 30 | } 31 | .hero span { 32 | font-family: var(--font-secondary); 33 | font-size: 2rem; 34 | display: block; 35 | margin-bottom: var(--spacing-sm); 36 | font-weight: bold; 37 | color: var(--color-accent); 38 | } 39 | .hero .cta { 40 | display: flex; 41 | flex-wrap: wrap; 42 | justify-content: center; 43 | /*@media screen and(max-width: 1000px){ 44 | justify-content: center; 45 | 46 | }*/ 47 | } 48 | .hero .cta .Button { 49 | margin: var(--spacing-sm) 0 0 var(--spacing-sm); 50 | } 51 | .hero .cta .Button:nth-child(1) { 52 | margin-left: 0; 53 | } 54 | .hero .img { 55 | margin: auto; 56 | } 57 | @media screen and (max-width: 1000px) { 58 | .hero .texts { 59 | order: 2; 60 | } 61 | } 62 | 63 | /*======== Presentation ========*/ 64 | .presentation { 65 | margin: var(--spacing-lg) 0; 66 | } 67 | .presentation h2 { 68 | text-align: center; 69 | } 70 | .presentation p { 71 | font-size: 1.5rem; 72 | margin: var(--spacing-sm) 0; 73 | } 74 | .presentation .media { 75 | background-color: var(--color-bg-tertiary); 76 | border-radius: 20px; 77 | width: 100%; 78 | height: 375px; 79 | display: flex; 80 | justify-content: center; 81 | align-items: center; 82 | margin: var(--spacing-xl) auto; 83 | box-shadow: 10px 10px 0 rgba(255, 208, 0, 0.5); 84 | } 85 | @media screen and (max-width: 1000px) { 86 | .presentation .media { 87 | width: 90%; 88 | margin: var(--spacing-md) auto; 89 | } 90 | } 91 | .presentation .media .imgMedia { 92 | width: 80%; 93 | height: 80%; 94 | } 95 | html[data-theme=light] .presentation .media .imgMedia { 96 | filter: invert(1); 97 | } 98 | .presentation .media :nth-child(1) { 99 | background: center url("/img/front-page/webmidi-demonstration.svg") no-repeat; 100 | } 101 | @media screen and (max-width: 1000px) { 102 | .presentation .media :nth-child(1) { 103 | background: center url("/img/front-page/webmidi-demonstration-vertical.svg") no-repeat; 104 | } 105 | } 106 | 107 | /*# sourceMappingURL=index.css.map */ 108 | -------------------------------------------------------------------------------- /website/src/css/index.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["index.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EAEA;;AAEA;EAVF;IAWI;IACA;;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;IACA;;;AAGF;EACE;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EAEA;AAQA;AAAA;;AAAA;;AANA;EACE;;AAEF;EACE;;AAOJ;EACE;;AAGA;EADF;IAEI;;;;AAIN;AACA;EACE;;AACA;EACE;;AAEF;EACE;EACA;;AAEF;EACE;EACA;EAEA;EACA;EAEA;EACA;EACA;EAEA;EAEA;;AAEA;EAfF;IAgBI;IACA;;;AAGF;EACE;EACA;;AAEA;EACE;;AAKN;EACE;;AACA;EAFF;IAGI","file":"index.css"} -------------------------------------------------------------------------------- /website/src/css/index.scss: -------------------------------------------------------------------------------- 1 | .hero{ 2 | padding: var(--spacing-lg) 0; 3 | min-height: 60vh; 4 | color: var(--color-text-primary); 5 | display: flex; 6 | align-items: center; 7 | background: var(--color-bg-primary); 8 | 9 | text-align: center; 10 | 11 | @media screen and(max-width: 1000px){ 12 | min-height: 40vh; 13 | padding-top: 0; 14 | } 15 | 16 | 17 | .logo { 18 | max-width: 600px; 19 | height: 300px; 20 | background: center url("/img/webmidijs-logo-dark.svg") no-repeat; 21 | margin: auto; 22 | 23 | @media screen and(max-width: 500px){ 24 | height: 10em; 25 | margin: var(--spacing-md) 0; 26 | } 27 | 28 | html[data-theme=dark] &{ 29 | background: center url("/img/webmidijs-logo-light.svg") no-repeat; 30 | } 31 | } 32 | span { 33 | font-family: var(--font-secondary); 34 | font-size: 2rem; 35 | display: block; 36 | margin-bottom: var(--spacing-sm); 37 | font-weight: bold; 38 | color: var(--color-accent); 39 | } 40 | 41 | .cta { 42 | display: flex; 43 | flex-wrap: wrap; 44 | 45 | justify-content: center; 46 | 47 | .Button{ 48 | margin: var(--spacing-sm) 0 0 var(--spacing-sm); 49 | } 50 | .Button:nth-child(1){ 51 | margin-left: 0; 52 | } 53 | /*@media screen and(max-width: 1000px){ 54 | justify-content: center; 55 | 56 | }*/ 57 | } 58 | .img{ 59 | margin: auto; 60 | } 61 | .texts{ 62 | @media screen and(max-width: 1000px){ 63 | order: 2; 64 | } 65 | } 66 | } 67 | /*======== Presentation ========*/ 68 | .presentation { 69 | margin: var(--spacing-lg) 0; 70 | h2 { 71 | text-align: center; 72 | } 73 | p{ 74 | font-size: 1.5rem; 75 | margin: var(--spacing-sm) 0; 76 | } 77 | .media { 78 | background-color: var(--color-bg-tertiary); 79 | border-radius: 20px; 80 | 81 | width: 100%; 82 | height: 375px; 83 | 84 | display: flex; 85 | justify-content: center; 86 | align-items: center; 87 | 88 | margin: var(--spacing-xl) auto; 89 | 90 | box-shadow: 10px 10px 0 rgba(255, 208, 0, 0.5); 91 | 92 | @media screen and(max-width: 1000px){ 93 | width: 90%; 94 | margin: var(--spacing-md) auto; 95 | } 96 | 97 | .imgMedia{ 98 | width: 80%; 99 | height: 80%; 100 | 101 | html[data-theme=light] &{ 102 | filter: invert(1); 103 | } 104 | } 105 | } 106 | 107 | .media :nth-child(1){ 108 | background: center url("/img/front-page/webmidi-demonstration.svg") no-repeat; 109 | @media screen and(max-width: 1000px){ 110 | background: center url("/img/front-page/webmidi-demonstration-vertical.svg") no-repeat; 111 | } 112 | } 113 | } 114 | 115 | 116 | -------------------------------------------------------------------------------- /website/src/pages/about/index.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | ## Who created this? 4 | 5 | **WEBMIDI.js** is a passion project of mine. I am Jean-Philippe Côté (a.k.a. 6 | [djip.co](https://djip.co)), an 7 | [academic](https://www.cegepmontpetit.ca/cegep/recherche/professeurs-chercheurs/jean-philippe-cote) 8 | and artist with particular interests in creative coding, interactive arts and music technology. You 9 | can reach out to me in different ways: 10 | 11 | * Twitter: **[@djipco](https://twitter.com/djipco)** or **[@webmidijs](https://twitter.com/webmidijs)** 12 | * Website: **[https://djip.co/](https://djip.co)** 13 | * GitHub: **[https://github.com/djipco/](https://github.com/djipco)** 14 | 15 | One of my students, **Jean-Marie Gariépy** has also been helping out in various capacities with the 16 | creation of this website. Let's all thank him for his contribution! 👏 17 | 18 | ## Sponsoring the project 19 | 20 | You can [sponsor the project](https://github.com/sponsors/djipco/) by becoming a GitHub sponsor. All 21 | you need is a GitHub account. If you see value in this library, please consider a contribution. 🙏🏻 22 | 23 | ## Licensing 24 | 25 | Starting with version 3.0.0, this library is licensed under the **Apache License, Version 2.0**. You 26 | may not use this library except in compliance with this license. You may obtain a copy at: 27 | 28 | * [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 29 | 30 | Unless required by applicable law or agreed to in writing, software distributed under this license 31 | is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 32 | implied. See the above license for the specific language governing permissions and limitations. 33 | 34 | © 2015-2023, Jean-Philippe Côté. 35 | -------------------------------------------------------------------------------- /website/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import useBaseUrl from '@docusaurus/useBaseUrl'; 3 | import Layout from "@theme/Layout"; 4 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 5 | import Button from "../components/Button"; 6 | import Column from "../components/Column"; 7 | import InformationBar from "../components/InformationBar"; 8 | 9 | function HomepageHero() { 10 | const {siteConfig} = useDocusaurusContext(); 11 | 12 | return ( 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 | {siteConfig.tagline} 21 |
22 | 28 |
29 |
30 |
31 |
32 | ); 33 | } 34 | 35 | function Presentation() { 36 | const {siteConfig} = useDocusaurusContext(); 37 | return ( 38 |
39 |
40 |

What is {siteConfig.title}?

41 | 44 |

45 | The existing Web MIDI API is a really exciting addition to the web platform 46 | allowing a web page to interact with MIDI musical instruments. 47 | However, while great, most developers will find the original API to be 48 | too low-level for their needs. Having to perform binary arithmetic 49 | or needing to read the 300-page MIDI spec is no fun (trust us on this!). 50 | The goal behind WEBMIDI.js is to get you started with your web-based 51 | MIDI project as efficiently as possible. 52 |

53 | 54 |
55 |
60 |
61 | 62 | 63 |
64 |
65 | ); 66 | } 67 | 68 | export default function Home() { 69 | const {siteConfig} = useDocusaurusContext(); 70 | return ( 71 | 74 | 75 |
76 | 77 | Version 3.0 has been released!
78 | Subscribe to the newsletter 82 |
83 | to learn about all the new features. 84 |
85 | 86 |
87 |
88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * CSS files with the .module.css suffix will be treated as CSS modules 4 | * and scoped locally. 5 | */ 6 | /* 7 | .heroBanner { 8 | padding: 4rem 0; 9 | text-align: center; 10 | position: relative; 11 | overflow: hidden; 12 | } 13 | 14 | @media screen and (max-width: 966px) { 15 | .heroBanner { 16 | padding: 2rem; 17 | } 18 | } 19 | 20 | .buttons { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | */ 26 | .hero { 27 | padding: var(--spacing-lg); 28 | min-height: 60vh; 29 | display: flex; 30 | align-items: center; 31 | } 32 | .hero h1 { 33 | margin: 0; 34 | text-transform: uppercase; 35 | } 36 | .hero span { 37 | font-family: var(--font-secondary); 38 | font-size: 1.56rem; 39 | display: block; 40 | margin-bottom: var(--spacing-sm); 41 | } 42 | .hero .cta { 43 | display: flex; 44 | } 45 | .hero .cta .button:nth-child(n) { 46 | margin-left: var(--spacing-md); 47 | } 48 | .hero .cta .button:first-child { 49 | margin-left: 0; 50 | } 51 | 52 | /*======== Boutons ========*/ 53 | .button { 54 | background-color: var(--color-accent); 55 | width: fit-content; 56 | border-radius: 10px; 57 | position: relative; 58 | overflow: hidden; 59 | } 60 | .button a { 61 | display: block; 62 | padding: var(--spacing-sm) var(--spacing-lg); 63 | font-weight: bold; 64 | font-family: var(--font-secondary); 65 | font-size: 1.56rem; 66 | position: relative; 67 | z-index: 2; 68 | } 69 | .button .button--bg { 70 | width: 100%; 71 | height: 100%; 72 | position: absolute; 73 | } 74 | .button.button-bg-full .button--bg { 75 | background-color: var(--color-accent-lighter); 76 | z-index: 1; 77 | top: 0; 78 | left: -100%; 79 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 80 | } 81 | .button.button-bg-full:hover .button--bg { 82 | left: 0; 83 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 84 | } 85 | .button.button-bg-empty { 86 | background-color: rgba(0, 0, 0, 0); 87 | border: solid 4px var(--color-accent); 88 | } 89 | .button.button-bg-empty a { 90 | color: var(--color-accent); 91 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 92 | } 93 | .button.button-bg-empty .button--bg { 94 | background-color: var(--color-accent-lighter); 95 | z-index: 1; 96 | top: 0; 97 | left: -100%; 98 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 99 | } 100 | .button.button-bg-empty:hover a { 101 | color: var(--color-white); 102 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 103 | } 104 | .button.button-bg-empty:hover .button--bg { 105 | left: 0; 106 | transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86); 107 | } 108 | 109 | /*======== Columns ========*/ 110 | .col-2 { 111 | display: grid; 112 | grid-template-columns: 48% 48%; 113 | justify-content: space-between; 114 | align-items: center; 115 | } 116 | 117 | /*# sourceMappingURL=index.module.css.map */ 118 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["index.module.scss"],"names":[],"mappings":"AAAA;AAEA;AAAA;AAAA;AAAA;AAIA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA;EACE;EACA;EAEA;EACA;;AACA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAEF;EACE;;;AAKN;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EAEA;EACA;;AAEF;EACE;EACA;EACA;;AAGA;EACE;EAEA;EACA;EACA;EACA;;AAGA;EACE;EACA;;AAIN;EACE;EACA;;AACA;EACE;EACA;;AAEF;EACE;EAEA;EACA;EACA;EACA;;AAGA;EACE;EACA;;AAEF;EACE;EACA;;;AAOR;AACA;EACE;EACA;EACA;EACA","file":"index.module.css"} -------------------------------------------------------------------------------- /website/src/pages/index.module.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | /* 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | */ 27 | .hero{ 28 | padding: var(--spacing-lg); 29 | min-height: 60vh; 30 | 31 | display: flex; 32 | align-items: center; 33 | h1 { 34 | margin: 0; 35 | text-transform: uppercase; 36 | } 37 | span { 38 | font-family: var(--font-secondary); 39 | font-size: 1.56rem; 40 | display: block; 41 | margin-bottom: var(--spacing-sm); 42 | } 43 | 44 | .cta { 45 | display: flex; 46 | .button:nth-child(n) { 47 | margin-left: var(--spacing-md); 48 | } 49 | .button:first-child { 50 | margin-left: 0; 51 | } 52 | } 53 | } 54 | 55 | /*======== Boutons ========*/ 56 | $ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86); 57 | .button { 58 | background-color: var(--color-accent); 59 | width: fit-content; 60 | border-radius: 10px; 61 | position: relative; 62 | overflow: hidden; 63 | 64 | a { 65 | display: block; 66 | padding: var(--spacing-sm) var(--spacing-lg); 67 | font-weight: bold; 68 | font-family: var(--font-secondary); 69 | font-size: 1.56rem; 70 | 71 | position: relative; 72 | z-index: 2; 73 | } 74 | .button--bg { 75 | width: 100%; 76 | height: 100%; 77 | position: absolute; 78 | } 79 | &.button-bg-full { 80 | .button--bg { 81 | background-color: var(--color-accent-lighter); 82 | 83 | z-index: 1; 84 | top: 0; 85 | left: -100%; 86 | transition: all 0.3s $ease-in-out-circ; 87 | } 88 | &:hover { 89 | .button--bg { 90 | left: 0; 91 | transition: all 0.3s $ease-in-out-circ; 92 | } 93 | } 94 | } 95 | &.button-bg-empty { 96 | background-color: rgba(0, 0, 0, 0%); 97 | border: solid 4px var(--color-accent); 98 | a { 99 | color: var(--color-accent); 100 | transition: all 0.3s $ease-in-out-circ; 101 | } 102 | .button--bg { 103 | background-color: var(--color-accent-lighter); 104 | 105 | z-index: 1; 106 | top: 0; 107 | left: -100%; 108 | transition: all 0.3s $ease-in-out-circ; 109 | } 110 | &:hover { 111 | a { 112 | color: var(--color-white); 113 | transition: all 0.3s $ease-in-out-circ; 114 | } 115 | .button--bg { 116 | left: 0; 117 | transition: all 0.3s $ease-in-out-circ; 118 | } 119 | } 120 | } 121 | 122 | } 123 | 124 | /*======== Columns ========*/ 125 | .col-2 { 126 | display: grid; 127 | grid-template-columns: 48% 48%; 128 | justify-content: space-between; 129 | align-items: center; 130 | } 131 | 132 | 133 | -------------------------------------------------------------------------------- /website/src/pages/research/index.md: -------------------------------------------------------------------------------- 1 | # Academic Research 2 | 3 | I invite all academics and researchers to show their support for this project by properly citing it 4 | wherever appropriate in your publications and references. 5 | 6 | ## Citing this Software 7 | 8 | I wrote a 9 | [paper about WEBMIDI.js](https://nime.pubpub.org/pub/user-friendly-midi-in-the-web-browser) and, 10 | more specifically, about how it tries to address the usability shortcomings of the Web MIDI API. I 11 | invite academics to cite it in their publication whenever appropriate: 12 | 13 | > Côté, J.-P. (2022). User-Friendly MIDI in the Web Browser. NIME 2022. 14 | > https://doi.org/10.21428/92fbeb44.388e4764 15 | 16 | You can also cite the library itself like so (APA style): 17 | 18 | > Côté, J. P. (2023). WEBMIDI.js v3.1.6 [Computer Software]. Retrieved from 19 | > https://github.com/djipco/webmidi 20 | 21 | ## Papers Citing Usage of WEBMIDI.js 22 | 23 | * Leischner, V., & Husa, P. (2023). Sonification of a Juggling Performance Using Spatial Audio. 24 | Proceedings of the ACM on Computer Graphics and Interactive Techniques, 6(2), 1–6. 25 | https://doi.org/10.1145/3597619 26 | 27 | * Baratè, A., & Ludovico, L. A. (2022). **Web MIDI API: State of the Art and Future Perspectives**. 28 | Journal of the Audio Engineering Society, 70(11), 918–925. 29 | https://www.aes.org/e-lib/browse.cfm?elib=22016 30 | 31 | * Graber, Z., & Henrion, W. (2021). **Music For Me**. Computer Science and Engineering Senior Theses. 32 | https://scholarcommons.scu.edu/cseng_senior/203 33 | 34 | * Kostek, B. (Éd.). (2021). **Postępy badań w inżynierii dźwięku i obrazu**. Politechnika Wrocławska, 35 | Oficyna Wydawnicza. https://doi.org/10.37190/ido2021 36 | 37 | * Walczak, M., & Łukasik, E. (2021). **Rozproszony system generowania, edycji i transmisji dźwięku 38 | wykorzystujący interfejsy Web Audio API, WebRTC i Web MIDI API**. In Postępy badań w inżynierii 39 | dźwięku i obrazu: Nowe trendy i zastosowania technologii dźwięku wielokanałowego oraz badania 40 | jakości dźwięku (pp. 83–104). Oficyna Wydawnicza Politechniki Wrocławskiej. 41 | https://doi.org/10.37190/ido2021 42 | 43 | * Krawczuk, J. (2020). **Real-Time and Post-Hoc-Visualizations of Guitar Performances as a Support 44 | for Music Education**. https://doi.org/10/gkb622 45 | 46 | * Lundh Haaland, M. (2020). **The Player as a Conductor: Utilizing an Expressive Performance 47 | System to Create an Interactive Video Game Soundtrack** (Dissertation). Retrieved from 48 | http://urn.kb.se/resolve?urn=urn:nbn:se:kth:diva-281324 49 | 50 | * Bazin, T. & Hadjeres, G. (2019). **NONOTO: A Model-agnostic Web Interface for Interactive 51 | Music Composition by Inpainting**, presented at 10th International Conference on Computational 52 | Creativity, Charlotte, 2019. Retrieved from https://arxiv.org/abs/1907.10380 53 | 54 | * Smith, A. (2019). **Sonification: Turning the Yield Curve into Music**. FT.com. 55 | http://search.proquest.com/docview/2191715473/fulltext/3D4C05EAFC6A4AEEPQ/1?accountid=14719 56 | 57 | * Cárdenas, A. & Mauricio B. (2018). **Diseño y desarrollo de un prototipo para integración de 58 | Tecnología de Tracking 3d con Tecnología MIDI** [Doctoral dissertation, Pontificia Universidad 59 | Católica del Ecuador]. Retrieved from http://repositorio.puce.edu.ec/handle/22000/15838 60 | 61 | If you are using WEBMIDI.js in your research, I would love to know about it. To notify me, you can 62 | simply drop me a note on [Twitter](https://twitter.com/djipco). By the way, I'm open to artistic 63 | and/or academic collaborations. 64 | -------------------------------------------------------------------------------- /website/src/pages/sponsors/index.md: -------------------------------------------------------------------------------- 1 | # Sponsors 2 | 3 | :::tip Consider a sponsorship 4 | 5 | Please help me grow and nurture WEBMIDI.js by ❤️ [sponsoring](https://github.com/sponsors/djipco) 6 | the project on GitHub. 7 | 8 | ::: 9 | 10 | ## Sponsoring Organizations 11 | --- 12 | 13 | 16 | 17 | ## Sponsors 18 | --- 19 | 20 | 21 | 22 | 23 | Andreas Watterott 24 | 25 | 26 | 27 | 28 | ## About Software Development... 29 | 30 | As you surely know, proper software development takes time. This time is spent on coding new 31 | features but also on various other important tasks such as writing useful documentation, answering 32 | questions, reviewing issues, fixing bugs, writing tests, etc. 33 | 34 | While WEBMIDI.js started as a hobby project, it is quietly becoming the "go to" library for MIDI on 35 | the web. Hopefully, your contributions will make allow me to continue developing, maintaining and 36 | advocating for this project that I cherish. 37 | 38 | Thank you so much. 😀 39 | -------------------------------------------------------------------------------- /website/src/pages/tester/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Layout from "@theme/Layout"; 3 | import {Helmet} from "react-helmet"; 4 | 5 | 6 | 7 | // import useBaseUrl from "@docusaurus/useBaseUrl"; 8 | // import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 9 | 10 | // const piano = new Nexus.Piano("#target",{ 11 | // size: [500,125], 12 | // mode: "button", // "button", "toggle", or "impulse" 13 | // lowNote: 24, 14 | // highNote: 60 15 | // }) 16 | 17 | function Tester() { 18 | return ( 19 | 20 | 21 | 22 | {/*
*/} 23 |
24 | 25 |
33 |

34 | Edit pages/helloReact.js and save to reload. 35 |

36 |
37 | 38 |
39 | ); 40 | } 41 | 42 | export default Tester; 43 | -------------------------------------------------------------------------------- /website/src/theme/CodeBlock/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CodeBlock from '@theme-original/CodeBlock'; 3 | 4 | export default function CodeBlockWrapper(props) { 5 | return ( 6 | <> 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /website/src/theme/Footer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | import React from "react"; 8 | import {useThemeConfig} from "@docusaurus/theme-common"; 9 | import useBaseUrl from "@docusaurus/useBaseUrl"; 10 | import styles from "./styles.module.scss"; 11 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 12 | import {Helmet} from "react-helmet"; 13 | 14 | function Footer() { 15 | 16 | const {footer} = useThemeConfig(); 17 | // eslint-disable-next-line no-unused-vars 18 | const {sponsors = []} = useDocusaurusContext(); 19 | const {copyright,} = footer || {}; 20 | 21 | 22 | if (!footer) { 23 | return null; 24 | } 25 | 26 | const sponsorLogoPath = useBaseUrl("img/sponsors/edouard-montpetit-logo.svg"); 27 | 28 | return ( 29 |