@m4l-jweb/wrapper 0.9.9 → 1.0.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 (3) hide show
  1. package/README.md +41 -0
  2. package/package.json +1 -1
  3. package/src/core.ts +26 -4
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @m4l-jweb/wrapper
2
+
3
+ The Max-side glue: the ES5 `[js]` script that owns a device's lifecycle, its LiveAPI work, transport polling, clip I/O and file writes. You do not usually import this - `@m4l-jweb/build` compiles it into every device it produces.
4
+
5
+ Part of **[m4l-jweb](https://github.com/alienmind/m4l-jweb)** - build Ableton Live devices (`.amxd`) from a TypeScript repo: React UI, LiveAPI glue, CI builds, no Max editor.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @m4l-jweb/wrapper
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ // Only a device repo extending the wrapper needs this: wrapper/device.ts is
17
+ // compiled together with the packaged sources into one ES5 script.
18
+ //
19
+ // Everything here must be ES5 - Max's [js] is not a modern JavaScript engine, and the
20
+ // build proves it with acorn before packaging.
21
+ ```
22
+
23
+ ## Notes
24
+
25
+ - `[js]` runs even inside a frozen device, and it is the only place LiveAPI exists - which is why the lifecycle lives here rather than in the browser.
26
+ - This package exists mainly so the build can find the wrapper sources (`@m4l-jweb/wrapper/sources`). It has no browser-facing API of its own.
27
+ - **`[node.script]` is never used.** It proved unstable in the field - silent non-start, then a full Live crash.
28
+
29
+ ## Requirements
30
+
31
+ Ableton Live 12 with Max 9. Devices are built on `[jweb~]`, the browser view with signal outlets; older hosts are unverified.
32
+
33
+ ## Links
34
+
35
+ - [Repository and full README](https://github.com/alienmind/m4l-jweb)
36
+ - [Architecture](https://github.com/alienmind/m4l-jweb/blob/main/doc/ARCHITECTURE.md)
37
+ - [What Max actually does: the measured facts](https://github.com/alienmind/m4l-jweb/blob/main/doc/MAX-FACTS.md)
38
+
39
+ ## License
40
+
41
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m4l-jweb/wrapper",
3
- "version": "0.9.9",
3
+ "version": "1.0.0",
4
4
  "description": "m4l-jweb: the Max for Live glue layer connecting a device to LiveAPI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/core.ts CHANGED
@@ -690,6 +690,28 @@ function partPath(destPath: string): string {
690
690
  return destPath + ".part";
691
691
  }
692
692
 
693
+ /**
694
+ * Where a SAVE lands before it has earned its destination - and deliberately NOT
695
+ * `<dest>.part`.
696
+ *
697
+ * `[js]` cannot delete a file, so a spent `.part` is truncated to zero bytes and left.
698
+ * For a download that is harmless: the destination is derived from the URL, so the same
699
+ * sample re-fetched reuses (and overwrites) the same scratch file. A SAVE has no such
700
+ * guarantee - an audio export names its file after the moment it was rendered, so every
701
+ * bounce would strand another 0-byte `superdough-export-<n>.wav.part` next to the real
702
+ * one, forever.
703
+ *
704
+ * One fixed scratch name per device folder instead: reused, overwritten, and never more
705
+ * than a single stray zero-byte file however many exports are made. Safe because saves
706
+ * are strictly one at a time (`activeSave`), and separate from the fetch scratch so a
707
+ * download in flight cannot collide with a save.
708
+ */
709
+ function savePartPath(destPath: string): string {
710
+ var cut = destPath.lastIndexOf("/");
711
+ var dir = cut < 0 ? "" : destPath.slice(0, cut + 1);
712
+ return dir + "m4l-jweb-save.part";
713
+ }
714
+
693
715
  interface ActiveFetch {
694
716
  requestId: string;
695
717
  url: string;
@@ -786,7 +808,7 @@ function save_begin(requestId: string, destPath: string, byteCount: number): voi
786
808
  if (activeSave && activeSave.file && activeSave.file.isopen) activeSave.file.close();
787
809
 
788
810
  var resolved = resolveFetchPath(destPath);
789
- var target = partPath(resolved);
811
+ var target = savePartPath(resolved);
790
812
  var f: File | null = null;
791
813
  try {
792
814
  f = new File(target, "write");
@@ -820,7 +842,7 @@ function save_end(requestId: string): void {
820
842
  var save = activeSave;
821
843
  if (save.file && save.file.isopen) save.file.close();
822
844
 
823
- var onDisk = fileSize(partPath(save.destPath));
845
+ var onDisk = fileSize(savePartPath(save.destPath));
824
846
  if (onDisk !== save.expect) {
825
847
  outlet(0, "save_error", requestId, "size mismatch: wrote " + onDisk + " bytes, expected " + save.expect);
826
848
  activeSave = null;
@@ -830,7 +852,7 @@ function save_end(requestId: string): void {
830
852
  // destPath must be a FLAT filename in the device folder - maxurl (libcurl) and Max's
831
853
  // [js] File resolve a subdirectory differently, so `sub/x.wav` writes the .part where
832
854
  // File agrees but maxurl cannot reach it, and the place returns -1. Keep saves flat.
833
- var placeUrl = encodeURI("file:///" + partPath(save.destPath));
855
+ var placeUrl = encodeURI("file:///" + savePartPath(save.destPath));
834
856
  post("m4l-jweb: save place " + placeUrl + " -> " + save.destPath + "\n");
835
857
  var reqDict = new Dict();
836
858
  reqDict.set("url", placeUrl);
@@ -851,7 +873,7 @@ function finishSavePlace(): void {
851
873
  activeSave = null;
852
874
  return;
853
875
  }
854
- truncate(partPath(save.destPath)); // [js] cannot delete; zero it
876
+ truncate(savePartPath(save.destPath)); // [js] cannot delete; zero it, and reuse it
855
877
  post("m4l-jweb: saved " + placed + " bytes to " + save.destPath + "\n");
856
878
  outlet(0, "save_done", save.requestId, placed);
857
879
  activeSave = null;