@soundor/vst3-host 0.0.0-stage → 0.1.1
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.
- package/CMakeLists.txt +194 -0
- package/CMakePresets.json +115 -0
- package/LICENSE +21 -0
- package/NOTICE.md +17 -0
- package/README.md +40 -2
- package/cmake/Vst3HostDependencies.cmake +179 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +88 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +586 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/prepare-ayjTVhfk.mjs +1149 -0
- package/dist/prepare-ayjTVhfk.mjs.map +1 -0
- package/native/src/Bundle.cpp +253 -0
- package/native/src/Bundle.h +32 -0
- package/native/src/EditorArea.h +92 -0
- package/native/src/EditorAreaMac.mm +52 -0
- package/native/src/EditorAreaWindows.cpp +66 -0
- package/native/src/EditorAreaX11.cpp +512 -0
- package/native/src/EditorWindow.h +72 -0
- package/native/src/EditorWindowMac.mm +134 -0
- package/native/src/EditorWindowWindows.cpp +148 -0
- package/native/src/EventLoop.h +50 -0
- package/native/src/EventLoopLinux.cpp +204 -0
- package/native/src/EventLoopMac.mm +146 -0
- package/native/src/EventLoopWindows.cpp +132 -0
- package/native/src/HostContext.cpp +217 -0
- package/native/src/HostContext.h +116 -0
- package/native/src/HostInterface.cpp +1026 -0
- package/native/src/HostInterface.h +138 -0
- package/native/src/HostWindow.cpp +901 -0
- package/native/src/HostWindow.h +187 -0
- package/native/src/Ipc.cpp +158 -0
- package/native/src/Ipc.h +91 -0
- package/native/src/Json.cpp +426 -0
- package/native/src/Json.h +85 -0
- package/native/src/LiveAudio.cpp +416 -0
- package/native/src/LiveAudio.h +121 -0
- package/native/src/Main.cpp +178 -0
- package/native/src/Paths.h +20 -0
- package/native/src/Plugin.cpp +1865 -0
- package/native/src/Plugin.h +321 -0
- package/native/src/Session.cpp +656 -0
- package/native/src/Session.h +82 -0
- package/native/src/Settings.cpp +93 -0
- package/native/src/Settings.h +23 -0
- package/native/src/Version.h.in +11 -0
- package/native/src/X11Library.cpp +127 -0
- package/native/src/X11Library.h +83 -0
- package/native/tools/Embed.cpp +49 -0
- package/package.json +58 -3
- package/protocol.json +7 -0
- package/targets.json +33 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepare-ayjTVhfk.mjs","names":["protocol.version","protocol.maxHeaderBytes","protocol.defaultMaxPayloadBytes","protocol.magic","table.targets"],"sources":["../src/errors.ts","../protocol.json","../src/protocol.ts","../targets.json","../src/targets.ts","../src/inputs.ts","../src/provenance.ts","../src/resolve.ts","../src/host.ts","../src/npm.ts","../src/prepare.ts"],"sourcesContent":["/** Codes the host reports, and the client's own. */\nexport type Vst3HostErrorCode =\n // The plugin or the request (from the host).\n | 'plugin-not-found'\n | 'invalid-bundle'\n | 'incompatible-architecture'\n | 'load-failed'\n | 'unsupported-plugin'\n | 'no-matching-class'\n | 'ambiguous-class'\n | 'initialization-failed'\n | 'unsupported-configuration'\n | 'unknown-parameter'\n | 'plugin-error'\n | 'no-editor'\n | 'no-display'\n | 'headless'\n | 'audio-unavailable'\n | 'invalid-request'\n | 'invalid-state'\n | 'unknown-method'\n | 'message-too-large'\n | 'internal-error'\n // The process and the protocol (from the client).\n | 'host-exited'\n | 'timeout'\n | 'aborted'\n | 'closed'\n | 'protocol-error'\n | 'incompatible-host'\n | 'executable-not-found'\n | 'platform-package-missing'\n | 'unsupported-platform'\n | 'selection-failed';\n\nexport class Vst3HostError extends Error {\n override readonly name = 'Vst3HostError';\n readonly code: Vst3HostErrorCode;\n readonly details: Record<string, unknown> | undefined;\n constructor(\n code: Vst3HostErrorCode,\n message: string,\n details?: Record<string, unknown>,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.code = code;\n this.details = details;\n }\n}\n","","// The framing both sides share (protocol.json; native/src/Ipc.h):\n//\n// \"SVH1\" | u32 LE header length | u32 LE payload length | JSON header | payload\n//\n// Requests are { id, method, params }, responses { id, result } or\n// { id, error: { code, message, details } }, events { event, params }.\nimport protocol from '../protocol.json' with { type: 'json' };\n\nexport const PROTOCOL_VERSION: number = protocol.version;\nexport const MAX_HEADER_BYTES: number = protocol.maxHeaderBytes;\nexport const DEFAULT_MAX_PAYLOAD_BYTES: number =\n protocol.defaultMaxPayloadBytes;\nconst MAGIC = Buffer.from(protocol.magic, 'latin1');\nconst PREFIX_BYTES = 12;\n\nexport interface Frame {\n header: unknown;\n payload: Buffer;\n}\n\nexport function encodeFrame(\n header: unknown,\n payload: Uint8Array = new Uint8Array(0),\n): Buffer[] {\n const json = Buffer.from(JSON.stringify(header), 'utf8');\n const prefix = Buffer.alloc(PREFIX_BYTES);\n MAGIC.copy(prefix, 0);\n prefix.writeUInt32LE(json.length, 4);\n prefix.writeUInt32LE(payload.byteLength, 8);\n return payload.byteLength > 0\n ? [\n prefix,\n json,\n Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength),\n ]\n : [prefix, json];\n}\n\n/** Turns a byte stream into frames; throws on a corrupt or oversized one,\n after which the stream cannot be read any further. */\nexport class FrameDecoder {\n #chunks: Buffer[] = [];\n #buffered = 0;\n #pending: { header: number; payload: number } | undefined;\n\n readonly maxPayloadBytes: number;\n constructor(maxPayloadBytes: number = DEFAULT_MAX_PAYLOAD_BYTES) {\n this.maxPayloadBytes = maxPayloadBytes;\n }\n\n push(chunk: Buffer): Frame[] {\n this.#chunks.push(chunk);\n this.#buffered += chunk.length;\n const frames: Frame[] = [];\n while (true) {\n if (this.#pending === undefined) {\n if (this.#buffered < PREFIX_BYTES) break;\n const prefix = this.#take(PREFIX_BYTES);\n if (!prefix.subarray(0, 4).equals(MAGIC))\n throw new Error(\n 'the host wrote something that is not a protocol frame (bad magic)',\n );\n const header = prefix.readUInt32LE(4);\n const payload = prefix.readUInt32LE(8);\n if (header > MAX_HEADER_BYTES)\n throw new Error(\n `a frame header of ${header} bytes exceeds the limit of ${MAX_HEADER_BYTES}`,\n );\n if (payload > this.maxPayloadBytes)\n throw new Error(\n `a frame payload of ${payload} bytes exceeds the limit of ${this.maxPayloadBytes}`,\n );\n this.#pending = { header, payload };\n }\n const { header, payload } = this.#pending;\n if (this.#buffered < header + payload) break;\n this.#pending = undefined;\n const text = this.#take(header).toString('utf8');\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new Error('the host wrote a frame whose header is not JSON');\n }\n frames.push({ header: parsed, payload: this.#take(payload) });\n }\n return frames;\n }\n\n #take(size: number): Buffer {\n if (size === 0) return Buffer.alloc(0);\n const first = this.#chunks[0]!;\n let result: Buffer;\n if (first.length >= size) {\n result = first.subarray(0, size);\n if (first.length === size) this.#chunks.shift();\n else this.#chunks[0] = first.subarray(size);\n } else {\n // Copied once into an aligned buffer of its own.\n result = Buffer.alloc(size);\n let offset = 0;\n while (offset < size) {\n const chunk = this.#chunks[0]!;\n const part = Math.min(chunk.length, size - offset);\n chunk.copy(result, offset, 0, part);\n offset += part;\n if (part === chunk.length) this.#chunks.shift();\n else this.#chunks[0] = chunk.subarray(part);\n }\n }\n this.#buffered -= size;\n return result;\n }\n}\n","","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport table from '../targets.json' with { type: 'json' };\n\nexport interface Target {\n target: string;\n package: string;\n os: string[];\n cpu: string[];\n libc?: string[];\n runner: string;\n architectures: string[];\n /** Relative to the platform package's root. */\n executable: string;\n}\n\n/** The platform packages a release generates (targets.json). */\nexport const targets: readonly Target[] = table.targets;\n\n/** This package's root: the directory of its package.json. */\nexport const packageRoot: string = fileURLToPath(\n new URL('..', import.meta.url),\n);\n\nexport interface PackageManifest {\n name: string;\n version: string;\n optionalDependencies?: Record<string, string>;\n}\n\nexport function ownManifest(root = packageRoot): PackageManifest {\n return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));\n}\n\n/** A checkout of the repository, not an installed package: the published\n manifest always pins its platform packages. */\nexport function isSourceWorkspace(root = packageRoot): boolean {\n return (\n ownManifest(root).optionalDependencies === undefined &&\n existsSync(join(root, 'native/src'))\n );\n}\n\nexport interface Platform {\n platform: NodeJS.Platform;\n arch: string;\n /** Linux: \"glibc\" or \"musl\". */\n libc?: string;\n /** Linux with glibc: its version, such as \"2.39\". */\n glibc?: string;\n}\n\nexport function currentPlatform(): Platform {\n if (process.platform !== 'linux')\n return { platform: process.platform, arch: process.arch };\n const header = (\n process.report.getReport() as { header?: { glibcVersionRuntime?: string } }\n ).header;\n return header?.glibcVersionRuntime\n ? {\n platform: 'linux',\n arch: process.arch,\n libc: 'glibc',\n glibc: header.glibcVersionRuntime,\n }\n : { platform: 'linux', arch: process.arch, libc: 'musl' };\n}\n\n/** The target whose executable runs here, if any. */\nexport function targetFor(\n platform: Platform = currentPlatform(),\n): Target | undefined {\n return targets.find(\n (target) =>\n target.os.includes(platform.platform) &&\n target.cpu.includes(platform.arch) &&\n (target.libc === undefined || target.libc.includes(platform.libc ?? '')),\n );\n}\n\nexport function describePlatform(\n platform: Platform = currentPlatform(),\n): string {\n return `${platform.platform}-${platform.arch}${platform.libc ? `-${platform.libc}` : ''}`;\n}\n\n/** Version comparison of dotted numbers (\"2.35\" < \"2.39\"). */\nexport function versionAtLeast(actual: string, required: string): boolean {\n const a = actual.split('.').map(Number);\n const b = required.split('.').map(Number);\n for (let i = 0; i < Math.max(a.length, b.length); i++) {\n const x = a[i] ?? 0;\n const y = b[i] ?? 0;\n if (x !== y) return x > y;\n }\n return true;\n}\n","// The content identity of the native executable: what a build of it\n// depends on, and nothing else. Documentation, tests, the Node client and the\n// lockfile do not change it; a source, header, protocol, CMake or dependency\n// pin change does, committed or not, and a reverted edit restores it.\nimport { createHash } from 'node:crypto';\nimport { readFileSync, readdirSync } from 'node:fs';\nimport { join, sep } from 'node:path';\n\nimport { packageRoot } from './targets.ts';\n\n/** Files, relative to the package root. Directories are walked. */\nconst INPUTS = [\n 'CMakeLists.txt',\n 'CMakePresets.json',\n 'protocol.json',\n 'cmake',\n 'native/src',\n 'native/tools',\n];\nconst TEXT = /\\.(?:cmake|cpp|c|h|hpp|mm|m|in|json|txt)$/;\nconst IGNORED = /(?:^\\.|~$|\\.(?:swp|tmp|orig|rej)$)/;\n\nexport interface NativeInputs {\n fingerprint: string;\n files: string[];\n version: string;\n}\n\nexport function nativeInputs(root = packageRoot): NativeInputs {\n const files: string[] = [];\n const visit = (relative: string) => {\n const path = join(root, relative);\n let entries;\n try {\n entries = readdirSync(path, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOTDIR') {\n files.push(relative);\n return;\n }\n throw error;\n }\n for (const entry of entries) {\n if (IGNORED.test(entry.name)) continue;\n const child = join(relative, entry.name);\n if (entry.isDirectory()) visit(child);\n else if (entry.isFile()) files.push(child);\n else if (entry.isSymbolicLink())\n throw new Error(`A native input is a symbolic link: ${child}`);\n }\n };\n for (const input of INPUTS) visit(input);\n const normalized = files.map((file) => file.split(sep).join('/')).sort();\n\n // The version is compiled in (the handshake reports it); the rest of\n // package.json is not an input.\n const { version } = JSON.parse(\n readFileSync(join(root, 'package.json'), 'utf8'),\n ) as { version: string };\n const hash = createHash('sha256').update(\n `soundor-vst3-host-inputs-v1\\0${version}\\0`,\n );\n for (const file of normalized) {\n const bytes = readFileSync(join(root, file));\n const content = TEXT.test(file)\n ? Buffer.from(bytes.toString('utf8').replaceAll('\\r\\n', '\\n'))\n : bytes;\n hash.update(file).update('\\0');\n hash.update(createHash('sha256').update(content).digest()).update('\\0');\n }\n return { fingerprint: hash.digest('hex'), files: normalized, version };\n}\n","// What every built executable is recorded with (soundor-vst3-host.json in a\n// CI artifact and in each platform package): enough to tell whether a binary\n// is the one a version, a checkout or a retry expects.\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\n\nimport { PROTOCOL_VERSION } from './protocol.ts';\n\nexport const PROVENANCE_FILE = 'soundor-vst3-host.json';\n\nexport interface Provenance {\n format: 1;\n name: '@soundor/vst3-host';\n version: string;\n protocol: number;\n target: string;\n architectures: string[];\n configuration: 'Release';\n /** The commit it was built from (CI), or null for a local build. */\n sourceCommit: string | null;\n /** nativeInputs() of the sources it was built from. */\n sourceFingerprint: string;\n executable: { path: string; sha256: string; size: number };\n /** Linux: the newest glibc symbol version the executable needs. */\n glibc?: string;\n}\n\nexport function sha256(bytes: Uint8Array): string {\n return createHash('sha256').update(bytes).digest('hex');\n}\n\nexport function sha256File(path: string): string {\n return sha256(readFileSync(path));\n}\n\n/** The newest GLIBC_x.y version an ELF executable references. */\nexport function requiredGlibc(bytes: Uint8Array): string | undefined {\n const text = Buffer.from(\n bytes.buffer,\n bytes.byteOffset,\n bytes.byteLength,\n ).toString('latin1');\n let newest: number[] | undefined;\n for (const match of text.matchAll(/GLIBC_(\\d+)\\.(\\d+)(?:\\.(\\d+))?/g)) {\n const version = [Number(match[1]), Number(match[2]), Number(match[3] ?? 0)];\n if (\n !newest ||\n version[0]! > newest[0]! ||\n (version[0] === newest[0] &&\n (version[1]! > newest[1]! ||\n (version[1] === newest[1] && version[2]! > newest[2]!)))\n )\n newest = version;\n }\n if (!newest) return undefined;\n return newest[2] ? newest.join('.') : `${newest[0]}.${newest[1]}`;\n}\n\n/** The problems with a provenance record, empty when it is well-formed. */\nexport function provenanceProblems(value: unknown): string[] {\n const p = value as Partial<Provenance> | null;\n const problems: string[] = [];\n if (p === null || typeof p !== 'object') return ['it is not an object'];\n if (p.format !== 1) problems.push(`format is ${p.format}, not 1`);\n if (p.name !== '@soundor/vst3-host') problems.push(`name is ${p.name}`);\n if (typeof p.version !== 'string') problems.push('version is missing');\n if (typeof p.protocol !== 'number') problems.push('protocol is missing');\n if (typeof p.target !== 'string') problems.push('target is missing');\n if (!Array.isArray(p.architectures))\n problems.push('architectures are missing');\n if (p.configuration !== 'Release')\n problems.push(`configuration is ${p.configuration}, not Release`);\n if (p.sourceCommit !== null && !/^[0-9a-f]{40}$/.test(p.sourceCommit ?? ''))\n problems.push('sourceCommit is not a full commit hash');\n if (!/^[0-9a-f]{64}$/.test(p.sourceFingerprint ?? ''))\n problems.push('sourceFingerprint is not a SHA-256');\n if (\n typeof p.executable?.path !== 'string' ||\n !/^[0-9a-f]{64}$/.test(p.executable.sha256 ?? '') ||\n typeof p.executable.size !== 'number'\n )\n problems.push('executable is not { path, sha256, size }');\n return problems;\n}\n\nexport function checkedProvenance(value: unknown, where: string): Provenance {\n const problems = provenanceProblems(value);\n if (problems.length)\n throw new Error(\n `${where} is not a valid host provenance record: ${problems.join('; ')}`,\n );\n return value as Provenance;\n}\n\n/** Whether a binary is interchangeable with what is expected: the same\n version, target, protocol and native inputs. The commit may differ (a\n later commit that left the native inputs alone builds the same thing). */\nexport function sameIdentity(\n actual: Provenance,\n expected: Pick<Provenance, 'version' | 'target' | 'sourceFingerprint'> & {\n protocol?: number;\n },\n): boolean {\n return (\n actual.version === expected.version &&\n actual.target === expected.target &&\n actual.protocol === (expected.protocol ?? PROTOCOL_VERSION) &&\n actual.sourceFingerprint === expected.sourceFingerprint\n );\n}\n","// Which executable launchHost() runs. Never builds and never downloads:\n//\n// 1. options.executable, or SOUNDOR_VST3_HOST_EXECUTABLE\n// 2. the installed platform package (@soundor/vst3-host-<target>), checked\n// against this client's version and protocol\n// 3. in a source checkout, the build or download prepareHost() selected,\n// if it still matches the native sources\nimport { existsSync, readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport { Vst3HostError } from './errors.ts';\nimport { nativeInputs } from './inputs.ts';\nimport { PROTOCOL_VERSION } from './protocol.ts';\nimport {\n PROVENANCE_FILE,\n type Provenance,\n checkedProvenance,\n sha256File,\n} from './provenance.ts';\nimport {\n currentPlatform,\n describePlatform,\n isSourceWorkspace,\n ownManifest,\n packageRoot,\n targetFor,\n versionAtLeast,\n} from './targets.ts';\n\nexport interface ResolvedHost {\n executable: string;\n source: 'explicit' | 'package' | 'selection';\n /** Why this one, for logs. */\n reason: string;\n provenance?: Provenance;\n}\n\nexport interface ResolveOptions {\n executable?: string;\n /** Default: this package's root. */\n root?: string;\n}\n\n/** The selection prepareHost() records in a source checkout. */\nexport interface Selection {\n mode: 'prebuilt' | 'source' | 'artifact';\n /** Relative to the package root. */\n executable: string;\n sha256: string;\n sourceFingerprint: string;\n version: string;\n target: string;\n reason: string;\n}\n\nexport function selectionFile(root = packageRoot): string {\n return join(root, 'build', 'selected.json');\n}\n\nconst verified = new Map<string, string>();\nfunction verifyExecutable(path: string, sha256: string, what: string): void {\n if (verified.get(path) === sha256) return;\n if (!existsSync(path))\n throw new Vst3HostError(\n 'executable-not-found',\n `${what}: ${path} does not exist`,\n );\n if (sha256File(path) !== sha256)\n throw new Vst3HostError(\n 'incompatible-host',\n `${what}: ${path} is not the executable its record describes (SHA-256 differs)`,\n );\n verified.set(path, sha256);\n}\n\nexport function resolveHost(options: ResolveOptions = {}): ResolvedHost {\n const root = options.root ?? packageRoot;\n const explicit =\n options.executable ?? process.env.SOUNDOR_VST3_HOST_EXECUTABLE;\n if (explicit) {\n const executable = resolve(explicit);\n if (!existsSync(executable))\n throw new Vst3HostError(\n 'executable-not-found',\n `the VST3 host executable ${executable} does not exist`,\n );\n return {\n executable,\n source: 'explicit',\n reason: options.executable\n ? 'given as `executable`'\n : 'SOUNDOR_VST3_HOST_EXECUTABLE',\n };\n }\n\n const platform = currentPlatform();\n const target = targetFor(platform);\n if (!target)\n throw new Vst3HostError(\n 'unsupported-platform',\n `@soundor/vst3-host has no executable for ${describePlatform(platform)}; it ships for Linux x64 (glibc), macOS (arm64 and x64) and Windows x64`,\n );\n const manifest = ownManifest(root);\n\n if (isSourceWorkspace(root)) {\n const file = selectionFile(root);\n if (!existsSync(file))\n throw new Vst3HostError(\n 'executable-not-found',\n 'no VST3 host executable is selected in this checkout: run `pnpm --filter @soundor/vst3-host build:native` (prepareHost()) first',\n );\n const selection = JSON.parse(readFileSync(file, 'utf8')) as Selection;\n const { fingerprint } = nativeInputs(root);\n if (\n selection.sourceFingerprint !== fingerprint ||\n selection.version !== manifest.version\n )\n throw new Vst3HostError(\n 'executable-not-found',\n 'the selected VST3 host executable was built from other native sources than this checkout has: run `pnpm --filter @soundor/vst3-host build:native` again',\n { selected: selection.sourceFingerprint, current: fingerprint },\n );\n const executable = isAbsolute(selection.executable)\n ? selection.executable\n : join(root, selection.executable);\n verifyExecutable(executable, selection.sha256, 'the selected VST3 host');\n return { executable, source: 'selection', reason: selection.reason };\n }\n\n let packageJson: string;\n try {\n packageJson = createRequire(join(root, 'package.json')).resolve(\n `${target.package}/package.json`,\n );\n } catch {\n throw new Vst3HostError(\n 'platform-package-missing',\n `${target.package} is not installed. npm or pnpm installs it with @soundor/vst3-host as an optional dependency for ${describePlatform(platform)}; it is missing when optional dependencies are omitted (--no-optional, --omit=optional) or node_modules came from another platform. Reinstall with optional dependencies.`,\n { package: target.package, version: manifest.version },\n );\n }\n const directory = dirname(packageJson);\n const platformManifest = JSON.parse(readFileSync(packageJson, 'utf8')) as {\n version: string;\n };\n const provenance = checkedProvenance(\n JSON.parse(readFileSync(join(directory, PROVENANCE_FILE), 'utf8')),\n `${target.package}/${PROVENANCE_FILE}`,\n );\n if (\n platformManifest.version !== manifest.version ||\n provenance.version !== manifest.version\n )\n throw new Vst3HostError(\n 'incompatible-host',\n `${target.package}@${platformManifest.version} is installed; @soundor/vst3-host@${manifest.version} needs exactly ${manifest.version}. Reinstall so both match.`,\n );\n if (\n provenance.protocol !== PROTOCOL_VERSION ||\n provenance.target !== target.target\n )\n throw new Vst3HostError(\n 'incompatible-host',\n `${target.package} holds a ${provenance.target} executable for protocol ${provenance.protocol}; this client needs ${target.target} and protocol ${PROTOCOL_VERSION}`,\n );\n if (\n provenance.glibc &&\n platform.glibc &&\n !versionAtLeast(platform.glibc, provenance.glibc)\n )\n throw new Vst3HostError(\n 'unsupported-platform',\n `the VST3 host needs glibc ${provenance.glibc} or newer; this system has ${platform.glibc}`,\n );\n const executable = join(directory, target.executable);\n verifyExecutable(executable, provenance.executable.sha256, target.package);\n return {\n executable,\n source: 'package',\n reason: `${target.package}@${platformManifest.version}`,\n provenance,\n };\n}\n","import { type ChildProcess, spawn } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\nimport { endianness } from 'node:os';\n\nimport { Vst3HostError } from './errors.ts';\nimport {\n DEFAULT_MAX_PAYLOAD_BYTES,\n type Frame,\n FrameDecoder,\n PROTOCOL_VERSION,\n encodeFrame,\n} from './protocol.ts';\nimport { resolveHost } from './resolve.ts';\nimport type {\n EditorInfo,\n HostEvents,\n HostParameter,\n LatencyInfo,\n ParameterInfo,\n ParameterValue,\n PluginInfo,\n PluginState,\n ProcessRequest,\n ProcessResult,\n ProcessingSetup,\n RequestOptions,\n AudioSettings,\n AudioStatus,\n Presentation,\n Transport,\n TransportUpdate,\n} from './types.ts';\n\nfunction audioParams(settings: AudioSettings): AudioSettings {\n return {\n input: settings.input,\n file: settings.file,\n muted: settings.muted,\n volume: settings.volume,\n };\n}\n\nexport interface LaunchOptions extends ProcessingSetup {\n /** The .vst3 bundle to load, by path; it need not be installed. */\n plugin: string;\n /** The audio processor class, when the bundle has several. */\n className?: string;\n classId?: string;\n /** UI mode: open the plugin's editor in a native window. Default false\n (headless: no window, no display needed). */\n editor?: boolean;\n /** The editor's content scale (Windows, Linux). Default: the window's. */\n scale?: number;\n /** How the editor is shown (with `editor: true`): `editor` (the default),\n a window that is the editor alone, or `host`, with the host interface.\n `null`: as the user's host settings say (`host` unless they turned the\n interface off), what the `open` command does. */\n presentation?: Presentation | null;\n /** The executable. Default: resolveHost(). */\n executable?: string;\n /** Runs the host in a process group of its own, so that a terminal's Ctrl+C\n reaches only Node, which can ask the plugin for its state before calling\n `close()`. It still ends with Node. Default false. */\n detached?: boolean;\n /** Added to (or, with inheritEnv: false, instead of) process.env.\n SOUNDOR_DEVTOOLS_SESSION reaches the plugin this way. */\n env?: Record<string, string | undefined>;\n inheritEnv?: boolean;\n cwd?: string;\n /** Milliseconds for the host to start and load the plugin. Default 30000. */\n startTimeout?: number;\n /** Default timeout of each request, in milliseconds. Default 60000. */\n requestTimeout?: number;\n /** The largest frame either side accepts. Default 256 MiB. */\n maxPayloadBytes?: number;\n /** Each line the host or the plugin writes to stderr. */\n onLog?: (line: string) => void;\n signal?: AbortSignal;\n}\n\ninterface Pending {\n id: number;\n method: string;\n params: unknown;\n payload: Uint8Array | undefined;\n timeout: number;\n signal: AbortSignal | undefined;\n resolve: (frame: { result: unknown; payload: Buffer }) => void;\n reject: (error: Error) => void;\n /** Answered or abandoned: its response, when it comes, is dropped. */\n settled: boolean;\n timer?: NodeJS.Timeout;\n onAbort?: () => void;\n}\n\n/**\n * The one parameter with this title, or `unknown-parameter`. A JUCE plugin with\n * a Bypass of its own also has the bypass JUCE adds for hosts, with the same\n * title and the bypass flag: the title means the plugin's own.\n */\nexport function parameterTitled(\n parameters: readonly ParameterInfo[],\n title: string,\n): ParameterInfo {\n let found = parameters.filter((info) => info.title === title);\n if (found.length > 1) {\n const own = found.filter((info) => !info.flags.bypass);\n if (own.length === 1) found = own;\n }\n if (found.length === 1) return found[0]!;\n throw new Vst3HostError(\n 'unknown-parameter',\n found.length === 0\n ? `No parameter is titled \"${title}\"; the plugin has: ${parameters.map((info) => `\"${info.title}\"`).join(', ')}`\n : `${found.length} parameters are titled \"${title}\"; use getParameters() and their ids`,\n );\n}\n\nconst live = new Set<ChildProcess>();\nlet exitHookInstalled = false;\n// No host outlives this process, whatever ends it.\nfunction track(child: ChildProcess): void {\n live.add(child);\n child.once('exit', () => live.delete(child));\n if (!exitHookInstalled) {\n exitHookInstalled = true;\n process.once('exit', () => {\n for (const host of live) host.kill('SIGKILL');\n });\n }\n}\n\nconst LITTLE_ENDIAN = endianness() === 'LE';\n\n/** Starts a host process, loads the plugin and resolves once it is ready. */\nexport async function launchHost(options: LaunchOptions): Promise<Vst3Host> {\n if (!LITTLE_ENDIAN)\n throw new Vst3HostError(\n 'unsupported-platform',\n 'the host protocol carries little-endian audio',\n );\n const executable = options.executable ?? resolveHost().executable;\n const maxPayloadBytes = options.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES;\n const args = [\n options.editor ? '--ui' : '--headless',\n '--max-payload',\n String(maxPayloadBytes),\n ];\n const env: Record<string, string | undefined> = {\n ...(options.inheritEnv === false ? {} : process.env),\n ...options.env,\n };\n for (const key of Object.keys(env))\n if (env[key] === undefined) delete env[key];\n\n const child = spawn(executable, args, {\n cwd: options.cwd,\n env: env as NodeJS.ProcessEnv,\n detached: options.detached ?? false,\n stdio: ['pipe', 'pipe', 'pipe'],\n windowsHide: true,\n });\n track(child);\n const host = new Vst3Host(child, {\n maxPayloadBytes,\n requestTimeout: options.requestTimeout ?? 60_000,\n onLog: options.onLog,\n executable,\n });\n const startTimeout = options.startTimeout ?? 30_000;\n try {\n await host.ready(startTimeout, options.signal);\n await host.load(options, startTimeout, options.signal);\n return host;\n } catch (error) {\n await host.close();\n throw error;\n }\n}\n\ninterface HostOptions {\n maxPayloadBytes: number;\n requestTimeout: number;\n onLog: ((line: string) => void) | undefined;\n executable: string;\n}\n\nexport interface HostIdentity {\n protocol: number;\n version: string;\n sdkVersion: string;\n platform: string;\n arch: string;\n mode: 'ui' | 'headless';\n maxPayloadBytes: number;\n}\n\n/** A running host with one plugin loaded. Requests run one at a time, in\n the order they are made. */\nexport class Vst3Host {\n #child: ChildProcess;\n #options: HostOptions;\n #events = new EventEmitter();\n #decoder: FrameDecoder;\n #queue: Pending[] = [];\n #current: Pending | undefined;\n #nextId = 1;\n #stderr = '';\n #partialLine = '';\n #exit: { code: number | null; signal: NodeJS.Signals | null } | undefined;\n #exited: Promise<{ code: number | null; signal: NodeJS.Signals | null }>;\n #failure: Vst3HostError | undefined;\n #closing:\n | Promise<{ code: number | null; signal: NodeJS.Signals | null }>\n | undefined;\n #closeRequested = false;\n #hello: ((identity: HostIdentity) => void) | undefined;\n #identity: HostIdentity | undefined;\n #info: PluginInfo | undefined;\n\n /** @internal Use launchHost(). */\n constructor(child: ChildProcess, options: HostOptions) {\n this.#child = child;\n this.#options = options;\n this.#decoder = new FrameDecoder(options.maxPayloadBytes);\n this.#events.setMaxListeners(0);\n this.#exited = new Promise((resolve) => {\n child.once('exit', (code, signal) => {\n this.#exit = { code, signal };\n this.#fail(\n this.#closeRequested\n ? new Vst3HostError('closed', 'the VST3 host was closed')\n : new Vst3HostError(\n 'host-exited',\n `the VST3 host exited (${signal ? `signal ${signal}` : `code ${code}`})${this.#stderrTail()}`,\n { code, signal, stderr: this.#stderr.slice(-4096) },\n ),\n );\n this.#events.emit('exit', { code, signal });\n resolve({ code, signal });\n });\n // Not started at all: there will be no 'exit'.\n child.once('error', (error) => {\n if (child.pid !== undefined) return;\n this.#fail(\n new Vst3HostError(\n 'executable-not-found',\n `the VST3 host could not be started from ${options.executable}: ${error.message}`,\n { executable: options.executable },\n { cause: error },\n ),\n );\n this.#exit = { code: null, signal: null };\n resolve(this.#exit);\n });\n });\n child.stdout!.on('data', (chunk: Buffer) => {\n let frames: Frame[];\n try {\n frames = this.#decoder.push(chunk);\n } catch (error) {\n this.#fail(\n new Vst3HostError('protocol-error', (error as Error).message),\n );\n child.kill('SIGKILL');\n return;\n }\n for (const frame of frames) this.#receive(frame);\n });\n child.stderr!.setEncoding('utf8');\n child.stderr!.on('data', (text: string) => {\n this.#stderr = (this.#stderr + text).slice(-65536);\n if (!this.#options.onLog) return;\n const lines = (this.#partialLine + text).split(/\\r?\\n/);\n this.#partialLine = lines.pop() ?? '';\n for (const line of lines) this.#options.onLog(line);\n });\n // A host that is gone cannot read: its requests fail through #fail.\n child.stdin!.on('error', () => {});\n }\n\n /** The host process's id. */\n get pid(): number | undefined {\n return this.#child.pid;\n }\n\n /** What the host reported when it started. */\n get identity(): HostIdentity {\n return this.#identity!;\n }\n\n /** The loaded plugin, as of launch or the last recreate/configure. */\n get info(): PluginInfo {\n return this.#info!;\n }\n\n /** Resolves when the process has ended. */\n get exited(): Promise<{\n code: number | null;\n signal: NodeJS.Signals | null;\n }> {\n return this.#exited;\n }\n\n /** The end of what the host and the plugin wrote to stderr. */\n get diagnostics(): string {\n return this.#stderr;\n }\n\n on<E extends keyof HostEvents>(\n event: E,\n listener: (params: HostEvents[E]) => void,\n ): this {\n this.#events.on(event, listener);\n return this;\n }\n\n off<E extends keyof HostEvents>(\n event: E,\n listener: (params: HostEvents[E]) => void,\n ): this {\n this.#events.off(event, listener);\n return this;\n }\n\n once<E extends keyof HostEvents>(\n event: E,\n listener: (params: HostEvents[E]) => void,\n ): this {\n this.#events.once(event, listener);\n return this;\n }\n\n /** @internal */\n async ready(timeout: number, signal?: AbortSignal): Promise<void> {\n const identity = await this.#withTimeout(\n new Promise<HostIdentity>((resolve, reject) => {\n this.#hello = resolve;\n void this.#exited.then(() => reject(this.#failure));\n }),\n timeout,\n signal,\n 'start',\n );\n if (identity.protocol !== PROTOCOL_VERSION)\n throw new Vst3HostError(\n 'incompatible-host',\n `${this.#options.executable} speaks protocol ${identity.protocol}; this client speaks ${PROTOCOL_VERSION}. Use the executable of the same @soundor/vst3-host version.`,\n { ...identity },\n );\n this.#identity = identity;\n }\n\n /** @internal */\n async load(\n options: LaunchOptions,\n timeout: number,\n signal?: AbortSignal,\n ): Promise<void> {\n const { result } = await this.#request(\n 'load',\n {\n path: options.plugin,\n className: options.className,\n classId: options.classId,\n sampleRate: options.sampleRate,\n blockSize: options.blockSize,\n processMode: options.processMode,\n inputs: options.inputs,\n outputs: options.outputs,\n openEditor: options.editor === true,\n scale: options.scale,\n presentation:\n options.presentation === undefined\n ? 'editor'\n : (options.presentation ?? undefined),\n },\n undefined,\n { timeout, signal },\n );\n this.#info = result as PluginInfo;\n }\n\n async getInfo(options?: RequestOptions): Promise<PluginInfo> {\n this.#info = (await this.#request('info', {}, undefined, options))\n .result as PluginInfo;\n return this.#info;\n }\n\n async getParameters(options?: RequestOptions): Promise<ParameterInfo[]> {\n return (await this.#request('getParameters', {}, undefined, options))\n .result as ParameterInfo[];\n }\n\n async getParameter(\n id: number,\n options?: RequestOptions,\n ): Promise<ParameterInfo> {\n return (await this.#request('getParameter', { id }, undefined, options))\n .result as ParameterInfo;\n }\n\n /** Sets the controller's value now and hands the change to the processor\n at the first frame of the next process call. */\n async setParameter(\n id: number,\n value: ParameterValue | number,\n options?: RequestOptions,\n ): Promise<ParameterInfo> {\n const values = typeof value === 'number' ? { normalized: value } : value;\n return (\n await this.#request('setParameter', { id, ...values }, undefined, options)\n ).result as ParameterInfo;\n }\n\n /** A parameter by its title, looked up at each use (see\n {@link parameterTitled}). */\n parameter(title: string): HostParameter {\n const find = async (options?: RequestOptions): Promise<ParameterInfo> =>\n parameterTitled(await this.getParameters(options), title);\n return {\n get: find,\n set: async (value, options) =>\n this.setParameter((await find(options)).id, value, options),\n };\n }\n\n async getLatency(options?: RequestOptions): Promise<LatencyInfo> {\n return (await this.#request('getLatency', {}, undefined, options))\n .result as LatencyInfo;\n }\n\n /** Processes `frames` frames, in blocks of at most the block size. */\n async process(request: ProcessRequest = {}): Promise<ProcessResult> {\n const info = this.#info!;\n const inputs = request.inputs ?? [];\n if (inputs.length > info.buses.audioInputs.length)\n throw new Vst3HostError(\n 'invalid-request',\n `${inputs.length} input buses were given; \"${info.name}\" has ${info.buses.audioInputs.length}`,\n );\n let frames = request.frames;\n for (const [index, bus] of inputs.entries()) {\n if (bus == null) continue;\n for (const [channel, samples] of bus.entries()) {\n if (!(samples instanceof Float32Array))\n throw new Vst3HostError(\n 'invalid-request',\n `inputs[${index}][${channel}] must be a Float32Array`,\n );\n frames ??= samples.length;\n if (samples.length !== frames)\n throw new Vst3HostError(\n 'invalid-request',\n `inputs[${index}][${channel}] has ${samples.length} frames; the call processes ${frames}`,\n );\n }\n }\n if (frames === undefined)\n throw new Vst3HostError(\n 'invalid-request',\n 'process() needs `frames` when no input audio is given',\n );\n if (!Number.isInteger(frames) || frames < 0)\n throw new Vst3HostError(\n 'invalid-request',\n `frames must be a non-negative integer, not ${frames}`,\n );\n const channels = inputs.map((bus) => bus?.length ?? 0);\n const bytes = channels.reduce((sum, count) => sum + count, 0) * frames * 4;\n const outputBytes =\n info.buses.audioOutputs\n .filter((bus) => bus.active)\n .reduce((sum, bus) => sum + bus.channels, 0) *\n frames *\n 4;\n if (\n bytes > this.#options.maxPayloadBytes ||\n outputBytes > this.#options.maxPayloadBytes\n )\n throw new Vst3HostError(\n 'message-too-large',\n `${frames} frames exceed the ${this.#options.maxPayloadBytes}-byte message limit; process them in several calls (the transport and plugin state carry over) or raise maxPayloadBytes`,\n );\n const payload = new Uint8Array(bytes);\n let offset = 0;\n for (const bus of inputs)\n for (const samples of bus ?? []) {\n payload.set(\n new Uint8Array(\n samples.buffer,\n samples.byteOffset,\n samples.byteLength,\n ),\n offset,\n );\n offset += samples.byteLength;\n }\n\n const { result, payload: output } = await this.#request(\n 'process',\n {\n frames,\n inputs: channels,\n events: request.events,\n parameterChanges: request.parameterChanges,\n transport: request.transport,\n },\n payload,\n request,\n );\n const response = result as Omit<ProcessResult, 'outputs'> & {\n outputs: number[];\n };\n // The output's own buffer: aligned, and the caller's to keep.\n const audio = new Float32Array(output.byteLength / 4);\n new Uint8Array(audio.buffer).set(output);\n let position = 0;\n const outputs = response.outputs.map((count) =>\n Array.from({ length: count }, () => {\n const channel = audio.subarray(position, position + frames);\n position += frames;\n return channel;\n }),\n );\n return { ...response, outputs };\n }\n\n /** The processor's and the controller's state. Pending parameter changes\n reach the processor first (in a process call of zero frames). */\n async getState(options?: RequestOptions): Promise<PluginState> {\n const { result, payload } = await this.#request(\n 'getState',\n {},\n undefined,\n options,\n );\n const { componentBytes } = result as { componentBytes: number };\n const bytes = new Uint8Array(payload);\n return {\n component: bytes.slice(0, componentBytes),\n controller: bytes.slice(componentBytes),\n };\n }\n\n async setState(state: PluginState, options?: RequestOptions): Promise<void> {\n const payload = new Uint8Array(\n state.component.byteLength + state.controller.byteLength,\n );\n payload.set(state.component, 0);\n payload.set(state.controller, state.component.byteLength);\n await this.#request(\n 'setState',\n {\n componentBytes: state.component.byteLength,\n controllerBytes: state.controller.byteLength,\n },\n payload,\n options,\n );\n }\n\n /** Deactivates and reactivates the plugin: it clears delay lines, held\n notes and other processing state. The transport is kept. */\n async reset(options?: RequestOptions): Promise<void> {\n await this.#request('reset', {}, undefined, options);\n }\n\n /** A fresh instance of the same class and setup, in the same process.\n The transport starts over. */\n async recreate(options?: RequestOptions): Promise<PluginInfo> {\n this.#info = (await this.#request('recreate', {}, undefined, options))\n .result as PluginInfo;\n return this.#info;\n }\n\n /** A new sample rate, block size, mode or bus layout. What is not given\n stays as it is. */\n async configure(\n setup: ProcessingSetup,\n options?: RequestOptions,\n ): Promise<PluginInfo> {\n this.#info = (await this.#request('configure', setup, undefined, options))\n .result as PluginInfo;\n return this.#info;\n }\n\n /** Opens the editor, or shows an open one in another presentation (it\n opens again there, at the same size). */\n async openEditor(\n options: RequestOptions & {\n scale?: number;\n presentation?: Presentation;\n } = {},\n ): Promise<EditorInfo> {\n return (\n await this.#request(\n 'openEditor',\n { scale: options.scale, presentation: options.presentation },\n undefined,\n options,\n )\n ).result as EditorInfo;\n }\n\n async closeEditor(options?: RequestOptions): Promise<EditorInfo> {\n return (await this.#request('closeEditor', {}, undefined, options))\n .result as EditorInfo;\n }\n\n async resizeEditor(\n width: number,\n height: number,\n options?: RequestOptions,\n ): Promise<EditorInfo> {\n return (\n await this.#request('resizeEditor', { width, height }, undefined, options)\n ).result as EditorInfo;\n }\n\n async getEditor(options?: RequestOptions): Promise<EditorInfo> {\n return (await this.#request('getEditor', {}, undefined, options))\n .result as EditorInfo;\n }\n\n /** Starts live audio through the default output device, with `settings`\n applied first. Fails with `audio-unavailable` without a device.\n While it runs, `process` fails: the plugin is processing live. */\n async startAudio(\n settings: AudioSettings & RequestOptions = {},\n ): Promise<AudioStatus> {\n return (\n await this.#request(\n 'startAudio',\n audioParams(settings),\n undefined,\n settings,\n )\n ).result as AudioStatus;\n }\n\n async stopAudio(options?: RequestOptions): Promise<AudioStatus> {\n return (await this.#request('stopAudio', {}, undefined, options))\n .result as AudioStatus;\n }\n\n /** Changes live audio's input, volume or muting, running or not. */\n async setAudio(\n settings: AudioSettings & RequestOptions,\n ): Promise<AudioStatus> {\n return (\n await this.#request(\n 'setAudio',\n audioParams(settings),\n undefined,\n settings,\n )\n ).result as AudioStatus;\n }\n\n async getAudio(options?: RequestOptions): Promise<AudioStatus> {\n return (await this.#request('getAudio', {}, undefined, options))\n .result as AudioStatus;\n }\n\n /** The host's transport, which live audio advances while it plays. */\n async getTransport(options?: RequestOptions): Promise<Transport> {\n return (await this.#request('getTransport', {}, undefined, options))\n .result as Transport;\n }\n\n /** Changes the transport, as `process({ transport })` does, without\n processing: play and stop live audio's transport, or set its tempo. */\n async setTransport(\n update: TransportUpdate,\n options?: RequestOptions,\n ): Promise<Transport> {\n return (\n await this.#request('setTransport', { ...update }, undefined, options)\n ).result as Transport;\n }\n\n /** Unloads the plugin and ends the process: politely, then by force.\n Safe to call more than once and after a failure. */\n close(timeout = 10_000): Promise<{\n code: number | null;\n signal: NodeJS.Signals | null;\n }> {\n this.#closing ??= (async () => {\n if (this.#exit) return this.#exit;\n this.#closeRequested = true;\n const kill = setTimeout(() => this.#child.kill('SIGKILL'), timeout);\n try {\n if (!this.#failure && this.#identity)\n await this.#request('shutdown', {}, undefined, { timeout }).catch(\n () => this.#child.kill('SIGKILL'),\n );\n else this.#child.kill('SIGKILL');\n this.#child.stdin!.end();\n return await this.#exited;\n } finally {\n clearTimeout(kill);\n }\n })();\n return this.#closing;\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n // ── Requests ─────────────────────────────────────────────────────────\n\n #request(\n method: string,\n params: unknown,\n payload: Uint8Array | undefined,\n options: RequestOptions | undefined,\n ): Promise<{ result: unknown; payload: Buffer }> {\n if (this.#failure && method !== 'shutdown')\n return Promise.reject(this.#failure);\n if (this.#closing && method !== 'shutdown')\n return Promise.reject(\n new Vst3HostError('closed', 'the VST3 host is closing'),\n );\n if (options?.signal?.aborted)\n return Promise.reject(this.#aborted(method, options.signal));\n return new Promise((resolve, reject) => {\n const pending: Pending = {\n id: this.#nextId++,\n method,\n params,\n payload,\n timeout: options?.timeout ?? this.#options.requestTimeout,\n signal: options?.signal,\n resolve,\n reject,\n settled: false,\n };\n if (pending.signal) {\n pending.onAbort = () => {\n // Queued: never sent. Sent: the host finishes it, the answer is\n // dropped, and later requests still wait for it (its timeout\n // still applies).\n const index = this.#queue.indexOf(pending);\n if (index >= 0) this.#queue.splice(index, 1);\n this.#settle(pending, pending !== this.#current);\n reject(this.#aborted(method, pending.signal!));\n };\n pending.signal.addEventListener('abort', pending.onAbort, {\n once: true,\n });\n }\n this.#queue.push(pending);\n this.#pump();\n });\n }\n\n #aborted(method: string, signal: AbortSignal): Vst3HostError {\n return new Vst3HostError('aborted', `${method} was aborted`, undefined, {\n cause: signal.reason,\n });\n }\n\n #settle(pending: Pending, clearTimer = true): void {\n pending.settled = true;\n if (clearTimer) clearTimeout(pending.timer);\n if (pending.onAbort)\n pending.signal?.removeEventListener('abort', pending.onAbort);\n }\n\n #pump(): void {\n if (this.#current || this.#failure) return;\n const next = this.#queue.shift();\n if (!next) return;\n this.#current = next;\n next.timer = setTimeout(() => {\n // The host's state is unknown after a request it never finished.\n const error = new Vst3HostError(\n 'timeout',\n `${next.method} did not finish within ${next.timeout} ms; the VST3 host was stopped${this.#stderrTail()}`,\n { method: next.method, timeout: next.timeout },\n );\n if (!next.settled) {\n this.#settle(next);\n next.reject(error);\n }\n this.#fail(error);\n this.#child.kill('SIGKILL');\n }, next.timeout);\n const frame = encodeFrame(\n { id: next.id, method: next.method, params: next.params },\n next.payload,\n );\n for (const part of frame) this.#child.stdin!.write(part);\n }\n\n #receive(frame: Frame): void {\n const header = frame.header as {\n id?: number;\n event?: string;\n params?: unknown;\n result?: unknown;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n };\n };\n if (typeof header.event === 'string') {\n if (header.event === 'hello')\n this.#hello?.(header.params as HostIdentity);\n else if (header.event === 'protocolError')\n this.#fail(\n new Vst3HostError(\n 'protocol-error',\n `the host rejected the request stream: ${(header.params as { message: string }).message}`,\n ),\n );\n else this.#events.emit(header.event, header.params);\n return;\n }\n const pending = this.#current;\n if (!pending || header.id !== pending.id) {\n this.#fail(\n new Vst3HostError(\n 'protocol-error',\n `the host answered request ${header.id}, not ${pending?.id}`,\n ),\n );\n this.#child.kill('SIGKILL');\n return;\n }\n this.#current = undefined;\n clearTimeout(pending.timer);\n if (!pending.settled) {\n this.#settle(pending);\n if (header.error)\n pending.reject(\n new Vst3HostError(\n header.error.code as Vst3HostError['code'],\n header.error.message,\n header.error.details,\n ),\n );\n else pending.resolve({ result: header.result, payload: frame.payload });\n }\n this.#pump();\n }\n\n #fail(error: Vst3HostError): void {\n this.#failure ??= error;\n const failed = [...(this.#current ? [this.#current] : []), ...this.#queue];\n this.#current = undefined;\n this.#queue = [];\n for (const pending of failed)\n if (!pending.settled) {\n this.#settle(pending);\n pending.reject(this.#failure);\n }\n }\n\n #stderrTail(): string {\n const tail = this.#stderr.trim().split('\\n').slice(-12).join('\\n');\n return tail ? `\\nhost stderr:\\n${tail}` : '';\n }\n\n async #withTimeout<T>(\n promise: Promise<T>,\n timeout: number,\n signal: AbortSignal | undefined,\n what: string,\n ): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n let onAbort: (() => void) | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Vst3HostError(\n 'timeout',\n `the VST3 host did not ${what} within ${timeout} ms${this.#stderrTail()}`,\n ),\n ),\n timeout,\n );\n if (signal) {\n onAbort = () => reject(this.#aborted(what, signal));\n if (signal.aborted) onAbort();\n else signal.addEventListener('abort', onAbort, { once: true });\n }\n }),\n ]);\n } finally {\n clearTimeout(timer);\n if (onAbort) signal?.removeEventListener('abort', onAbort);\n }\n }\n}\n","// The small part of npm the host needs without npm itself: a version's\n// metadata, its tarball checked against the registry's SHA-512 integrity,\n// and files read out of the tarball without extracting it.\nimport { createHash, timingSafeEqual } from 'node:crypto';\nimport { gunzipSync } from 'node:zlib';\n\nexport const NPM_REGISTRY = 'https://registry.npmjs.org';\n\nexport type Fetch = typeof fetch;\n\nexport interface VersionMetadata {\n name: string;\n version: string;\n dist: { tarball: string; integrity: string };\n optionalDependencies?: Record<string, string>;\n}\n\n/** Never \"missing\" for anything but the registry's 404: a network,\n authentication or server error is an error. */\nexport class RegistryError extends Error {\n override readonly name = 'RegistryError';\n readonly status: number | undefined;\n constructor(message: string, status?: number, options?: ErrorOptions) {\n super(message, options);\n this.status = status;\n }\n}\n\nexport function versionUrl(\n registry: string,\n name: string,\n version: string,\n): string {\n return `${registry.replace(/\\/$/, '')}/${name.replace('/', '%2f')}/${encodeURIComponent(version)}`;\n}\n\n/** The version's metadata, or null when the registry says it does not exist. */\nexport async function versionMetadata(\n name: string,\n version: string,\n {\n registry = NPM_REGISTRY,\n fetcher = fetch,\n }: { registry?: string; fetcher?: Fetch } = {},\n): Promise<VersionMetadata | null> {\n const url = versionUrl(registry, name, version);\n let response: Response;\n try {\n response = await fetcher(url, {\n headers: { accept: 'application/json' },\n signal: AbortSignal.timeout(30_000),\n });\n } catch (error) {\n throw new RegistryError(\n `could not reach ${url}: ${(error as Error).message}`,\n undefined,\n {\n cause: error,\n },\n );\n }\n if (response.status === 404) return null;\n if (!response.ok)\n throw new RegistryError(\n `${url} answered HTTP ${response.status}`,\n response.status,\n );\n let metadata: VersionMetadata;\n try {\n metadata = (await response.json()) as VersionMetadata;\n } catch (error) {\n throw new RegistryError(`${url} did not answer JSON`, response.status, {\n cause: error,\n });\n }\n if (\n metadata.name !== name ||\n metadata.version !== version ||\n !metadata.dist?.integrity?.startsWith('sha512-') ||\n typeof metadata.dist.tarball !== 'string'\n )\n throw new RegistryError(\n `${url} has no ${name}@${version} with a SHA-512 integrity`,\n );\n return metadata;\n}\n\nexport function integrityOf(bytes: Uint8Array): string {\n return `sha512-${createHash('sha512').update(bytes).digest('base64')}`;\n}\n\nexport function matchesIntegrity(\n bytes: Uint8Array,\n integrity: string,\n): boolean {\n if (!integrity.startsWith('sha512-')) return false;\n const expected = Buffer.from(integrity.slice(7), 'base64');\n const actual = createHash('sha512').update(bytes).digest();\n return expected.length === actual.length && timingSafeEqual(expected, actual);\n}\n\n/** The version's tarball, verified against its metadata's integrity. */\nexport async function downloadTarball(\n metadata: VersionMetadata,\n {\n registry = NPM_REGISTRY,\n fetcher = fetch,\n }: { registry?: string; fetcher?: Fetch } = {},\n): Promise<Buffer> {\n const url = new URL(metadata.dist.tarball);\n const allowed = new URL(registry);\n if (url.protocol !== allowed.protocol || url.host !== allowed.host)\n throw new RegistryError(\n `${metadata.name}'s tarball must come from ${allowed.origin}, not ${url.origin}`,\n );\n let response: Response;\n try {\n response = await fetcher(url.href, {\n signal: AbortSignal.timeout(120_000),\n });\n } catch (error) {\n throw new RegistryError(\n `could not download ${url.href}: ${(error as Error).message}`,\n undefined,\n {\n cause: error,\n },\n );\n }\n if (!response.ok)\n throw new RegistryError(\n `${url.href} answered HTTP ${response.status}`,\n response.status,\n );\n const bytes = Buffer.from(await response.arrayBuffer());\n if (!matchesIntegrity(bytes, metadata.dist.integrity))\n throw new RegistryError(\n `${metadata.name}@${metadata.version}'s tarball failed its SHA-512 integrity check`,\n );\n return bytes;\n}\n\nexport interface TarEntry {\n /** Without npm's \"package/\" prefix. */\n path: string;\n mode: number;\n type: 'file' | 'directory' | 'other';\n data: Buffer;\n}\n\n/** Every entry of a gzipped tarball (ustar with pax path records, as npm\n writes them). Links and devices are reported as \"other\". */\nexport function readTarball(\n gzipped: Uint8Array,\n maxBytes = 512 * 1024 * 1024,\n): TarEntry[] {\n const tar = gunzipSync(gzipped, { maxOutputLength: maxBytes });\n const entries: TarEntry[] = [];\n let paxPath: string | undefined;\n for (let offset = 0; offset + 512 <= tar.length;) {\n const header = tar.subarray(offset, offset + 512);\n if (header.every((byte) => byte === 0)) break;\n const field = (start: number, length: number) =>\n header.toString('utf8', start, start + length).split('\\0')[0]!;\n const size = Number.parseInt(field(124, 12).trim() || '0', 8);\n if (\n !Number.isSafeInteger(size) ||\n size < 0 ||\n offset + 512 + size > tar.length\n )\n throw new Error('the tarball is truncated or corrupt');\n const typeFlag = String.fromCharCode(header[156]!);\n const data = tar.subarray(offset + 512, offset + 512 + size);\n offset += 512 + Math.ceil(size / 512) * 512;\n if (typeFlag === 'x') {\n const match = /\\d+ path=([^\\n]*)\\n/.exec(data.toString('utf8'));\n paxPath = match?.[1];\n continue;\n }\n if (typeFlag === 'g') continue;\n const prefix = field(345, 155);\n let path =\n paxPath ?? (prefix ? `${prefix}/${field(0, 100)}` : field(0, 100));\n paxPath = undefined;\n path = path.replace(/^package\\//, '');\n entries.push({\n path,\n mode: Number.parseInt(field(100, 8).trim() || '0', 8),\n type:\n typeFlag === '0' || typeFlag === '\\0'\n ? 'file'\n : typeFlag === '5'\n ? 'directory'\n : 'other',\n data: Buffer.from(data),\n });\n }\n return entries;\n}\n","// Makes an executable available, for a source checkout (and, explicitly,\n// for an installed package that should build its own):\n//\n// prebuilt (default) the published executable of this exact version, if\n// its native inputs match these sources; in a source\n// checkout, a local build otherwise (an unpublished\n// version, or modified native sources)\n// published the same published executable, and nothing else: an\n// error if there is none (CI, which planned on it)\n// source always a local build\n// artifact a CI artifact (SOUNDOR_VST3_HOST_ARTIFACT), which must\n// match this checkout exactly; never anything else\n//\n// A network, registry or integrity failure is an error, never \"unpublished\".\nimport { spawnSync } from 'node:child_process';\nimport { createHash, randomUUID } from 'node:crypto';\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, relative } from 'node:path';\n\nimport { Vst3HostError } from './errors.ts';\nimport { nativeInputs } from './inputs.ts';\nimport {\n type Fetch,\n NPM_REGISTRY,\n downloadTarball,\n readTarball,\n versionMetadata,\n} from './npm.ts';\nimport { PROTOCOL_VERSION } from './protocol.ts';\nimport {\n PROVENANCE_FILE,\n type Provenance,\n checkedProvenance,\n sha256,\n sha256File,\n} from './provenance.ts';\nimport { type Selection, resolveHost, selectionFile } from './resolve.ts';\nimport {\n type Target,\n currentPlatform,\n describePlatform,\n isSourceWorkspace,\n ownManifest,\n packageRoot,\n targetFor,\n} from './targets.ts';\n\nexport type BuildMode = 'prebuilt' | 'published' | 'source';\n\nexport interface PrepareOptions {\n mode?: BuildMode;\n /** A CI artifact directory (an executable and its soundor-vst3-host.json).\n Default: SOUNDOR_VST3_HOST_ARTIFACT. */\n artifact?: string;\n /** The commit an artifact must come from. Default: GITHUB_SHA when set. */\n commit?: string;\n /** Default: SOUNDOR_CACHE, else the user cache directory. */\n cache?: string;\n registry?: string;\n fetcher?: Fetch;\n /** Default: console.log. */\n log?: (message: string) => void;\n root?: string;\n /** @internal Replaces the CMake build (tests). */\n build?: (reason: string) => PreparedHost;\n}\n\nexport interface PreparedHost {\n executable: string;\n mode: 'prebuilt' | 'source' | 'artifact';\n reason: string;\n sourceFingerprint: string;\n}\n\nexport function cacheRoot(): string {\n if (process.env.SOUNDOR_CACHE) return process.env.SOUNDOR_CACHE;\n if (process.platform === 'win32')\n return join(process.env.LOCALAPPDATA ?? homedir(), 'Soundor', 'cache');\n if (process.platform === 'darwin')\n return join(homedir(), 'Library', 'Caches', 'Soundor');\n return join(\n process.env.XDG_CACHE_HOME || join(homedir(), '.cache'),\n 'soundor',\n );\n}\n\nfunction currentTarget(): Target {\n const platform = currentPlatform();\n const target = targetFor(platform);\n if (!target)\n throw new Vst3HostError(\n 'unsupported-platform',\n `@soundor/vst3-host has no executable for ${describePlatform(platform)}`,\n );\n return target;\n}\n\nfunction writeAtomically(\n path: string,\n data: string | Uint8Array,\n mode?: number,\n): void {\n const partial = `${path}.${randomUUID()}.part`;\n writeFileSync(partial, data);\n if (mode !== undefined) chmodSync(partial, mode);\n renameSync(partial, path);\n}\n\nexport async function prepareHost(\n options: PrepareOptions = {},\n): Promise<PreparedHost> {\n const root = options.root ?? packageRoot;\n const log = options.log ?? ((message: string) => console.log(message));\n const workspace = isSourceWorkspace(root);\n const target = currentTarget();\n const { fingerprint, version } = nativeInputs(root);\n const artifact = options.artifact ?? process.env.SOUNDOR_VST3_HOST_ARTIFACT;\n const build =\n options.build ??\n ((reason: string) => buildFromSource(root, fingerprint, reason, log));\n\n const select = (prepared: PreparedHost): PreparedHost => {\n log(\n `VST3 host: ${prepared.executable} (${prepared.mode}: ${prepared.reason})`,\n );\n if (workspace) {\n mkdirSync(join(root, 'build'), { recursive: true });\n const selection: Selection = {\n mode: prepared.mode,\n executable: relative(root, prepared.executable),\n sha256: sha256File(prepared.executable),\n sourceFingerprint: fingerprint,\n version,\n target: target.target,\n reason: prepared.reason,\n };\n writeAtomically(\n selectionFile(root),\n `${JSON.stringify(selection, null, 2)}\\n`,\n );\n }\n return prepared;\n };\n\n if (artifact) {\n const executable = checkArtifact(artifact, {\n target,\n version,\n fingerprint,\n commit: options.commit ?? process.env.GITHUB_SHA,\n });\n return select({\n executable,\n mode: 'artifact',\n reason: `the CI artifact in ${artifact}, matching this checkout`,\n sourceFingerprint: fingerprint,\n });\n }\n\n const mode = options.mode ?? 'prebuilt';\n if (mode !== 'source' && !workspace) {\n // An installed package: its platform package, nothing else.\n const resolved = resolveHost({ root });\n return {\n executable: resolved.executable,\n mode: 'prebuilt',\n reason: resolved.reason,\n sourceFingerprint: resolved.provenance?.sourceFingerprint ?? fingerprint,\n };\n }\n\n if (mode !== 'source') {\n const cache = options.cache ?? cacheRoot();\n let published: PreparedHost | 'unpublished' | 'different';\n try {\n published = await publishedExecutable({\n target,\n version,\n fingerprint,\n cache,\n registry: options.registry ?? NPM_REGISTRY,\n fetcher: options.fetcher ?? fetch,\n });\n } catch (error) {\n throw new Vst3HostError(\n 'selection-failed',\n `could not check the published VST3 host executable (${(error as Error).message}). Build it instead with mode \"source\" (pnpm --filter @soundor/vst3-host build:native).`,\n undefined,\n { cause: error },\n );\n }\n if (typeof published === 'object') return select(published);\n const reason =\n published === 'unpublished'\n ? `${target.package}@${version} is not published`\n : `the native sources differ from ${target.package}@${version}'s`;\n if (mode === 'published')\n throw new Vst3HostError(\n 'selection-failed',\n `no published VST3 host executable to use: ${reason}`,\n );\n log(`VST3 host: building from source (${reason})`);\n return select(build(`${reason}; built from source`));\n }\n\n return select(build('built from source as requested'));\n}\n\n/** What CI's planning job decides from, before anything is built: whether the\n published executable can be used, or this checkout's must be built. */\nexport async function selectHost(\n options: Pick<PrepareOptions, 'cache' | 'registry' | 'fetcher' | 'root'> = {},\n): Promise<{ mode: 'prebuilt' | 'source'; reason: string }> {\n const target = currentTarget();\n const { fingerprint, version } = nativeInputs(options.root ?? packageRoot);\n const published = await publishedExecutable({\n target,\n version,\n fingerprint,\n cache: options.cache ?? cacheRoot(),\n registry: options.registry ?? NPM_REGISTRY,\n fetcher: options.fetcher ?? fetch,\n });\n if (typeof published === 'object')\n return { mode: 'prebuilt', reason: published.reason };\n return {\n mode: 'source',\n reason:\n published === 'unpublished'\n ? `${target.package}@${version} is not published`\n : `the native sources differ from ${target.package}@${version}'s`,\n };\n}\n\nasync function publishedExecutable(context: {\n target: Target;\n version: string;\n fingerprint: string;\n cache: string;\n registry: string;\n fetcher: Fetch;\n}): Promise<PreparedHost | 'unpublished' | 'different'> {\n const { target, version, fingerprint } = context;\n // npm versions are immutable: a verified download is reused offline.\n const directory = join(\n context.cache,\n 'vst3-host',\n 'prebuilt',\n version,\n target.target,\n );\n const cachedProvenance = join(directory, PROVENANCE_FILE);\n if (existsSync(cachedProvenance)) {\n const provenance = checkedProvenance(\n JSON.parse(readFileSync(cachedProvenance, 'utf8')),\n cachedProvenance,\n );\n const executable = join(directory, provenance.executable.path);\n if (\n existsSync(executable) &&\n sha256File(executable) === provenance.executable.sha256\n ) {\n if (provenance.sourceFingerprint !== fingerprint) return 'different';\n return {\n executable,\n mode: 'prebuilt',\n reason: `${target.package}@${version}, cached in ${directory}; its native inputs match`,\n sourceFingerprint: fingerprint,\n };\n }\n }\n\n const metadata = await versionMetadata(target.package, version, context);\n if (metadata === null) return 'unpublished';\n const tarball = await downloadTarball(metadata, context);\n const entries = readTarball(tarball);\n const record = entries.find((entry) => entry.path === PROVENANCE_FILE);\n if (!record)\n throw new Error(`${target.package}@${version} has no ${PROVENANCE_FILE}`);\n const provenance = checkedProvenance(\n JSON.parse(record.data.toString('utf8')),\n `${target.package}@${version}/${PROVENANCE_FILE}`,\n );\n if (\n provenance.version !== version ||\n provenance.target !== target.target ||\n provenance.protocol !== PROTOCOL_VERSION\n )\n throw new Error(\n `${target.package}@${version} describes another build (${provenance.target} ${provenance.version})`,\n );\n const binary = entries.find(\n (entry) => entry.path === provenance.executable.path,\n );\n if (!binary || sha256(binary.data) !== provenance.executable.sha256)\n throw new Error(\n `${target.package}@${version}'s executable does not match its provenance`,\n );\n mkdirSync(join(directory, 'bin'), { recursive: true });\n const executable = join(directory, provenance.executable.path);\n writeAtomically(executable, binary.data, 0o755);\n writeAtomically(cachedProvenance, `${JSON.stringify(provenance, null, 2)}\\n`);\n if (provenance.sourceFingerprint !== fingerprint) return 'different';\n return {\n executable,\n mode: 'prebuilt',\n reason: `${target.package}@${version} from npm; its native inputs match`,\n sourceFingerprint: fingerprint,\n };\n}\n\n/** A CI artifact: right target, version, sources and commit, intact. */\nexport function checkArtifact(\n directory: string,\n expected: {\n target: Target;\n version: string;\n fingerprint: string;\n commit?: string;\n },\n): string {\n const file = join(directory, PROVENANCE_FILE);\n if (!existsSync(file))\n throw new Vst3HostError(\n 'selection-failed',\n `${directory} has no ${PROVENANCE_FILE}`,\n );\n let provenance: Provenance;\n try {\n provenance = checkedProvenance(\n JSON.parse(readFileSync(file, 'utf8')),\n file,\n );\n } catch (error) {\n throw new Vst3HostError('selection-failed', (error as Error).message);\n }\n const problems: string[] = [];\n if (provenance.target !== expected.target.target)\n problems.push(\n `it is for ${provenance.target}, not ${expected.target.target}`,\n );\n if (provenance.version !== expected.version)\n problems.push(\n `it is version ${provenance.version}, not ${expected.version}`,\n );\n if (provenance.protocol !== PROTOCOL_VERSION)\n problems.push(\n `it speaks protocol ${provenance.protocol}, not ${PROTOCOL_VERSION}`,\n );\n if (provenance.sourceFingerprint !== expected.fingerprint)\n problems.push(\n 'it was built from other native sources than this checkout has',\n );\n if (expected.commit && provenance.sourceCommit !== expected.commit)\n problems.push(\n `it was built from ${provenance.sourceCommit}, not ${expected.commit}`,\n );\n const executable = join(directory, provenance.executable.path);\n if (!existsSync(executable))\n problems.push(`${provenance.executable.path} is missing`);\n else if (sha256File(executable) !== provenance.executable.sha256)\n problems.push(`${provenance.executable.path} does not match its SHA-256`);\n if (problems.length)\n throw new Vst3HostError(\n 'selection-failed',\n `the VST3 host artifact in ${directory} does not match: ${problems.join('; ')}`,\n );\n // An uploaded artifact loses its file modes.\n if (process.platform !== 'win32') chmodSync(executable, 0o755);\n return executable;\n}\n\n/** The build directory for the current toolchain: a build directory keeps\n the compiler and generator it was configured with. */\nexport function sourceBuildDirectory(root = packageRoot): string {\n const toolchain = [\n process.platform,\n process.arch,\n process.env.CMAKE_GENERATOR ?? '',\n process.env.CC ?? '',\n process.env.CXX ?? '',\n process.env.CMAKE_OSX_ARCHITECTURES ?? '',\n ].join('\\0');\n const id = createHash('sha256').update(toolchain).digest('hex').slice(0, 12);\n return join(root, 'build', `host-${id}`);\n}\n\nfunction run(command: string, args: string[], cwd: string): void {\n const result = spawnSync(command, args, { cwd, stdio: 'inherit' });\n if (result.error)\n throw new Vst3HostError(\n 'selection-failed',\n `could not run ${command}: ${result.error.message} (building the VST3 host needs CMake and a C++20 compiler)`,\n );\n if (result.status !== 0)\n throw new Vst3HostError(\n 'selection-failed',\n `${command} ${args.join(' ')} failed (exit ${result.status})`,\n );\n}\n\n/** An incremental Release build of the executable alone. */\nexport function buildFromSource(\n root: string,\n fingerprint: string,\n reason: string,\n log: (message: string) => void = console.log,\n): PreparedHost {\n const directory = sourceBuildDirectory(root);\n log(`VST3 host: building in ${directory}`);\n run(\n 'cmake',\n ['-S', root, '-B', directory, '-DCMAKE_BUILD_TYPE=Release'],\n root,\n );\n run(\n 'cmake',\n [\n '--build',\n directory,\n '--config',\n 'Release',\n '--target',\n 'soundor-vst3-host',\n ],\n root,\n );\n const executable = join(\n directory,\n 'bin',\n process.platform === 'win32'\n ? 'soundor-vst3-host.exe'\n : 'soundor-vst3-host',\n );\n if (!existsSync(executable))\n throw new Vst3HostError(\n 'selection-failed',\n `the build did not produce ${executable}`,\n );\n return { executable, mode: 'source', reason, sourceFingerprint: fingerprint };\n}\n\n/** For the CLI's `inputs` command and the release scripts. */\nexport function describeInputs(root = packageRoot): {\n fingerprint: string;\n version: string;\n files: string[];\n} {\n const manifest = ownManifest(root);\n const inputs = nativeInputs(root);\n return {\n fingerprint: inputs.fingerprint,\n version: manifest.version,\n files: inputs.files,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#include "Bundle.h"
|
|
2
|
+
|
|
3
|
+
#include "Paths.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <array>
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <filesystem>
|
|
9
|
+
#include <fstream>
|
|
10
|
+
|
|
11
|
+
namespace soundor::vst3host
|
|
12
|
+
{
|
|
13
|
+
namespace fs = std::filesystem;
|
|
14
|
+
|
|
15
|
+
std::string hostArchitecture()
|
|
16
|
+
{
|
|
17
|
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
|
18
|
+
return "arm64";
|
|
19
|
+
#elif defined(__x86_64__) || defined(_M_X64)
|
|
20
|
+
return "x64";
|
|
21
|
+
#elif defined(__i386__) || defined(_M_IX86)
|
|
22
|
+
return "x86";
|
|
23
|
+
#else
|
|
24
|
+
return "unknown";
|
|
25
|
+
#endif
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
namespace
|
|
29
|
+
{
|
|
30
|
+
uint32_t le32(const uint8_t* p)
|
|
31
|
+
{
|
|
32
|
+
return p[0] | (p[1] << 8U) | (p[2] << 16U) | (static_cast<uint32_t>(p[3]) << 24U);
|
|
33
|
+
}
|
|
34
|
+
uint32_t be32(const uint8_t* p)
|
|
35
|
+
{
|
|
36
|
+
return (static_cast<uint32_t>(p[0]) << 24U) | (p[1] << 16U) | (p[2] << 8U) | p[3];
|
|
37
|
+
}
|
|
38
|
+
uint16_t le16(const uint8_t* p)
|
|
39
|
+
{
|
|
40
|
+
return static_cast<uint16_t>(p[0] | (p[1] << 8U));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
std::string machOCpu(uint32_t cpu)
|
|
44
|
+
{
|
|
45
|
+
switch (cpu)
|
|
46
|
+
{
|
|
47
|
+
case 0x01000007:
|
|
48
|
+
return "x64";
|
|
49
|
+
case 0x0100000C:
|
|
50
|
+
return "arm64";
|
|
51
|
+
case 0x00000007:
|
|
52
|
+
return "x86";
|
|
53
|
+
case 0x0000000C:
|
|
54
|
+
return "arm";
|
|
55
|
+
default:
|
|
56
|
+
return "unknown";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} // namespace
|
|
60
|
+
|
|
61
|
+
std::vector<std::string> binaryArchitectures(const std::string& path)
|
|
62
|
+
{
|
|
63
|
+
std::ifstream file(toPath(path), std::ios::binary);
|
|
64
|
+
std::array<uint8_t, 4096> head {};
|
|
65
|
+
file.read(reinterpret_cast<char*>(head.data()), head.size());
|
|
66
|
+
const auto size = static_cast<size_t>(file.gcount());
|
|
67
|
+
if (size < 64)
|
|
68
|
+
return {};
|
|
69
|
+
const uint8_t* p = head.data();
|
|
70
|
+
|
|
71
|
+
// ELF: e_machine.
|
|
72
|
+
if (p[0] == 0x7F && p[1] == 'E' && p[2] == 'L' && p[3] == 'F')
|
|
73
|
+
{
|
|
74
|
+
switch (le16(p + 18))
|
|
75
|
+
{
|
|
76
|
+
case 0x3E:
|
|
77
|
+
return { "x64" };
|
|
78
|
+
case 0xB7:
|
|
79
|
+
return { "arm64" };
|
|
80
|
+
case 0x03:
|
|
81
|
+
return { "x86" };
|
|
82
|
+
case 0x28:
|
|
83
|
+
return { "arm" };
|
|
84
|
+
default:
|
|
85
|
+
return { "unknown" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Universal Mach-O: big-endian fat header, then one entry per slice.
|
|
89
|
+
if (be32(p) == 0xCAFEBABE || be32(p) == 0xCAFEBABF)
|
|
90
|
+
{
|
|
91
|
+
const bool wide = be32(p) == 0xCAFEBABF;
|
|
92
|
+
const uint32_t count = be32(p + 4);
|
|
93
|
+
const size_t entry = wide ? 32 : 20;
|
|
94
|
+
std::vector<std::string> result;
|
|
95
|
+
for (uint32_t i = 0; i < count && 8 + (i + 1) * entry <= size; ++i)
|
|
96
|
+
result.push_back(machOCpu(be32(p + 8 + i * entry)));
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
// Thin 64-bit or 32-bit Mach-O, little-endian.
|
|
100
|
+
if (le32(p) == 0xFEEDFACF || le32(p) == 0xFEEDFACE)
|
|
101
|
+
return { machOCpu(le32(p + 4)) };
|
|
102
|
+
// PE: the COFF header's Machine.
|
|
103
|
+
if (p[0] == 'M' && p[1] == 'Z')
|
|
104
|
+
{
|
|
105
|
+
const uint32_t offset = le32(p + 0x3C);
|
|
106
|
+
if (offset + 6 <= size && p[offset] == 'P' && p[offset + 1] == 'E' && p[offset + 2] == 0
|
|
107
|
+
&& p[offset + 3] == 0)
|
|
108
|
+
{
|
|
109
|
+
switch (le16(p + offset + 4))
|
|
110
|
+
{
|
|
111
|
+
case 0x8664:
|
|
112
|
+
return { "x64" };
|
|
113
|
+
case 0xAA64:
|
|
114
|
+
return { "arm64" };
|
|
115
|
+
case 0xA641: // ARM64EC/ARM64X
|
|
116
|
+
return { "arm64", "x64" };
|
|
117
|
+
case 0x014C:
|
|
118
|
+
return { "x86" };
|
|
119
|
+
default:
|
|
120
|
+
return { "unknown" };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
namespace
|
|
128
|
+
{
|
|
129
|
+
std::string joined(const std::vector<std::string>& items)
|
|
130
|
+
{
|
|
131
|
+
std::string out;
|
|
132
|
+
for (const auto& item : items)
|
|
133
|
+
out += (out.empty() ? "" : ", ") + item;
|
|
134
|
+
return out.empty() ? "none" : out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#if ! defined(__APPLE__)
|
|
138
|
+
// Linux and Windows bundles keep one binary per architecture in
|
|
139
|
+
// Contents/<arch>-<os>/; macOS keeps one (possibly universal) binary.
|
|
140
|
+
std::string folderArchitecture(const std::string& name)
|
|
141
|
+
{
|
|
142
|
+
if (name.starts_with("x86_64-"))
|
|
143
|
+
return "x64";
|
|
144
|
+
if (name.starts_with("aarch64-") || name.starts_with("arm64-") || name.starts_with("arm64ec-")
|
|
145
|
+
|| name.starts_with("arm64x-"))
|
|
146
|
+
return "arm64";
|
|
147
|
+
if (name.starts_with("i386-") || name.starts_with("x86-"))
|
|
148
|
+
return "x86";
|
|
149
|
+
if (name.starts_with("armv7l-") || name.starts_with("armv8l-"))
|
|
150
|
+
return "arm";
|
|
151
|
+
return "unknown";
|
|
152
|
+
}
|
|
153
|
+
#endif
|
|
154
|
+
|
|
155
|
+
BundleCheck fail(std::string code, std::string message)
|
|
156
|
+
{
|
|
157
|
+
BundleCheck check;
|
|
158
|
+
check.code = std::move(code);
|
|
159
|
+
check.message = std::move(message);
|
|
160
|
+
return check;
|
|
161
|
+
}
|
|
162
|
+
} // namespace
|
|
163
|
+
|
|
164
|
+
BundleCheck checkBundle(const std::string& path)
|
|
165
|
+
{
|
|
166
|
+
std::error_code error;
|
|
167
|
+
const fs::path bundle = toPath(path);
|
|
168
|
+
if (! fs::exists(bundle, error))
|
|
169
|
+
return fail("plugin-not-found", "no VST3 plugin exists at " + path);
|
|
170
|
+
if (bundle.extension() != ".vst3")
|
|
171
|
+
return fail("invalid-bundle", path + " is not a VST3 plugin (a .vst3 bundle)");
|
|
172
|
+
|
|
173
|
+
const std::string arch = hostArchitecture();
|
|
174
|
+
BundleCheck check;
|
|
175
|
+
#if defined(_WIN32)
|
|
176
|
+
if (fs::is_regular_file(bundle, error))
|
|
177
|
+
{
|
|
178
|
+
// A single-file plugin (VST 3.6.x layout).
|
|
179
|
+
check.binary = path;
|
|
180
|
+
check.architectures = binaryArchitectures(path);
|
|
181
|
+
}
|
|
182
|
+
#endif
|
|
183
|
+
if (check.binary.empty())
|
|
184
|
+
{
|
|
185
|
+
const fs::path contents = bundle / "Contents";
|
|
186
|
+
if (! fs::is_directory(contents, error))
|
|
187
|
+
return fail("invalid-bundle", path + " has no Contents directory");
|
|
188
|
+
#if defined(__APPLE__)
|
|
189
|
+
const fs::path macos = contents / "MacOS";
|
|
190
|
+
if (fs::is_directory(macos, error))
|
|
191
|
+
for (const auto& entry : fs::directory_iterator(macos, error))
|
|
192
|
+
if (entry.is_regular_file(error))
|
|
193
|
+
{
|
|
194
|
+
check.binary = fromPath(entry.path());
|
|
195
|
+
check.architectures = binaryArchitectures(check.binary);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (check.binary.empty())
|
|
199
|
+
return fail("invalid-bundle", path + " has no binary in Contents/MacOS");
|
|
200
|
+
#else
|
|
201
|
+
#if defined(_WIN32)
|
|
202
|
+
const std::string suffix = "-win";
|
|
203
|
+
const std::string extension = ".vst3";
|
|
204
|
+
const std::string wanted = arch == "arm64" ? "arm64-win" : arch == "x86" ? "x86-win" : "x86_64-win";
|
|
205
|
+
#else
|
|
206
|
+
const std::string suffix = "-linux";
|
|
207
|
+
const std::string extension = ".so";
|
|
208
|
+
const std::string wanted = arch == "arm64" ? "aarch64-linux"
|
|
209
|
+
: arch == "x86" ? "i386-linux"
|
|
210
|
+
: "x86_64-linux";
|
|
211
|
+
#endif
|
|
212
|
+
for (const auto& entry : fs::directory_iterator(contents, error))
|
|
213
|
+
{
|
|
214
|
+
const std::string name = fromPath(entry.path().filename());
|
|
215
|
+
if (! entry.is_directory(error) || ! name.ends_with(suffix))
|
|
216
|
+
continue;
|
|
217
|
+
check.architectures.push_back(folderArchitecture(name));
|
|
218
|
+
if (name != wanted)
|
|
219
|
+
continue;
|
|
220
|
+
const fs::path binary = entry.path() / toPath(fromPath(bundle.stem()) + extension);
|
|
221
|
+
if (fs::exists(binary, error))
|
|
222
|
+
check.binary = fromPath(binary);
|
|
223
|
+
}
|
|
224
|
+
std::sort(check.architectures.begin(), check.architectures.end());
|
|
225
|
+
if (check.binary.empty())
|
|
226
|
+
{
|
|
227
|
+
if (check.architectures.empty())
|
|
228
|
+
return fail("invalid-bundle", path + " has no Contents/<architecture>" + suffix + " binary");
|
|
229
|
+
auto result = fail("incompatible-architecture", path + " has no " + arch + " binary (Contents/" + wanted
|
|
230
|
+
+ "/" + fromPath(bundle.stem()) + extension
|
|
231
|
+
+ "); it provides " + joined(check.architectures)
|
|
232
|
+
+ ", and this host runs as " + arch);
|
|
233
|
+
result.architectures = check.architectures;
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
// The folder may be misnamed: trust the binary's header.
|
|
237
|
+
const auto actual = binaryArchitectures(check.binary);
|
|
238
|
+
if (! actual.empty())
|
|
239
|
+
check.architectures = actual;
|
|
240
|
+
#endif
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (! check.architectures.empty()
|
|
244
|
+
&& std::find(check.architectures.begin(), check.architectures.end(), arch) == check.architectures.end())
|
|
245
|
+
{
|
|
246
|
+
auto result = fail("incompatible-architecture", check.binary + " contains " + joined(check.architectures)
|
|
247
|
+
+ " code, and this host runs as " + arch);
|
|
248
|
+
result.architectures = check.architectures;
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
return check;
|
|
252
|
+
}
|
|
253
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Checks a VST3 bundle before the SDK loads it, so that a missing binary or a
|
|
2
|
+
// binary for another CPU is reported as such rather than as the loader's
|
|
3
|
+
// generic failure (dlopen, LoadLibrary, CFBundle).
|
|
4
|
+
#pragma once
|
|
5
|
+
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace soundor::vst3host
|
|
10
|
+
{
|
|
11
|
+
/** "x64" or "arm64": the architecture this host process runs as. */
|
|
12
|
+
std::string hostArchitecture();
|
|
13
|
+
|
|
14
|
+
/** The CPU architectures a Mach-O, ELF or PE file contains ("x64",
|
|
15
|
+
"arm64", "x86", "arm"; empty when the format is unknown). */
|
|
16
|
+
std::vector<std::string> binaryArchitectures(const std::string& path);
|
|
17
|
+
|
|
18
|
+
struct BundleCheck
|
|
19
|
+
{
|
|
20
|
+
/** Empty when the bundle can be loaded by this host. Otherwise a
|
|
21
|
+
protocol error code: plugin-not-found, invalid-bundle or
|
|
22
|
+
incompatible-architecture. */
|
|
23
|
+
std::string code;
|
|
24
|
+
std::string message;
|
|
25
|
+
/** The binary the loader will open. */
|
|
26
|
+
std::string binary;
|
|
27
|
+
/** Every architecture the bundle provides a binary for. */
|
|
28
|
+
std::vector<std::string> architectures;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
BundleCheck checkBundle(const std::string& path);
|
|
32
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// The native view an editor attaches to inside the host interface's window:
|
|
2
|
+
// an X11 child window, an NSView or a child HWND, placed over the window's
|
|
3
|
+
// editor rectangle. The interface draws around it and never under it.
|
|
4
|
+
#pragma once
|
|
5
|
+
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <optional>
|
|
8
|
+
#include <string>
|
|
9
|
+
|
|
10
|
+
struct SDL_Window;
|
|
11
|
+
|
|
12
|
+
namespace soundor::vst3host
|
|
13
|
+
{
|
|
14
|
+
class EditorArea
|
|
15
|
+
{
|
|
16
|
+
public:
|
|
17
|
+
virtual ~EditorArea() = default;
|
|
18
|
+
|
|
19
|
+
/** nullptr and `error` when the window has no native view to embed in. */
|
|
20
|
+
static std::unique_ptr<EditorArea> create(SDL_Window* window, std::string& error);
|
|
21
|
+
|
|
22
|
+
[[nodiscard]] virtual const char* platformType() const = 0;
|
|
23
|
+
[[nodiscard]] virtual void* handle() const = 0;
|
|
24
|
+
/** In the window's coordinates, from its top left. */
|
|
25
|
+
virtual void setFrame(int x, int y, int width, int height) = 0;
|
|
26
|
+
/** False if the window's resizing has moved the view from there. */
|
|
27
|
+
[[nodiscard]] virtual bool isAt(int x, int y) const
|
|
28
|
+
{
|
|
29
|
+
(void)x, (void)y;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
/** A window manager may show a resized window before the host has drawn
|
|
33
|
+
it, so the window keeps the view around its middle, where the stage
|
|
34
|
+
will centre it. A resize the host asks for keeps its top left: it
|
|
35
|
+
knows where the view stays. */
|
|
36
|
+
virtual void keepTopLeft(bool keep) { (void)keep; }
|
|
37
|
+
|
|
38
|
+
// Live presentation, while the editor's geometry changes: the view
|
|
39
|
+
// stops showing and keeps painting, and the window draws
|
|
40
|
+
// its images in its own frames, so that the window, the interface
|
|
41
|
+
// and the editor always change together. Input does not reach the
|
|
42
|
+
// editor meanwhile. Platforms that present a window's views together
|
|
43
|
+
// do not need it.
|
|
44
|
+
|
|
45
|
+
/** RGBX pixels, 32 bits each, valid until the next takeImage(). */
|
|
46
|
+
struct Image
|
|
47
|
+
{
|
|
48
|
+
const void* pixels = nullptr;
|
|
49
|
+
int width = 0;
|
|
50
|
+
int height = 0;
|
|
51
|
+
int pitch = 0;
|
|
52
|
+
/** The first image since the view showed, with where it was in the
|
|
53
|
+
window: drawn there, it can take the view's place unseen. If the
|
|
54
|
+
view was partly out of sight it holds only what showed (partial):
|
|
55
|
+
this part of it. */
|
|
56
|
+
bool first = false;
|
|
57
|
+
bool partial = false;
|
|
58
|
+
int validX = 0;
|
|
59
|
+
int validY = 0;
|
|
60
|
+
int validWidth = 0;
|
|
61
|
+
int validHeight = 0;
|
|
62
|
+
int shownX = 0;
|
|
63
|
+
int shownY = 0;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** False where the platform has no live presentation. */
|
|
67
|
+
[[nodiscard]] virtual bool beginLive() { return false; }
|
|
68
|
+
virtual void endLive() {}
|
|
69
|
+
/** The editor's newest whole image, if it has a new one. An editor
|
|
70
|
+
that has been resized and not yet painted has none: its last
|
|
71
|
+
image stands in for it, as a browser's layer does. */
|
|
72
|
+
[[nodiscard]] virtual std::optional<Image> takeImage() { return std::nullopt; }
|
|
73
|
+
/** The window can only be moved, resized and closed, not maximized or
|
|
74
|
+
minimized. */
|
|
75
|
+
virtual void allowOnlyMoveResizeClose() {}
|
|
76
|
+
|
|
77
|
+
/** Stops the view showing, once the window draws the editor's image.
|
|
78
|
+
False while it has no image to draw yet. */
|
|
79
|
+
virtual bool hideView() { return true; }
|
|
80
|
+
/** The window around the view has just been resized. */
|
|
81
|
+
virtual void windowChanged() {}
|
|
82
|
+
/** The editor was just given this size: images of another size, or of
|
|
83
|
+
this one before it is painted, are not whole. */
|
|
84
|
+
virtual void editorGiven(int width, int height) { (void)width, (void)height; }
|
|
85
|
+
/** False while the editor has not painted the size it last took: it
|
|
86
|
+
is not asked for another yet, however quickly they come. */
|
|
87
|
+
[[nodiscard]] virtual bool settled() const { return true; }
|
|
88
|
+
/** False while the editor's view would show a size not yet painted:
|
|
89
|
+
live presentation goes on until it can hand the view back. */
|
|
90
|
+
[[nodiscard]] virtual bool whole() const { return true; }
|
|
91
|
+
};
|
|
92
|
+
} // namespace soundor::vst3host
|