@audiofab-io/fv1-core 0.2.3 → 0.4.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.
- package/blocks/ATL_DEVELOPER_REFERENCE.md +156 -0
- package/blocks/control/constant.atl +36 -0
- package/blocks/control/entropy_lfo.atl +74 -0
- package/blocks/control/envelope.atl +121 -0
- package/blocks/control/invert.atl +33 -0
- package/blocks/control/pot.atl +150 -0
- package/blocks/control/power.atl +77 -0
- package/blocks/control/ramp_lfo.atl +122 -0
- package/blocks/control/scale_offset.atl +84 -0
- package/blocks/control/sincos_lfo.atl +126 -0
- package/blocks/control/smoother.atl +48 -0
- package/blocks/control/tremolizer.atl +55 -0
- package/blocks/effects/delay/micro_stutter.atl +77 -0
- package/blocks/effects/delay/mn3011.atl +281 -0
- package/blocks/effects/delay/simple_delay.atl +96 -0
- package/blocks/effects/delay/triple_tap_delay.atl +176 -0
- package/blocks/effects/lo-fi/bit_mangler.atl +74 -0
- package/blocks/effects/lo-fi/chiptune.atl +311 -0
- package/blocks/effects/lo-fi/tape_degrade.atl +181 -0
- package/blocks/effects/modulation/chorus.atl +141 -0
- package/blocks/effects/modulation/chorus_4voice.atl +188 -0
- package/blocks/effects/modulation/flanger.atl +184 -0
- package/blocks/effects/modulation/guitar_synth.atl +350 -0
- package/blocks/effects/modulation/harmonic_trem.atl +129 -0
- package/blocks/effects/modulation/organ_synth.atl +326 -0
- package/blocks/effects/modulation/phaser.atl +300 -0
- package/blocks/effects/pitch/octave_up_down.atl +80 -0
- package/blocks/effects/pitch/pitch_offset.atl +149 -0
- package/blocks/effects/pitch/pitch_offset_dual.atl +197 -0
- package/blocks/effects/pitch/pitch_shift.atl +115 -0
- package/blocks/effects/pitch/sub_octave.atl +100 -0
- package/blocks/effects/reverb/ducking_reverb.atl +145 -0
- package/blocks/effects/reverb/min_reverb.atl +132 -0
- package/blocks/effects/reverb/plate_reverb.atl +344 -0
- package/blocks/effects/reverb/room_reverb.atl +293 -0
- package/blocks/effects/reverb/smear.atl +90 -0
- package/blocks/effects/reverb/spring_reverb.atl +353 -0
- package/blocks/filter/1p_high_pass.atl +63 -0
- package/blocks/filter/1p_low_pass.atl +59 -0
- package/blocks/filter/auto_wah.atl +207 -0
- package/blocks/filter/bbd_loss.atl +79 -0
- package/blocks/filter/shelving_high_pass.atl +76 -0
- package/blocks/filter/shelving_low_pass.atl +76 -0
- package/blocks/filter/svf_2p.atl +116 -0
- package/blocks/gain_mix/crossfade.atl +93 -0
- package/blocks/gain_mix/crossfade2.atl +86 -0
- package/blocks/gain_mix/crossfade3.atl +71 -0
- package/blocks/gain_mix/gainboost.atl +54 -0
- package/blocks/gain_mix/mixer2.atl +76 -0
- package/blocks/gain_mix/mixer3.atl +109 -0
- package/blocks/gain_mix/mixer4.atl +152 -0
- package/blocks/gain_mix/volume.atl +51 -0
- package/blocks/io/adc.atl +53 -0
- package/blocks/io/dac.atl +61 -0
- package/blocks/other/stickynote.atl +24 -0
- package/blocks/other/tone_gen_adjustable.atl +137 -0
- package/blocks/other/tone_gen_fixed.atl +109 -0
- package/dist/blockDiagram/blocks/BlockDirectoryLoader.d.ts +13 -0
- package/dist/blockDiagram/blocks/BlockDirectoryLoader.d.ts.map +1 -0
- package/dist/blockDiagram/blocks/BlockDirectoryLoader.js +44 -0
- package/dist/blockDiagram/blocks/BlockDirectoryLoader.js.map +1 -0
- package/dist/blockDiagram/blocks/BlockRegistry.d.ts +48 -0
- package/dist/blockDiagram/blocks/BlockRegistry.d.ts.map +1 -0
- package/dist/blockDiagram/blocks/BlockRegistry.js +109 -0
- package/dist/blockDiagram/blocks/BlockRegistry.js.map +1 -0
- package/dist/blockDiagram/blocks/TemplateBlock.d.ts +20 -0
- package/dist/blockDiagram/blocks/TemplateBlock.d.ts.map +1 -0
- package/dist/blockDiagram/blocks/TemplateBlock.js +82 -0
- package/dist/blockDiagram/blocks/TemplateBlock.js.map +1 -0
- package/dist/blockDiagram/blocks/base/BaseBlock.d.ts +248 -0
- package/dist/blockDiagram/blocks/base/BaseBlock.d.ts.map +1 -0
- package/dist/blockDiagram/blocks/base/BaseBlock.js +402 -0
- package/dist/blockDiagram/blocks/base/BaseBlock.js.map +1 -0
- package/dist/blockDiagram/builtinBlocks.d.ts +9 -0
- package/dist/blockDiagram/builtinBlocks.d.ts.map +1 -0
- package/dist/blockDiagram/builtinBlocks.js +4912 -0
- package/dist/blockDiagram/builtinBlocks.js.map +1 -0
- package/dist/blockDiagram/compiler/BlockTemplate.d.ts +37 -0
- package/dist/blockDiagram/compiler/BlockTemplate.d.ts.map +1 -0
- package/dist/blockDiagram/compiler/BlockTemplate.js +860 -0
- package/dist/blockDiagram/compiler/BlockTemplate.js.map +1 -0
- package/dist/blockDiagram/compiler/CodeOptimizer.d.ts +75 -0
- package/dist/blockDiagram/compiler/CodeOptimizer.d.ts.map +1 -0
- package/dist/blockDiagram/compiler/CodeOptimizer.js +443 -0
- package/dist/blockDiagram/compiler/CodeOptimizer.js.map +1 -0
- package/dist/blockDiagram/compiler/GraphCompiler.d.ts +63 -0
- package/dist/blockDiagram/compiler/GraphCompiler.d.ts.map +1 -0
- package/dist/blockDiagram/compiler/GraphCompiler.js +656 -0
- package/dist/blockDiagram/compiler/GraphCompiler.js.map +1 -0
- package/dist/blockDiagram/compiler/TopologicalSort.d.ts +63 -0
- package/dist/blockDiagram/compiler/TopologicalSort.d.ts.map +1 -0
- package/dist/blockDiagram/compiler/TopologicalSort.js +268 -0
- package/dist/blockDiagram/compiler/TopologicalSort.js.map +1 -0
- package/dist/blockDiagram/index.d.ts +30 -0
- package/dist/blockDiagram/index.d.ts.map +1 -0
- package/dist/blockDiagram/index.js +29 -0
- package/dist/blockDiagram/index.js.map +1 -0
- package/dist/blockDiagram/types/Block.d.ts +178 -0
- package/dist/blockDiagram/types/Block.d.ts.map +1 -0
- package/dist/blockDiagram/types/Block.js +5 -0
- package/dist/blockDiagram/types/Block.js.map +1 -0
- package/dist/blockDiagram/types/CodeGenContext.d.ts +235 -0
- package/dist/blockDiagram/types/CodeGenContext.d.ts.map +1 -0
- package/dist/blockDiagram/types/CodeGenContext.js +554 -0
- package/dist/blockDiagram/types/CodeGenContext.js.map +1 -0
- package/dist/blockDiagram/types/Connection.d.ts +17 -0
- package/dist/blockDiagram/types/Connection.d.ts.map +1 -0
- package/dist/blockDiagram/types/Connection.js +5 -0
- package/dist/blockDiagram/types/Connection.js.map +1 -0
- package/dist/blockDiagram/types/Graph.d.ts +28 -0
- package/dist/blockDiagram/types/Graph.d.ts.map +1 -0
- package/dist/blockDiagram/types/Graph.js +24 -0
- package/dist/blockDiagram/types/Graph.js.map +1 -0
- package/dist/blockDiagram/types/IR.d.ts +79 -0
- package/dist/blockDiagram/types/IR.d.ts.map +1 -0
- package/dist/blockDiagram/types/IR.js +6 -0
- package/dist/blockDiagram/types/IR.js.map +1 -0
- package/dist/blockDiagram/utils/SpinCADConverter.d.ts +17 -0
- package/dist/blockDiagram/utils/SpinCADConverter.d.ts.map +1 -0
- package/dist/blockDiagram/utils/SpinCADConverter.js +307 -0
- package/dist/blockDiagram/utils/SpinCADConverter.js.map +1 -0
- package/dist/effect/compileEffect.d.ts +51 -0
- package/dist/effect/compileEffect.d.ts.map +1 -0
- package/dist/effect/compileEffect.js +133 -0
- package/dist/effect/compileEffect.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +17 -5
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for block definitions
|
|
3
|
+
* Provides common functionality and structure for all blocks
|
|
4
|
+
*/
|
|
5
|
+
import { IBlockDefinition, BlockPort, BlockParameter, BlockMetadata, ValidationResult, ValidationContext, CodeGenContext } from '../../types/Block.js';
|
|
6
|
+
/**
|
|
7
|
+
* Abstract base class for block definitions
|
|
8
|
+
* Provides common functionality and structure for all blocks
|
|
9
|
+
*
|
|
10
|
+
* Sample Rate Configuration:
|
|
11
|
+
* -------------------------
|
|
12
|
+
* The FV-1 sample rate defaults to 32768 Hz, which gives a maximum delay time of 1.0 second
|
|
13
|
+
* (32768 samples / 32768 Hz = 1.0s).
|
|
14
|
+
*
|
|
15
|
+
* To support different sample rates, override the getSampleRate() method in your block class:
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* class MyBlock extends BaseBlock {
|
|
20
|
+
* protected getSampleRate(): number {
|
|
21
|
+
* return 48000; // Custom sample rate
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* This affects:
|
|
27
|
+
* - timeToSamples() / samplesToTime() conversion
|
|
28
|
+
* - getMaxDelayTime() calculation (32768 samples / sample rate)
|
|
29
|
+
* - All delay-based effects that allocate memory
|
|
30
|
+
*/
|
|
31
|
+
export declare abstract class BaseBlock implements IBlockDefinition {
|
|
32
|
+
static readonly DEFAULT_SAMPLE_RATE = 32768;
|
|
33
|
+
static readonly MAX_DELAY_MEMORY = 32768;
|
|
34
|
+
/**
|
|
35
|
+
* DBLEVEL: Convert linear gain (0.0-1.0) to decibels
|
|
36
|
+
* SpinCAD formula: 20 * log10(linear)
|
|
37
|
+
* @param linear Linear gain value (0.0 to 1.0)
|
|
38
|
+
* @returns Gain in decibels (dB)
|
|
39
|
+
*/
|
|
40
|
+
static linearToDb(linear: number): number;
|
|
41
|
+
/**
|
|
42
|
+
* DBLEVEL: Convert decibels to linear gain (0.0-1.0)
|
|
43
|
+
* SpinCAD formula: 10^(dB/20)
|
|
44
|
+
* @param dB Gain in decibels
|
|
45
|
+
* @returns Linear gain value (0.0 to 1.0)
|
|
46
|
+
*/
|
|
47
|
+
static dbToLinear(dB: number): number;
|
|
48
|
+
abstract readonly type: string;
|
|
49
|
+
abstract readonly category: string;
|
|
50
|
+
readonly subcategory?: string;
|
|
51
|
+
abstract readonly name: string;
|
|
52
|
+
abstract readonly description: string;
|
|
53
|
+
color: string;
|
|
54
|
+
readonly icon?: string;
|
|
55
|
+
width: number;
|
|
56
|
+
private _height;
|
|
57
|
+
protected _inputs: BlockPort[];
|
|
58
|
+
protected _outputs: BlockPort[];
|
|
59
|
+
protected _parameters: BlockParameter[];
|
|
60
|
+
get inputs(): BlockPort[];
|
|
61
|
+
get outputs(): BlockPort[];
|
|
62
|
+
get parameters(): BlockParameter[];
|
|
63
|
+
/**
|
|
64
|
+
* Get block height - automatically calculated based on port count unless overridden
|
|
65
|
+
*/
|
|
66
|
+
get height(): number;
|
|
67
|
+
/**
|
|
68
|
+
* Set custom height (optional - will auto-calculate if not called)
|
|
69
|
+
*/
|
|
70
|
+
protected setHeight(value: number): void;
|
|
71
|
+
/**
|
|
72
|
+
* Auto-calculate and set height based on port count
|
|
73
|
+
* Called automatically after ports are defined
|
|
74
|
+
*/
|
|
75
|
+
protected autoCalculateHeight(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Calculate the minimum height needed to fit all ports
|
|
78
|
+
* Port layout: first port at y=40, then 20px spacing between ports
|
|
79
|
+
* Add 20px padding at bottom
|
|
80
|
+
*/
|
|
81
|
+
protected calculateMinHeight(): number;
|
|
82
|
+
/**
|
|
83
|
+
* Get custom label to display in the center of the block
|
|
84
|
+
* Override this method to provide dynamic labels based on parameters
|
|
85
|
+
* @param parameters Current parameter values for this block instance
|
|
86
|
+
* @returns Label text to display, or null for no label
|
|
87
|
+
*/
|
|
88
|
+
getCustomLabel?(parameters: Record<string, any>): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Generate FV-1 assembly code for this block
|
|
91
|
+
* Blocks should push code to appropriate sections using ctx.pushInitCode(),
|
|
92
|
+
* ctx.pushInputCode(), ctx.pushMainCode(), or ctx.pushOutputCode()
|
|
93
|
+
*/
|
|
94
|
+
abstract generateCode(ctx: CodeGenContext): void;
|
|
95
|
+
/**
|
|
96
|
+
* Validate this block's configuration and connections
|
|
97
|
+
* Default implementation checks required inputs
|
|
98
|
+
*/
|
|
99
|
+
validate(ctx: ValidationContext): ValidationResult;
|
|
100
|
+
/**
|
|
101
|
+
* Convert a code value to display value for a specific parameter
|
|
102
|
+
*/
|
|
103
|
+
getDisplayValue(parameterId: string, codeValue: number): number;
|
|
104
|
+
/**
|
|
105
|
+
* Convert a display value to code value for a specific parameter
|
|
106
|
+
*/
|
|
107
|
+
getCodeValue(parameterId: string, displayValue: number): number;
|
|
108
|
+
/**
|
|
109
|
+
* Get metadata about this block type
|
|
110
|
+
*/
|
|
111
|
+
getMetadata(): BlockMetadata;
|
|
112
|
+
/**
|
|
113
|
+
* Helper: Get a parameter value with type safety
|
|
114
|
+
*/
|
|
115
|
+
protected getParameterValue<T = any>(ctx: CodeGenContext, blockId: string, parameterId: string, defaultValue?: T): T;
|
|
116
|
+
/**
|
|
117
|
+
* Helper: Format a number as FV-1 S1.14 fixed point
|
|
118
|
+
* Used by most FV-1 instructions (RDAX, SOF, MULX, etc.)
|
|
119
|
+
* Range: -2.0 to 1.99993896484
|
|
120
|
+
*/
|
|
121
|
+
protected formatS1_14(value: number): string;
|
|
122
|
+
/**
|
|
123
|
+
* Helper: Format a number as FV-1 S.15 fixed point
|
|
124
|
+
* Used only by CHO SOF instruction
|
|
125
|
+
* Range: -1.0 to 0.99996948242
|
|
126
|
+
*/
|
|
127
|
+
protected formatS15(value: number): string;
|
|
128
|
+
/**
|
|
129
|
+
* Helper: Format a number as FV-1 S.10 fixed point
|
|
130
|
+
* Used by some FV-1 instructions
|
|
131
|
+
* Range: -1.0 to 0.9990234375
|
|
132
|
+
*/
|
|
133
|
+
protected formatS10(value: number): string;
|
|
134
|
+
/**
|
|
135
|
+
* Helper: Format a number as FV-1 S1.9 fixed point
|
|
136
|
+
* Used by some FV-1 instructions
|
|
137
|
+
* Range: -2.0 to 1.998046875
|
|
138
|
+
*/
|
|
139
|
+
protected formatS1_9(value: number): string;
|
|
140
|
+
/**
|
|
141
|
+
* Get the FV-1 sample rate in Hz
|
|
142
|
+
* Override this method to support different sample rates
|
|
143
|
+
* Default: 32768 Hz
|
|
144
|
+
*/
|
|
145
|
+
protected getSampleRate(): number;
|
|
146
|
+
/**
|
|
147
|
+
* Get the maximum delay time in seconds based on available memory and sample rate
|
|
148
|
+
* FV-1 has 32768 samples of delay memory total
|
|
149
|
+
* @returns Maximum delay time in seconds
|
|
150
|
+
*/
|
|
151
|
+
protected getMaxDelayTime(): number;
|
|
152
|
+
/**
|
|
153
|
+
* Helper: Calculate delay samples from time in seconds
|
|
154
|
+
* Uses the configurable sample rate
|
|
155
|
+
*/
|
|
156
|
+
protected timeToSamples(timeSeconds: number): number;
|
|
157
|
+
/**
|
|
158
|
+
* Helper: Calculate time in seconds from sample count
|
|
159
|
+
*/
|
|
160
|
+
protected samplesToTime(samples: number): number;
|
|
161
|
+
/**
|
|
162
|
+
* LENGTHTOTIME: Convert samples to milliseconds (uses getSampleRate())
|
|
163
|
+
* @param samples Number of delay samples
|
|
164
|
+
* @returns Time in milliseconds
|
|
165
|
+
*/
|
|
166
|
+
protected samplesToMs(samples: number): number;
|
|
167
|
+
/**
|
|
168
|
+
* LENGTHTOTIME: Convert milliseconds to samples (uses getSampleRate())
|
|
169
|
+
* @param ms Time in milliseconds
|
|
170
|
+
* @returns Number of delay samples (rounded)
|
|
171
|
+
*/
|
|
172
|
+
protected msToSamples(ms: number): number;
|
|
173
|
+
/**
|
|
174
|
+
* SINLFOFREQ: Convert LFO rate value to frequency in Hz (uses getSampleRate())
|
|
175
|
+
* FV-1 AN-001 formula: f = Kf * Fs / (2^17 * 2*pi)
|
|
176
|
+
* @param rate LFO rate value (0-32767, typically 0-511 for SIN LFO)
|
|
177
|
+
* @returns Frequency in Hz
|
|
178
|
+
*/
|
|
179
|
+
protected lfoRateToHz(rate: number): number;
|
|
180
|
+
/**
|
|
181
|
+
* SINLFOFREQ: Convert frequency in Hz to LFO rate value (uses getSampleRate())
|
|
182
|
+
* FV-1 AN-001 formula: Kf = 2^17 * (2*pi*f / Fs)
|
|
183
|
+
* @param hz Frequency in Hz
|
|
184
|
+
* @returns LFO rate value (0-32767, rounded)
|
|
185
|
+
*/
|
|
186
|
+
protected hzToLfoRate(hz: number): number;
|
|
187
|
+
/**
|
|
188
|
+
* LOGFREQ: Convert frequency in Hz to filter coefficient for RDFX/WRLX/WRHX
|
|
189
|
+
* Used for single-pole filters (low-pass, high-pass, shelving)
|
|
190
|
+
* SpinCAD formula: coefficient = 1 - e^(-2π * frequency / sampleRate)
|
|
191
|
+
* This is the one-pole exponential filter coefficient
|
|
192
|
+
* @param hz Frequency in Hz
|
|
193
|
+
* @returns Filter coefficient (0.0 to ~1.0)
|
|
194
|
+
*/
|
|
195
|
+
protected hzToFilterCoeff(hz: number): number;
|
|
196
|
+
/**
|
|
197
|
+
* LOGFREQ: Convert filter coefficient back to frequency in Hz
|
|
198
|
+
* Inverse of hzToFilterCoeff
|
|
199
|
+
* SpinCAD formula: frequency = -(ln(1 - coefficient)) * sampleRate / (2π)
|
|
200
|
+
* @param coeff Filter coefficient (0.0 to ~1.0)
|
|
201
|
+
* @returns Frequency in Hz
|
|
202
|
+
*/
|
|
203
|
+
protected filterCoeffToHz(coeff: number): number;
|
|
204
|
+
/**
|
|
205
|
+
* LOGFREQ2: Convert frequency in Hz to SVF filter coefficient
|
|
206
|
+
* Used for two-pole State Variable Filters
|
|
207
|
+
* SpinCAD formula: coefficient = 2 * sin(π * frequency / sampleRate)
|
|
208
|
+
* @param hz Frequency in Hz
|
|
209
|
+
* @returns Filter coefficient (0.0 to ~2.0)
|
|
210
|
+
*/
|
|
211
|
+
protected hzToSvfCoeff(hz: number): number;
|
|
212
|
+
/**
|
|
213
|
+
* LOGFREQ2: Convert SVF filter coefficient back to frequency in Hz
|
|
214
|
+
* Inverse of hzToSvfCoeff
|
|
215
|
+
* SpinCAD formula: frequency = asin(coefficient / 2) * sampleRate / π
|
|
216
|
+
* @param coeff Filter coefficient (0.0 to ~2.0)
|
|
217
|
+
* @returns Frequency in Hz
|
|
218
|
+
*/
|
|
219
|
+
protected svfCoeffToHz(coeff: number): number;
|
|
220
|
+
/**
|
|
221
|
+
* FILTTOTIME: Convert rise time in seconds to filter coefficient
|
|
222
|
+
* SpinCAD formula: freq = 0.35/time, then coefficient = 1 - e^(-2π * freq / Fs)
|
|
223
|
+
* Used for envelope followers and smoothing filters
|
|
224
|
+
* @param timeSeconds Rise time in seconds
|
|
225
|
+
* @returns Filter coefficient (0.0 to ~1.0), or -1.0 if time is 0
|
|
226
|
+
*/
|
|
227
|
+
protected timeToFilterCoeff(timeSeconds: number): number;
|
|
228
|
+
/**
|
|
229
|
+
* FILTTOTIME: Convert filter coefficient to rise time in seconds
|
|
230
|
+
* Inverse of timeToFilterCoeff
|
|
231
|
+
* SpinCAD formula: freq = -(ln(1 - coeff)) * Fs / (2π), then time = 0.35/freq
|
|
232
|
+
* @param coeff Filter coefficient (0.0 to ~1.0)
|
|
233
|
+
* @returns Rise time in seconds
|
|
234
|
+
*/
|
|
235
|
+
protected filterCoeffToTime(coeff: number): number;
|
|
236
|
+
/**
|
|
237
|
+
* Helper: Generate a unique label for this block
|
|
238
|
+
*/
|
|
239
|
+
protected generateLabel(blockId: string, suffix: string): string;
|
|
240
|
+
/**
|
|
241
|
+
* Sanitize a string for use as an assembly label
|
|
242
|
+
* Replaces dots with underscores and removes any non-alphanumeric characters except underscores
|
|
243
|
+
* @param label The label to sanitize
|
|
244
|
+
* @returns Sanitized label safe for assembly code
|
|
245
|
+
*/
|
|
246
|
+
protected sanitizeLabelForAsm(label: string): string;
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=BaseBlock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseBlock.d.ts","sourceRoot":"","sources":["../../../../src/blockDiagram/blocks/base/BaseBlock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for block definitions
|
|
3
|
+
* Provides common functionality and structure for all blocks
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Abstract base class for block definitions
|
|
7
|
+
* Provides common functionality and structure for all blocks
|
|
8
|
+
*
|
|
9
|
+
* Sample Rate Configuration:
|
|
10
|
+
* -------------------------
|
|
11
|
+
* The FV-1 sample rate defaults to 32768 Hz, which gives a maximum delay time of 1.0 second
|
|
12
|
+
* (32768 samples / 32768 Hz = 1.0s).
|
|
13
|
+
*
|
|
14
|
+
* To support different sample rates, override the getSampleRate() method in your block class:
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* class MyBlock extends BaseBlock {
|
|
19
|
+
* protected getSampleRate(): number {
|
|
20
|
+
* return 48000; // Custom sample rate
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* This affects:
|
|
26
|
+
* - timeToSamples() / samplesToTime() conversion
|
|
27
|
+
* - getMaxDelayTime() calculation (32768 samples / sample rate)
|
|
28
|
+
* - All delay-based effects that allocate memory
|
|
29
|
+
*/
|
|
30
|
+
export class BaseBlock {
|
|
31
|
+
constructor() {
|
|
32
|
+
// Visual properties (can be overridden)
|
|
33
|
+
this.color = '#607D8B';
|
|
34
|
+
this.width = 200;
|
|
35
|
+
this._height = 100;
|
|
36
|
+
// I/O definition (use protected to allow subclass initialization)
|
|
37
|
+
this._inputs = [];
|
|
38
|
+
this._outputs = [];
|
|
39
|
+
// Parameters
|
|
40
|
+
this._parameters = [];
|
|
41
|
+
}
|
|
42
|
+
// =========================================================================
|
|
43
|
+
// Static Conversion Functions (SpinCAD-compatible)
|
|
44
|
+
// For explicit sample rate when needed, otherwise use instance methods
|
|
45
|
+
// =========================================================================
|
|
46
|
+
/**
|
|
47
|
+
* DBLEVEL: Convert linear gain (0.0-1.0) to decibels
|
|
48
|
+
* SpinCAD formula: 20 * log10(linear)
|
|
49
|
+
* @param linear Linear gain value (0.0 to 1.0)
|
|
50
|
+
* @returns Gain in decibels (dB)
|
|
51
|
+
*/
|
|
52
|
+
static linearToDb(linear) {
|
|
53
|
+
if (linear <= 0)
|
|
54
|
+
return -Infinity;
|
|
55
|
+
return 20 * Math.log10(linear);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* DBLEVEL: Convert decibels to linear gain (0.0-1.0)
|
|
59
|
+
* SpinCAD formula: 10^(dB/20)
|
|
60
|
+
* @param dB Gain in decibels
|
|
61
|
+
* @returns Linear gain value (0.0 to 1.0)
|
|
62
|
+
*/
|
|
63
|
+
static dbToLinear(dB) {
|
|
64
|
+
return Math.pow(10.0, dB / 20.0);
|
|
65
|
+
}
|
|
66
|
+
get inputs() { return this._inputs; }
|
|
67
|
+
get outputs() { return this._outputs; }
|
|
68
|
+
get parameters() { return this._parameters; }
|
|
69
|
+
/**
|
|
70
|
+
* Get block height - automatically calculated based on port count unless overridden
|
|
71
|
+
*/
|
|
72
|
+
get height() {
|
|
73
|
+
return this._height;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Set custom height (optional - will auto-calculate if not called)
|
|
77
|
+
*/
|
|
78
|
+
setHeight(value) {
|
|
79
|
+
this._height = value;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Auto-calculate and set height based on port count
|
|
83
|
+
* Called automatically after ports are defined
|
|
84
|
+
*/
|
|
85
|
+
autoCalculateHeight() {
|
|
86
|
+
this._height = this.calculateMinHeight();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Calculate the minimum height needed to fit all ports
|
|
90
|
+
* Port layout: first port at y=40, then 20px spacing between ports
|
|
91
|
+
* Add 20px padding at bottom
|
|
92
|
+
*/
|
|
93
|
+
calculateMinHeight() {
|
|
94
|
+
const portSpacing = 20;
|
|
95
|
+
const firstPortY = 40;
|
|
96
|
+
const bottomPadding = 20;
|
|
97
|
+
const maxPorts = Math.max(this._inputs.length, this._outputs.length);
|
|
98
|
+
if (maxPorts === 0) {
|
|
99
|
+
return 100; // Default minimum height
|
|
100
|
+
}
|
|
101
|
+
const lastPortY = firstPortY + (maxPorts - 1) * portSpacing;
|
|
102
|
+
return lastPortY + bottomPadding;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Validate this block's configuration and connections
|
|
106
|
+
* Default implementation checks required inputs
|
|
107
|
+
*/
|
|
108
|
+
validate(ctx) {
|
|
109
|
+
const warnings = [];
|
|
110
|
+
// Check required inputs
|
|
111
|
+
for (const input of this.inputs) {
|
|
112
|
+
if (input.required && !ctx.hasInput(ctx.getBlock(this.type)?.id || '', input.id)) {
|
|
113
|
+
return {
|
|
114
|
+
valid: false,
|
|
115
|
+
error: `Required input '${input.name}' is not connected`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Check if outputs are used (warning only)
|
|
120
|
+
const block = ctx.getBlock(this.type);
|
|
121
|
+
if (block && this.outputs.length > 0) {
|
|
122
|
+
const connectedOutputs = ctx.getInputs(block.id);
|
|
123
|
+
if (connectedOutputs.length === 0) {
|
|
124
|
+
warnings.push('Block outputs are not connected to anything');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
valid: true,
|
|
129
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Convert a code value to display value for a specific parameter
|
|
134
|
+
*/
|
|
135
|
+
getDisplayValue(parameterId, codeValue) {
|
|
136
|
+
const param = this._parameters.find(p => p.id === parameterId);
|
|
137
|
+
if (!param) {
|
|
138
|
+
throw new Error(`Parameter '${parameterId}' not found in block '${this.type}'`);
|
|
139
|
+
}
|
|
140
|
+
return param.toDisplay ? param.toDisplay(codeValue) : codeValue;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Convert a display value to code value for a specific parameter
|
|
144
|
+
*/
|
|
145
|
+
getCodeValue(parameterId, displayValue) {
|
|
146
|
+
const param = this._parameters.find(p => p.id === parameterId);
|
|
147
|
+
if (!param) {
|
|
148
|
+
throw new Error(`Parameter '${parameterId}' not found in block '${this.type}'`);
|
|
149
|
+
}
|
|
150
|
+
return param.fromDisplay ? param.fromDisplay(displayValue) : displayValue;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get metadata about this block type
|
|
154
|
+
*/
|
|
155
|
+
getMetadata() {
|
|
156
|
+
// Serialize parameters without conversion functions (they stay server-side)
|
|
157
|
+
const serializedParams = this.parameters.map(param => {
|
|
158
|
+
const { toDisplay, fromDisplay, ...serialized } = param;
|
|
159
|
+
return serialized;
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
type: this.type,
|
|
163
|
+
category: this.category,
|
|
164
|
+
subcategory: this.subcategory,
|
|
165
|
+
name: this.name,
|
|
166
|
+
description: this.description,
|
|
167
|
+
color: this.color,
|
|
168
|
+
icon: this.icon,
|
|
169
|
+
width: this.width,
|
|
170
|
+
height: this.height,
|
|
171
|
+
inputs: this.inputs,
|
|
172
|
+
outputs: this.outputs,
|
|
173
|
+
parameters: serializedParams,
|
|
174
|
+
hasCustomLabel: typeof this.getCustomLabel === 'function'
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Helper: Get a parameter value with type safety
|
|
179
|
+
*/
|
|
180
|
+
getParameterValue(ctx, blockId, parameterId, defaultValue) {
|
|
181
|
+
const value = ctx.getParameter(blockId, parameterId);
|
|
182
|
+
return value !== undefined ? value : defaultValue;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Helper: Format a number as FV-1 S1.14 fixed point
|
|
186
|
+
* Used by most FV-1 instructions (RDAX, SOF, MULX, etc.)
|
|
187
|
+
* Range: -2.0 to 1.99993896484
|
|
188
|
+
*/
|
|
189
|
+
formatS1_14(value) {
|
|
190
|
+
// Clamp to valid range
|
|
191
|
+
value = Math.max(-2.0, Math.min(1.99993896484, value));
|
|
192
|
+
// Format with appropriate precision
|
|
193
|
+
if (value === 0)
|
|
194
|
+
return '0.0';
|
|
195
|
+
if (value === 1.0)
|
|
196
|
+
return '1.0';
|
|
197
|
+
if (value === -1.0)
|
|
198
|
+
return '-1.0';
|
|
199
|
+
if (value === -2.0)
|
|
200
|
+
return '-2.0';
|
|
201
|
+
return value.toFixed(8);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Helper: Format a number as FV-1 S.15 fixed point
|
|
205
|
+
* Used only by CHO SOF instruction
|
|
206
|
+
* Range: -1.0 to 0.99996948242
|
|
207
|
+
*/
|
|
208
|
+
formatS15(value) {
|
|
209
|
+
// Clamp to valid range
|
|
210
|
+
value = Math.max(-1.0, Math.min(0.99996948242, value));
|
|
211
|
+
// Format with appropriate precision
|
|
212
|
+
if (value === 0)
|
|
213
|
+
return '0.0';
|
|
214
|
+
if (value === -1.0)
|
|
215
|
+
return '-1.0';
|
|
216
|
+
return value.toFixed(8);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Helper: Format a number as FV-1 S.10 fixed point
|
|
220
|
+
* Used by some FV-1 instructions
|
|
221
|
+
* Range: -1.0 to 0.9990234375
|
|
222
|
+
*/
|
|
223
|
+
formatS10(value) {
|
|
224
|
+
// Clamp to valid range
|
|
225
|
+
value = Math.max(-1.0, Math.min(0.9990234375, value));
|
|
226
|
+
// Format with appropriate precision
|
|
227
|
+
if (value === 0)
|
|
228
|
+
return '0.0';
|
|
229
|
+
if (value === -1.0)
|
|
230
|
+
return '-1.0';
|
|
231
|
+
return value.toFixed(8);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Helper: Format a number as FV-1 S1.9 fixed point
|
|
235
|
+
* Used by some FV-1 instructions
|
|
236
|
+
* Range: -2.0 to 1.998046875
|
|
237
|
+
*/
|
|
238
|
+
formatS1_9(value) {
|
|
239
|
+
// Clamp to valid range
|
|
240
|
+
value = Math.max(-2.0, Math.min(1.998046875, value));
|
|
241
|
+
// Format with appropriate precision
|
|
242
|
+
if (value === 0)
|
|
243
|
+
return '0.0';
|
|
244
|
+
if (value === 1.0)
|
|
245
|
+
return '1.0';
|
|
246
|
+
if (value === -1.0)
|
|
247
|
+
return '-1.0';
|
|
248
|
+
if (value === -2.0)
|
|
249
|
+
return '-2.0';
|
|
250
|
+
return value.toFixed(8);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get the FV-1 sample rate in Hz
|
|
254
|
+
* Override this method to support different sample rates
|
|
255
|
+
* Default: 32768 Hz
|
|
256
|
+
*/
|
|
257
|
+
getSampleRate() {
|
|
258
|
+
return BaseBlock.DEFAULT_SAMPLE_RATE;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get the maximum delay time in seconds based on available memory and sample rate
|
|
262
|
+
* FV-1 has 32768 samples of delay memory total
|
|
263
|
+
* @returns Maximum delay time in seconds
|
|
264
|
+
*/
|
|
265
|
+
getMaxDelayTime() {
|
|
266
|
+
return BaseBlock.MAX_DELAY_MEMORY / this.getSampleRate();
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Helper: Calculate delay samples from time in seconds
|
|
270
|
+
* Uses the configurable sample rate
|
|
271
|
+
*/
|
|
272
|
+
timeToSamples(timeSeconds) {
|
|
273
|
+
return Math.floor(timeSeconds * this.getSampleRate());
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Helper: Calculate time in seconds from sample count
|
|
277
|
+
*/
|
|
278
|
+
samplesToTime(samples) {
|
|
279
|
+
return samples / this.getSampleRate();
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* LENGTHTOTIME: Convert samples to milliseconds (uses getSampleRate())
|
|
283
|
+
* @param samples Number of delay samples
|
|
284
|
+
* @returns Time in milliseconds
|
|
285
|
+
*/
|
|
286
|
+
samplesToMs(samples) {
|
|
287
|
+
return (samples / this.getSampleRate()) * 1000;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* LENGTHTOTIME: Convert milliseconds to samples (uses getSampleRate())
|
|
291
|
+
* @param ms Time in milliseconds
|
|
292
|
+
* @returns Number of delay samples (rounded)
|
|
293
|
+
*/
|
|
294
|
+
msToSamples(ms) {
|
|
295
|
+
return Math.round((ms / 1000) * this.getSampleRate());
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* SINLFOFREQ: Convert LFO rate value to frequency in Hz (uses getSampleRate())
|
|
299
|
+
* FV-1 AN-001 formula: f = Kf * Fs / (2^17 * 2*pi)
|
|
300
|
+
* @param rate LFO rate value (0-32767, typically 0-511 for SIN LFO)
|
|
301
|
+
* @returns Frequency in Hz
|
|
302
|
+
*/
|
|
303
|
+
lfoRateToHz(rate) {
|
|
304
|
+
return rate * this.getSampleRate() / (2 ** 17 * 2.0 * Math.PI);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* SINLFOFREQ: Convert frequency in Hz to LFO rate value (uses getSampleRate())
|
|
308
|
+
* FV-1 AN-001 formula: Kf = 2^17 * (2*pi*f / Fs)
|
|
309
|
+
* @param hz Frequency in Hz
|
|
310
|
+
* @returns LFO rate value (0-32767, rounded)
|
|
311
|
+
*/
|
|
312
|
+
hzToLfoRate(hz) {
|
|
313
|
+
return Math.round(hz * 2 ** 17 / this.getSampleRate() * 2.0 * Math.PI);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* LOGFREQ: Convert frequency in Hz to filter coefficient for RDFX/WRLX/WRHX
|
|
317
|
+
* Used for single-pole filters (low-pass, high-pass, shelving)
|
|
318
|
+
* SpinCAD formula: coefficient = 1 - e^(-2π * frequency / sampleRate)
|
|
319
|
+
* This is the one-pole exponential filter coefficient
|
|
320
|
+
* @param hz Frequency in Hz
|
|
321
|
+
* @returns Filter coefficient (0.0 to ~1.0)
|
|
322
|
+
*/
|
|
323
|
+
hzToFilterCoeff(hz) {
|
|
324
|
+
const omega = 2.0 * Math.PI * hz / this.getSampleRate();
|
|
325
|
+
return 1.0 - Math.pow(Math.E, -omega);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* LOGFREQ: Convert filter coefficient back to frequency in Hz
|
|
329
|
+
* Inverse of hzToFilterCoeff
|
|
330
|
+
* SpinCAD formula: frequency = -(ln(1 - coefficient)) * sampleRate / (2π)
|
|
331
|
+
* @param coeff Filter coefficient (0.0 to ~1.0)
|
|
332
|
+
* @returns Frequency in Hz
|
|
333
|
+
*/
|
|
334
|
+
filterCoeffToHz(coeff) {
|
|
335
|
+
return -(Math.log(1.0 - coeff)) * this.getSampleRate() / (2.0 * Math.PI);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* LOGFREQ2: Convert frequency in Hz to SVF filter coefficient
|
|
339
|
+
* Used for two-pole State Variable Filters
|
|
340
|
+
* SpinCAD formula: coefficient = 2 * sin(π * frequency / sampleRate)
|
|
341
|
+
* @param hz Frequency in Hz
|
|
342
|
+
* @returns Filter coefficient (0.0 to ~2.0)
|
|
343
|
+
*/
|
|
344
|
+
hzToSvfCoeff(hz) {
|
|
345
|
+
return 2.0 * Math.sin(Math.PI * hz / this.getSampleRate());
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* LOGFREQ2: Convert SVF filter coefficient back to frequency in Hz
|
|
349
|
+
* Inverse of hzToSvfCoeff
|
|
350
|
+
* SpinCAD formula: frequency = asin(coefficient / 2) * sampleRate / π
|
|
351
|
+
* @param coeff Filter coefficient (0.0 to ~2.0)
|
|
352
|
+
* @returns Frequency in Hz
|
|
353
|
+
*/
|
|
354
|
+
svfCoeffToHz(coeff) {
|
|
355
|
+
return Math.asin(coeff / 2.0) * this.getSampleRate() / Math.PI;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* FILTTOTIME: Convert rise time in seconds to filter coefficient
|
|
359
|
+
* SpinCAD formula: freq = 0.35/time, then coefficient = 1 - e^(-2π * freq / Fs)
|
|
360
|
+
* Used for envelope followers and smoothing filters
|
|
361
|
+
* @param timeSeconds Rise time in seconds
|
|
362
|
+
* @returns Filter coefficient (0.0 to ~1.0), or -1.0 if time is 0
|
|
363
|
+
*/
|
|
364
|
+
timeToFilterCoeff(timeSeconds) {
|
|
365
|
+
if (timeSeconds === 0.0) {
|
|
366
|
+
return -1.0;
|
|
367
|
+
}
|
|
368
|
+
const freq = 0.35 / timeSeconds;
|
|
369
|
+
const omega = 2.0 * Math.PI * freq / this.getSampleRate();
|
|
370
|
+
return 1.0 - Math.pow(Math.E, -omega);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* FILTTOTIME: Convert filter coefficient to rise time in seconds
|
|
374
|
+
* Inverse of timeToFilterCoeff
|
|
375
|
+
* SpinCAD formula: freq = -(ln(1 - coeff)) * Fs / (2π), then time = 0.35/freq
|
|
376
|
+
* @param coeff Filter coefficient (0.0 to ~1.0)
|
|
377
|
+
* @returns Rise time in seconds
|
|
378
|
+
*/
|
|
379
|
+
filterCoeffToTime(coeff) {
|
|
380
|
+
const freq = -(Math.log(1.0 - coeff)) * this.getSampleRate() / (2.0 * Math.PI);
|
|
381
|
+
return 0.35 / freq;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Helper: Generate a unique label for this block
|
|
385
|
+
*/
|
|
386
|
+
generateLabel(blockId, suffix) {
|
|
387
|
+
return `${blockId.replace(/[^a-zA-Z0-9]/g, '_')}_${suffix}`;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Sanitize a string for use as an assembly label
|
|
391
|
+
* Replaces dots with underscores and removes any non-alphanumeric characters except underscores
|
|
392
|
+
* @param label The label to sanitize
|
|
393
|
+
* @returns Sanitized label safe for assembly code
|
|
394
|
+
*/
|
|
395
|
+
sanitizeLabelForAsm(label) {
|
|
396
|
+
return label.replace(/\./g, '_').replace(/[^a-zA-Z0-9_]/g, '');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Constants
|
|
400
|
+
BaseBlock.DEFAULT_SAMPLE_RATE = 32768;
|
|
401
|
+
BaseBlock.MAX_DELAY_MEMORY = 32768;
|
|
402
|
+
//# sourceMappingURL=BaseBlock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseBlock.js","sourceRoot":"","sources":["../../../../src/blockDiagram/blocks/base/BaseBlock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-GENERATED — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Regenerate via `npm run build` (runs scripts/build-manifest.mjs before tsc).
|
|
5
|
+
* Source of truth: the .atl files under `blocks/`.
|
|
6
|
+
*/
|
|
7
|
+
import type { BlockTemplateDefinition } from './types/IR.js';
|
|
8
|
+
export declare const BUILTIN_BLOCKS: BlockTemplateDefinition[];
|
|
9
|
+
//# sourceMappingURL=builtinBlocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtinBlocks.d.ts","sourceRoot":"","sources":["../../src/blockDiagram/builtinBlocks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D,eAAO,MAAM,cAAc,EAAE,uBAAuB,EAwyJnD,CAAC"}
|