frausto 0.2.1 → 0.2.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c1ff4be227078cbc60a23393acb8fc563c0175e5fbffe921864275d2bf50337
4
- data.tar.gz: 6333880ae0f3c7bf85530a2c5cc8c1d44f7041f2358f04283efff77ccf4d828f
3
+ metadata.gz: '093f5e9746f4614c8b4085708ccd9b2bf092d06c3a50f5ff08fec837a86ff978'
4
+ data.tar.gz: 4f1262a4cf6d6e9357f9315729b9628b123034c5092867b9a48f78850acef31f
5
5
  SHA512:
6
- metadata.gz: 0f6c2e262a74d874be53a627c09d5ec01f12d5d31bc078654c153034ec1588a1de07ddcfc69a0c386c83c582e16cdacb24fe6e6d383c0ca320853c34c2ec6f06
7
- data.tar.gz: 06b09d654d37583068304b2743b9cb14baa7f11b712d8748f9686639c016f8b6d322708d1a77cf4499f70fc2d155f43306fea716c621f3759b150e4e54fd9ee3
6
+ metadata.gz: f2b150ac6dfedb611dd4e924b0497f6c9170122593401d13eff0e8771effc241f3512227fd592d2df6f49278c870015c6c3b7221fd9010e035a1635a4bf1464b
7
+ data.tar.gz: 7bffe90c34b3ba5a7576044fe6d2f5c0768dbffdf893ded31fb10bfb9425e57b99e1028fb2e5c37ee4eb2d6d5c28c70672b06c76cc19916a0f42a39f6f14e6f9
data/README.md CHANGED
@@ -24,25 +24,31 @@ gem 'frausto'
24
24
  - **[ruby2faust](ruby2faust.md)** - Ruby DSL that generates Faust DSP code
25
25
  - **[faust2ruby](faust2ruby.md)** - Convert Faust DSP code to Ruby DSL
26
26
 
27
- ## Quick Example
27
+ ## Quick Examples
28
28
 
29
29
  ```ruby
30
30
  require 'ruby2faust'
31
31
 
32
32
  code = Ruby2Faust.generate do
33
- osc(440) >> lp(800) >> gain(0.3)
33
+ freq = hslider("freq", init: 48, min: 20, max: 100, step: 1) >> midi2hz >> smoo
34
+ amp = hslider("amp", init: -12, min: -60, max: 0, step: 1) >> db2linear >> smoo
35
+ osc(freq) >> lp(2000) >> gain(amp)
34
36
  end
35
37
 
36
38
  puts code
37
- # => import("stdfaust.lib");
38
- # process = (os.osc(440) : fi.lowpass(1, 800) : *(0.3));
39
+ # import("stdfaust.lib");
40
+ #
41
+ # process =
42
+ # os.osc(hslider("freq", 48, 20, 100, 1) : ba.midikey2hz : si.smoo)
43
+ # : fi.lowpass(1, 2000)
44
+ # : *(hslider("amp", -12, -60, 0, 1) : ba.db2linear : si.smoo);
39
45
  ```
40
46
 
41
47
  ```ruby
42
48
  require 'faust2ruby'
43
49
 
44
50
  ruby_code = Faust2Ruby.to_ruby('process = os.osc(440) : *(0.5);')
45
- # => "osc(440) >> gain(0.5)"
51
+ # => "0.5 * osc(440)"
46
52
  ```
47
53
 
48
54
  ## License
data/bin/faust2ruby CHANGED
@@ -18,7 +18,7 @@ parser = OptionParser.new do |opts|
18
18
  opts.separator ""
19
19
  opts.separator "Options:"
20
20
 
21
- opts.on("-o", "--output FILE", "Output Ruby file (default: stdout)") do |file|
21
+ opts.on("-o", "--output FILE", "Output Ruby file (default: <input>.rb)") do |file|
22
22
  options[:output] = file
23
23
  end
24
24
 
@@ -74,6 +74,8 @@ else
74
74
  end
75
75
  source = File.read(input_path)
76
76
  input_name = input_path
77
+ # Default output to input.dsp.rb (unless expression-only mode or explicit -o)
78
+ options[:output] ||= "#{input_path}.rb" unless options[:expression_only]
77
79
  end
78
80
 
79
81
  begin
data/faust2ruby.md CHANGED
@@ -4,8 +4,6 @@ Convert Faust DSP code to Ruby DSL code compatible with ruby2faust.
4
4
 
5
5
  ## Installation
6
6
 
7
- faust2ruby is included with the frausto gem:
8
-
9
7
  ```bash
10
8
  gem install frausto
11
9
  ```
@@ -15,29 +13,19 @@ gem install frausto
15
13
  ### Command Line
16
14
 
17
15
  ```bash
18
- # Convert a Faust file to Ruby
19
- faust2ruby input.dsp -o output.rb
16
+ # Convert a Faust file to Ruby (writes to file.dsp.rb)
17
+ faust2ruby file.dsp
18
+
19
+ # Specify output file
20
+ faust2ruby file.dsp -o other.rb
20
21
 
21
- # Output only the process expression (no boilerplate)
22
- faust2ruby -e input.dsp
22
+ # Output only the process expression (no boilerplate, to stdout)
23
+ faust2ruby -e file.dsp
23
24
 
24
25
  # Read from stdin
25
26
  echo 'process = os.osc(440) : *(0.5);' | faust2ruby -e
26
27
  ```
27
28
 
28
- ### Options
29
-
30
- ```
31
- Usage: faust2ruby [options] <input.dsp>
32
- -o, --output FILE Output Ruby file (default: stdout)
33
- -e, --expression Output only process expression (no boilerplate)
34
- -v, --verbose Verbose output (show parsing info)
35
- -t, --tokens Show lexer tokens (debug mode)
36
- -a, --ast Show AST (debug mode)
37
- --version Show version
38
- -h, --help Show help
39
- ```
40
-
41
29
  ### Ruby API
42
30
 
43
31
  ```ruby
@@ -46,106 +34,67 @@ require 'faust2ruby'
46
34
  # Convert Faust to Ruby code
47
35
  faust_code = 'process = os.osc(440) : *(0.5);'
48
36
  ruby_code = Faust2Ruby.to_ruby(faust_code)
49
- puts ruby_code
50
37
 
51
38
  # Expression only
52
39
  ruby_expr = Faust2Ruby.to_ruby(faust_code, expression_only: true)
53
- # => "(osc(440) >> gain(0.5))"
54
-
55
- # Parse to AST
56
- program = Faust2Ruby.parse(faust_code)
57
-
58
- # Tokenize
59
- tokens = Faust2Ruby.tokenize(faust_code)
40
+ # => "0.5 * osc(440)"
60
41
  ```
61
42
 
62
43
  ## Examples
63
44
 
64
45
  ### Simple Oscillator
65
46
 
66
- **Input (Faust):**
67
47
  ```faust
68
48
  process = os.osc(440) : *(0.5);
69
49
  ```
70
-
71
- **Output (Ruby):**
72
50
  ```ruby
73
- require 'ruby2faust'
74
- include Ruby2Faust::DSL
75
-
76
- process = (osc(440) >> gain(0.5))
77
-
78
- puts Ruby2Faust::Emitter.program(process)
51
+ 0.5 * osc(440)
79
52
  ```
80
53
 
81
54
  ### Synthesizer with Controls
82
55
 
83
- **Input (Faust):**
84
56
  ```faust
85
57
  import("stdfaust.lib");
86
- declare name "synth";
87
-
88
58
  freq = hslider("freq", 440, 20, 20000, 1);
89
- gain = hslider("gain", 0.5, 0, 1, 0.01);
90
-
91
- process = os.osc(freq) : *(gain);
59
+ amp = hslider("amp", 0.5, 0, 1, 0.01);
60
+ process = os.osc(freq) : *(amp);
92
61
  ```
93
-
94
- **Output (Ruby):**
95
62
  ```ruby
96
- require 'ruby2faust'
97
- include Ruby2Faust::DSL
98
-
99
- # declare name "synth"
100
-
101
- freq = slider("freq", init: 440, min: 20, max: 20000, step: 1)
102
-
103
- gain = slider("gain", init: 0.5, min: 0, max: 1, step: 0.01)
104
-
105
- process = (osc(freq) >> gain(gain))
106
-
107
- puts Ruby2Faust::Emitter.program(process)
63
+ freq = hslider("freq", init: 440, min: 20, max: 20000, step: 1)
64
+ amp = hslider("amp", init: 0.5, min: 0, max: 1, step: 0.01)
65
+ process = amp * osc(freq)
108
66
  ```
109
67
 
110
- ### Parallel Composition
68
+ ### Stereo Output
111
69
 
112
- **Input (Faust):**
113
70
  ```faust
114
71
  process = os.osc(440) , os.osc(880);
115
72
  ```
116
-
117
- **Output (Ruby):**
118
73
  ```ruby
119
- (osc(440) | osc(880))
74
+ osc(440) | osc(880)
120
75
  ```
121
76
 
122
- ### Feedback Loop
77
+ ### Feedback Delay
123
78
 
124
- **Input (Faust):**
125
79
  ```faust
126
80
  process = _ ~ (de.delay(44100, 22050) : *(0.5));
127
81
  ```
128
-
129
- **Output (Ruby):**
130
82
  ```ruby
131
- (wire ~ (delay(44100, 22050) >> gain(0.5)))
83
+ wire ~ (0.5 * delay(44100, 22050))
132
84
  ```
133
85
 
134
86
  ### Iteration
135
87
 
136
- **Input (Faust):**
137
88
  ```faust
138
89
  process = par(i, 4, os.osc(i * 100));
139
90
  ```
140
-
141
- **Output (Ruby):**
142
91
  ```ruby
143
- fpar(4) { |i| osc((i * 100)) }
92
+ fpar(4) { |i| osc(i * 100) }
144
93
  ```
145
94
 
146
- ## Supported Faust Constructs
95
+ ## Mapping Overview
147
96
 
148
- ### Composition Operators
97
+ ### Composition
149
98
 
150
99
  | Faust | Ruby |
151
100
  |-------|------|
@@ -154,88 +103,27 @@ fpar(4) { |i| osc((i * 100)) }
154
103
  | `a <: b` | `a.split(b)` |
155
104
  | `a :> b` | `a.merge(b)` |
156
105
  | `a ~ b` | `a ~ b` |
106
+ | `*(x)` | `x *` (idiomatic) or `gain(x)` |
157
107
 
158
- ### Arithmetic
108
+ ### Numeric Extensions
159
109
 
160
- | Faust | Ruby |
161
- |-------|------|
162
- | `a + b` | `a + b` |
163
- | `a - b` | `a - b` |
164
- | `a * b` | `a * b` |
165
- | `a / b` | `a / b` |
166
- | `a % b` | `a % b` |
167
- | `*(x)` | `gain(x)` |
168
-
169
- ### Comparison
110
+ When arguments are numeric literals, faust2ruby emits idiomatic Ruby:
170
111
 
171
112
  | Faust | Ruby |
172
113
  |-------|------|
173
- | `a < b` | `a < b` |
174
- | `a > b` | `a > b` |
175
- | `a <= b` | `a <= b` |
176
- | `a >= b` | `a >= b` |
177
- | `a == b` | `a.eq(b)` |
178
- | `a != b` | `a.neq(b)` |
179
-
180
- Note: `eq()` and `neq()` are methods because Ruby's `==` must return boolean.
181
-
182
- ### Bitwise
183
-
184
- | Faust | Ruby |
185
- |-------|------|
186
- | `a & b` | `a & b` |
187
- | `a \| b` (bitwise) | `a.bor(b)` |
188
- | `xor(a, b)` | `a ^ b` |
189
-
190
- Note: `bor()` is a method because `\|` is used for parallel composition in the DSL.
114
+ | `ba.db2linear(-6)` | `-6.db` |
115
+ | `ba.midikey2hz(60)` | `60.midi` |
116
+ | `ba.sec2samp(0.1)` | `0.1.sec` |
191
117
 
192
118
  ### Library Functions
193
119
 
194
- | Faust | Ruby |
195
- |-------|------|
196
- | `os.osc(f)` | `osc(f)` |
197
- | `os.sawtooth(f)` | `saw(f)` |
198
- | `os.square(f)` | `square(f)` |
199
- | `os.triangle(f)` | `triangle(f)` |
200
- | `no.noise` | `noise` |
201
- | `fi.lowpass(n, f)` | `lp(f, order: n)` |
202
- | `fi.highpass(n, f)` | `hp(f, order: n)` |
203
- | `fi.resonlp(f, q, g)` | `resonlp(f, q, g)` |
204
- | `de.delay(m, d)` | `delay(m, d)` |
205
- | `de.fdelay(m, d)` | `fdelay(m, d)` |
206
- | `en.adsr(a,d,s,r,g)` | `adsr(a, d, s, r, g)` |
207
- | `ba.db2linear(x)` | `db2linear(x)` |
208
- | `si.smoo` | `smoo` |
209
- | `sp.panner(p)` | `panner(p)` |
210
-
211
- ### UI Elements
212
-
213
- | Faust | Ruby |
214
- |-------|------|
215
- | `hslider("n", i, mn, mx, s)` | `slider("n", init: i, min: mn, max: mx, step: s)` |
216
- | `vslider(...)` | `vslider(...)` |
217
- | `nentry(...)` | `nentry(...)` |
218
- | `button("n")` | `button("n")` |
219
- | `checkbox("n")` | `checkbox("n")` |
220
- | `hgroup("n", e)` | `hgroup("n") { e }` |
221
- | `vgroup("n", e)` | `vgroup("n") { e }` |
120
+ Most `os.*`, `fi.*`, `de.*`, `en.*`, `ba.*`, `si.*`, `re.*`, `co.*`, `sp.*`, `aa.*`, `an.*`, `ef.*` functions are mapped. Examples:
222
121
 
223
- ### Iteration
224
-
225
- | Faust | Ruby |
226
- |-------|------|
227
- | `par(i, n, e)` | `fpar(n) { \|i\| e }` |
228
- | `seq(i, n, e)` | `fseq(n) { \|i\| e }` |
229
- | `sum(i, n, e)` | `fsum(n) { \|i\| e }` |
230
- | `prod(i, n, e)` | `fprod(n) { \|i\| e }` |
231
-
232
- ### Tables
233
-
234
- | Faust | Ruby |
235
- |-------|------|
236
- | `waveform{v1, v2, ...}` | `waveform(v1, v2, ...)` |
237
- | `rdtable(n, i, r)` | `rdtable(n, i, r)` |
238
- | `rwtable(n, i, w, s, r)` | `rwtable(n, i, w, s, r)` |
122
+ - `os.osc(f)` → `osc(f)`
123
+ - `fi.lowpass(n, f)` → `lp(f, order: n)`
124
+ - `de.delay(m, d)` `delay(m, d)`
125
+ - `en.adsr(a,d,s,r,g)` → `adsr(a, d, s, r, g)`
126
+ - `si.smoo` `smoo`
239
127
 
240
128
  ### Primitives
241
129
 
@@ -245,164 +133,43 @@ Note: `bor()` is a method because `\|` is used for parallel composition in the D
245
133
  | `!` | `cut` |
246
134
  | `mem` | `mem` |
247
135
  | `ma.SR` | `sr` |
248
- | `ma.PI` | `pi` |
249
136
 
250
137
  ## Round-trip Conversion
251
138
 
252
- faust2ruby is designed to work with ruby2faust for round-trip conversion:
253
-
254
139
  ```ruby
255
- require 'faust2ruby'
256
- require 'ruby2faust'
140
+ require 'frausto'
257
141
 
258
- # Faust → Ruby
142
+ # Faust → Ruby → Faust
259
143
  faust_input = 'process = os.osc(440) : *(0.5);'
260
- ruby_code = Faust2Ruby.to_ruby(faust_input, expression_only: true)
144
+ ruby_expr = Faust2Ruby.to_ruby(faust_input, expression_only: true)
261
145
 
262
- # Ruby → Faust
263
146
  include Ruby2Faust::DSL
264
- process = eval(ruby_code)
147
+ process = eval(ruby_expr)
265
148
  faust_output = Ruby2Faust::Emitter.program(process)
266
149
  ```
267
150
 
268
- ## With Clauses
151
+ ## Advanced Features
269
152
 
270
- `with` clauses are converted to Ruby lambdas for proper scoping:
153
+ ### With Clauses
154
+
155
+ Local definitions are converted to Ruby lambdas:
271
156
 
272
- **Input (Faust):**
273
157
  ```faust
274
- myDSP = result with {
158
+ myDSP = _ * gain with {
275
159
  gain = 0.5;
276
- result = _ * gain;
277
160
  };
278
161
  ```
279
-
280
- **Output (Ruby):**
281
162
  ```ruby
282
163
  myDSP = -> {
283
164
  gain = 0.5
284
- result = (wire * gain)
285
- result
165
+ (wire * gain)
286
166
  }.call
287
167
  ```
288
168
 
289
- Function-style local definitions use `flambda`:
290
- ```ruby
291
- adaa = flambda(:x0, :x1) { |x0, x1| select2(...) }
292
- ```
293
-
294
- ## Partial Application
295
-
296
- Faust's partial application creates reusable signal processors by providing some arguments upfront.
297
-
298
- ### Clipping / Limiting
299
-
300
- **Input (Faust):**
301
- ```faust
302
- // Clip signal to [-1, 1] range
303
- safetyLimit = min(1) : max(-1);
304
- process = osc(440) : safetyLimit;
305
- ```
306
-
307
- **Output (Ruby):**
308
- ```ruby
309
- safetyLimit = (flambda(:x) { |x| min_(x, 1) } >> flambda(:x) { |x| max_(x, (-1)) })
310
- process = (osc(440) >> safetyLimit)
311
- ```
312
-
313
- ### Conditional Routing
314
-
315
- **Input (Faust):**
316
- ```faust
317
- // Switch between two signals based on condition
318
- useWet = checkbox("wet");
319
- effect = _ <: _, reverb : select2(useWet);
320
- ```
321
-
322
- **Output (Ruby):**
323
- ```ruby
324
- useWet = checkbox("wet")
325
- effect = wire.split(wire, reverb) >> flambda(:x, :y) { |x, y| select2(useWet, x, y) }
326
- ```
327
-
328
- ### Gain Stages
329
-
330
- **Input (Faust):**
331
- ```faust
332
- // Partial application of multiplication
333
- halfGain = *(0.5);
334
- quarterGain = *(0.25);
335
- process = osc(440) : halfGain;
336
- ```
337
-
338
- **Output (Ruby):**
339
- ```ruby
340
- halfGain = gain(0.5)
341
- quarterGain = gain(0.25)
342
- process = (osc(440) >> halfGain)
343
- ```
344
-
345
- ### Filter Configuration
346
-
347
- **Input (Faust):**
348
- ```faust
349
- // Second-order lowpass waiting for cutoff frequency
350
- smoothFilter = fi.lowpass(2);
351
- process = _ : smoothFilter(1000);
352
- ```
353
-
354
- **Output (Ruby):**
355
- ```ruby
356
- smoothFilter = flambda(:x) { |x| lp(x, order: 2) }
357
- process = (wire >> smoothFilter.call(1000)) # Note: needs .call in Ruby
358
- ```
359
-
360
- ### Math Functions as Processors
361
-
362
- **Input (Faust):**
363
- ```faust
364
- // 0-arg functions applied to signals
365
- softclip(x) = tanh(x);
366
- process = osc(440) * 2 : softclip;
367
- ```
368
-
369
- **Output (Ruby):**
370
- ```ruby
371
- def softclip(x)
372
- (x >> tanh_)
373
- end
374
- process = ((osc(440) * 2) >> softclip(wire))
375
- ```
376
-
377
- ## What Uses `literal()`
378
-
379
- Some Faust constructs are emitted as `literal()` calls to preserve semantics:
380
-
381
- | Construct | Example | Reason |
382
- |-----------|---------|--------|
383
- | Letrec blocks | `letrec { 'x = ... }` | Complex state semantics |
384
- | Unmapped library functions | `an.amp_follower(t)` | Not in library mapper |
385
- | Partial app (4+ args) | `route(4, 4, ...)` | Requires complex curry |
386
-
387
- Most common Faust library functions are mapped, including `fi.*`, `os.*`, `de.*`, `en.*`, `ba.*`, `si.*`, `aa.*`, and math primitives.
388
-
389
- ## Limitations
390
-
391
- **Supported with limitations:**
392
- - `with` clauses: Local definitions work, scoped via Ruby lambda
393
- - Partial application: Works for 1-3 missing arguments
394
- - Forward references: Functions defined later in `with` blocks are resolved
395
-
396
- **Not supported:**
397
- - Pattern matching on function arguments (see below)
398
- - Foreign functions (`ffunction`)
399
- - Component/library imports beyond path tracking
400
-
401
169
  ### Case Expressions
402
170
 
403
- Case expressions with integer patterns are converted to `select2` chains:
171
+ Faust's `case` creates a pattern-matching function—the `(n)` pattern binds the input signal to variable `n`:
404
172
 
405
- **Input (Faust):**
406
173
  ```faust
407
174
  process = case {
408
175
  (0) => 1;
@@ -410,114 +177,35 @@ process = case {
410
177
  (n) => n * 2;
411
178
  };
412
179
  ```
413
-
414
- **Output (Ruby):**
415
180
  ```ruby
416
- flambda(:n) { |n| select2(n.eq(0), select2(n.eq(1), (n * 2), 2), 1) }
181
+ fcase(0 => 1, 1 => 2) { |n| (n * 2) }
417
182
  ```
418
183
 
419
- The variable pattern `(n)` becomes the default/else case, and integer patterns are checked in order.
420
-
421
- **Limitations:**
422
- - Only integer patterns are converted to `select2` (variable patterns become default)
423
- - Complex patterns (tuples, nested expressions) fall back to `literal()`
424
- - Recursive functions like `fact(0) = 1; fact(n) = n * fact(n-1)` require compile-time evaluation not available at runtime
184
+ The `fcase` DSL method handles integer patterns as a hash, with the block as the default case.
425
185
 
426
- ### Pattern Matching on Function Arguments
186
+ ### Partial Application
427
187
 
428
- Multi-rule function definitions with single-parameter pattern matching are now supported:
429
-
430
- **Input (Faust):**
431
188
  ```faust
432
- fact(0) = 1;
433
- fact(n) = n * fact(n - 1);
189
+ halfGain = *(0.5);
190
+ process = osc(440) : halfGain;
434
191
  ```
435
-
436
- **Output (Ruby):**
437
192
  ```ruby
438
- fact = flambda(:n) { |n| select2(n.eq(0), (n * fact((n - 1))), 1) }
193
+ halfGain = gain(0.5)
194
+ process = osc(440) >> halfGain
439
195
  ```
440
196
 
441
- Multiple definitions with the same name are automatically merged into a case expression, then converted to `select2` chains.
442
-
443
- **Limitations:**
444
- - Only single-parameter pattern matching is supported
445
- - Multi-parameter patterns (e.g., `foo(0, 0) = a; foo(x, y) = b;`) are not merged
446
- - Recursive functions like factorial require compile-time evaluation (the Ruby output is syntactically correct but may not produce the same runtime behavior)
447
-
448
- ## Known Issues
449
-
450
- ### letrec Blocks
451
-
452
- `letrec` blocks are emitted as `literal()` calls because they implement complex recursive state semantics that don't have a direct Ruby DSL equivalent.
453
-
454
- **Example Faust:**
455
- ```faust
456
- // Spring-damper physics simulation
457
- follower(input) = pos letrec {
458
- 'v = v + step * (-damping * v - stiffness * (pos - input));
459
- 'pos = pos + step * v;
460
- };
461
- ```
197
+ ## Limitations
462
198
 
463
- **Generated Ruby:**
464
- ```ruby
465
- literal("letrec { 'v = (v + (step * ...)); 'pos = (pos + (step * v)) } pos")
466
- ```
199
+ **Not fully supported:**
200
+ - `letrec` blocks (emitted as `literal()`)
201
+ - Foreign functions (`ffunction`)
202
+ - Multi-parameter pattern matching
203
+ - Some library namespaces: `ve.*`, `pm.*`, `sy.*`, `dx.*`
467
204
 
468
- **Why this happens:**
469
- - `'v` means "next value of v" (sample n+1)
470
- - `v` means "current value of v" (sample n)
471
- - This creates mutually recursive signals with feedback
472
- - Ruby lacks native syntax for this pattern
473
-
474
- **Workarounds:**
475
- 1. **Accept the literal** - Round-trip conversion still works; the Faust code is preserved
476
- 2. **Use simple feedback** - For single-variable recursion, use `wire ~ expr` instead
477
- 3. **Refactor in Faust** - Sometimes letrec can be rewritten using standard feedback
478
-
479
- **Impact:** In practice, letrec is rare. Complex DSP files like triode.lib (500+ lines) convert with only 1 literal remaining (the letrec block).
480
-
481
- ### Unmapped Library Functions
482
-
483
- The following Faust library namespaces are not yet mapped and will emit `literal()`:
484
-
485
- | Namespace | Description | Status |
486
- |-----------|-------------|--------|
487
- | `ve.*` | Virtual analog (Moog filters, etc.) | Not mapped |
488
- | `pm.*` | Physical modeling | Not mapped |
489
- | `sy.*` | Synthesizers | Not mapped |
490
- | `dx.*` | DX7 emulation | Not mapped |
491
- | `pf.*` | Phaflangers | Not mapped |
492
- | `dm.*` | Demos | Not mapped |
493
-
494
- **Currently mapped:**
495
- - `os.*` - Oscillators (osc, saw, square, triangle, phasor, lf_*)
496
- - `no.*` - Noise (noise, pink_noise)
497
- - `fi.*` - Filters (lowpass, highpass, resonlp, svf.*, allpass, dcblocker, peak_eq, tf1/tf2, etc.)
498
- - `de.*` - Delays (delay, fdelay, sdelay)
499
- - `en.*` - Envelopes (ar, asr, adsr, adsre)
500
- - `ba.*` - Basics (db2linear, linear2db, tau2pole, midikey2hz, selectn, if, take)
501
- - `si.*` - Signals (smooth, smoo, bus, block)
502
- - `ma.*` - Math (SR, PI, tempo, tanh)
503
- - `re.*` - Reverbs (mono_freeverb, zita_rev1_stereo, jpverb)
504
- - `co.*` - Compressors (compressor_mono, limiter_1176_R4_mono)
505
- - `sp.*` - Spatial (panner)
506
- - `aa.*` - Antialiasing (tanh1, tanh2, arctan, softclip, hardclip, etc.)
507
- - `an.*` - Analyzers (amp_follower, amp_follower_ar, rms_envelope_*, abs_envelope_*, peak_envelope)
508
- - `ef.*` - Effects (gate_mono/stereo, flanger_mono/stereo, phaser2_mono/stereo, wah4, auto_wah, crybaby, echo, transpose, vocoder, speakerbp, cubicnl, dryWetMixer)
509
- - Math primitives (sin, cos, tan, tanh, sinh, cosh, abs, min, max, pow, sqrt, exp, log, floor, ceil, etc.)
510
-
511
- **Contributing:** To add support for unmapped functions, edit `lib/faust2ruby/library_mapper.rb` and add corresponding entries to `lib/ruby2faust/ir.rb`, `lib/ruby2faust/dsl.rb`, and `lib/ruby2faust/emitter.rb`.
205
+ Unmapped functions are preserved as `literal("...")` for round-trip compatibility.
512
206
 
513
207
  ## Architecture
514
208
 
515
209
  ```
516
210
  Faust Source → Lexer → Parser → AST → Ruby Generator → Ruby DSL
517
211
  ```
518
-
519
- - **Lexer** (`lexer.rb`): StringScanner-based tokenizer
520
- - **Parser** (`parser.rb`): Recursive descent parser
521
- - **AST** (`ast.rb`): Abstract syntax tree nodes
522
- - **Generator** (`ruby_generator.rb`): Produces Ruby DSL code
523
- - **Library Mapper** (`library_mapper.rb`): Maps Faust functions to Ruby methods