updated plan

This commit is contained in:
André Michelle
2026-03-19 08:01:17 +01:00
parent 551e8be46e
commit c45e4d762e
2 changed files with 316 additions and 96 deletions
+96 -96
View File
@@ -2990,6 +2990,90 @@
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/@turbo/darwin-64": {
"version": "2.8.18",
"resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.8.18.tgz",
"integrity": "sha512-3QNJ8VR2y1+AZX1HRB2uAvBWHWYpyWMYuDPAPHwhmpC74YweQmPh41dQZ9fkNcgT1eZNgYKf6Q3Hq7i0XK3Ybg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@turbo/darwin-arm64": {
"version": "2.8.18",
"resolved": "https://registry.npmjs.org/@turbo/darwin-arm64/-/darwin-arm64-2.8.18.tgz",
"integrity": "sha512-kiFEHQ+l64yzRtFcBSdHG9s/EF3aEzzmkuR8e1ygv7ol1YnWwz5EQEbZz6Ajt1sNYYRlo0GXw/d1YanVS9KWPA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@turbo/linux-64": {
"version": "2.8.18",
"resolved": "https://registry.npmjs.org/@turbo/linux-64/-/linux-64-2.8.18.tgz",
"integrity": "sha512-GlMHBGfob6pOQv7t6imx11O0WJapVVNcJxw+HkliIr1ez8vr8SskM7njqOtqKBTaooCmGA71BFInNRZNYO9qlg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@turbo/linux-arm64": {
"version": "2.8.18",
"resolved": "https://registry.npmjs.org/@turbo/linux-arm64/-/linux-arm64-2.8.18.tgz",
"integrity": "sha512-ogWEZ8K/NznjnJU1idHYOA62Y7xYyMnt+kKTZzRNJ9Ye2japtjDkixC31yHaA9QJayb+yS5V8gNDjVfh2yGrIw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@turbo/windows-64": {
"version": "2.8.18",
"resolved": "https://registry.npmjs.org/@turbo/windows-64/-/windows-64-2.8.18.tgz",
"integrity": "sha512-29g6qryc18ls7BKUmVsjjHAbx2eDgab5CUSPhbCKZYCBfzaW+i79wCzyVoC0NJ8IRNwbLch2QEzgGEoRC6uGOQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@turbo/windows-arm64": {
"version": "2.8.18",
"resolved": "https://registry.npmjs.org/@turbo/windows-arm64/-/windows-arm64-2.8.18.tgz",
"integrity": "sha512-hLsgNDzDDO+Tq2YJ9uvmB2PFTYCgeNj6cyAOOBZP9rsOx0kxNBo4/OvDIgYl/cfo7wsoRBxNQ9giYR+rZMcAjA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@tweenjs/tween.js": {
"version": "25.0.0",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz",
@@ -8948,9 +9032,9 @@
"license": "ISC"
},
"node_modules/nan": {
"version": "2.26.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.26.1.tgz",
"integrity": "sha512-vodKprLlmaKmraa9E/TxHQwpH4eKYTJbLdeQE49pb9GOmrLs68zESjJu0LQOz1W6JwJmftOWD5Ls4dpd/elQtQ==",
"version": "2.26.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz",
"integrity": "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==",
"dev": true,
"license": "MIT",
"optional": true
@@ -11791,107 +11875,23 @@
}
},
"node_modules/turbo": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/turbo/-/turbo-2.8.17.tgz",
"integrity": "sha512-YwPsNSqU2f/RXU/+Kcb7cPkPZARxom4+me7LKEdN5jsvy2tpfze3zDZ4EiGrJnvOm9Avu9rK0aaYsP7qZ3iz7A==",
"version": "2.8.18",
"resolved": "https://registry.npmjs.org/turbo/-/turbo-2.8.18.tgz",
"integrity": "sha512-ZO5aYR8wXIcZ+KRL8W7RFWbqIRDyYHtesF/g9zPMndmq+KkX34qrZgG9M+YswJ6BGX4jlYC1FlJjleKENagJdw==",
"dev": true,
"license": "MIT",
"bin": {
"turbo": "bin/turbo"
},
"optionalDependencies": {
"turbo-darwin-64": "2.8.17",
"turbo-darwin-arm64": "2.8.17",
"turbo-linux-64": "2.8.17",
"turbo-linux-arm64": "2.8.17",
"turbo-windows-64": "2.8.17",
"turbo-windows-arm64": "2.8.17"
"@turbo/darwin-64": "2.8.18",
"@turbo/darwin-arm64": "2.8.18",
"@turbo/linux-64": "2.8.18",
"@turbo/linux-arm64": "2.8.18",
"@turbo/windows-64": "2.8.18",
"@turbo/windows-arm64": "2.8.18"
}
},
"node_modules/turbo-darwin-64": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.8.17.tgz",
"integrity": "sha512-ZFkv2hv7zHpAPEXBF6ouRRXshllOavYc+jjcrYyVHvxVTTwJWsBZwJ/gpPzmOKGvkSjsEyDO5V6aqqtZzwVF+Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/turbo-darwin-arm64": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.8.17.tgz",
"integrity": "sha512-5DXqhQUt24ycEryXDfMNKEkW5TBHs+QmU23a2qxXwwFDaJsWcPo2obEhBxxdEPOv7qmotjad+09RGeWCcJ9JDw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/turbo-linux-64": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.8.17.tgz",
"integrity": "sha512-KLUbz6w7F73D/Ihh51hVagrKR0/CTsPEbRkvXLXvoND014XJ4BCrQUqSxlQ4/hu+nqp1v5WlM85/h3ldeyujuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/turbo-linux-arm64": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.8.17.tgz",
"integrity": "sha512-pJK67XcNJH40lTAjFu7s/rUlobgVXyB3A3lDoq+/JccB3hf+SysmkpR4Itlc93s8LEaFAI4mamhFuTV17Z6wOg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/turbo-windows-64": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.8.17.tgz",
"integrity": "sha512-EijeQ6zszDMmGZLP2vT2RXTs/GVi9rM0zv2/G4rNu2SSRSGFapgZdxgW4b5zUYLVaSkzmkpWlGfPfj76SW9yUg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/turbo-windows-arm64": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.8.17.tgz",
"integrity": "sha512-crpfeMPkfECd4V1PQ/hMoiyVcOy04+bWedu/if89S15WhOalHZ2BYUi6DOJhZrszY+mTT99OwpOsj4wNfb/GHQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+220
View File
@@ -0,0 +1,220 @@
# Custom Parameter Mapping for Werkstatt & Spielwerk
## Goal
Enable `@param` declarations to specify value mapping (linear, exp, int, bool), range, default in mapped space, and optional unit. Parameters are displayed with their mapped values and units on the knobs, and `paramChanged` delivers the mapped value to user code.
---
## @param Format
```
// @param <name> [default] [min max type [unit]]
```
### Parsing Rules
| Format | Interpretation |
|---|---|
| `// @param gain` | unipolar 01, default 0 |
| `// @param gain 0.5` | unipolar 01, default 0.5 |
| `// @param bypass true` | bool, default 1 (true) |
| `// @param bypass false` | bool, default 0 (false) |
| `// @param bypass bool` | bool, default 0 (false) |
| `// @param bypass true bool` | bool, default 1 (true), redundant type allowed |
| `// @param cutoff 1000 20 20000 exp` | exponential 2020000, default 1000 |
| `// @param cutoff 1000 20 20000 exp Hz` | same with unit "Hz" |
| `// @param mode 0 0 3 int` | integer 03, default 0 |
| `// @param time 500 1 2000 linear ms` | linear 12000, default 500, unit "ms" |
Detection logic:
1. If second token is `true` or `false`**bool** (optional trailing `bool` keyword ignored)
2. If second token is `bool`**bool**, default false
3. If exactly 1 token → **unipolar**, default 0
4. If exactly 2 tokens (second is numeric) → **unipolar**, default = token
5. If 5+ tokens → **mapped**: `name default min max type [unit]`
6. Anything else → **error**
### Supported Mapping Types
| Type | ValueMapping | StringMapping | paramChanged receives |
|---|---|---|---|
| `unipolar` (default) | `ValueMapping.unipolar()` | `StringMapping.percent()` | `number` (01) |
| `linear` | `ValueMapping.linear(min, max)` | `StringMapping.numeric({unit})` | `number` (minmax) |
| `exp` | `ValueMapping.exponential(min, max)` | `StringMapping.numeric({unit})` | `number` (minmax) |
| `int` | `ValueMapping.linearInteger(min, max)` | `StringMapping.numeric({unit, fractionDigits: 0})` | `number` (integer) |
| `bool` | `ValueMapping.linearInteger(0, 1)` | `StringMapping.values("", [0, 1], ["Off", "On"])` | `number` (0 or 1) |
Bool uses `linearInteger(0, 1)` so the type stays `number` everywhere — no signature change to `paramChanged`.
---
## Storage
### How openDAW Stores Parameter Values
openDAW stores **mapped values** in typed fields, not unitValues:
- `Float32Field` stores the actual float value (e.g., 1000.0 Hz)
- `Int32Field` stores the actual integer (e.g., 3)
- `BooleanField` stores true/false
The `ValueMapping<T>` converts between the stored value (type T) and unitValue (01) for knob position, automation lanes, and modulation. The field type `T` and the ValueMapping type `T` must match — a type mismatch crashes (e.g., `ValueMapping.bool` on `Float32Field` tries to store `true` in a number field).
### WerkstattParameterBox — Forge Schema Change Required
The `value` and `defaultValue` fields must change from `"unipolar"` constraint to `"any"`:
```typescript
// Before: float32 unipolar — clamps to [0, 1], cannot store mapped values
4: {type: "float32", name: "value", constraints: "unipolar", unit: "%", pointerRules: ParameterPointerRules},
5: {type: "float32", name: "defaultValue", constraints: "unipolar", unit: "%"}
// After: float32 any — stores the mapped value directly
4: {type: "float32", name: "value", constraints: "any", unit: "", pointerRules: ParameterPointerRules},
5: {type: "float32", name: "defaultValue", constraints: "any", unit: ""}
```
This is required because with `ValueMapping.linear(20, 20000)`, calling `setUnitValue(0.5)` stores `10010.0` in the field — the unipolar constraint would clamp this to `1.0`.
**Backward compatible**: existing projects have values in 01 range, which are valid `"any"` values. With `ValueMapping.unipolar()` (identity mapping), behavior is unchanged.
### Bool Uses linearInteger(0, 1), Not ValueMapping.bool
`ValueMapping.bool` returns `boolean`, which cannot be stored in a `Float32Field` (type mismatch crash). Instead, bool parameters use `ValueMapping.linearInteger(0, 1)`:
- Stores `0.0` or `1.0` in the Float32Field ✓
- `paramChanged` receives `0` or `1` (numbers) ✓
- Knob snaps between two positions (`floating()` returns `false`) ✓
- No new box type needed ✓
### Int Uses linearInteger(min, max)
`ValueMapping.linearInteger(min, max)` returns `int` (branded `number`), compatible with `Float32Field`. Integers are stored as floats (e.g., `3.0`). Float32 represents all integers up to 2^23 exactly — sufficient for any practical Werkstatt parameter range.
### No New Boxes Needed
A single `WerkstattParameterBox` with `Float32Field "any"` handles all mapping types:
| Mapping | Field stores | ValueMapping |
|---|---|---|
| unipolar | 0.5 | `unipolar()` |
| linear(20, 20000) | 1000.0 | `linear(20, 20000)` |
| exp(20, 20000) | 1000.0 | `exponential(20, 20000)` |
| int(0, 127) | 64.0 | `linearInteger(0, 127)` |
| bool | 0.0 or 1.0 | `linearInteger(0, 1)` |
### Mapping Metadata
The mapping info (min, max, type, unit) is **not stored in the box**. It is derived from the `@param` comment in the code string at two points:
1. **ScriptCompiler.reconcileParameters** — parses params, stores the mapped default directly in the box
2. **Adapter constructor** — parses params from the code to determine the ValueMapping for each parameter by label
The code is the single source of truth for mapping info.
---
## Changes
### 0. WerkstattParameterBox Forge Schema
Change fields 4 and 5 in `packages/studio/forge-boxes/src/schema/devices/audio-effects/WerkstattParameterBox.ts`:
- `value`: constraint `"unipolar"``"any"`, unit `"%"``""`
- `defaultValue`: constraint `"unipolar"``"any"`, unit `"%"``""`
Then regenerate the box class.
### 1. ScriptCompiler.ts — Extended Parsing
**ParamDeclaration** — extend the interface:
```typescript
interface ParamDeclaration {
label: string
defaultValue: number // in mapped space (e.g., 1000 for Hz)
min: number // mapping range min
max: number // mapping range max
mapping: "unipolar" | "linear" | "exp" | "int" | "bool"
unit: string // display unit (e.g., "Hz", "ms")
}
```
**parseParams** — rewrite to handle the extended format. Return the full declaration including mapping info.
**reconcileParameters** — store the mapped default value directly in the box (the field is now `"any"`, not `"unipolar"`):
```typescript
paramBox.value.setValue(declaration.defaultValue)
paramBox.defaultValue.setValue(declaration.defaultValue)
```
**New helper**`resolveValueMapping(declaration)` returns the appropriate `ValueMapping<number>` based on the declaration's mapping type, min, and max.
**New helper**`resolveStringMapping(declaration)` returns the appropriate `StringMapping<number>` based on the mapping type and unit.
Export both helpers so adapters can reuse them.
### 2. WerkstattDeviceBoxAdapter.ts / SpielwerkDeviceBoxAdapter.ts — Dynamic Mapping
In the `onAdded` callback, instead of hardcoding `ValueMapping.unipolar()`:
1. Parse the params from `box.code.getValue()` (using the compiler's `parseParams`)
2. Find the declaration matching the parameter's label
3. Create the appropriate `ValueMapping` and `StringMapping` from the declaration
```typescript
onAdded: ({box: parameterBox}) => {
const paramBox = asInstanceOf(parameterBox, WerkstattParameterBox)
const label = paramBox.label.getValue()
const declaration = declarations.find(decl => decl.label === label)
const valueMapping = resolveValueMapping(declaration)
const stringMapping = resolveStringMapping(declaration)
this.#parametric.createParameter(paramBox.value, valueMapping, stringMapping, label)
}
```
The `declarations` array is parsed once from `box.code.getValue()` and refreshed on code changes.
### 3. Processors — No Change
`WerkstattDeviceProcessor.parameterChanged` and `SpielwerkDeviceProcessor.parameterChanged` already call `parameter.getValue()`, which returns the mapped value through the `ValueMapping`. Since the adapter now sets the correct mapping, the processor automatically delivers mapped values to `paramChanged`.
### 4. Manual Pages — Update @param Documentation
Update the Werkstatt and Spielwerk manual pages to document the extended `@param` format, supported types, and examples.
### 5. Examples — Update to Use Mapped Parameters
Update the `.js` example files to use the new format where appropriate. For example:
```javascript
// @param cutoff 1000 20 20000 exp Hz
// @param resonance 0.707 0.1 10 linear
```
Instead of the current manual scaling in `paramChanged`.
---
## Backward Compatibility
- `// @param name` and `// @param name default` continue to work as unipolar (01)
- Existing projects with unipolar parameters load unchanged — values in 01 range are valid `"any"` values, and `ValueMapping.unipolar()` is identity
- Forge schema change (unipolar → any) is backward compatible: existing float32 values in [0, 1] are valid in "any" constraint
---
## Reconciliation Behavior
When the user recompiles with changed parameters:
- **Label unchanged, mapping changed** — The unitValue stays. The adapter recreates the parameter with the new ValueMapping. The processor receives a `paramChanged` call with the newly mapped value. If the default value changed, the control is reset to the new default.
- **Label unchanged, default changed** — The value is reset to the new default (converted to unitValue). The processor receives `paramChanged` with the new default.
- **Label added** — New parameter box created, value set to default. Processor receives `paramChanged`.
- **Label removed** — Parameter box deleted, knob removed.
After every recompile, all parameter values are re-pushed to the processor via `paramChanged` (the `#pushAllParameters` call in `#swapProcessor` already handles this).
---
## Validation
The compiler validates `@param` declarations and throws on:
- `default` outside `[min, max]` (with tolerance for floating-point error, e.g., `Math.abs(default - min) > 1e-6`)
- `min >= max` (with same tolerance)
- Unknown mapping type (not one of `linear`, `exp`, `int`, `bool`)
- Malformed syntax (wrong number of tokens, non-numeric values where numbers expected)