@vessel-dsp/core 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +6 -0
  3. package/package.json +56 -0
  4. package/src/editor/commands.ts +344 -0
  5. package/src/editor/factory.ts +148 -0
  6. package/src/editor/history.ts +142 -0
  7. package/src/editor/index.ts +11 -0
  8. package/src/editor/layout.ts +207 -0
  9. package/src/formats/circuit-json/serializer.ts +1410 -0
  10. package/src/formats/document.ts +274 -0
  11. package/src/formats/interchange/parser.ts +1165 -0
  12. package/src/formats/interchange/serializer.ts +594 -0
  13. package/src/formats/ltspice/catalog.ts +181 -0
  14. package/src/formats/ltspice/encoding.ts +151 -0
  15. package/src/formats/ltspice/parser.ts +432 -0
  16. package/src/formats/ltspice/serializer.ts +169 -0
  17. package/src/formats/schx/catalog.ts +439 -0
  18. package/src/formats/schx/parser.ts +261 -0
  19. package/src/formats/schx/runtime-descriptors.ts +502 -0
  20. package/src/formats/schx/serializer.ts +211 -0
  21. package/src/formats/schx/transforms.ts +38 -0
  22. package/src/formats/spice/parser.ts +373 -0
  23. package/src/formats/spice/serializer.ts +43 -0
  24. package/src/index.ts +205 -0
  25. package/src/model/connectivity.ts +239 -0
  26. package/src/model/netlist.ts +375 -0
  27. package/src/model/properties.ts +101 -0
  28. package/src/model/quantity.ts +173 -0
  29. package/src/model/types.ts +309 -0
  30. package/src/model/validation.ts +985 -0
  31. package/src/model/wires.ts +86 -0
  32. package/src/panel/extract.ts +878 -0
  33. package/src/panel/index.ts +39 -0
  34. package/src/panel/knobs.ts +70 -0
  35. package/src/panel/protocol.ts +117 -0
  36. package/src/panel/types.ts +180 -0
  37. package/src/preview/bounds.ts +85 -0
  38. package/src/preview/box-layout.ts +24 -0
  39. package/src/preview/colors.ts +43 -0
  40. package/src/preview/hanging.ts +94 -0
  41. package/src/preview/junctions.ts +94 -0
  42. package/src/preview/label-layout.ts +90 -0
  43. package/src/preview/ports.ts +101 -0
  44. package/src/preview/renderable-wires.ts +113 -0
  45. package/src/preview/routing.ts +15 -0
  46. package/src/preview/snap.ts +104 -0
  47. package/src/preview/symbols/analog-switch.svg +17 -0
  48. package/src/preview/symbols/battery.svg +16 -0
  49. package/src/preview/symbols/bbd.svg +21 -0
  50. package/src/preview/symbols/bjt-npn.svg +16 -0
  51. package/src/preview/symbols/bjt-pnp.svg +17 -0
  52. package/src/preview/symbols/capacitor-electrolytic.svg +13 -0
  53. package/src/preview/symbols/capacitor.svg +12 -0
  54. package/src/preview/symbols/current-source.svg +14 -0
  55. package/src/preview/symbols/delay-ic.svg +22 -0
  56. package/src/preview/symbols/diode-schottky.svg +12 -0
  57. package/src/preview/symbols/diode-zener.svg +12 -0
  58. package/src/preview/symbols/diode.svg +13 -0
  59. package/src/preview/symbols/flipflop.svg +20 -0
  60. package/src/preview/symbols/ground.svg +12 -0
  61. package/src/preview/symbols/ic-block.svg +20 -0
  62. package/src/preview/symbols/ic.svg +19 -0
  63. package/src/preview/symbols/inductor.svg +11 -0
  64. package/src/preview/symbols/jack-input.svg +16 -0
  65. package/src/preview/symbols/jack-output.svg +16 -0
  66. package/src/preview/symbols/jfet-junction-n.svg +17 -0
  67. package/src/preview/symbols/jfet-n.svg +17 -0
  68. package/src/preview/symbols/jfet-p.svg +17 -0
  69. package/src/preview/symbols/label.svg +8 -0
  70. package/src/preview/symbols/led.svg +18 -0
  71. package/src/preview/symbols/mosfet-n.svg +21 -0
  72. package/src/preview/symbols/mosfet-p.svg +21 -0
  73. package/src/preview/symbols/named-wire.svg +11 -0
  74. package/src/preview/symbols/opamp.svg +21 -0
  75. package/src/preview/symbols/optocoupler.svg +30 -0
  76. package/src/preview/symbols/ota.svg +20 -0
  77. package/src/preview/symbols/pentode.svg +25 -0
  78. package/src/preview/symbols/photoresistor.svg +19 -0
  79. package/src/preview/symbols/port.svg +8 -0
  80. package/src/preview/symbols/potentiometer.svg +15 -0
  81. package/src/preview/symbols/power-amp.svg +20 -0
  82. package/src/preview/symbols/rail.svg +11 -0
  83. package/src/preview/symbols/regulator.svg +13 -0
  84. package/src/preview/symbols/relay.svg +20 -0
  85. package/src/preview/symbols/resistor.svg +11 -0
  86. package/src/preview/symbols/svg-content.ts +59 -0
  87. package/src/preview/symbols/switch-3pdt.svg +32 -0
  88. package/src/preview/symbols/switch-rotary.svg +23 -0
  89. package/src/preview/symbols/switch-spdt.svg +16 -0
  90. package/src/preview/symbols/switch-spst.svg +14 -0
  91. package/src/preview/symbols/switch-toggle.svg +14 -0
  92. package/src/preview/symbols/transformer.svg +17 -0
  93. package/src/preview/symbols/triode.svg +17 -0
  94. package/src/preview/symbols/tube-diode.svg +13 -0
  95. package/src/preview/symbols/unsupported.svg +8 -0
  96. package/src/preview/symbols/variable-resistor.svg +13 -0
  97. package/src/preview/symbols/voltage-source.svg +15 -0
  98. package/src/preview/symbols.ts +207 -0
  99. package/src/preview/wire-chains.ts +153 -0
package/src/index.ts ADDED
@@ -0,0 +1,205 @@
1
+ export const VERSION = '0.5.0';
2
+
3
+ export type {
4
+ CircuitDocument,
5
+ CircuitDocumentDevice,
6
+ CircuitDocumentDeviceKind,
7
+ Component,
8
+ ComponentKind,
9
+ ControlApplicabilityPredicate,
10
+ ControlContext,
11
+ ControlGroup,
12
+ ControlInterface,
13
+ ControlInterfaceAssignmentHint,
14
+ ControlInterfaceBinding,
15
+ ControlInterfaceConnector,
16
+ ControlInterfacePolarity,
17
+ ControlInterfaceRole,
18
+ ControlOutput,
19
+ ControlOutputSwitchMode,
20
+ DeviceInterface,
21
+ DeviceInterfaceBinding,
22
+ DeviceInterfaceControl,
23
+ DeviceInterfaceControlKind,
24
+ DocumentMetadata,
25
+ DocumentSource,
26
+ PanelColumnOrder,
27
+ PanelControlKind,
28
+ PanelControlPlacement,
29
+ PanelElementBinding,
30
+ PanelElementPlacement,
31
+ PanelFace,
32
+ PanelGridIndexing,
33
+ PanelGridLayout,
34
+ PanelGridPosition,
35
+ PanelPlacementMetadata,
36
+ PanelRowOrder,
37
+ ParsedQuantity,
38
+ Point,
39
+ PropertyObject,
40
+ PropertyValue,
41
+ Rotation,
42
+ Terminal,
43
+ Warning,
44
+ Wire,
45
+ } from './model/types';
46
+ export { EMPTY_DOCUMENT } from './model/types';
47
+ export {
48
+ isParsedQuantity,
49
+ isPropertyObject,
50
+ propertyBooleanValue,
51
+ propertyNumericValue,
52
+ propertyQuantityValue,
53
+ propertyStringValue,
54
+ propertyValueForSourceAttribute,
55
+ } from './model/properties';
56
+
57
+ export { parseQuantity } from './model/quantity';
58
+
59
+ export type { Connectivity, NodeId, PinRef } from './model/connectivity';
60
+ export { getPinNode, pinKey, resolveConnectivity } from './model/connectivity';
61
+
62
+ export type { NetlistComponent, NetlistView, SpiceLetter } from './model/netlist';
63
+ export { getSpiceLetter, getSpiceNodeOrder, kindForSpiceLetter, toNetlistView } from './model/netlist';
64
+
65
+ export type {
66
+ PropertyRule,
67
+ QuantityRule,
68
+ StringRule,
69
+ ValidationCode,
70
+ ValidationIssue,
71
+ ValidationSeverity,
72
+ } from './model/validation';
73
+ export { getRulesForKind, hasErrors, validateComponent, validateDocument } from './model/validation';
74
+
75
+ export { parseSchx } from './formats/schx/parser';
76
+ export { serializeSchx } from './formats/schx/serializer';
77
+ export { parseSpiceNetlist } from './formats/spice/parser';
78
+ export { serializeSpiceNetlist } from './formats/spice/serializer';
79
+ export type { InterchangeSourceFormat, SerializeInterchangeYamlOptions } from './formats/interchange/serializer';
80
+ export { parseInterchangeYaml } from './formats/interchange/parser';
81
+ export { serializeInterchangeYaml } from './formats/interchange/serializer';
82
+ export type {
83
+ CircuitFormat,
84
+ CircuitDocumentFileFormat,
85
+ ParseCircuitDocumentOptions,
86
+ ParseCircuitDocumentFileOptions,
87
+ SerializeCircuitDocumentFileOptions,
88
+ SerializeVdspCircuitDocumentOptions,
89
+ ConvertCircuitDocumentFileOptions,
90
+ VdspSchemaValidationIssue,
91
+ VdspSchemaValidationResult,
92
+ } from './formats/document';
93
+ export {
94
+ detectCircuitFormat,
95
+ parseCircuitDocument,
96
+ vdspFileExtension,
97
+ isVdspFilename,
98
+ detectCircuitDocumentFileFormat,
99
+ vdspFilenameFromName,
100
+ parseVdspCircuitDocument,
101
+ validateVdspCircuitDocumentSchema,
102
+ parseCircuitDocumentFile,
103
+ serializeCircuitDocumentFile,
104
+ convertCircuitDocumentFile,
105
+ serializeVdspCircuitDocument,
106
+ } from './formats/document';
107
+ export { parseLtspiceAsc } from './formats/ltspice/parser';
108
+ export type { SerializeLtspiceAscOptions } from './formats/ltspice/serializer';
109
+ export { serializeLtspiceAsc } from './formats/ltspice/serializer';
110
+
111
+ export type {
112
+ AnyCircuitElement,
113
+ AnyCircuitElementInput,
114
+ CircuitJson,
115
+ CircuitJsonElement,
116
+ CircuitJsonExport,
117
+ CircuitJsonExportOptions,
118
+ CircuitJsonExportTarget,
119
+ CircuitJsonSchemaValidationIssue,
120
+ CircuitJsonSchemaValidationResult,
121
+ ParseCircuitJsonDocumentOptions,
122
+ CircuitJsonSourceComponent,
123
+ CircuitJsonSourceNet,
124
+ CircuitJsonSourcePort,
125
+ CircuitJsonSourceTrace,
126
+ } from './formats/circuit-json/serializer';
127
+ export {
128
+ parseCircuitJsonDocument,
129
+ serializeCircuitJsonDocument,
130
+ validateCircuitJsonDocument,
131
+ } from './formats/circuit-json/serializer';
132
+
133
+ export type { CreateComponentArgs, DocumentCommand, EditorCommand, EditorState } from './editor';
134
+ export {
135
+ applyDocumentCommand,
136
+ applyEditorCommand,
137
+ buildComponent,
138
+ canRedo,
139
+ canUndo,
140
+ createEditorState,
141
+ resetEditorState,
142
+ tidyDocumentLayout,
143
+ } from './editor';
144
+ export type { TidyLayoutOptions } from './editor';
145
+
146
+ export type { Bounds } from './preview/bounds';
147
+ export { computeComponentBox } from './preview/box-layout';
148
+ export { computeDocumentBounds, viewBoxString } from './preview/bounds';
149
+ export { colorForKind } from './preview/colors';
150
+ export type { SymbolDef } from './preview/symbols';
151
+ export { symbolFor, COMPONENT_KINDS } from './preview/symbols';
152
+ export type { HangingEndpoint } from './preview/hanging';
153
+ export { findHangingEndpoints } from './preview/hanging';
154
+ export { findJunctions } from './preview/junctions';
155
+ export { computeLabelTextBoxLayout, shouldRenderLabelTextBox } from './preview/label-layout';
156
+ export type { Port, WireBodyHit } from './preview/ports';
157
+ export { collectPorts, findNearestPort, findNearestWireBodyHit } from './preview/ports';
158
+ export { buildRenderableWires } from './preview/renderable-wires';
159
+ export { orthogonalPath, pointsToSvg } from './preview/routing';
160
+ export { findSnap } from './preview/snap';
161
+ export { findChainCorners, findWireChain } from './preview/wire-chains';
162
+
163
+ export type {
164
+ ControlState,
165
+ ControlValue,
166
+ DeviceInterfaceProvenance,
167
+ ExternalControlAssignmentHint,
168
+ ExtractedDeviceInterface,
169
+ ExtractedDeviceInterfaceControl,
170
+ JackAudioRole,
171
+ JackPort,
172
+ JackRole,
173
+ Knob,
174
+ KnobControlMode,
175
+ KnobStep,
176
+ KnobTaper,
177
+ KnobValue,
178
+ LedIndicator,
179
+ LedValue,
180
+ Panel,
181
+ PanelMessage,
182
+ SliderControl,
183
+ SliderOrientation,
184
+ SliderRange,
185
+ SliderValue,
186
+ SwitchControl,
187
+ SwitchKind,
188
+ SwitchValue,
189
+ } from './panel';
190
+ export {
191
+ applyControlMessage,
192
+ defaultControlState,
193
+ extractDeviceInterface,
194
+ extractPanel,
195
+ isKnobPositionOnStep,
196
+ isKnob,
197
+ isLed,
198
+ isSlider,
199
+ isSwitch,
200
+ knobStepSize,
201
+ nearestKnobStep,
202
+ PANEL_PROTOCOL_VERSION,
203
+ snapKnobPosition,
204
+ validateMessage,
205
+ } from './panel';
@@ -0,0 +1,239 @@
1
+ import type { CircuitDocument, Point } from './types';
2
+
3
+ export type NodeId = number;
4
+
5
+ export type PinRef = Readonly<{
6
+ componentId: string;
7
+ terminalName: string;
8
+ }>;
9
+
10
+ export type Connectivity = Readonly<{
11
+ pinToNode: ReadonlyMap<string, NodeId>;
12
+ nodeMembers: ReadonlyMap<NodeId, readonly PinRef[]>;
13
+ groundNodeId: NodeId | null;
14
+ nodeCount: number;
15
+ }>;
16
+
17
+ export function pinKey(pin: PinRef): string {
18
+ return `${pin.componentId}:${pin.terminalName}`;
19
+ }
20
+
21
+ export function getPinNode(connectivity: Connectivity, pin: PinRef): NodeId | undefined {
22
+ return connectivity.pinToNode.get(pinKey(pin));
23
+ }
24
+
25
+ class UnionFind {
26
+ private readonly parent = new Map<string, string>();
27
+
28
+ add(x: string): void {
29
+ if (!this.parent.has(x)) {
30
+ this.parent.set(x, x);
31
+ }
32
+ }
33
+
34
+ find(x: string): string {
35
+ this.add(x);
36
+ let cur = x;
37
+ while (true) {
38
+ const par = this.parent.get(cur);
39
+ if (par === undefined || par === cur) {
40
+ return cur;
41
+ }
42
+ const grand = this.parent.get(par);
43
+ if (grand === undefined || grand === par) {
44
+ return par;
45
+ }
46
+ this.parent.set(cur, grand);
47
+ cur = grand;
48
+ }
49
+ }
50
+
51
+ union(a: string, b: string): void {
52
+ const rootA = this.find(a);
53
+ const rootB = this.find(b);
54
+ if (rootA !== rootB) {
55
+ this.parent.set(rootA, rootB);
56
+ }
57
+ }
58
+ }
59
+
60
+ function pointKey(p: Point): string {
61
+ return `${p.x},${p.y}`;
62
+ }
63
+
64
+ function unionTJunctions(uf: UnionFind, doc: CircuitDocument): void {
65
+ const candidates: Point[] = [];
66
+ for (const wire of doc.wires) {
67
+ candidates.push(wire.endpoints[0], wire.endpoints[1]);
68
+ }
69
+ for (const component of doc.components) {
70
+ for (const terminal of component.terminals) {
71
+ candidates.push(terminal.position);
72
+ }
73
+ }
74
+
75
+ for (const point of candidates) {
76
+ for (const wire of doc.wires) {
77
+ const a = wire.endpoints[0];
78
+ const b = wire.endpoints[1];
79
+ if ((point.x === a.x && point.y === a.y) || (point.x === b.x && point.y === b.y)) {
80
+ continue;
81
+ }
82
+ if (pointOnSegment(point, a, b)) {
83
+ uf.union(pointKey(point), pointKey(a));
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ type LeadAxis = Readonly<{ x: number; y: number; orientation: 'vertical' | 'horizontal'; reach: number }>;
90
+
91
+ function computeLeadAxis(terminal: Point, origin: Point): LeadAxis | null {
92
+ const dx = terminal.x - origin.x;
93
+ const dy = terminal.y - origin.y;
94
+ if (dx === 0 && dy === 0) {
95
+ return null;
96
+ }
97
+ if (Math.abs(dy) >= Math.abs(dx)) {
98
+ return { x: terminal.x, y: terminal.y, orientation: 'vertical', reach: origin.y };
99
+ }
100
+ return { x: terminal.x, y: terminal.y, orientation: 'horizontal', reach: origin.x };
101
+ }
102
+
103
+ function pointOnLeadAxis(p: Point, lead: LeadAxis): boolean {
104
+ if (lead.orientation === 'vertical') {
105
+ if (p.x !== lead.x) return false;
106
+ const lo = Math.min(lead.y, lead.reach);
107
+ const hi = Math.max(lead.y, lead.reach);
108
+ return p.y > lo && p.y < hi;
109
+ }
110
+ if (p.y !== lead.y) return false;
111
+ const lo = Math.min(lead.x, lead.reach);
112
+ const hi = Math.max(lead.x, lead.reach);
113
+ return p.x > lo && p.x < hi;
114
+ }
115
+
116
+ function unionLeadTaps(uf: UnionFind, doc: CircuitDocument): void {
117
+ for (const component of doc.components) {
118
+ for (const terminal of component.terminals) {
119
+ const lead = computeLeadAxis(terminal.position, component.origin);
120
+ if (lead === null) {
121
+ continue;
122
+ }
123
+ const terminalKey = pointKey(terminal.position);
124
+ for (const wire of doc.wires) {
125
+ for (const endpoint of wire.endpoints) {
126
+ if (endpoint.x === terminal.position.x && endpoint.y === terminal.position.y) {
127
+ continue;
128
+ }
129
+ if (pointOnLeadAxis(endpoint, lead)) {
130
+ uf.union(pointKey(endpoint), terminalKey);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ function pointOnSegment(p: Point, a: Point, b: Point): boolean {
139
+ const cross = (p.x - a.x) * (b.y - a.y) - (p.y - a.y) * (b.x - a.x);
140
+ if (cross !== 0) {
141
+ return false;
142
+ }
143
+ const minX = Math.min(a.x, b.x);
144
+ const maxX = Math.max(a.x, b.x);
145
+ const minY = Math.min(a.y, b.y);
146
+ const maxY = Math.max(a.y, b.y);
147
+ if (minX === maxX && minY === maxY) {
148
+ return false;
149
+ }
150
+ const inXRange = p.x > minX && p.x < maxX;
151
+ const inYRange = p.y > minY && p.y < maxY;
152
+ if (minX === maxX) {
153
+ return inYRange;
154
+ }
155
+ if (minY === maxY) {
156
+ return inXRange;
157
+ }
158
+ return inXRange && inYRange;
159
+ }
160
+
161
+ export function resolveConnectivity(doc: CircuitDocument): Connectivity {
162
+ const uf = new UnionFind();
163
+ const pinsByPoint = new Map<string, PinRef[]>();
164
+ const groundPoints: string[] = [];
165
+
166
+ for (const component of doc.components) {
167
+ const isGround = component.kind === 'ground';
168
+ for (const terminal of component.terminals) {
169
+ const pk = pointKey(terminal.position);
170
+ uf.add(pk);
171
+ const pin: PinRef = { componentId: component.id, terminalName: terminal.name };
172
+ const existing = pinsByPoint.get(pk);
173
+ if (existing) {
174
+ existing.push(pin);
175
+ } else {
176
+ pinsByPoint.set(pk, [pin]);
177
+ }
178
+ if (isGround) {
179
+ groundPoints.push(pk);
180
+ }
181
+ }
182
+ }
183
+
184
+ for (const wire of doc.wires) {
185
+ const a = pointKey(wire.endpoints[0]);
186
+ const b = pointKey(wire.endpoints[1]);
187
+ uf.union(a, b);
188
+ }
189
+
190
+ unionTJunctions(uf, doc);
191
+ unionLeadTaps(uf, doc);
192
+
193
+ const first = groundPoints[0];
194
+ if (first !== undefined) {
195
+ for (let i = 1; i < groundPoints.length; i += 1) {
196
+ const next = groundPoints[i];
197
+ if (next !== undefined) {
198
+ uf.union(first, next);
199
+ }
200
+ }
201
+ }
202
+ const groundRoot = first !== undefined ? uf.find(first) : null;
203
+
204
+ const rootToNodeId = new Map<string, NodeId>();
205
+ const pinToNode = new Map<string, NodeId>();
206
+ const nodeMembers = new Map<NodeId, PinRef[]>();
207
+ let nextId: NodeId = groundRoot !== null ? 1 : 0;
208
+
209
+ if (groundRoot !== null) {
210
+ rootToNodeId.set(groundRoot, 0);
211
+ nodeMembers.set(0, []);
212
+ }
213
+
214
+ for (const [pointKeyStr, pins] of pinsByPoint) {
215
+ const root = uf.find(pointKeyStr);
216
+ let nodeId = rootToNodeId.get(root);
217
+ if (nodeId === undefined) {
218
+ nodeId = nextId;
219
+ nextId += 1;
220
+ rootToNodeId.set(root, nodeId);
221
+ nodeMembers.set(nodeId, []);
222
+ }
223
+ const members = nodeMembers.get(nodeId);
224
+ if (members === undefined) {
225
+ throw new Error(`unreachable: node ${nodeId} missing from nodeMembers`);
226
+ }
227
+ for (const pin of pins) {
228
+ pinToNode.set(pinKey(pin), nodeId);
229
+ members.push(pin);
230
+ }
231
+ }
232
+
233
+ return {
234
+ pinToNode,
235
+ nodeMembers,
236
+ groundNodeId: groundRoot !== null ? 0 : null,
237
+ nodeCount: rootToNodeId.size,
238
+ };
239
+ }