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.
- hyperframes_vst_host-0.1.1/PKG-INFO +194 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/pyproject.toml +2 -1
- hyperframes_vst_host-0.1.0/PKG-INFO +0 -14
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/.github/workflows/ci.yml +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/.github/workflows/publish.yml +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/.gitignore +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/LICENSE +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/README.md +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/__init__.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/__main__.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/bounce.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/carve.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/chain.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/scan.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/server.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/src/hyperframes_vst/stream.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_bounce.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_carve.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_carve_bounce.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_carve_cli.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_chain.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_scan.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_server.py +0 -0
- {hyperframes_vst_host-0.1.0 → hyperframes_vst_host-0.1.1}/tests/test_stream.py +0 -0
- {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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|