@aethervtt/shard-audio-editor 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/dom.d.ts +6 -0
  4. package/dist/dom.js +11 -0
  5. package/dist/drawing.d.ts +2 -0
  6. package/dist/drawing.js +12 -0
  7. package/dist/icons.d.ts +1 -0
  8. package/dist/icons.js +29 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/memory.d.ts +13 -0
  12. package/dist/memory.js +93 -0
  13. package/dist/methods.d.ts +4 -0
  14. package/dist/methods.js +79 -0
  15. package/dist/model.d.ts +61 -0
  16. package/dist/model.js +560 -0
  17. package/dist/panels/common.d.ts +33 -0
  18. package/dist/panels/common.js +1556 -0
  19. package/dist/panels/diagnostics.d.ts +2 -0
  20. package/dist/panels/diagnostics.js +7 -0
  21. package/dist/panels/event.d.ts +2 -0
  22. package/dist/panels/event.js +7 -0
  23. package/dist/panels/library.d.ts +2 -0
  24. package/dist/panels/library.js +7 -0
  25. package/dist/panels/mixer.d.ts +2 -0
  26. package/dist/panels/mixer.js +7 -0
  27. package/dist/panels/music.d.ts +2 -0
  28. package/dist/panels/music.js +7 -0
  29. package/dist/panels/parameters.d.ts +2 -0
  30. package/dist/panels/parameters.js +7 -0
  31. package/dist/preview-export.d.ts +68 -0
  32. package/dist/preview-export.js +143 -0
  33. package/dist/preview-render.d.ts +2 -0
  34. package/dist/preview-render.js +6 -0
  35. package/dist/preview-runtime.d.ts +114 -0
  36. package/dist/preview-runtime.js +414 -0
  37. package/dist/preview.d.ts +25 -0
  38. package/dist/preview.js +296 -0
  39. package/dist/project.d.ts +15 -0
  40. package/dist/project.js +332 -0
  41. package/dist/schema.d.ts +22 -0
  42. package/dist/schema.js +155 -0
  43. package/dist/styles.d.ts +2 -0
  44. package/dist/styles.js +192 -0
  45. package/dist/types.d.ts +113 -0
  46. package/dist/types.js +0 -0
  47. package/dist/waveform.d.ts +16 -0
  48. package/dist/waveform.js +154 -0
  49. package/package.json +83 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick Dias
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # Temporary Holding Version
1
+ # @aethervtt/shard-audio-editor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Part of [Shard](https://github.com/aethervtt/shard), a WebGPU-first 2D/3D game engine in TypeScript. See the repository for documentation.
package/dist/dom.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { type AudioEditorMountOptions, type AudioEditorView } from './panels/common.js';
2
+ export { drawAudioWaveform } from './drawing.js';
3
+ export type { AudioEditorMountOptions, AudioEditorPanel, AudioEditorSceneSelection, AudioEditorView, } from './panels/common.js';
4
+ export { audioEditorStyles } from './styles.js';
5
+ /** Mount an owned, scoped view. The caller retains ownership of its session and host container. */
6
+ export declare function mountAudioEditor(container: HTMLElement, options: AudioEditorMountOptions): AudioEditorView;
package/dist/dom.js ADDED
@@ -0,0 +1,11 @@
1
+ import { mountPanel } from "./panels/common.js";
2
+ import { drawAudioWaveform } from "./drawing.js";
3
+ import { audioEditorStyles } from "./styles.js";
4
+ function mountAudioEditor(container, options) {
5
+ return mountPanel(container, options);
6
+ }
7
+ export {
8
+ audioEditorStyles,
9
+ drawAudioWaveform,
10
+ mountAudioEditor
11
+ };
@@ -0,0 +1,2 @@
1
+ /** Draw a cached envelope. No decoding, timers or canvas ownership is hidden in the renderer. */
2
+ export declare function drawAudioWaveform(context: CanvasRenderingContext2D, peaks: Float32Array, width: number, height: number, color: string): void;
@@ -0,0 +1,12 @@
1
+ function drawAudioWaveform(context, peaks, width, height, color) {
2
+ context.clearRect(0, 0, width, height);
3
+ context.fillStyle = color;
4
+ const scale = width / peaks.length;
5
+ for (let i = 0; i < peaks.length; i++) {
6
+ const amplitude = Math.min(1, Math.abs(peaks[i])) * height * 0.94;
7
+ context.fillRect(i * scale, (height - amplitude) / 2, Math.max(1, scale), amplitude);
8
+ }
9
+ }
10
+ export {
11
+ drawAudioWaveform
12
+ };
@@ -0,0 +1 @@
1
+ export declare function audioIcon(document: Document, name: string): SVGSVGElement;
package/dist/icons.js ADDED
@@ -0,0 +1,29 @@
1
+ const paths = {
2
+ library: "M3 5h6l2 2h10v12H3z M3 9h18",
3
+ event: "M3 12h3l2-7 4 14 3-10 2 3h4",
4
+ mixer: "M5 3v7m0 4v7m7-18v13m0 4v1m7-18v3m0 4v11 M2 10h6v4H2z M9 16h6v4H9z M16 6h6v4h-6z",
5
+ music: "M9 18V5l11-2v13 M9 8l11-2 M9 18c0 4-7 4-7 1s7-4 7-1 M20 16c0 4-7 4-7 1s7-4 7-1",
6
+ parameters: "M3 7h7m4 0h7M3 17h3m4 0h11 M10 4v6m4-6v6M6 14v6m4-6v6",
7
+ diagnostics: "M3 3v18h18 M7 15l4-5 4 3 5-8",
8
+ undo: "M9 5 4 10l5 5 M4 10h10a6 6 0 0 1 0 12",
9
+ redo: "m15 5 5 5-5 5 M20 10H10a6 6 0 0 0 0 12",
10
+ save: "M5 3h12l4 4v14H3V3z M7 3v6h10V3 M7 21v-8h10v8",
11
+ play: "m8 4 12 8-12 8z",
12
+ stop: "M6 6h12v12H6z",
13
+ clip: "M3 10v4m4-8v12m5-15v18m5-15v12m4-8v4",
14
+ snapshot: "M4 5h16v14H4z M8 9h8m-8 4h5"
15
+ };
16
+ function audioIcon(document, name) {
17
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
18
+ svg.setAttribute("viewBox", "0 0 24 24");
19
+ svg.setAttribute("aria-hidden", "true");
20
+ svg.setAttribute("focusable", "false");
21
+ svg.classList.add("icon");
22
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
23
+ path.setAttribute("d", paths[name] ?? paths.event);
24
+ svg.append(path);
25
+ return svg;
26
+ }
27
+ export {
28
+ audioIcon
29
+ };
@@ -0,0 +1,2 @@
1
+ export * from './methods.js';
2
+ export * from './model.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./methods.js";
2
+ export * from "./model.js";
@@ -0,0 +1,13 @@
1
+ import type { AudioAssetCatalog, AudioAssetContent, AudioAssetEntry, AudioDocument, AudioDocumentStore } from './types.js';
2
+ /** Reference adapter for static hosts. No filesystem, fetch fallback or ambient browser storage. */
3
+ export declare function createMemoryAudioHost(options?: {
4
+ scope?: string;
5
+ documents?: readonly AudioDocument[];
6
+ assets?: readonly (AudioAssetEntry & {
7
+ content: AudioAssetContent;
8
+ })[];
9
+ readOnly?: boolean;
10
+ }): {
11
+ documents: AudioDocumentStore;
12
+ assets: AudioAssetCatalog;
13
+ };
package/dist/memory.js ADDED
@@ -0,0 +1,93 @@
1
+ import { clone, editorError } from "./schema.js";
2
+ function createMemoryAudioHost(options = {}) {
3
+ const scope = options.scope ?? "memory";
4
+ const records = new Map(options.documents?.map((doc) => [doc.id, clone(doc)]));
5
+ const clips = new Map(options.assets?.map((asset) => [asset.id, asset]));
6
+ const listeners = /* @__PURE__ */ new Set();
7
+ const revisionScope = crypto.randomUUID();
8
+ let revision = 0;
9
+ const documents = {
10
+ scope,
11
+ atomic: true,
12
+ async read(id, signal) {
13
+ signal?.throwIfAborted();
14
+ const doc = records.get(id);
15
+ if (!doc) throw editorError("asset-unavailable", `Document ${id} is unavailable.`);
16
+ return clone(doc);
17
+ },
18
+ subscribe(listener) {
19
+ listeners.add(listener);
20
+ return () => {
21
+ listeners.delete(listener);
22
+ };
23
+ },
24
+ ...options.readOnly ? {} : {
25
+ async commit(changes, signal) {
26
+ signal?.throwIfAborted();
27
+ const ids = /* @__PURE__ */ new Set();
28
+ for (const change of changes) {
29
+ if (ids.has(change.document.id))
30
+ throw editorError("conflict", "A batch repeats the same document.");
31
+ ids.add(change.document.id);
32
+ const current = records.get(change.document.id);
33
+ if ((current?.revision ?? null) !== change.expectedRevision)
34
+ throw editorError(
35
+ "conflict",
36
+ `Document ${change.document.id} changed since it was opened.`
37
+ );
38
+ }
39
+ const saved = changes.map((change) => ({
40
+ ...clone(change.document),
41
+ revision: `memory:${revisionScope}:${++revision}`
42
+ }));
43
+ for (const doc of saved) records.set(doc.id, doc);
44
+ for (const listener of listeners) listener([...ids]);
45
+ return clone(saved);
46
+ }
47
+ }
48
+ };
49
+ const assets = {
50
+ scope,
51
+ async list(query) {
52
+ query.signal?.throwIfAborted();
53
+ const all = [...records.values()].map(({ id, name, type, revision: revision2 }) => ({
54
+ id,
55
+ name,
56
+ type,
57
+ revision: revision2
58
+ }));
59
+ for (const { content: _, ...entry } of clips.values()) all.push(entry);
60
+ const filtered = all.filter(
61
+ (entry) => (!query.type || entry.type === query.type) && (!query.search || entry.name.toLowerCase().includes(query.search.toLowerCase()))
62
+ );
63
+ const offset = Math.max(0, Number(query.cursor) || 0);
64
+ const limit = Math.max(1, Math.min(100, query.limit ?? 50));
65
+ return {
66
+ items: clone(filtered.slice(offset, offset + limit)),
67
+ total: filtered.length,
68
+ nextCursor: offset + limit < filtered.length ? String(offset + limit) : void 0
69
+ };
70
+ },
71
+ async get(id, signal) {
72
+ signal?.throwIfAborted();
73
+ const clip = clips.get(id);
74
+ if (clip) {
75
+ const { content: _, ...entry } = clip;
76
+ return clone(entry);
77
+ }
78
+ const doc = await documents.read(id, signal);
79
+ return { id: doc.id, name: doc.name, type: doc.type, revision: doc.revision };
80
+ },
81
+ async resolve(id, signal) {
82
+ signal?.throwIfAborted();
83
+ const entry = clips.get(id);
84
+ if (!entry) throw editorError("asset-unavailable", `Audio asset ${id} is unavailable.`);
85
+ return { ...entry.content, bytes: entry.content.bytes?.slice() };
86
+ },
87
+ subscribe: documents.subscribe
88
+ };
89
+ return { documents, assets };
90
+ }
91
+ export {
92
+ createMemoryAudioHost
93
+ };
@@ -0,0 +1,4 @@
1
+ import type { AppMethod } from '@aethervtt/shard-runtime';
2
+ import type { AudioEditorSession } from './model.js';
3
+ /** Explicit registration only. The host grants protocol access to this editor session. */
4
+ export declare function audioEditorMethods(session: AudioEditorSession): AppMethod[];
@@ -0,0 +1,79 @@
1
+ import { defineSchema, t } from "@aethervtt/shard-core";
2
+ function audioEditorMethods(session) {
3
+ const empty = defineSchema("audio-editor/Empty", {});
4
+ return [
5
+ {
6
+ name: "audio.editor.describe",
7
+ description: "Inspect audio document revision, draft and diagnostics.",
8
+ params: empty,
9
+ handler: () => {
10
+ const { schema, diagnostics, ...state } = session.state;
11
+ return {
12
+ ...state,
13
+ schema: schema?.jsonSchema(),
14
+ diagnostics: diagnostics.map((e) => ({
15
+ code: e.code,
16
+ message: e.message,
17
+ path: e.path,
18
+ hint: e.hint
19
+ }))
20
+ };
21
+ }
22
+ },
23
+ {
24
+ name: "audio.editor.edit",
25
+ description: "Edit one schema-backed audio document field.",
26
+ params: defineSchema("audio-editor/Edit", {
27
+ path: t.string({ required: true }),
28
+ value: t.json(),
29
+ remove: t.bool()
30
+ }),
31
+ handler: (_, p) => {
32
+ session.edit({
33
+ path: p.path,
34
+ value: p.value,
35
+ remove: p.remove
36
+ });
37
+ return { updated: true };
38
+ }
39
+ },
40
+ {
41
+ name: "audio.editor.validate",
42
+ description: "Validate the draft against the runtime schema and host asset catalog.",
43
+ params: empty,
44
+ handler: async () => (await session.validate()).map((e) => ({
45
+ code: e.code,
46
+ message: e.message,
47
+ path: e.path,
48
+ hint: e.hint
49
+ }))
50
+ },
51
+ {
52
+ name: "audio.editor.undo",
53
+ description: "Undo one authored document transaction.",
54
+ params: empty,
55
+ handler: () => {
56
+ session.undo();
57
+ return { updated: true };
58
+ }
59
+ },
60
+ {
61
+ name: "audio.editor.redo",
62
+ description: "Redo one authored document transaction.",
63
+ params: empty,
64
+ handler: () => {
65
+ session.redo();
66
+ return { updated: true };
67
+ }
68
+ },
69
+ {
70
+ name: "audio.editor.commit",
71
+ description: "Validate and save the draft through the host revision boundary.",
72
+ params: empty,
73
+ handler: () => session.save()
74
+ }
75
+ ];
76
+ }
77
+ export {
78
+ audioEditorMethods
79
+ };
@@ -0,0 +1,61 @@
1
+ import type { JsonObject, ShardError } from '@aethervtt/shard-core';
2
+ import type { AudioDocument, AudioDocumentChange, AudioEditorEdit, AudioEditorOptions, AudioEditorState } from './types.js';
3
+ export * from './memory.js';
4
+ export * from './schema.js';
5
+ export * from './types.js';
6
+ /** Editor work is event-driven; allocating history and view snapshots does not enter engine frames. */
7
+ export declare class AudioEditorSession {
8
+ readonly documents: AudioEditorOptions['documents'];
9
+ readonly assets: AudioEditorOptions['assets'];
10
+ readonly preview: AudioEditorOptions['preview'];
11
+ private current;
12
+ private baseline?;
13
+ private listeners;
14
+ private history;
15
+ private future;
16
+ private transaction?;
17
+ private readonly limit;
18
+ private readonly byteLimit;
19
+ private disposed;
20
+ private generation;
21
+ private validationGeneration;
22
+ private validationAbort?;
23
+ private readAbort?;
24
+ private previewAbort?;
25
+ private unsubscribe?;
26
+ private unsubscribeAssets?;
27
+ constructor(options: AudioEditorOptions);
28
+ get state(): AudioEditorState;
29
+ subscribe(listener: (state: AudioEditorState) => void): () => void;
30
+ private alive;
31
+ private editable;
32
+ private emit;
33
+ private adopt;
34
+ open(id: string): Promise<void>;
35
+ create(document: AudioDocument): Promise<void>;
36
+ createNew(type: string, name: string, id?: `${string}-${string}-${string}-${string}-${string}`): Promise<void>;
37
+ duplicate(name: string, id?: `${string}-${string}-${string}-${string}-${string}`): Promise<void>;
38
+ edit(edit: AudioEditorEdit): void;
39
+ replace(value: JsonObject): void;
40
+ beginTransaction(_label?: string): void;
41
+ endTransaction(): void;
42
+ commitTransaction(): void;
43
+ cancelTransaction(): void;
44
+ private record;
45
+ undo(): void;
46
+ redo(): void;
47
+ private refresh;
48
+ validate(): Promise<ShardError[]>;
49
+ save(): Promise<AudioDocument | undefined>;
50
+ saveAs(id: string, name: string): Promise<void>;
51
+ reload(): Promise<void>;
52
+ private remoteChanged;
53
+ /** Batch edits have an explicit adapter capability and one compare-and-swap boundary. */
54
+ commitDocuments(changes: readonly AudioDocumentChange[]): Promise<AudioDocument[]>;
55
+ /** Explicit dependent IDs avoid renaming an unrelated parameter with the same display name. */
56
+ renameParameter(from: string, to: string, dependentIds?: readonly string[]): Promise<void>;
57
+ play(): Promise<unknown>;
58
+ stop(): Promise<void>;
59
+ dispose(): Promise<void>;
60
+ }
61
+ export declare function createAudioEditorSession(options: AudioEditorOptions): AudioEditorSession;