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
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
ac6f021
Add midi extension
lselden Mar 5, 2025
0be617e
updated midi.js - tweak string/parsing/formatting
lselden Mar 7, 2025
767e8db
midi - cleanup comments and types
lselden Mar 10, 2025
2981a0f
Update midi.js
lselden Mar 10, 2025
3cbb48a
midi.js - linting and ensure all inputs use Scratch.Cast
lselden Mar 10, 2025
4848f42
Update midi.js
lselden Mar 10, 2025
488e76b
midi - add "whenDeviceEvent" hat for detecting device connect/disconnect
lselden Mar 11, 2025
065976d
add midi to extensions list
lselden Mar 11, 2025
1b8ec5b
bugfix in string parsing and minor formatting
lselden Mar 11, 2025
9f8dfea
midi - change params for sendOutputEvent
lselden Mar 11, 2025
17a9e6b
midi - treat "pos" and "beats" as beats unit, "time" and "dur" as sec…
lselden Mar 11, 2025
ccda886
allow string event input into "play note for beats" command
lselden Mar 11, 2025
eb5b977
reformatting as per comments
lselden Mar 13, 2025
a12a863
ensure device index starts at 1
lselden Mar 13, 2025
13ac2b4
Merge remote-tracking branch 'upstream/master' into ext_midi
lselden Mar 13, 2025
6d0a1ca
build(deps): bump @turbowarp/types from `4b59b95` to `b89be2e` (#2038)
dependabot[bot] Mar 17, 2025
53449c7
build(deps): bump image-size from 2.0.0 to 2.0.1 (#2039)
dependabot[bot] Mar 17, 2025
3cc720d
Merge in MIDI Parser extension by CHCAT1320
lselden Mar 18, 2025
3fdf79a
minor refactor - rearrange code
lselden Mar 18, 2025
74001d8
change parseMidiDataUrl to use same keys
lselden Mar 18, 2025
3209710
round midi file times to 1 millisecond
lselden Mar 18, 2025
8b48acc
fix json to event formatting
lselden Mar 18, 2025
1c4dfb5
fix linting
lselden Mar 18, 2025
5263530
cloudlink: remove broken servers, put major verison in name (#2043)
GarboMuffin Mar 22, 2025
50e87cc
Lily/AllMenus: mark as scratch-compatible (#2045)
GarboMuffin Mar 26, 2025
40cc01d
[Automated] Update translations 2025-03-26 (#2049)
DangoCat Mar 26, 2025
7147add
build(deps-dev): bump eslint from 9.22.0 to 9.23.0 (#2046)
dependabot[bot] Mar 28, 2025
35bf8c9
cloudlink: more fixes (#2044)
GarboMuffin Mar 31, 2025
f9969ff
Skyhigh173/json: fix undefined issues (#2058)
yuri-kiss Mar 31, 2025
37cb2c4
build(deps): bump @turbowarp/types from `b89be2e` to `79ab4cc` (#2060)
dependabot[bot] Mar 31, 2025
1ae69ed
Documentation enhancement: support markdown alerts (#2054)
Brackets-Coder Mar 31, 2025
4b1ec35
SharkPool/Camera: Clones should inherit camera from parent, add epsil…
SharkPool-SP Apr 1, 2025
01ca273
build(deps-dev): bump eslint from 9.23.0 to 9.24.0 (#2069)
dependabot[bot] Apr 8, 2025
6085feb
build(deps): bump image-size from 2.0.1 to 2.0.2 (#2068)
dependabot[bot] Apr 8, 2025
5703354
[Automated] Update translations 2025-04-12 (#2078)
DangoCat Apr 12, 2025
afb763c
Add midi extension
lselden Mar 5, 2025
7998725
updated midi.js - tweak string/parsing/formatting
lselden Mar 7, 2025
2ccd655
midi - cleanup comments and types
lselden Mar 10, 2025
1a285e9
Update midi.js
lselden Mar 10, 2025
7085252
midi.js - linting and ensure all inputs use Scratch.Cast
lselden Mar 10, 2025
9ff419f
Update midi.js
lselden Mar 10, 2025
cc44e9b
midi - add "whenDeviceEvent" hat for detecting device connect/disconnect
lselden Mar 11, 2025
e6e553c
add midi to extensions list
lselden Mar 11, 2025
88184f0
bugfix in string parsing and minor formatting
lselden Mar 11, 2025
6a0db5c
midi - change params for sendOutputEvent
lselden Mar 11, 2025
389bedd
midi - treat "pos" and "beats" as beats unit, "time" and "dur" as sec…
lselden Mar 11, 2025
a21e702
allow string event input into "play note for beats" command
lselden Mar 11, 2025
cbe780d
reformatting as per comments
lselden Mar 13, 2025
ef717aa
ensure device index starts at 1
lselden Mar 13, 2025
24d98e0
Merge in MIDI Parser extension by CHCAT1320
lselden Mar 18, 2025
1bc7e6a
minor refactor - rearrange code
lselden Mar 18, 2025
b8f694e
change parseMidiDataUrl to use same keys
lselden Mar 18, 2025
958fc9a
round midi file times to 1 millisecond
lselden Mar 18, 2025
9869c24
fix json to event formatting
lselden Mar 18, 2025
fd1c19d
fix linting
lselden Mar 18, 2025
40810df
Merge branch 'ext_midi' of https://github.com/lselden/extensions into…
lselden Apr 17, 2025
f87bc9b
allow formatting pitch as sharp/flat or number
lselden Apr 18, 2025
c98d827
always include node/noteon type in string output and catch some edge …
lselden Apr 18, 2025
b029769
add generic key=value parser/formatter
lselden Apr 18, 2025
29de046
revamp midi file parsing to use Scratch.fetch, include meta events li…
lselden Apr 18, 2025
c012e35
add license credits
lselden Apr 18, 2025
1a7ffd8
add midi extension to extensions.json
lselden Apr 18, 2025
774ba76
change "noteOn" to "note", add Duration/Instrument event props, bugfixes
lselden Apr 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions development/docs-template.ejs
Original file line number Diff line number Diff line change
@@ -141,6 +141,44 @@
margin: 1rem 0;
font-size: small;
}

/* General Alert Styles */
.alert {
padding-left: 15px;
line-height: 1;
}

/* Border colors for specific alerts */
.alert.alert-note { border-left: .25em solid #1f6feb; }
.alert.alert-tip { border-left: .25em solid #238636; }
.alert.alert-important { border-left: .25em solid #8957e5; }
.alert.alert-warning { border-left: .25em solid #9e6a03; }
.alert.alert-caution { border-left: .25em solid #da3633; }

/* Shared Styling for Alert Text */
.alert p:first-child {
padding-top: 10px;
font-weight: 500;
font-size: 14px;
display: flex;
}

.alert p:last-child {
padding-bottom: 10px;
}

/* Specific Color Overrides for Each Alert */
.alert.alert-note > p:first-child { color: #4493f8; }
.alert.alert-tip > p:first-child { color: #3fb950; }
.alert.alert-important > p:first-child { color: #ab7df8; }
.alert.alert-warning > p:first-child { color: #d29922; }
.alert.alert-caution > p:first-child { color: #f85149; }

/* Icon Spacing */
div.alert svg {
margin-right: 8px;
}

</style>
</head>
<body>
77 changes: 77 additions & 0 deletions development/render-docs.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,15 @@ const path = require("path");
const MarkdownIt = require("markdown-it");
const renderTemplate = require("./render-template");

// From GitHub's markdown alert SVG icons: https://github.com/primer/octicons
const blockIcons = {
note: `<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true" fill="#4493f8"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>`,
tip: `<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true" fill="#3fb950"><path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"></path></svg>`,
important: `<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true" fill="#ab7df8"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>`,
warning: `<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true" fill="#d29922"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>`,
caution: `<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true" fill="#f85149"><path d="M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>`,
};

const md = new MarkdownIt({
html: true,
linkify: true,
@@ -25,6 +34,74 @@ md.renderer.rules.fence = function (tokens, idx, options, env, self) {
)}">${md.utils.escapeHtml(token.content)}</pre>`;
};

md.block.ruler.before(
"blockquote",
"custom_blockquote",
function (state, startLine, endLine, silent) {
const marker = state.src.slice(
state.bMarks[startLine],
state.eMarks[startLine]
);

const match = marker.match(/^\s*>\s*\[!([A-Z]+)]\s*(.*)/);
if (!match) return false;

const type = match[1].toLowerCase();
if (!blockIcons[type]) {
throw new TypeError(`Invalid alert type: ${match[1]}`);
}
const icon = blockIcons[type];

if (silent) return true;

// Open alert div
const tokenOpen = state.push("html_block", "", 0);
tokenOpen.content = `<div class="alert alert-${type}">`;

// Render title with Markdown support
state.push("paragraph_open", "p", 1);
const tokenTitle = state.push("inline", "", 0);
tokenTitle.content = `${icon} **${type.charAt(0).toUpperCase() + type.slice(1)}**`;
tokenTitle.children = [];
state.push("paragraph_close", "p", -1);

// Process all lines inside the blockquote
let nextLine = startLine + 1;
while (nextLine < endLine) {
const nextMarker = state.src.slice(
state.bMarks[nextLine],
state.eMarks[nextLine]
);

if (/^\s*>/.test(nextMarker)) {
const lineContent = nextMarker.replace(/^\s*>?\s*/, "").trim();

if (lineContent === "") {
state.push("paragraph_open", "p", 1);
state.push("paragraph_close", "p", -1);
} else {
state.push("paragraph_open", "p", 1);
const token = state.push("inline", "", 0);
token.content = lineContent;
token.children = [];
state.push("paragraph_close", "p", -1);
}

nextLine++;
} else {
break;
}
}

// Close alert div
const tokenClose = state.push("html_block", "", 0);
tokenClose.content = `</div>`;

state.line = nextLine;
return true;
}
);

/**
* @param {string} markdownSource Markdown code
* @param {string} slug Path slug like 'TestMuffin/fetch'
3 changes: 2 additions & 1 deletion docs/CST1229/zip.md
Original file line number Diff line number Diff line change
@@ -45,7 +45,8 @@ The type can be one of the following:

The name is used for dealing with multiple archives at time; it can be any non-empty string and does *not* have to be the archive's filename.

If the file is not of zip format (like RAR or 7z) or is password-protected, it won't be opened. Make sure to check if it loaded successfully with the `error opening archive?` block.
> [!TIP]
> If the file is not of zip format (like RAR or 7z) or is password-protected, it won't be opened. Make sure to check if it loaded successfully with the `error opening archive?` block.

---

11 changes: 6 additions & 5 deletions docs/CubesterYT/WindowControls.md
Original file line number Diff line number Diff line change
@@ -2,11 +2,12 @@

This extension provides a set of blocks that gives you greater control over the Program Window.

**Note: Most of these blocks only work in Electron, Pop Ups/Web Apps containing HTML packaged projects, and normal Web Apps.**

Examples include, but are not limited to: **TurboWarp Desktop App, TurboWarp Web App, Pop Up/Web App windows that contain the HTML packaged project, and plain Electron.**

**Blocks that still work outside of these will be specified.**
> [!NOTE]
> **Most of these blocks only work in Electron, Pop Ups/Web Apps containing HTML packaged projects, and normal Web Apps.**
>
> Examples include, but are not limited to: **TurboWarp Desktop App, TurboWarp Web App, Pop Up/Web App windows that contain the HTML packaged project, and plain Electron.**
>
> **Blocks that still work outside of these will be specified.**

## Move Window Block

3 changes: 2 additions & 1 deletion docs/DNin/wake-lock.md
Original file line number Diff line number Diff line change
@@ -19,7 +19,8 @@ Wake lock will also be released automatically when the project stops or is resta

## Browser support

Not all browsers support wake lock (notably, Firefox does not). In these browsers requesting wake lock will not do anything.
> [!WARNING]
> Not all browsers support wake lock **(notably, Firefox does not)**. In these browsers requesting wake lock will not do anything.

## Note

7 changes: 4 additions & 3 deletions docs/Lily/Skins.md
Original file line number Diff line number Diff line change
@@ -26,9 +26,10 @@ load skin from (costume 1 v) as [my skin] :: #6b56ff
```
The second way is by loading a skin from a costume.

It's important to note that this block will require the Advanced Option "Remove raw asset data after loading to save RAM" to be disabled in the packager in order for this block to work correctly in a packaged environment. **You do not need to do this within the editor.**

If you intend to package your project, we don't encourage using this block for that reason. **None of the other blocks in this extension require this option to be disabled.**
> [!IMPORTANT]
> It's important to note that this block will require the Advanced Option "Remove raw asset data after loading to save RAM" to be disabled in the packager in order for this block to work correctly in a packaged environment. **You do not need to do this within the editor.**
>
> If you intend to package your project, we don't encourage using this block for that reason. **None of the other blocks in this extension require this option to be disabled.**

---

6 changes: 4 additions & 2 deletions docs/TheShovel/ShovelUtils.md
Original file line number Diff line number Diff line change
@@ -2,15 +2,17 @@

Shovel Utils is an extension focused mostly on injecting and modifying sprites and assets inside the project, as well as several other functions.

**Disclaimer: Modifying and importing assets can be dangerous, and has the potential to corrupt your project. Be careful!**
> [!CAUTION]
> **Modifying and importing assets can be dangerous, and has the potential to corrupt your project. Be careful!**

## Importing Assets

Shovel Utils offers an easy way to import several types of assets, including sprites, costumes, sounds, extensions, and even full projects.

---

**This goes for all blocks that fetch from a link: If you're experiences errors and are not able to import an asset from a link, check your console! You may be running into a CORS error. To resolve this, use a proxy like [corsproxy.io](https://corsproxy.io).**
> [!TIP]
> **This goes for all blocks that fetch from a link: If you're experiences errors and are not able to import an asset from a link, check your console! You may be running into a CORS error. To resolve this, use a proxy like [corsproxy.io](https://corsproxy.io).**

```scratch
import sprite from [Link or data uri here]
14 changes: 10 additions & 4 deletions docs/Xeltalliv/simple3D.md
Original file line number Diff line number Diff line change
@@ -42,7 +42,9 @@ So in short, **this extension does not have any kind of scenes, objects, cameras
3D models consist of vertices which together form primitives (points, lines, triangles). Each vertex has either 2D (XY) or 3D (XYZ) location described with 2 or 3 numbers respectively. Before drawing the mesh, you would usually set up transformation, which tells how to take those initial locations and transform them to correct location on your 2D screen. The typical way to do it, is to chain multiple simple transformations together. Simple transformations can be translation (offsetting), rotation, scaling, mirroring, skewing, etc.

## Drawing things <a name="simple-drawing"></a>
**Note:** For a more complete tutorial, see [here](https://xeltalliv.github.io/simple3d-extension/examples/) (external link).

> [!TIP]
> For a more complete tutorial, see [here](https://xeltalliv.github.io/simple3d-extension/examples/) (external link).

For now let's not worry about transformations and just draw something as is.
First step would be to clear the screen:
@@ -543,7 +545,8 @@ set [my mesh] from [off v] [list v] :: sensing
```
Decodes a 3D model file and uploads it into a mesh. Block continues instantly, but the model loading is performed in a separate thread, and it finishes with a delay. Currently, only one thread is used, so everything is queued and processed one by one. In the future, multiple threads might be used.

**Note: This block is designed as a more of a shortcut for quick testing, rather than the main way of loading 3D models. For anything more complex make your own 3D model parser.**
> [!IMPORTANT]
> This block is designed as a more of a shortcut for quick testing, rather than the main way of loading 3D models. For anything more complex, make your own 3D model parser.

File formats:
- [obj](https://en.wikipedia.org/wiki/Wavefront_.obj_file) is a very common and well known 3D model file format. It supports UV texture coordinates, materials with colors and textures. However it does not have a standartized way to do vertex colors. This block implements a non-standart but widely supported way to represent vertex colors as 4th - 7th elements of `v`. The OBJ and MTL specification describes a lot of features, only some of which are currently (or even can be) supported by this importer. In particular, there is currently no way to import models which use multiple textures as this extensions only supports 1 texture per mesh. Normals and anything lighting related isn't and can't be supported. **In case both OBJ and MTL files need to be imported, combine them all into 1 list sequentially, first all of the MTL files and then the OBJ file.**
@@ -714,8 +717,11 @@ This block may cause stutter when drawing something for the first time, as it wi
```
Creates texture from image at specified URL.
Will show a prompt if URL is not approved.
If an image fails to load, you can usually open browser console and see what the error is. (F12 or Ctrl+Shift+I)
**Note that websites cannot access any data from any other websites unless those other sites explicetly allow it. The correct term for it is CORS (Cross Origin Resource Sharing). You can use some CORS proxy to bypass it.**
If an image fails to load, you can usually open browser console and see what the error is (F12 or Ctrl+Shift+I).

> [!WARNING]
> Websites cannot access any data from any other websites unless those other sites explicetly allow it. The correct term for it is CORS (Cross Origin Resource Sharing). You can use some CORS proxy to bypass it.

🐢 Texture gets loaded with a delay.

---
3 changes: 2 additions & 1 deletion docs/ar.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,8 @@
- [ARCore](https://play.google.com/store/apps/details?id=com.google.ar.core) (if on Android)
- browser with [WebXR API and immersive-ar](https://immersive-web.github.io/webxr-samples/report/) session type support

At the moment of writing, only Chromium-based browsers on Android support immersive-ar session type.
> [!WARNING]
> At the moment of writing, only Chromium-based browsers on Android support immersive-ar session type.

## Other information

5 changes: 4 additions & 1 deletion docs/box2d.md
Original file line number Diff line number Diff line change
@@ -53,7 +53,10 @@ Make physics apply to this sprite. It can also collide with other sprites that h
- `this circle`: Enable physics for the current sprite or clone as if it were shaped like a circle.
- `all sprites`: Enable physics for all sprites.

Precision mode will make the sprite work extra hard to make sure it doesn't overlap with anything. Note that this can decrease performance and even cause the project to get stuck, so use with care.
Precision mode will make the sprite work extra hard to make sure it doesn't overlap with anything.

> [!CAUTION]
> Precision mode should be used with care as it can decrease performance and even cause the project to get stuck.

---

3 changes: 2 additions & 1 deletion docs/godslayerakp/ws.md
Original file line number Diff line number Diff line change
@@ -15,7 +15,8 @@ The URL should start with `ws://` or `wss://`. For security reasons, `ws://` URL

Something simple to play with is the echo server: `wss://echoserver.redman13.repl.co`. Any message you send to it, it'll send right back to you.

Note that connections are **per sprite**. Each sprite (or clone) can connect to one server at a time. Multiple sprites can connect to the same or different servers as much as your computer allows, but note those will all be separate connections.
> [!NOTE]
> Connections are **per sprite**. Each sprite (or clone) can connect to one server at a time. Multiple sprites can connect to the same or different servers as much as your computer allows, but note those will all be separate connections.

---

7 changes: 4 additions & 3 deletions docs/steamworks.md
Original file line number Diff line number Diff line change
@@ -27,9 +27,10 @@ You can run the packaged executable directly as usual; you don't need to start t

## Security considerations

Using the Steamworks extension will not prevent people from pirating your game.

The Steamworks extension is also inherently client-side, so a cheater could manipulate all of the Steamworks blocks to return whatever they want. You shouldn't use them for things that are security critical.
> [!CAUTION]
> **Using the Steamworks extension will not prevent people from pirating your game.**
>
> The Steamworks extension is also inherently client-side, so a cheater could manipulate all of the Steamworks blocks to return whatever they want. You shouldn't use them for things that are security critical.

## Demo game <a name="demo-game"></a>

1 change: 1 addition & 0 deletions extensions/Lily/AllMenus.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
// Description: Special category with every menu from every Scratch category and extensions.
// By: LilyMakesThings <https://scratch.mit.edu/users/LilyMakesThings/>
// License: MIT AND LGPL-3.0
// Scratch-compatible: true

(function (Scratch) {
"use strict";
39 changes: 30 additions & 9 deletions extensions/SharkPool/Camera.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
// By: SharkPool
// License: MIT

// Version V.1.0.07
// Version V.1.0.08

(function (Scratch) {
"use strict";
@@ -53,6 +53,10 @@
}

// camera utils
const radianConstant = Math.PI / 180;
const epsilon = 1e-12;
const applyEpsilon = (value) => (Math.abs(value) < epsilon ? 0 : value);

function setupState(drawable) {
drawable[cameraSymbol] = {
name: "default",
@@ -65,23 +69,26 @@

function translatePosition(xy, invert, camData) {
if (invert) {
const invRads = (camData.ogDir / 180) * Math.PI;
const invRads = camData.ogDir * radianConstant;
const invSin = Math.sin(invRads),
invCos = Math.cos(invRads);
const scaledX = xy[0] / camData.ogSZ;
const scaledY = xy[1] / camData.ogSZ;
const invOffX = scaledX * invCos + scaledY * invSin;
const invOffY = -scaledX * invSin + scaledY * invCos;
return [invOffX - camData.ogXY[0], invOffY - camData.ogXY[1]];
return [
applyEpsilon(invOffX - camData.ogXY[0]),
applyEpsilon(invOffY - camData.ogXY[1]),
];
} else {
const rads = (camData.dir / 180) * Math.PI;
const rads = camData.dir * radianConstant;
const sin = Math.sin(rads),
cos = Math.cos(rads);
const offX = xy[0] + camData.xy[0];
const offY = xy[1] + camData.xy[1];
return [
camData.zoom * (offX * cos - offY * sin),
camData.zoom * (offX * sin + offY * cos),
applyEpsilon(camData.zoom * (offX * cos - offY * sin)),
applyEpsilon(camData.zoom * (offX * sin + offY * cos)),
];
}
}
@@ -252,6 +259,20 @@
this.skin?.emitWasAltered();
};

// Clones should inherit the parents camera
const ogInitDrawable = vm.exports.RenderedTarget.prototype.initDrawable;
vm.exports.RenderedTarget.prototype.initDrawable = function (layerGroup) {
ogInitDrawable.call(this, layerGroup);
if (this.isOriginal) return;

const parentSprite = this.sprite.clones[0]; // clone[0] is always the original
const parentDrawable = render._allDrawables[parentSprite.drawableID];
const name = parentDrawable[cameraSymbol]?.name ?? "default";

const drawable = render._allDrawables[this.drawableID];
bindDrawable(drawable, name);
};

// Turbowarp Extension Storage
runtime.on("PROJECT_LOADED", () => {
const stored = runtime.extensionStorage["SPcamera"];
@@ -700,10 +721,10 @@
}

translateAngledMovement(xy, steps, direction) {
const radians = direction * (Math.PI / 180);
const radians = direction * radianConstant;
return [
xy[0] + steps * Math.cos(radians),
xy[1] + steps * Math.sin(radians),
applyEpsilon(xy[0] + steps * Math.cos(radians)),
applyEpsilon(xy[1] + steps * Math.sin(radians)),
];
}

Loading
Loading