@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
@@ -0,0 +1,274 @@
1
+ import type { CircuitDocument, DocumentSource } from '../model/types';
2
+ import { parseLtspiceAsc } from './ltspice/parser';
3
+ import { serializeLtspiceAsc } from './ltspice/serializer';
4
+ import { parseSchx } from './schx/parser';
5
+ import { serializeSchx } from './schx/serializer';
6
+ import { parseSpiceNetlist } from './spice/parser';
7
+ import { serializeSpiceNetlist } from './spice/serializer';
8
+ import { parseInterchangeYaml } from './interchange/parser';
9
+ import { serializeInterchangeYaml } from './interchange/serializer';
10
+ import { parseCircuitJsonDocument, serializeCircuitJsonDocument } from './circuit-json/serializer';
11
+
12
+ export type CircuitFormat = 'schx' | 'spice' | 'ltspice-asc';
13
+
14
+ export type CircuitDocumentFileFormat = CircuitFormat | 'vdsp' | 'yaml' | 'circuit-json';
15
+
16
+ export type ParseCircuitDocumentOptions = Readonly<{
17
+ filename?: string;
18
+ format?: CircuitFormat;
19
+ }>;
20
+
21
+ export type ParseCircuitDocumentFileOptions = Readonly<{
22
+ filename: string;
23
+ }>;
24
+
25
+ export type SerializeVdspCircuitDocumentOptions = Readonly<{
26
+ filename?: string;
27
+ source?: DocumentSource;
28
+ }>;
29
+
30
+ export type SerializeCircuitDocumentFileOptions = Readonly<{
31
+ format: CircuitDocumentFileFormat;
32
+ filename?: string;
33
+ }>;
34
+
35
+ export type ConvertCircuitDocumentFileOptions = Readonly<{
36
+ inputFilename: string;
37
+ outputFormat: CircuitDocumentFileFormat;
38
+ outputFilename?: string;
39
+ }>;
40
+
41
+ export type VdspSchemaValidationIssue = Readonly<{
42
+ code: 'vdsp-schema-invalid';
43
+ message: string;
44
+ path?: string;
45
+ }>;
46
+
47
+ export type VdspSchemaValidationResult =
48
+ | Readonly<{
49
+ valid: true;
50
+ document: CircuitDocument;
51
+ errors: readonly [];
52
+ }>
53
+ | Readonly<{
54
+ valid: false;
55
+ errors: readonly VdspSchemaValidationIssue[];
56
+ }>;
57
+
58
+ export const vdspFileExtension = '.vdsp';
59
+
60
+ export function detectCircuitFormat(filename: string): CircuitFormat | null {
61
+ const lower = filename.toLowerCase();
62
+ if (lower.endsWith('.schx')) {
63
+ return 'schx';
64
+ }
65
+ if (lower.endsWith('.cir') || lower.endsWith('.net') || lower.endsWith('.spice')) {
66
+ return 'spice';
67
+ }
68
+ if (lower.endsWith('.asc')) {
69
+ return 'ltspice-asc';
70
+ }
71
+ return null;
72
+ }
73
+
74
+ export function parseCircuitDocument(
75
+ source: string,
76
+ options: ParseCircuitDocumentOptions = {},
77
+ ): CircuitDocument {
78
+ const format = options.format ?? detectFormat(source, options.filename);
79
+ if (format === null) {
80
+ throw new Error('unsupported circuit format: provide a supported filename or explicit format');
81
+ }
82
+
83
+ switch (format) {
84
+ case 'schx':
85
+ return parseSchx(source);
86
+ case 'spice':
87
+ return parseSpiceNetlist(source);
88
+ case 'ltspice-asc':
89
+ return parseLtspiceAsc(source);
90
+ }
91
+ }
92
+
93
+ function detectFormat(source: string, filename: string | undefined): CircuitFormat | null {
94
+ if (filename !== undefined) {
95
+ return detectCircuitFormat(filename);
96
+ }
97
+ const firstLine = source
98
+ .replace(/^/, '')
99
+ .split(/\r?\n/)
100
+ .find((line) => line.trim().length > 0)
101
+ ?.trim() ?? '';
102
+
103
+ if (firstLine.startsWith('<?xml') || firstLine.startsWith('<Schematic')) {
104
+ return 'schx';
105
+ }
106
+ if (firstLine.toUpperCase().startsWith('VERSION ')) {
107
+ return 'ltspice-asc';
108
+ }
109
+ if (firstLine.startsWith('.') || /^[A-Za-z]/.test(firstLine)) {
110
+ return 'spice';
111
+ }
112
+ return null;
113
+ }
114
+
115
+ export function isVdspFilename(filename: string): boolean {
116
+ return filename.trim().toLowerCase().endsWith(vdspFileExtension);
117
+ }
118
+
119
+ export function detectCircuitDocumentFileFormat(filename: string): CircuitDocumentFileFormat | null {
120
+ const lower = filename.trim().toLowerCase();
121
+ if (lower.endsWith('.vdsp')) {
122
+ return 'vdsp';
123
+ }
124
+ if (lower.endsWith('.yaml') || lower.endsWith('.yml')) {
125
+ return 'yaml';
126
+ }
127
+ if (lower.endsWith('.circuit.json')) {
128
+ return 'circuit-json';
129
+ }
130
+ return detectCircuitFormat(filename);
131
+ }
132
+
133
+ export function vdspFilenameFromName(name: string): string {
134
+ const slug = name
135
+ .normalize('NFKD')
136
+ .toLowerCase()
137
+ .replace(/[^a-z0-9]+/g, '-')
138
+ .replace(/^-+|-+$/g, '')
139
+ .replace(/-{2,}/g, '-');
140
+ return `${slug || 'untitled-preset'}${vdspFileExtension}`;
141
+ }
142
+
143
+ export function parseVdspCircuitDocument(source: string): CircuitDocument {
144
+ return parseInterchangeYaml(source);
145
+ }
146
+
147
+ export function validateVdspCircuitDocumentSchema(source: string): VdspSchemaValidationResult {
148
+ try {
149
+ return {
150
+ valid: true,
151
+ document: parseVdspCircuitDocument(source),
152
+ errors: [],
153
+ };
154
+ } catch (error) {
155
+ const message = error instanceof Error ? error.message : String(error);
156
+ const path = pathFromSchemaMessage(message);
157
+ return {
158
+ valid: false,
159
+ errors: [{
160
+ code: 'vdsp-schema-invalid',
161
+ message,
162
+ ...(path === undefined ? {} : { path }),
163
+ }],
164
+ };
165
+ }
166
+ }
167
+
168
+ export function parseCircuitDocumentFile(
169
+ source: string,
170
+ options: ParseCircuitDocumentFileOptions,
171
+ ): CircuitDocument {
172
+ const format = detectCircuitDocumentFileFormat(options.filename);
173
+ if (format === null) {
174
+ throw new Error(`unsupported circuit document file extension: ${options.filename}`);
175
+ }
176
+ if (format === 'vdsp' || format === 'yaml') {
177
+ return parseInterchangeYaml(source);
178
+ }
179
+ if (format === 'circuit-json') {
180
+ const parsed: unknown = JSON.parse(source);
181
+ return parseCircuitJsonDocument(parsed, options.filename === undefined ? {} : { filename: options.filename });
182
+ }
183
+ return parseCircuitDocument(source, {
184
+ filename: options.filename,
185
+ format,
186
+ });
187
+ }
188
+
189
+ export function serializeCircuitDocumentFile(
190
+ document: CircuitDocument,
191
+ options: SerializeCircuitDocumentFileOptions,
192
+ ): string {
193
+ switch (options.format) {
194
+ case 'vdsp':
195
+ case 'yaml':
196
+ return serializeVdspCircuitDocument(
197
+ document,
198
+ options.filename === undefined ? {} : { filename: options.filename },
199
+ );
200
+ case 'schx':
201
+ return serializeSchx(document);
202
+ case 'ltspice-asc':
203
+ return serializeLtspiceAsc(document);
204
+ case 'spice':
205
+ return serializeSpiceNetlist(document);
206
+ case 'circuit-json':
207
+ return `${JSON.stringify(serializeCircuitJsonDocument(document).elements, null, 2)}\n`;
208
+ }
209
+ }
210
+
211
+ export function convertCircuitDocumentFile(
212
+ source: string,
213
+ options: ConvertCircuitDocumentFileOptions,
214
+ ): string {
215
+ return serializeCircuitDocumentFile(parseCircuitDocumentFile(source, {
216
+ filename: options.inputFilename,
217
+ }), {
218
+ format: options.outputFormat,
219
+ ...(options.outputFilename === undefined ? {} : { filename: options.outputFilename }),
220
+ });
221
+ }
222
+
223
+ export function serializeVdspCircuitDocument(
224
+ document: CircuitDocument,
225
+ options: SerializeVdspCircuitDocumentOptions = {},
226
+ ): string {
227
+ return serializeInterchangeYaml(document, {
228
+ source: vdspSource(document, options),
229
+ });
230
+ }
231
+
232
+ function vdspSource(
233
+ document: CircuitDocument,
234
+ options: SerializeVdspCircuitDocumentOptions,
235
+ ): DocumentSource {
236
+ const source: Record<string, string> = {
237
+ ...(document.source ?? {}),
238
+ ...(options.source ?? {}),
239
+ };
240
+ if (source.format === undefined) {
241
+ source.format = 'interchange';
242
+ }
243
+ if (options.filename !== undefined) {
244
+ source.filename = normalizeVdspFilename(options.filename, document.metadata.name);
245
+ }
246
+ if (source.filename === undefined || source.filename.length === 0) {
247
+ source.filename = vdspFilenameFromName(document.metadata.name);
248
+ }
249
+ return source;
250
+ }
251
+
252
+ function normalizeVdspFilename(filename: string | undefined, fallbackName: string): string {
253
+ const trimmed = filename?.trim() ?? '';
254
+ if (trimmed.length === 0) {
255
+ return vdspFilenameFromName(fallbackName);
256
+ }
257
+ if (isVdspFilename(trimmed)) {
258
+ return trimmed;
259
+ }
260
+ const withoutExtension = trimmed.replace(/\.[^.\\\/]+$/, '');
261
+ return `${withoutExtension || 'untitled-preset'}${vdspFileExtension}`;
262
+ }
263
+
264
+ function pathFromSchemaMessage(message: string): string | undefined {
265
+ const colonIndex = message.indexOf(':');
266
+ if (colonIndex <= 0) {
267
+ return undefined;
268
+ }
269
+ const candidate = message.slice(0, colonIndex);
270
+ if (/^[A-Za-z_][A-Za-z0-9_]*(?:\[\d+\])?(?:\.[A-Za-z_][A-Za-z0-9_]*(?:\[\d+\])?)*$/.test(candidate)) {
271
+ return candidate;
272
+ }
273
+ return undefined;
274
+ }