Skip to content

WebMIDI In/Out extension #2022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 63 commits into
base: master
Choose a base branch
from
Open

WebMIDI In/Out extension #2022

wants to merge 63 commits into from

Conversation

lselden
Copy link

@lselden lselden commented Mar 10, 2025

This is an extension that adds MIDI support using the WebMIDI API. It tries to cover most use cases - it supports notes, CC, Pitch Bend, Program Change messages, etc.

Features

  • Device detection/enumeration
  • MIDI Input (When Note On/Off, When any midi event)
  • MIDI Output (similar syntax to the "Music" extension) - uses music "tempo" if set
  • Event reporter for thread context's current event
  • human-readable (and forgiving) string formatting of events for parsing/formatting

Todo

  • Translations
  • Add blocks documentation
  • Add string format documentation
  • Add "Getting Started" / browser compatibility documentation
  • Test on target devices (in particular mac / android)
  • Feedback on blocks - are any missing? should some be pruned? Better naming convention?
  • Image
  • (OPTIONAL) Separate icons for input vs output?

lselden added 3 commits March 5, 2025 12:12
Added midi extension for input/output of midi using the WebMidi API. Still pending documentation and peer-review
use "t=<time>" instead of "@<time>". Allow using "type=note pitch=60" instead of "note 60" if need to be verbose. Some bugfixes
@github-actions github-actions bot added the pr: new extension Pull requests that add a new extension label Mar 10, 2025
@lselden lselden mentioned this pull request Mar 10, 2025
@Brackets-Coder
Copy link
Contributor

Brackets-Coder commented Mar 11, 2025

At a glance, this looks much better than mine. You'll definitely need to add it to the list in extensions.json and probably make a gallery thumbnail too for it to be accepted though. You probably spent a lot more than four hours on it like I did. I might give it a full review (just for my opinion) when I have more time but I'm a bit busy right now. If you'd like, I can submit a PR or so to your fork that adds translations so you have one less thing to worry about (but again, I'm a bit busy right now, I'll have to get around to it when I can).

*/
function noteNameToMidiPitch(note, defaultOctave = 4) {
const parts =
/(?<pitch>[A-G])(?<flat>[b♭]+)?(?<sharp>[#♯]+)?_?(?<octave>-?\d+)?/i.exec(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be more human-readable if possible

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a regex with named groups to describe what's matched. added method comments to help describe what's valid. If anyone's interested in other implementations I used these for reference: strudel and tonaljs

function stringToMidi(text, opts = {}) {
if (typeof text !== "string") text = Cast.toString(text);
const fullRe =
/^\s*(?<type>[a-zA-Z]{2,})?\s*((?<pitch>[A-G][#b♯♭_]*-?\d?)|(?<data1>\b-?[0-9a-f]{1,5}\b))?\s*(?<data2>\b[0-9a-f]{1,3}\b)?\s*(?<keyvals>.+)\s*$/;
Copy link
Contributor

@Brackets-Coder Brackets-Coder Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't fun to try to understand... Also you should use Scratch.Cast instead of Cast

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cast here is a local const of Scratch.Cast. Switched to the verbose Scratch.Cast to avoid confusion in the future

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cast here is a local const of Scratch.Cast. Switched to the verbose Scratch.Cast to avoid confusion in the future

This can be changed back, using the const is fine, and is more readable

Comment on lines 2104 to 2105
// sanity check - don't let a note be more than 1 minute
// if anyone ever needs a longer note then they should manually write event string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?
I know it's a rare occurrence but as a musician (pianist), developer, and Scratch user you shouldn't generally limit the user's capabilities unless necessary. Notes technically can be a minute long and a user shouldn't have that length unexpectedly cut off...
Keep in mind this is just a quick look-over and I haven't fully analyzed it in depth yet

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear ya. I checked and the Scratch Music extension clamps to 100 beats max (100 seconds at 60bpm) - updated code accordingly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following what the music extension does isnt required but you can if you want

}

//#endregion
Scratch.extensions.register(new MidiExtension());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class name is a bit overgeneralized?
Also your code style is not exactly like mine but I guess it's just a matter of preference. seems a bit cluttered to me but I guess it's just a personal problem.

Other than that it looks fine

Copy link
Member

@yuri-kiss yuri-kiss Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension class doesnt have to be anything special, I for one just use extension as my class name in all my extensions, it doesnt change anything

@Brackets-Coder
Copy link
Contributor

Just a quick review, nothing in depth. Going over best practices for TW extensions in particular, not necessarily anything related to the functionality of the code.

dependabot bot and others added 7 commits March 17, 2025 00:27
…Warp#2038)

Bumps [@turbowarp/types](https://github.com/TurboWarp/types-tw) from
`4b59b95` to `b89be2e`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/TurboWarp/types-tw/commit/b89be2e3ce64fa84774540dff026a42dd915ee3c"><code>b89be2e</code></a>
TW: add pause events</li>
<li>See full diff in <a
href="https://github.com/TurboWarp/types-tw/compare/4b59b95e3b02560ff626a36a871869b89dbe9a48...b89be2e3ce64fa84774540dff026a42dd915ee3c">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [image-size](https://github.com/image-size/image-size) from 2.0.0
to 2.0.1.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/image-size/image-size/commit/a5750c51dc1d81756521521f5821da732038d8c4"><code>a5750c5</code></a>
2.0.1</li>
<li><a
href="https://github.com/image-size/image-size/commit/6c3cb7188aa244c84581e699d80474ddc581ec2c"><code>6c3cb71</code></a>
fix the bin script</li>
<li><a
href="https://github.com/image-size/image-size/commit/9cfcb4f395928e39dc30f0ba71e698af0cb5b863"><code>9cfcb4f</code></a>
Add a sync usage example</li>
<li>See full diff in <a
href="https://github.com/image-size/image-size/compare/v2.0.0...v2.0.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=image-size&package-manager=npm_and_yarn&previous-version=2.0.0&new-version=2.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Existing code uses "dur" for "duration", and "pitch" for "noteName"
midi precision doesn't go under 2-5 milliseconds, so forcing numbers to be readable 1ms precision
dependabot bot and others added 30 commits April 7, 2025 22:47
Bumps [image-size](https://github.com/image-size/image-size) from 2.0.1
to 2.0.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/image-size/image-size/releases">image-size's
releases</a>.</em></p>
<blockquote>
<h2>v2.0.2</h2>
<h2>Fixes</h2>
<ul>
<li>fix potential Denial of Service via specially crafted payloads in <a
href="https://redirect.github.com/image-size/image-size/pull/436">image-size/image-size#436</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/image-size/image-size/compare/v2.0.1...v2.0.2">https://github.com/image-size/image-size/compare/v2.0.1...v2.0.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/image-size/image-size/commit/032c3347b86f09a2e16449e17537cf5e1009520c"><code>032c334</code></a>
2.0.2</li>
<li><a
href="https://github.com/image-size/image-size/commit/8994131c7c3ee8da1699e04700c95e0e683a0c68"><code>8994131</code></a>
fix potential Denial of Service via specially crafted payloads (<a
href="https://redirect.github.com/image-size/image-size/issues/436">#436</a>)</li>
<li><a
href="https://github.com/image-size/image-size/commit/e62c61f1c58126310e9d7faac1b2e7ad1de9ea82"><code>e62c61f</code></a>
delete the part about partial downloading</li>
<li>See full diff in <a
href="https://github.com/image-size/image-size/compare/v2.0.1...v2.0.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=image-size&package-manager=npm_and_yarn&previous-version=2.0.1&new-version=2.0.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This pull request was made by a robot.

Co-authored-by: DangoCat[bot] <[email protected]>
Added midi extension for input/output of midi using the WebMidi API. Still pending documentation and peer-review
use "t=<time>" instead of "@<time>". Allow using "type=note pitch=60" instead of "note 60" if need to be verbose. Some bugfixes
Existing code uses "dur" for "duration", and "pitch" for "noteName"
midi precision doesn't go under 2-5 milliseconds, so forcing numbers to be readable 1ms precision
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: new extension Pull requests that add a new extension
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants