Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 17 additions & 1 deletion src/Workspace.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import * as engine from "./index.js";
import * as stateMachine from "@mixery/state-machine";
import { LiveNote } from "./midi/Note.js";
import { Addon } from "./addons/Addon.js";
import { ExternalAddonLoader } from "./addons/ExternalAddonLoader.js";

export class Workspace {
addonRequires = new Map<string, any>();
addons: Addon[] = [];

public constructor(
public readonly audioContext: BaseAudioContext
) {}
) {
this.addonRequires.set("@mixery/engine", engine);
this.addonRequires.set("@mixery/state-machine", stateMachine);
}

private _lastNoteId = 0;

public autoNoteId(note: LiveNote) {
note.id = this._lastNoteId++;
}

async loadAddonFromUrl(url: string) {
let addon = await ExternalAddonLoader.loadFromUrl(this, url);
this.addons.push(addon);
return addon;
}
}
5 changes: 5 additions & 0 deletions src/addons/Addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export interface AddonInfo {
description?: string;
}

export interface AddonMetadata extends AddonInfo {
id: string;
main: string;
}

export type AddonFactory = (addon: Addon) => any;

export class Addon {
Expand Down
46 changes: 46 additions & 0 deletions src/addons/ExternalAddonLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Addon, AddonMetadata } from "../addons/Addon.js";
import { Workspace } from "../Workspace.js";

export class ExternalAddonLoader {
metadata: AddonMetadata;
addon: Addon;

constructor(
public readonly workspace: Workspace
) {}

static async loadFromUrl(workspace: Workspace, url: string) {
// Security risk: Loading addons from URLs allows scripts to have access to your
// data (projects, presets, etc) that is stored inside Mixery.

if (url.endsWith("/")) url = url.substring(0, url.length - 1);
const metadata: AddonMetadata = await (await fetch(`${url}/addon.metadata.json`)).json();
const script = await (await fetch(`${url}/${metadata.main}`)).text();
const loader = new ExternalAddonLoader(workspace);

loader.metadata = metadata;
loader.createAddonInstance();
loader.runScript(script);
return loader.addon;
}

require(name: string) {
let module = this.workspace.addonRequires.get(name);
if (!module) throw new Error(`Can't find module ${name} (Have you registered your module using Workspace#addonRequires.set() yet?).`);
return module;
}

createAddonInstance() {
this.addon = new Addon(this.metadata.id, this.metadata);
}

runScript(script: string) {
let func = new Function("require", "id", "metadata", "addon", script);
func(
(name: string) => this.require(name),
this.metadata.id,
this.metadata,
this.addon
);
}
}
5 changes: 4 additions & 1 deletion src/test.workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,7 @@ function render() {
ctx.stroke();
ctx.closePath();
}
window.requestAnimationFrame(render);
window.requestAnimationFrame(render);

// Expose some methods
globalThis.loadAddonFromUrl = (url: string) => workspace.loadAddonFromUrl(url);