@vessel-dsp/stompbox
v0.6.14
Published
Headless stompbox drill-layout and preview manifests for VesselDSP .vdsp documents.
Downloads
1,497
Maintainers
Readme
@vessel-dsp/stompbox
Headless stompbox drill-layout and preview manifest helpers for .vdsp
documents parsed by @vessel-dsp/core.
This package does not render UI. It emits headless artifacts that downstream tools can display or save:
- drill-layout manifests;
- drill-template SVG strings in
previewand A4printmodes; - mesh-backed stompbox preview GLB bytes assembled from caller-provided CAD
part GLBs and STEP companions via
hardwareProfileplusbasePath; - orthographic preview SVG views for
top,bottom,left,right, andback; - optional text, SVG, or image decals for brand/model/custom sticker artwork;
- headless pedal state, interaction, and preview-patch helpers for live stompbox previews.
Applications own production part profiles, enclosure profiles, and asset roots.
Keep named style presets in your application or docs layer and pass them through
styleProfile when you want preset-specific default parts or placement rules.
When a .vdsp document already contains mechanical.enclosure,
partProfiles, and panel physical.partProfileId data, use
createStompboxHardwareProfileFromVdsp() to derive a generated-stub hardware
profile for drill layout and SVG preview workflows. Production CAD-quality GLB
assembly still needs an application-provided hardware profile or asset
overrides because portable .vdsp part profiles do not carry full GLB target
metadata.
Drill-template holes are fabrication holes: their circle diameters come from each part profile's panel drill clearance, such as the PJ-629HAN 9.5 mm drill and the DC099 8 mm barrel opening. Preview views and collision checks may use larger visible exterior geometry, such as jack rings, bushings, nuts, and bezels.
When .vdsp physical placement is present, stompbox preserves it as
vdsp-declared. When placement or common enclosure hardware is absent, stompbox
generates deterministic auto-generated placements for knobs, status LED,
bypass footswitch, input/output jacks, and the 9V connector. Set
includePowerJack: false to omit the synthesized 9V connector.
Pass decals to preview or drill-template helpers to place custom text, SVG,
or image artwork on the enclosure. Preview SVG views render the decal content
on the box, preview GLB output includes decal plane nodes with sticker metadata,
drill template preview mode shows decal outlines, and A4 print mode places the
decals in a separate sticker sheet area.
Decals can target the top, left, right, back, or bottom plane. Use
placement: { kind: "grid" } when the sticker should snap to the center of a
face grid cell. The grid uses the requested columns and rows, capped so
each cell remains at least 12 mm wide or tall. For example, a 40 mm wide face
can address at most 3 columns.
const decals = [
{
id: "brand-label",
kind: "text",
text: "FUZZ LAB",
face: "top",
color: "#2563eb",
fontFamily: '"Roboto", sans-serif',
placement: { kind: "grid", columns: 4, rows: 4, column: 4, row: 2 },
sizeMm: { widthMm: 28, heightMm: 7 },
},
{
id: "side-vector",
kind: "svg",
face: "left",
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><path d="M1 5 H9" stroke="currentColor"/></svg>',
color: "#ef4444",
placement: { kind: "grid", columns: 2, rows: 4, column: 2, row: 3 },
sizeMm: { widthMm: 10, heightMm: 8 },
},
{
id: "back-image",
kind: "image",
face: "back",
href: "/artwork/sticker.png",
placement: { kind: "grid", columns: 4, rows: 1, column: 4, row: 1 },
sizeMm: { widthMm: 16, heightMm: 8 },
},
] as const;Helper map
Use FromVdsp helpers when the caller has serialized .vdsp text. Use the
document helpers when the caller has already parsed or edited a
CircuitDocument.
| Need | .vdsp helper | CircuitDocument helper |
| --- | --- | --- |
| Drill hole coordinates and diagnostics | createStompboxDrillLayoutFromVdsp | createStompboxDrillLayout |
| Drill-template manifest | createStompboxDrillTemplateFromVdsp | createStompboxDrillTemplate |
| Drill-template SVG string | createStompboxDrillTemplateSvgFromVdsp | createStompboxDrillTemplateSvg |
| Preview manifest | createStompboxPreviewFromVdsp | createStompboxPreview |
| Orthographic 2D SVG views | createStompboxPreviewSvgViewsFromVdsp | createStompboxPreviewSvgViews |
| Mesh-backed 3D GLB bytes | createStompboxPreviewGlbFromVdsp | createStompboxPreviewGlb |
| Generated-stub hardware profile from .vdsp metadata | createStompboxHardwareProfileFromVdsp | createStompboxHardwareProfileFromDocument |
| Frontend recolor patch | createStompboxAppearancePatch | createStompboxAppearancePatch |
| Resolved appearance alias | resolveStompboxAppearance | resolveStompboxAppearance |
| Source/compiled control surface | parseCircuitDocumentFile then createStompboxControlSurface | createStompboxControlSurface |
| Default live pedal state | createDefaultStompboxPedalStateFromVdsp | createDefaultStompboxPedalState |
| Headless live state store | createStompboxPedalStateStore | createStompboxPedalStateStore |
| Preview state patch | createStompboxPreviewStatePatch | createStompboxPreviewStatePatch |
Generated docs examples are published at:
- 2D preview SVG:
/core/examples/stompbox-mxr-style-preview-top.svg - 3D preview GLB:
/core/examples/stompbox-mxr-style-preview.glb - Drill-template preview SVG:
/core/examples/stompbox-mxr-style-drill-template-preview.svg - Drill-layout JSON:
/core/examples/stompbox-mxr-style-drill-layout.json
Deriving a hardware profile from .vdsp
If the .vdsp source already declares mechanical metadata, stompbox can derive
a minimal hardware profile from it:
import {
createStompboxDrillLayoutFromVdsp,
createStompboxHardwareProfileFromVdsp,
} from "@vessel-dsp/stompbox";
const hardwareProfile = createStompboxHardwareProfileFromVdsp(vdspSource, {
id: "pedal-derived-hardware",
label: "Pedal derived hardware",
});
const layout = createStompboxDrillLayoutFromVdsp(vdspSource, {
hardwareProfile,
});The derived profile maps .vdsp potentiometer/selector, LED, footswitch, audio
jack, DC jack, and enclosure part profiles into generated-stub stompbox
profiles. Pass defaultPartIds, enclosureAssets, or partAssets when the
document needs specific fallback parts or served asset paths.
Providing CAD assets for 3D preview
createStompboxPreviewGlbFromVdsp() and createStompboxPreviewGlb() read GLB
files from disk. Put each part and enclosure GLB under an application-owned
asset root, reference those files from hardwareProfile, and pass that root as
basePath.
import {
createStompboxPreviewGlbFromVdsp,
type StompboxHardwareProfile,
} from "@vessel-dsp/stompbox";
const hardwareProfile: StompboxHardwareProfile = {
id: "my-app-hardware",
label: "My app hardware",
defaultEnclosureId: "box-1590b",
defaultPartIds: {
knob: "my-knob",
largeKnob: "my-knob",
smallKnob: "my-knob",
led: "my-led",
footswitch: "my-footswitch",
audioJack: "my-audio-jack",
dcJack: "my-dc-jack",
},
enclosureProfiles: {
"box-1590b": {
variantId: "box-1590b",
label: "1590B enclosure",
dimensionsMm: { widthMm: 60.5, lengthMm: 111.5, depthMm: 31 },
topFace: { usableRectMm: { x: -29.25, y: -54.75, width: 58.5, height: 109.5 } },
assets: {
glbRelativePath: "enclosures/1590b.glb",
stepRelativePath: "enclosures/1590b.step",
},
},
},
partProfiles: {
"my-knob": {
id: "my-knob",
label: "My knob",
family: "knob",
level: "exterior",
status: "generated-stub",
panelHoleDrillMm: 7.14375,
drillHoleProfileId: "sixteen-mm-pot-9-32",
geometry: { kind: "knob", diameterMm: 20, depthMm: 11, shaftDiameterMm: 6.35 },
assets: {
glbRelativePath: "parts/my-knob.glb",
stepRelativePath: "parts/my-knob.step",
},
},
// Add my-led, my-footswitch, my-audio-jack, and my-dc-jack.
},
};
const glb = createStompboxPreviewGlbFromVdsp(vdspSource, {
hardwareProfile,
basePath: "/absolute/path/to/cad-assets",
});The example above reads /absolute/path/to/cad-assets/parts/my-knob.glb and
/absolute/path/to/cad-assets/enclosures/1590b.glb. Use baseUrl for served
preview references; use basePath when assembling a GLB because the files are
read from the filesystem.
Live pedal state
Stompbox can manage preview state for a pedal instance without owning the UI or audio runtime. Use the state helpers to rotate knobs, light LEDs, depress footswitches, and drive synthesized bypass hardware in the generated preview.
import {
createDefaultStompboxPedalStateFromVdsp,
createStompboxControlSurface,
createStompboxPedalStateStore,
createStompboxPreviewFromVdsp,
} from "@vessel-dsp/stompbox";
import { parseCircuitDocumentFile } from "@vessel-dsp/core";
const document = parseCircuitDocumentFile(vdspSource, { filename: "pedal.vdsp" });
const surface = createStompboxControlSurface(document, {
pedalId: "stage-1",
compiledControls: runtimeControlsFromYourCompiler,
});
const preview = createStompboxPreviewFromVdsp(vdspSource, { hardwareProfile });
const state = createDefaultStompboxPedalStateFromVdsp(vdspSource, {
pedalId: "stage-1",
});
const store = createStompboxPedalStateStore(state, { preview });
store.subscribePreviewPatch((patch) => {
// Apply patch.parts["part-knob-GAIN"], patch.parts["part-led-status"], etc.
// to your SVG elements or loaded GLB node/material objects.
});
store.turnKnob("GAIN", 0.82);
store.pressFootswitch("switch-bypass", true);compiledControls is optional. When present, pass a downstream compiler output
shaped like { id, sourceComponentId, name, kind, value, min, max, step } plus
optional unit, sweep, options, and targets. Stompbox uses it only to
merge audio-bound control metadata with .vdsp source-panel order and to
return headless runtime command descriptions. It does not compile .vdsp or
send worklet messages.
Live GLB previews require semantic state targets on user-provided LED and footswitch part assets. Declare those targets in the part profile so renderers can affect the LED lens and the moving footswitch actuator instead of guessing against the whole part assembly:
const ledPartProfile = {
// ...
stateTargets: {
led: {
lens: {
selector: {
nodeName: "o1.2",
meshNameIncludes: "5mm_led_lens",
},
},
},
},
};
const footswitchPartProfile = {
// ...
stateTargets: {
footswitch: {
actuator: {
selector: {
nodeName: "o1.3",
meshNameIncludes: "plunger",
},
},
travelAxis: "z",
travelMm: 1.2,
},
},
};Validate caller-provided GLBs before using them for live state:
import {
validateStompboxGlbAssetFile,
validateStompboxHardwareProfileAssets,
} from "@vessel-dsp/stompbox";
const ledValidation = validateStompboxGlbAssetFile(
"/cad/parts/led-bezel-lh5/led.step.glb",
ledPartProfile,
);
const profileValidation = validateStompboxHardwareProfileAssets(hardwareProfile, {
basePath: "/cad/parts",
});Missing or ambiguous live targets produce diagnostics such as
missing-state-target-contract, missing-state-target, and
ambiguous-state-target. Static drill layouts and static SVG previews can still
be generated; live GLB preview code should treat those diagnostics as a signal
to avoid whole-part recolor or movement guesses.
For pointer interaction, convert viewer events into state commands, then apply the resulting patch in your renderer:
import {
applyStompboxPreviewInteraction,
createStompboxFootswitchPressCommand,
createStompboxKnobTurnCommand,
createStompboxPreviewStatePatch,
} from "@vessel-dsp/stompbox";
const knobCommand = createStompboxKnobTurnCommand(surface, {
controlId: "GAIN",
position: 0.65,
});
const nextState = applyStompboxPreviewInteraction(store.getSnapshot(), knobCommand);
const patch = createStompboxPreviewStatePatch(preview, nextState, store.getSnapshot());
store.dispatch(knobCommand);
applyPatchToYourViewer(patch);
const bypassCommand = createStompboxFootswitchPressCommand(surface, {
partId: "switch-bypass",
pressed: true,
});
store.dispatch(bypassCommand);Synthesized switch-bypass and led-status follow the pedal-level enabled
state. Source-declared knobs, switches, and LEDs follow their ControlState
entries. A 9V connector is still generated by default for pedal previews and can
be omitted with includePowerJack: false.
React context, hooks, pointer handlers, SVG mutation, Three.js node updates,
and audio/worklet routing remain downstream responsibilities. A React app can
wrap createStompboxPedalStateStore() with its own context and
useSyncExternalStore, but @vessel-dsp/stompbox stays framework-neutral.
2D and 3D preview viewers
For static 2D previews, use createStompboxPreviewSvgViewsFromVdsp() or
createStompboxPreviewSvgViews() and display the returned top, bottom,
left, right, or back SVG string directly in the consuming app.
For model-backed 2D or interactive 3D previews, keep Three.js in the consuming
app. @vessel-dsp/stompbox should only create the preview GLB bytes and
metadata; the app can load that GLB, choose a camera, and add viewer-only
effects such as CAD-style linework.
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { createStompboxPreviewGlbFromVdsp } from "@vessel-dsp/stompbox";
const assembly = createStompboxPreviewGlbFromVdsp(vdspSource, {
hardwareProfile,
basePath: "/absolute/path/to/cad-assets",
});
const previewUrl = URL.createObjectURL(
new Blob([assembly.bytes], { type: assembly.mimeType }),
);
const gltf = await new GLTFLoader().loadAsync(previewUrl);
scene.add(gltf.scene);Use a perspective camera and orbit controls for a 3D preview. For a 2D
model-backed preview, use an orthographic camera aimed at the desired face.
The assembled GLB uses millimeters and stable node names such as
enclosure-box-1590b, part-knob-GAIN, and hole-backing-jack-IN, so the
viewer can frame or select individual parts without parsing source documents.
function setTopPreviewCamera(
camera: THREE.OrthographicCamera,
dimensionsMm: { widthMm: number; lengthMm: number },
) {
const marginMm = 12;
camera.left = -dimensionsMm.widthMm / 2 - marginMm;
camera.right = dimensionsMm.widthMm / 2 + marginMm;
camera.top = dimensionsMm.lengthMm / 2 + marginMm;
camera.bottom = -dimensionsMm.lengthMm / 2 - marginMm;
camera.near = 0.1;
camera.far = 500;
camera.position.set(0, 0, 220);
camera.up.set(0, 1, 0);
camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix();
}
setTopPreviewCamera(camera, assembly.preview.enclosure.dimensionsMm);To render CAD-style border linework, expose a viewer option such as linework
and lineworkColor, then add an EdgesGeometry overlay after loading the GLB.
This is a display-only effect and should not be written back into the stompbox
artifact.
function addCadLinework(root: THREE.Object3D, lineworkColor = "#111827") {
const meshes: THREE.Mesh[] = [];
const material = new THREE.LineBasicMaterial({
color: new THREE.Color(lineworkColor),
transparent: true,
opacity: 0.85,
depthTest: true,
});
root.traverse((object) => {
if (object instanceof THREE.Mesh) {
meshes.push(object);
}
});
for (const object of meshes) {
if (!(object.geometry instanceof THREE.BufferGeometry)) continue;
const edges = new THREE.EdgesGeometry(object.geometry, 35);
const lines = new THREE.LineSegments(edges, material);
lines.name = `${object.name || "mesh"}-cad-linework`;
lines.renderOrder = 10;
object.add(lines);
}
}
const linework = true;
const lineworkColor = "#eb7223";
if (linework) {
addCadLinework(gltf.scene, lineworkColor);
}LineBasicMaterial is usually limited to one device pixel in WebGL. If the
viewer needs heavier strokes, use Three.js LineSegments2 and LineMaterial
from three/addons/lines in the application layer.
Appearance customization
Pass appearance to preview, GLB, SVG-view, or drill-template helpers to style
the generated artifacts without changing .vdsp placement data. state remains
for live values such as knob position, LED on/off, and footswitch pressed state;
appearance is for enclosure, LED, label, template, and non-knob material
hints. Knob bodies keep the material colors from their imported CAD/GLB assets.
Portable .vdsp v3 documents can also self-contain stompbox visual design at
top-level appearance:
appearance:
kind: stompbox
enclosure:
color: "#f8fafc"
strokeColor: "#111827"
defaults:
label:
color: "#2563eb"
fontFamily: Vessel Blockappearance.kind is mutually exclusive: use stompbox for pedal enclosure,
template, part, and label styling, or amp for amp-specific appearance. Helper
options still override embedded .vdsp appearance when both are provided.
import {
createStompboxAppearancePatch,
createStompboxPreviewFromVdsp,
createStompboxPreviewSvgViewsFromVdsp,
} from "@vessel-dsp/stompbox";
const appearance = {
enclosure: { color: "#f97316", strokeColor: "#7c2d12", roughnessFactor: 0.45 },
template: {
guideColor: "#0ea5e9",
foldColor: "#334155",
holeStrokeColor: "#7c3aed",
holeFillColor: "#faf5ff",
centerDotColor: "#581c87",
},
defaults: {
led: { color: "#ef4444", offColor: "#fee2e2" },
label: { color: "#111827" },
},
controls: {
GAIN: {
label: { text: "DRIVE", color: "#ffffff" },
},
LED1: {
led: { color: "#22c55e", offColor: "#064e3b" },
label: { text: "READY", color: "#16a34a" },
},
},
} as const;
const preview = createStompboxPreviewFromVdsp(vdspSource, {
hardwareProfile,
appearance,
});
const views = createStompboxPreviewSvgViewsFromVdsp(vdspSource, {
hardwareProfile,
appearance,
});
const patch = createStompboxAppearancePatch(preview);Preview SVG output includes stable hooks such as data-control-id,
data-part-family, .knob-body, .knob-indicator, .led-lens, .label-text,
.top-panel, .hole, .drill-hole-center-dot, .fold-line, and
.guide-line. Preview GLB output bakes available material colors into copied
GLB materials and writes the same frontend-friendly patch into
asset.extras.appearance. Knob GLB materials are left as imported.
