hyperframes-vst-host 0.1.0__tar.gz → 0.1.1__tar.gz

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 (25) hide show
  1. hyperframes_vst_host-0.1.1/PKG-INFO +194 -0
  2. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/pyproject.toml +2 -1
  3. hyperframes_vst_host-0.1.0/PKG-INFO +0 -14
  4. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/.github/workflows/ci.yml +0 -0
  5. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/.github/workflows/publish.yml +0 -0
  6. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/.gitignore +0 -0
  7. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/LICENSE +0 -0
  8. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/README.md +0 -0
  9. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/__init__.py +0 -0
  10. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/__main__.py +0 -0
  11. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/bounce.py +0 -0
  12. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/carve.py +0 -0
  13. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/chain.py +0 -0
  14. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/scan.py +0 -0
  15. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/server.py +0 -0
  16. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/stream.py +0 -0
  17. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_bounce.py +0 -0
  18. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_carve.py +0 -0
  19. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_carve_bounce.py +0 -0
  20. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_carve_cli.py +0 -0
  21. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_chain.py +0 -0
  22. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_scan.py +0 -0
  23. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_server.py +0 -0
  24. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_stream.py +0 -0
  25. {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/uv.lock +0 -0
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyperframes-vst-host
3
+ Version: 0.1.1
4
+ Summary: VST3/AU sidecar host for HyperFrames Studio
5
+ Project-URL: Homepage, https://github.com/heygen-com/hyperframes-vst-host
6
+ License-Expression: GPL-3.0-or-later
7
+ License-File: LICENSE
8
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Multimedia :: Sound/Audio
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: numpy>=1.26
13
+ Requires-Dist: pedalboard>=0.9.24
14
+ Requires-Dist: websockets>=12
15
+ Description-Content-Type: text/markdown
16
+
17
+ # hyperframes-vst-host
18
+
19
+ Python sidecar that hosts VST3/AU plugins (via [pedalboard](https://github.com/spotify/pedalboard))
20
+ for HyperFrames Studio and the render pipeline: an offline WAV → WAV `bounce`
21
+ used at render time, and a WebSocket `serve` mode used for live FX preview
22
+ (parameter tweaking, native plugin editor windows, streamed processed audio)
23
+ in Studio.
24
+
25
+ ## Install
26
+
27
+ - **Standalone (published package):**
28
+
29
+ ```bash
30
+ uv tool install hyperframes-vst-host
31
+ ```
32
+
33
+ - **Monorepo dev (source checkout, no install step):**
34
+
35
+ ```bash
36
+ uv run --project packages/vst-host hyperframes-vst <command>
37
+ ```
38
+
39
+ This is what the TypeScript callers use automatically — neither requires a
40
+ manual install in a source checkout. `resolveVstHostCommand()`
41
+ (`packages/engine/src/services/vstBounce.ts` for render, and its sibling
42
+ copy in `packages/studio-server/src/vstSidecar.ts` for the Studio sidecar)
43
+ falls back to `uv run --project <packages/vst-host> hyperframes-vst` the
44
+ moment `packages/vst-host/pyproject.toml` is found relative to the caller,
45
+ and only falls back further to a bare `hyperframes-vst` on `PATH` (the
46
+ installed/published case) when that monorepo layout isn't present.
47
+
48
+ - **Override (CI / dev, arbitrary executable or a test fake):** set
49
+ `HF_VST_HOST_CMD` to a space-split command; it takes precedence over both
50
+ of the above.
51
+
52
+ ## CLI
53
+
54
+ ```
55
+ hyperframes-vst <command>
56
+ ```
57
+
58
+ | command | flags | purpose |
59
+ | -------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
60
+ | `bounce` | `--input <wav> --chain <chain.json> --output <wav>` | Offline render a WAV through a chain, block-by-block, no realtime clock. Used by the render pipeline's `applyVstChainToWav`. |
61
+ | `scan` | `--dirs [...] --json` | Scan plugin directories (or the default set), print a registry JSON to stdout. |
62
+ | `probe` | `<path>` | Probe one plugin bundle in a subprocess — some bundles crash pedalboard's loader, so callers isolate one path at a time. |
63
+ | `carve` | `--voice <wav> --max-cut-db <dB> --json` | Analyze a voiceover WAV and print recommended PeakFilter "vocal pocket" carve bands as JSON (used by Studio's Make room for voiceover action). |
64
+ | `serve` | `--port N` (default: OS-assigned), `--parent-pid P` | Run the WebSocket sidecar used by Studio's live FX preview. With `--parent-pid`, self-exits when that process dies (orphan reaping). |
65
+
66
+ ### `bounce` exit codes
67
+
68
+ - **`0`** — success; the processed WAV is written to `--output`.
69
+ - **`3`** — `PLUGIN_MISSING <name>` printed to stderr: the chain file
70
+ references a plugin that isn't installed/found on this machine. The render
71
+ pipeline (`packages/engine/src/services/vstBounce.ts`'s
72
+ `applyVstChainToWav`) reads this exit code + stderr line and hard-fails the
73
+ whole render, naming the specific missing plugin and the track — a missing
74
+ plugin at render time is never a silent fallback to unprocessed dry audio.
75
+ - Any other non-zero code is treated as a generic sidecar failure and
76
+ surfaced with the tail of stderr.
77
+
78
+ ## Sidecar readiness handshake
79
+
80
+ `serve` prints exactly one readiness line to stdout the moment its WebSocket
81
+ server is bound:
82
+
83
+ ```
84
+ VST-HOST-LISTENING port=<N> token=<T>
85
+ ```
86
+
87
+ `token` is a per-process shared secret; every WebSocket upgrade must carry it
88
+ as a `?token=` query param or the handshake is rejected before any command is
89
+ dispatched. Callers wait for this line (matched against
90
+ `/VST-HOST-LISTENING port=(\d+) token=(\S+)/`) before treating the sidecar
91
+ as ready; a 30-second timeout
92
+ without it is treated as a failed start (see `startVstSidecar` in
93
+ `packages/studio-server/src/vstSidecar.ts`).
94
+
95
+ ## WebSocket protocol
96
+
97
+ One socket, two lanes: JSON text frames for control commands/events, and raw
98
+ binary frames for interleaved PCM samples during playback. Implemented in
99
+ `src/hyperframes_vst/server.py` (dispatch) and `src/hyperframes_vst/stream.py`
100
+ (PCM framing); mirrored client-side in `packages/studio/src/hooks/useVstHost.ts`.
101
+
102
+ ### Client → server (`{"cmd": ...}`)
103
+
104
+ | cmd | fields | effect |
105
+ | -------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
106
+ | `scan` | `paths?: string[]` | Scans plugin dirs (or the default set); replies `registry` |
107
+ | `load-chain` | `trackId, chainJson, wavPath` | Builds the live plugin chain for a track, (re)opens its audio stream; replies `chain-loaded` |
108
+ | `unload-chain` | `trackId` | Tears down a track's stream and plugin instances |
109
+ | `set-param` | `trackId, pluginIndex, param, value` | Sets one live plugin parameter |
110
+ | `open-editor` | `trackId, pluginIndex` | Opens the plugin's native editor window (spawned on its own thread) |
111
+ | `close-editor` | `trackId, pluginIndex` | No-op server-side — pedalboard editor windows close from their own window chrome, not this command |
112
+ | `get-state` | `trackId` | Replies `state` with each plugin's base64-encoded state |
113
+ | `transport` | `action: "play" \| "pause" \| "seek", timeSec?, rate?` | Drives playback/seek for every loaded track's PCM streaming lane |
114
+
115
+ ### Server → client (`{"event": ...}`)
116
+
117
+ | event | fields | meaning |
118
+ | -------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
119
+ | `registry` | `plugins: [...]` | Result of a `scan` |
120
+ | `chain-loaded` | `trackId, params, sampleRate, stable` | A `load-chain` completed; `params` is per-plugin parameter metadata, `sampleRate` the dry file's real rate, `stable` false when the chain can't be hosted without NaN/runaway output (client keeps the track dry). |
121
+ | `state` | `trackId, plugins: string[]` | Result of `get-state` — one base64 state blob per plugin, in chain order |
122
+ | `error` | `code, plugin?, trackId?` | A command failed. `code: "plugin_missing"` carries the plugin name in `plugin`; anything else is `code: "bad_command"` |
123
+
124
+ ### Binary PCM frame (server → client, during `transport: play`)
125
+
126
+ Little-endian, one frame per streamed block:
127
+
128
+ ```
129
+ u32 trackIndex
130
+ f64 samplePos
131
+ f32[...] interleaved stereo samples
132
+ ```
133
+
134
+ 1024 samples per block at 48 kHz (`block_size` / `sample_rate` in
135
+ `src/hyperframes_vst/stream.py`'s `TrackStream`).
136
+
137
+ ## Manual E2E verification checklist
138
+
139
+ Run through this after any change that touches the sidecar, the render
140
+ pipeline's VST bounce, or the Studio FX panel/preview path — it's the only
141
+ check that exercises native plugin windows and real audio playback, which
142
+ automated tests can't cover.
143
+
144
+ 1. Load a composition with an audio track in Studio.
145
+ 2. Add an EQ/effect to that track via the FX property panel.
146
+ 3. Open the plugin's native editor window from the panel.
147
+ 4. Twist a knob in the native editor.
148
+ 5. Play the composition back and confirm you hear the change live (streamed
149
+ processed audio, not the dry track).
150
+ 6. Close the editor window — confirm the tweaked state persists to the
151
+ track's `.vstchain.json` chain file (re-open the panel or reload the
152
+ project and see the same parameter value).
153
+ 7. Run `hyperframes render` on the same composition.
154
+ 8. Confirm the rendered audio reflects the same processing character you
155
+ heard live in preview (same effect, same rough parameter feel).
156
+ 9. Delete or rename the plugin's bundle on disk (VST3/AU) so it can no
157
+ longer be found.
158
+ 10. Render the same composition again — confirm it **fails**, with an error
159
+ naming the specific missing plugin (not a silent fallback to dry audio).
160
+ 11. Attempt a Lambda cloud render (`hyperframes lambda render`) on the same
161
+ composition — confirm it's rejected **before any AWS call**, with an
162
+ error naming the track(s) carrying a VST chain (plugins can't run in
163
+ Lambda; see the guard in `packages/cli/src/commands/lambda.ts`).
164
+
165
+ ## Chain file (`.vstchain.json`)
166
+
167
+ The persisted chain format shared with the HyperFrames TypeScript side
168
+ (`packages/studio/src/utils/vstChainFile.ts`):
169
+
170
+ ```jsonc
171
+ {
172
+ "version": 1,
173
+ "plugins": [
174
+ {
175
+ "format": "builtin", // "builtin" | "vst3" | "au"
176
+ "path": "PeakFilter", // builtin: pedalboard class; vst3/au: bundle path
177
+ "pluginName": null, // vst3/au sub-plugin name for load_plugin
178
+ "name": "Carve 250Hz", // display name
179
+ "stateB64": "...", // builtin: base64 JSON {param: number}; vst3/au: raw_state
180
+ "enabled": true // optional; absent = enabled. Disabled plugins stay in
181
+ // the chain (slot/params preserved) but are bypassed by
182
+ // the processing board in preview AND render.
183
+ }
184
+ ]
185
+ }
186
+ ```
187
+
188
+ ## License
189
+
190
+ GPL-3.0-or-later — see [LICENSE](LICENSE). This package imports
191
+ [pedalboard](https://github.com/spotify/pedalboard), which bundles JUCE and
192
+ the Steinberg VST3 SDK under their GPL options. The HyperFrames monorepo
193
+ (Apache-2.0) never links this code: it only spawns the CLI or talks to the
194
+ WebSocket sidecar as a separate process.
@@ -1,7 +1,8 @@
1
1
  [project]
2
2
  name = "hyperframes-vst-host"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "VST3/AU sidecar host for HyperFrames Studio"
5
+ readme = "README.md"
5
6
  # GPL-3.0 because pedalboard bundles JUCE + the Steinberg VST3 SDK under
6
7
  # their GPL options; this package imports it in-process. The HyperFrames
7
8
  # monorepo (Apache-2.0) only ever talks to this sidecar over a socket /
@@ -1,14 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: hyperframes-vst-host
3
- Version: 0.1.0
4
- Summary: VST3/AU sidecar host for HyperFrames Studio
5
- Project-URL: Homepage, https://github.com/heygen-com/hyperframes-vst-host
6
- License-Expression: GPL-3.0-or-later
7
- License-File: LICENSE
8
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Topic :: Multimedia :: Sound/Audio
11
- Requires-Python: >=3.11
12
- Requires-Dist: numpy>=1.26
13
- Requires-Dist: pedalboard>=0.9.24
14
- Requires-Dist: websockets>=12