frausto 0.2.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.
@@ -0,0 +1,1332 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ir"
4
+
5
+ module Ruby2Faust
6
+ # DSP wrapper class for building graphs with method chaining.
7
+ class DSP
8
+ attr_reader :node
9
+
10
+ def initialize(node)
11
+ @node = node
12
+ end
13
+
14
+ # Sequential composition (Faust :)
15
+ def then(other)
16
+ other = DSL.to_dsp(other)
17
+ DSP.new(Node.new(type: NodeType::SEQ, inputs: [node, other.node], channels: other.node.channels))
18
+ end
19
+ alias >> then
20
+
21
+ # Parallel composition (Faust ,)
22
+ def par(other)
23
+ other = DSL.to_dsp(other)
24
+ DSP.new(Node.new(type: NodeType::PAR, inputs: [node, other.node], channels: node.channels + other.node.channels))
25
+ end
26
+ alias | par
27
+
28
+ # Fan-out / split (Faust <:)
29
+ def split(*others)
30
+ others = others.map { |o| DSL.to_dsp(o) }
31
+ total_channels = others.sum { |o| o.node.channels }
32
+ DSP.new(Node.new(type: NodeType::SPLIT, inputs: [node] + others.map(&:node), channels: total_channels))
33
+ end
34
+
35
+ # Fan-in / merge (Faust :>)
36
+ def merge(other)
37
+ other = DSL.to_dsp(other)
38
+ DSP.new(Node.new(type: NodeType::MERGE, inputs: [node, other.node], channels: other.node.channels))
39
+ end
40
+
41
+ # Feedback loop (Faust ~)
42
+ def feedback(other)
43
+ other = DSL.to_dsp(other)
44
+ DSP.new(Node.new(type: NodeType::FEEDBACK, inputs: [node, other.node], channels: node.channels))
45
+ end
46
+ alias ~ feedback
47
+
48
+ def channels
49
+ node.channels
50
+ end
51
+
52
+ # Attach a Faust comment to this node
53
+ # @param text [String] Comment text
54
+ # @return [DSP] New DSP with comment wrapper
55
+ def doc(text)
56
+ DSP.new(Node.new(type: NodeType::DOC, args: [text], inputs: [node], channels: node.channels))
57
+ end
58
+
59
+ # Multiply / gain (Faust *)
60
+ # @param other [Numeric, DSP, Symbol]
61
+ # @return [DSP]
62
+ def *(other)
63
+ other = DSL.to_dsp(other)
64
+ DSP.new(Node.new(type: NodeType::MUL, inputs: [node, other.node]))
65
+ end
66
+
67
+ def to_s
68
+ Emitter.emit(node)
69
+ end
70
+
71
+ def inspect
72
+ "#<Ruby2Faust::DSP #{to_s}>"
73
+ end
74
+
75
+ # Add / mix signals (Faust +)
76
+ # @param other [Numeric, DSP, Symbol]
77
+ # @return [DSP]
78
+ def +(other)
79
+ other = DSL.to_dsp(other)
80
+ DSP.new(Node.new(type: NodeType::ADD, inputs: [node, other.node]))
81
+ end
82
+
83
+ # Subtract signals (Faust -)
84
+ # @param other [Numeric, DSP, Symbol]
85
+ # @return [DSP]
86
+ def -(other)
87
+ other = DSL.to_dsp(other)
88
+ DSP.new(Node.new(type: NodeType::SUB, inputs: [node, other.node]))
89
+ end
90
+
91
+ # Divide signals (Faust /)
92
+ # @param other [Numeric, DSP, Symbol]
93
+ # @return [DSP]
94
+ def /(other)
95
+ other = DSL.to_dsp(other)
96
+ DSP.new(Node.new(type: NodeType::DIV, inputs: [node, other.node]))
97
+ end
98
+
99
+ # Negative of signal (Faust 0 - x)
100
+ # @return [DSP]
101
+ def neg
102
+ DSP.new(Node.new(type: NodeType::NEG, inputs: [node]))
103
+ end
104
+ alias -@ neg
105
+
106
+ # Modulo (Faust %)
107
+ # @param other [Numeric, DSP]
108
+ # @return [DSP]
109
+ def %(other)
110
+ other = DSL.to_dsp(other)
111
+ DSP.new(Node.new(type: NodeType::MOD, inputs: [node, other.node]))
112
+ end
113
+
114
+ # Comparison operators - return DSP nodes for Faust comparisons
115
+ # Note: These return DSP, not boolean, for signal processing
116
+
117
+ # Less than (Faust <)
118
+ def <(other)
119
+ other = DSL.to_dsp(other)
120
+ DSP.new(Node.new(type: NodeType::LT, inputs: [node, other.node]))
121
+ end
122
+
123
+ # Greater than (Faust >)
124
+ def >(other)
125
+ other = DSL.to_dsp(other)
126
+ DSP.new(Node.new(type: NodeType::GT, inputs: [node, other.node]))
127
+ end
128
+
129
+ # Less than or equal (Faust <=)
130
+ def <=(other)
131
+ other = DSL.to_dsp(other)
132
+ DSP.new(Node.new(type: NodeType::LE, inputs: [node, other.node]))
133
+ end
134
+
135
+ # Greater than or equal (Faust >=)
136
+ def >=(other)
137
+ other = DSL.to_dsp(other)
138
+ DSP.new(Node.new(type: NodeType::GE, inputs: [node, other.node]))
139
+ end
140
+
141
+ # Equal (Faust ==) - named method since Ruby == must return boolean
142
+ def eq(other)
143
+ other = DSL.to_dsp(other)
144
+ DSP.new(Node.new(type: NodeType::EQ, inputs: [node, other.node]))
145
+ end
146
+
147
+ # Not equal (Faust !=)
148
+ def neq(other)
149
+ other = DSL.to_dsp(other)
150
+ DSP.new(Node.new(type: NodeType::NEQ, inputs: [node, other.node]))
151
+ end
152
+
153
+ # Bitwise AND (Faust &)
154
+ def &(other)
155
+ other = DSL.to_dsp(other)
156
+ DSP.new(Node.new(type: NodeType::BAND, inputs: [node, other.node]))
157
+ end
158
+
159
+ # Bitwise XOR (Faust xor)
160
+ def ^(other)
161
+ other = DSL.to_dsp(other)
162
+ DSP.new(Node.new(type: NodeType::XOR, inputs: [node, other.node]))
163
+ end
164
+
165
+ # Bitwise OR - use method name to avoid conflict with parallel composition
166
+ def bor(other)
167
+ other = DSL.to_dsp(other)
168
+ DSP.new(Node.new(type: NodeType::BOR, inputs: [node, other.node]))
169
+ end
170
+ end
171
+
172
+ # DSL module with comprehensive Faust library primitives.
173
+ module DSL
174
+ module_function
175
+
176
+ def to_dsp(value)
177
+ case value
178
+ when DSP then value
179
+ when Numeric then literal(value.to_s)
180
+ when String then literal(value)
181
+ when Symbol then literal(value.to_s)
182
+ else raise ArgumentError, "Cannot convert #{value.class} to DSP"
183
+ end
184
+ end
185
+
186
+ # =========================================================================
187
+ # COMMENTS / DOCUMENTATION
188
+ # =========================================================================
189
+
190
+ # Line comment (appears on its own line in Faust output)
191
+ # @param text [String] Comment text
192
+ # @return [DSP] Comment node
193
+ def doc(text)
194
+ DSP.new(Node.new(type: NodeType::COMMENT, args: [text], channels: 0))
195
+ end
196
+
197
+ # =========================================================================
198
+ # OSCILLATORS (os.)
199
+ # =========================================================================
200
+
201
+ def osc(freq = wire)
202
+ freq = to_dsp(freq)
203
+ DSP.new(Node.new(type: NodeType::OSC, inputs: [freq.node]))
204
+ end
205
+
206
+ def saw(freq = wire)
207
+ freq = to_dsp(freq)
208
+ DSP.new(Node.new(type: NodeType::SAW, inputs: [freq.node]))
209
+ end
210
+
211
+ def square(freq = wire)
212
+ freq = to_dsp(freq)
213
+ DSP.new(Node.new(type: NodeType::SQUARE, inputs: [freq.node]))
214
+ end
215
+
216
+ def triangle(freq = wire)
217
+ freq = to_dsp(freq)
218
+ DSP.new(Node.new(type: NodeType::TRIANGLE, inputs: [freq.node]))
219
+ end
220
+
221
+ def phasor(tablesize, freq)
222
+ tablesize = to_dsp(tablesize)
223
+ freq = to_dsp(freq)
224
+ DSP.new(Node.new(type: NodeType::PHASOR, inputs: [tablesize.node, freq.node]))
225
+ end
226
+
227
+ def lf_saw(freq)
228
+ freq = to_dsp(freq)
229
+ DSP.new(Node.new(type: NodeType::LF_SAW, inputs: [freq.node]))
230
+ end
231
+
232
+ def lf_triangle(freq)
233
+ freq = to_dsp(freq)
234
+ DSP.new(Node.new(type: NodeType::LF_TRIANGLE, inputs: [freq.node]))
235
+ end
236
+
237
+ def lf_square(freq)
238
+ freq = to_dsp(freq)
239
+ DSP.new(Node.new(type: NodeType::LF_SQUARE, inputs: [freq.node]))
240
+ end
241
+
242
+ def imptrain(freq = wire)
243
+ freq = to_dsp(freq)
244
+ DSP.new(Node.new(type: NodeType::IMPTRAIN, inputs: [freq.node]))
245
+ end
246
+
247
+ def pulsetrain(freq, duty)
248
+ freq = to_dsp(freq)
249
+ duty = to_dsp(duty)
250
+ DSP.new(Node.new(type: NodeType::PULSETRAIN, inputs: [freq.node, duty.node]))
251
+ end
252
+
253
+ # =========================================================================
254
+ # NOISE (no.)
255
+ # =========================================================================
256
+
257
+ def noise
258
+ DSP.new(Node.new(type: NodeType::NOISE))
259
+ end
260
+
261
+ def pink_noise
262
+ DSP.new(Node.new(type: NodeType::PINK_NOISE))
263
+ end
264
+
265
+ # =========================================================================
266
+ # FILTERS (fi.)
267
+ # =========================================================================
268
+
269
+ def lp(freq = wire, order: 1)
270
+ freq = to_dsp(freq)
271
+ DSP.new(Node.new(type: NodeType::LP, args: [order], inputs: [freq.node]))
272
+ end
273
+
274
+ def hp(freq = wire, order: 1)
275
+ freq = to_dsp(freq)
276
+ DSP.new(Node.new(type: NodeType::HP, args: [order], inputs: [freq.node]))
277
+ end
278
+
279
+ def bp(freq = wire, q: 1)
280
+ freq = to_dsp(freq)
281
+ q = to_dsp(q)
282
+ DSP.new(Node.new(type: NodeType::BP, inputs: [freq.node, q.node]))
283
+ end
284
+
285
+ def resonlp(freq, q, gain = 1)
286
+ freq = to_dsp(freq)
287
+ q = to_dsp(q)
288
+ gain = to_dsp(gain)
289
+ DSP.new(Node.new(type: NodeType::RESONLP, inputs: [freq.node, q.node, gain.node]))
290
+ end
291
+
292
+ def resonhp(freq, q, gain = 1)
293
+ freq = to_dsp(freq)
294
+ q = to_dsp(q)
295
+ gain = to_dsp(gain)
296
+ DSP.new(Node.new(type: NodeType::RESONHP, inputs: [freq.node, q.node, gain.node]))
297
+ end
298
+
299
+ def resonbp(freq, q, gain = 1)
300
+ freq = to_dsp(freq)
301
+ q = to_dsp(q)
302
+ gain = to_dsp(gain)
303
+ DSP.new(Node.new(type: NodeType::RESONBP, inputs: [freq.node, q.node, gain.node]))
304
+ end
305
+
306
+ def allpass(maxdelay, delay, feedback)
307
+ maxdelay = to_dsp(maxdelay)
308
+ delay = to_dsp(delay)
309
+ feedback = to_dsp(feedback)
310
+ DSP.new(Node.new(type: NodeType::ALLPASS, inputs: [maxdelay.node, delay.node, feedback.node]))
311
+ end
312
+
313
+ def dcblock
314
+ DSP.new(Node.new(type: NodeType::DCBLOCK))
315
+ end
316
+
317
+ def peak_eq(freq, q, gain_db)
318
+ freq = to_dsp(freq)
319
+ q = to_dsp(q)
320
+ gain_db = to_dsp(gain_db)
321
+ DSP.new(Node.new(type: NodeType::PEAK_EQ, inputs: [freq.node, q.node, gain_db.node]))
322
+ end
323
+
324
+ # SVF (State Variable Filter) filters
325
+
326
+ def svf_lp(freq, q)
327
+ freq = to_dsp(freq)
328
+ q = to_dsp(q)
329
+ DSP.new(Node.new(type: NodeType::SVF_LP, inputs: [freq.node, q.node]))
330
+ end
331
+
332
+ def svf_hp(freq, q)
333
+ freq = to_dsp(freq)
334
+ q = to_dsp(q)
335
+ DSP.new(Node.new(type: NodeType::SVF_HP, inputs: [freq.node, q.node]))
336
+ end
337
+
338
+ def svf_bp(freq, q)
339
+ freq = to_dsp(freq)
340
+ q = to_dsp(q)
341
+ DSP.new(Node.new(type: NodeType::SVF_BP, inputs: [freq.node, q.node]))
342
+ end
343
+
344
+ def svf_notch(freq, q)
345
+ freq = to_dsp(freq)
346
+ q = to_dsp(q)
347
+ DSP.new(Node.new(type: NodeType::SVF_NOTCH, inputs: [freq.node, q.node]))
348
+ end
349
+
350
+ def svf_ap(freq, q)
351
+ freq = to_dsp(freq)
352
+ q = to_dsp(q)
353
+ DSP.new(Node.new(type: NodeType::SVF_AP, inputs: [freq.node, q.node]))
354
+ end
355
+
356
+ def svf_bell(freq, q, gain)
357
+ freq = to_dsp(freq)
358
+ q = to_dsp(q)
359
+ gain = to_dsp(gain)
360
+ DSP.new(Node.new(type: NodeType::SVF_BELL, inputs: [freq.node, q.node, gain.node]))
361
+ end
362
+
363
+ def svf_ls(freq, q, gain)
364
+ freq = to_dsp(freq)
365
+ q = to_dsp(q)
366
+ gain = to_dsp(gain)
367
+ DSP.new(Node.new(type: NodeType::SVF_LS, inputs: [freq.node, q.node, gain.node]))
368
+ end
369
+
370
+ def svf_hs(freq, q, gain)
371
+ freq = to_dsp(freq)
372
+ q = to_dsp(q)
373
+ gain = to_dsp(gain)
374
+ DSP.new(Node.new(type: NodeType::SVF_HS, inputs: [freq.node, q.node, gain.node]))
375
+ end
376
+
377
+ # Convenience alias for svf_ls (low shelf)
378
+ def svfLowShelf(freq, q, gain = 1)
379
+ svf_ls(freq, q, gain)
380
+ end
381
+
382
+ # Other filter types
383
+
384
+ def lowpass3e(freq)
385
+ freq = to_dsp(freq)
386
+ DSP.new(Node.new(type: NodeType::LOWPASS3E, inputs: [freq.node]))
387
+ end
388
+
389
+ def highpass3e(freq)
390
+ freq = to_dsp(freq)
391
+ DSP.new(Node.new(type: NodeType::HIGHPASS3E, inputs: [freq.node]))
392
+ end
393
+
394
+ def lowpass6e(freq)
395
+ freq = to_dsp(freq)
396
+ DSP.new(Node.new(type: NodeType::LOWPASS6E, inputs: [freq.node]))
397
+ end
398
+
399
+ def highpass6e(freq)
400
+ freq = to_dsp(freq)
401
+ DSP.new(Node.new(type: NodeType::HIGHPASS6E, inputs: [freq.node]))
402
+ end
403
+
404
+ def bandstop(order, freq, q)
405
+ order = to_dsp(order)
406
+ freq = to_dsp(freq)
407
+ q = to_dsp(q)
408
+ DSP.new(Node.new(type: NodeType::BANDSTOP, inputs: [order.node, freq.node, q.node]))
409
+ end
410
+
411
+ def notchw(freq, width)
412
+ freq = to_dsp(freq)
413
+ width = to_dsp(width)
414
+ DSP.new(Node.new(type: NodeType::NOTCHW, inputs: [freq.node, width.node]))
415
+ end
416
+
417
+ def low_shelf(freq, q, gain)
418
+ freq = to_dsp(freq)
419
+ q = to_dsp(q)
420
+ gain = to_dsp(gain)
421
+ DSP.new(Node.new(type: NodeType::LOW_SHELF, inputs: [freq.node, q.node, gain.node]))
422
+ end
423
+
424
+ def high_shelf(freq, q, gain)
425
+ freq = to_dsp(freq)
426
+ q = to_dsp(q)
427
+ gain = to_dsp(gain)
428
+ DSP.new(Node.new(type: NodeType::HIGH_SHELF, inputs: [freq.node, q.node, gain.node]))
429
+ end
430
+
431
+ def peak_eq_cq(freq, q, gain)
432
+ freq = to_dsp(freq)
433
+ q = to_dsp(q)
434
+ gain = to_dsp(gain)
435
+ DSP.new(Node.new(type: NodeType::PEAK_EQ_CQ, inputs: [freq.node, q.node, gain.node]))
436
+ end
437
+
438
+ def fi_pole(p)
439
+ p = to_dsp(p)
440
+ DSP.new(Node.new(type: NodeType::FI_POLE, inputs: [p.node]))
441
+ end
442
+
443
+ def fi_zero(z)
444
+ z = to_dsp(z)
445
+ DSP.new(Node.new(type: NodeType::FI_ZERO, inputs: [z.node]))
446
+ end
447
+
448
+ def tf1(b0, b1, a1)
449
+ b0 = to_dsp(b0)
450
+ b1 = to_dsp(b1)
451
+ a1 = to_dsp(a1)
452
+ DSP.new(Node.new(type: NodeType::TF1, inputs: [b0.node, b1.node, a1.node]))
453
+ end
454
+
455
+ def tf2(b0, b1, b2, a1, a2)
456
+ b0 = to_dsp(b0)
457
+ b1 = to_dsp(b1)
458
+ b2 = to_dsp(b2)
459
+ a1 = to_dsp(a1)
460
+ a2 = to_dsp(a2)
461
+ DSP.new(Node.new(type: NodeType::TF2, inputs: [b0.node, b1.node, b2.node, a1.node, a2.node]))
462
+ end
463
+
464
+ def tf1s(b0, b1, a1)
465
+ b0 = to_dsp(b0)
466
+ b1 = to_dsp(b1)
467
+ a1 = to_dsp(a1)
468
+ DSP.new(Node.new(type: NodeType::TF1S, inputs: [b0.node, b1.node, a1.node]))
469
+ end
470
+
471
+ def tf2s(b0, b1, b2, a1, a2)
472
+ b0 = to_dsp(b0)
473
+ b1 = to_dsp(b1)
474
+ b2 = to_dsp(b2)
475
+ a1 = to_dsp(a1)
476
+ a2 = to_dsp(a2)
477
+ DSP.new(Node.new(type: NodeType::TF2S, inputs: [b0.node, b1.node, b2.node, a1.node, a2.node]))
478
+ end
479
+
480
+ def iir(bcoeffs, acoeffs)
481
+ bcoeffs = to_dsp(bcoeffs)
482
+ acoeffs = to_dsp(acoeffs)
483
+ DSP.new(Node.new(type: NodeType::IIR, inputs: [bcoeffs.node, acoeffs.node]))
484
+ end
485
+
486
+ def fir(coeffs)
487
+ coeffs = to_dsp(coeffs)
488
+ DSP.new(Node.new(type: NodeType::FIR, inputs: [coeffs.node]))
489
+ end
490
+
491
+ def conv(impulse, size)
492
+ impulse = to_dsp(impulse)
493
+ size = to_dsp(size)
494
+ DSP.new(Node.new(type: NodeType::CONV, inputs: [impulse.node, size.node]))
495
+ end
496
+
497
+ def fbcombfilter(maxdel, del, fb)
498
+ maxdel = to_dsp(maxdel)
499
+ del = to_dsp(del)
500
+ fb = to_dsp(fb)
501
+ DSP.new(Node.new(type: NodeType::FBCOMBFILTER, inputs: [maxdel.node, del.node, fb.node]))
502
+ end
503
+
504
+ def ffcombfilter(maxdel, del)
505
+ maxdel = to_dsp(maxdel)
506
+ del = to_dsp(del)
507
+ DSP.new(Node.new(type: NodeType::FFCOMBFILTER, inputs: [maxdel.node, del.node]))
508
+ end
509
+
510
+ # =========================================================================
511
+ # DELAYS (de.)
512
+ # =========================================================================
513
+
514
+ def delay(maxdelay, d)
515
+ maxdelay = to_dsp(maxdelay)
516
+ d = to_dsp(d)
517
+ DSP.new(Node.new(type: NodeType::DELAY, inputs: [maxdelay.node, d.node]))
518
+ end
519
+
520
+ def fdelay(maxdelay, d)
521
+ maxdelay = to_dsp(maxdelay)
522
+ d = to_dsp(d)
523
+ DSP.new(Node.new(type: NodeType::FDELAY, inputs: [maxdelay.node, d.node]))
524
+ end
525
+
526
+ def sdelay(maxdelay, interp, d)
527
+ maxdelay = to_dsp(maxdelay)
528
+ interp = to_dsp(interp)
529
+ d = to_dsp(d)
530
+ DSP.new(Node.new(type: NodeType::SDELAY, inputs: [maxdelay.node, interp.node, d.node]))
531
+ end
532
+
533
+ # =========================================================================
534
+ # ENVELOPES (en.)
535
+ # =========================================================================
536
+
537
+ def ar(attack, release, gate)
538
+ attack = to_dsp(attack)
539
+ release = to_dsp(release)
540
+ gate = to_dsp(gate)
541
+ DSP.new(Node.new(type: NodeType::AR, inputs: [attack.node, release.node, gate.node]))
542
+ end
543
+
544
+ def asr(attack, sustain_level, release, gate)
545
+ attack = to_dsp(attack)
546
+ sustain_level = to_dsp(sustain_level)
547
+ release = to_dsp(release)
548
+ gate = to_dsp(gate)
549
+ DSP.new(Node.new(type: NodeType::ASR, inputs: [attack.node, sustain_level.node, release.node, gate.node]))
550
+ end
551
+
552
+ def adsr(attack, decay, sustain, release, gate)
553
+ attack = to_dsp(attack)
554
+ decay = to_dsp(decay)
555
+ sustain = to_dsp(sustain)
556
+ release = to_dsp(release)
557
+ gate = to_dsp(gate)
558
+ DSP.new(Node.new(type: NodeType::ADSR, inputs: [attack.node, decay.node, sustain.node, release.node, gate.node]))
559
+ end
560
+
561
+ def adsre(attack, decay, sustain, release, gate)
562
+ attack = to_dsp(attack)
563
+ decay = to_dsp(decay)
564
+ sustain = to_dsp(sustain)
565
+ release = to_dsp(release)
566
+ gate = to_dsp(gate)
567
+ DSP.new(Node.new(type: NodeType::ADSRE, inputs: [attack.node, decay.node, sustain.node, release.node, gate.node]))
568
+ end
569
+
570
+ # =========================================================================
571
+ # MATH (primitives + ma.)
572
+ # =========================================================================
573
+
574
+ def gain(x)
575
+ x = to_dsp(x)
576
+ DSP.new(Node.new(type: NodeType::GAIN, inputs: [x.node]))
577
+ end
578
+
579
+ def add
580
+ DSP.new(Node.new(type: NodeType::ADD))
581
+ end
582
+
583
+ def mul
584
+ DSP.new(Node.new(type: NodeType::MUL))
585
+ end
586
+
587
+ def sub
588
+ DSP.new(Node.new(type: NodeType::SUB))
589
+ end
590
+
591
+ def div
592
+ DSP.new(Node.new(type: NodeType::DIV))
593
+ end
594
+
595
+ def neg
596
+ DSP.new(Node.new(type: NodeType::NEG))
597
+ end
598
+
599
+ def abs_
600
+ DSP.new(Node.new(type: NodeType::ABS))
601
+ end
602
+
603
+ def min_(a, b)
604
+ a = to_dsp(a)
605
+ b = to_dsp(b)
606
+ DSP.new(Node.new(type: NodeType::MIN, inputs: [a.node, b.node]))
607
+ end
608
+
609
+ def max_(a, b)
610
+ a = to_dsp(a)
611
+ b = to_dsp(b)
612
+ DSP.new(Node.new(type: NodeType::MAX, inputs: [a.node, b.node]))
613
+ end
614
+
615
+ def clip(min_val, max_val)
616
+ min_val = to_dsp(min_val)
617
+ max_val = to_dsp(max_val)
618
+ DSP.new(Node.new(type: NodeType::CLIP, inputs: [min_val.node, max_val.node]))
619
+ end
620
+
621
+ def pow(base, exponent)
622
+ base = to_dsp(base)
623
+ exponent = to_dsp(exponent)
624
+ DSP.new(Node.new(type: NodeType::POW, inputs: [base.node, exponent.node]))
625
+ end
626
+
627
+ def sqrt_
628
+ DSP.new(Node.new(type: NodeType::SQRT))
629
+ end
630
+
631
+ def exp_
632
+ DSP.new(Node.new(type: NodeType::EXP))
633
+ end
634
+
635
+ def log_
636
+ DSP.new(Node.new(type: NodeType::LOG))
637
+ end
638
+
639
+ def log10_
640
+ DSP.new(Node.new(type: NodeType::LOG10))
641
+ end
642
+
643
+ def sin_
644
+ DSP.new(Node.new(type: NodeType::SIN))
645
+ end
646
+
647
+ def cos_
648
+ DSP.new(Node.new(type: NodeType::COS))
649
+ end
650
+
651
+ def tan_
652
+ DSP.new(Node.new(type: NodeType::TAN))
653
+ end
654
+
655
+ def tanh_
656
+ DSP.new(Node.new(type: NodeType::TANH))
657
+ end
658
+
659
+ def sinh_
660
+ DSP.new(Node.new(type: NodeType::SINH))
661
+ end
662
+
663
+ def cosh_
664
+ DSP.new(Node.new(type: NodeType::COSH))
665
+ end
666
+
667
+ def asinh_
668
+ DSP.new(Node.new(type: NodeType::ASINH))
669
+ end
670
+
671
+ def acosh_
672
+ DSP.new(Node.new(type: NodeType::ACOSH))
673
+ end
674
+
675
+ def atanh_
676
+ DSP.new(Node.new(type: NodeType::ATANH))
677
+ end
678
+
679
+ def asin_
680
+ DSP.new(Node.new(type: NodeType::ASIN))
681
+ end
682
+
683
+ def acos_
684
+ DSP.new(Node.new(type: NodeType::ACOS))
685
+ end
686
+
687
+ def atan_
688
+ DSP.new(Node.new(type: NodeType::ATAN))
689
+ end
690
+
691
+ def atan2(y, x)
692
+ y = to_dsp(y)
693
+ x = to_dsp(x)
694
+ DSP.new(Node.new(type: NodeType::ATAN2, inputs: [y.node, x.node]))
695
+ end
696
+
697
+ def floor_
698
+ DSP.new(Node.new(type: NodeType::FLOOR))
699
+ end
700
+
701
+ def ceil_
702
+ DSP.new(Node.new(type: NodeType::CEIL))
703
+ end
704
+
705
+ def rint_
706
+ DSP.new(Node.new(type: NodeType::RINT))
707
+ end
708
+
709
+ def fmod(x, y)
710
+ x = to_dsp(x)
711
+ y = to_dsp(y)
712
+ DSP.new(Node.new(type: NodeType::FMOD, inputs: [x.node, y.node]))
713
+ end
714
+
715
+ def int_
716
+ DSP.new(Node.new(type: NodeType::INT))
717
+ end
718
+
719
+ def float_
720
+ DSP.new(Node.new(type: NodeType::FLOAT))
721
+ end
722
+
723
+ # =========================================================================
724
+ # CONVERSION (ba.)
725
+ # =========================================================================
726
+
727
+ def db2linear(x)
728
+ x = to_dsp(x)
729
+ DSP.new(Node.new(type: NodeType::DB2LINEAR, inputs: [x.node]))
730
+ end
731
+
732
+ def linear2db(x)
733
+ x = to_dsp(x)
734
+ DSP.new(Node.new(type: NodeType::LINEAR2DB, inputs: [x.node]))
735
+ end
736
+
737
+ def samp2sec(x)
738
+ x = to_dsp(x)
739
+ DSP.new(Node.new(type: NodeType::SAMP2SEC, inputs: [x.node]))
740
+ end
741
+
742
+ def sec2samp(x)
743
+ x = to_dsp(x)
744
+ DSP.new(Node.new(type: NodeType::SEC2SAMP, inputs: [x.node]))
745
+ end
746
+
747
+ def midi2hz(x)
748
+ x = to_dsp(x)
749
+ DSP.new(Node.new(type: NodeType::MIDI2HZ, inputs: [x.node]))
750
+ end
751
+
752
+ def hz2midi(x)
753
+ x = to_dsp(x)
754
+ DSP.new(Node.new(type: NodeType::HZ2MIDI, inputs: [x.node]))
755
+ end
756
+
757
+ def tau2pole(tau)
758
+ tau = to_dsp(tau)
759
+ DSP.new(Node.new(type: NodeType::TAU2POLE, inputs: [tau.node]))
760
+ end
761
+
762
+ def pole2tau(pole)
763
+ pole = to_dsp(pole)
764
+ DSP.new(Node.new(type: NodeType::POLE2TAU, inputs: [pole.node]))
765
+ end
766
+
767
+ def ba_if(cond, then_val, else_val)
768
+ cond = to_dsp(cond)
769
+ then_val = to_dsp(then_val)
770
+ else_val = to_dsp(else_val)
771
+ DSP.new(Node.new(type: NodeType::BA_IF, inputs: [cond.node, then_val.node, else_val.node]))
772
+ end
773
+
774
+ def selector(n, sel, *inputs)
775
+ sel = to_dsp(sel)
776
+ inputs = inputs.map { |i| to_dsp(i) }
777
+ DSP.new(Node.new(type: NodeType::SELECTOR, args: [n], inputs: [sel.node] + inputs.map(&:node)))
778
+ end
779
+
780
+ def ba_take(idx, tuple)
781
+ idx = to_dsp(idx)
782
+ tuple = to_dsp(tuple)
783
+ DSP.new(Node.new(type: NodeType::BA_TAKE, inputs: [idx.node, tuple.node]))
784
+ end
785
+
786
+ # =========================================================================
787
+ # SMOOTHING (si.)
788
+ # =========================================================================
789
+
790
+ def smooth(tau)
791
+ tau = to_dsp(tau)
792
+ DSP.new(Node.new(type: NodeType::SMOOTH, inputs: [tau.node]))
793
+ end
794
+
795
+ def smoo
796
+ DSP.new(Node.new(type: NodeType::SMOO))
797
+ end
798
+
799
+ # =========================================================================
800
+ # SELECTORS
801
+ # =========================================================================
802
+
803
+ def select2(condition, a, b)
804
+ condition = to_dsp(condition)
805
+ a = to_dsp(a)
806
+ b = to_dsp(b)
807
+ DSP.new(Node.new(type: NodeType::SELECT2, inputs: [condition.node, a.node, b.node], channels: a.node.channels))
808
+ end
809
+
810
+ def selectn(n, index, *signals)
811
+ index = to_dsp(index)
812
+ signals = signals.map { |s| to_dsp(s) }
813
+ DSP.new(Node.new(type: NodeType::SELECTN, args: [n], inputs: [index.node] + signals.map(&:node), channels: signals.first&.node&.channels || 1))
814
+ end
815
+
816
+ # =========================================================================
817
+ # ROUTING (si./ro.)
818
+ # =========================================================================
819
+
820
+ def bus(n)
821
+ DSP.new(Node.new(type: NodeType::BUS, args: [n], channels: n))
822
+ end
823
+
824
+ def block(n)
825
+ DSP.new(Node.new(type: NodeType::BLOCK, args: [n], channels: 0))
826
+ end
827
+
828
+ # =========================================================================
829
+ # REVERBS (re.)
830
+ # =========================================================================
831
+
832
+ def freeverb(fb1, fb2, damp, spread)
833
+ fb1 = to_dsp(fb1)
834
+ fb2 = to_dsp(fb2)
835
+ damp = to_dsp(damp)
836
+ spread = to_dsp(spread)
837
+ DSP.new(Node.new(type: NodeType::FREEVERB, inputs: [fb1.node, fb2.node, damp.node, spread.node]))
838
+ end
839
+
840
+ def zita_rev(rdel, f1, f2, t60dc, t60m, fsmax)
841
+ DSP.new(Node.new(type: NodeType::ZITA_REV, args: [rdel, f1, f2, t60dc, t60m, fsmax], channels: 2))
842
+ end
843
+
844
+ def jpverb(t60, damp, size, early_diff, mod_depth, mod_freq, low, mid, high, low_cut, high_cut)
845
+ DSP.new(Node.new(type: NodeType::JPVERB, args: [t60, damp, size, early_diff, mod_depth, mod_freq, low, mid, high, low_cut, high_cut], channels: 2))
846
+ end
847
+
848
+ # =========================================================================
849
+ # COMPRESSORS (co.)
850
+ # =========================================================================
851
+
852
+ def compressor(ratio, thresh, attack, release)
853
+ ratio = to_dsp(ratio)
854
+ thresh = to_dsp(thresh)
855
+ attack = to_dsp(attack)
856
+ release = to_dsp(release)
857
+ DSP.new(Node.new(type: NodeType::COMPRESSOR, inputs: [ratio.node, thresh.node, attack.node, release.node]))
858
+ end
859
+
860
+ def limiter
861
+ DSP.new(Node.new(type: NodeType::LIMITER))
862
+ end
863
+
864
+ # =========================================================================
865
+ # SPATIAL (sp.)
866
+ # =========================================================================
867
+
868
+ def panner(pan)
869
+ pan = to_dsp(pan)
870
+ DSP.new(Node.new(type: NodeType::PANNER, inputs: [pan.node], channels: 2))
871
+ end
872
+
873
+ # =========================================================================
874
+ # UI CONTROLS
875
+ # =========================================================================
876
+
877
+ # Build Faust metadata string from kwargs
878
+ def self.build_metadata(name, order: nil, style: nil, unit: nil, tooltip: nil, scale: nil, **_)
879
+ meta = ""
880
+ meta += "[#{order}]" if order
881
+ meta += "[style:#{style}]" if style
882
+ meta += "[unit:#{unit}]" if unit
883
+ meta += "[tooltip:#{tooltip}]" if tooltip
884
+ meta += "[scale:#{scale}]" if scale
885
+ # If name already has metadata, append; otherwise prefix
886
+ name.to_s.include?("[") ? name.to_s : "#{meta}#{name}"
887
+ end
888
+
889
+ def slider(name, init:, min:, max:, step: 0.01, **meta)
890
+ full_name = DSL.build_metadata(name, **meta)
891
+ DSP.new(Node.new(type: NodeType::SLIDER, args: [full_name, init, min, max, step]))
892
+ end
893
+
894
+ def vslider(name, init:, min:, max:, step: 0.01)
895
+ DSP.new(Node.new(type: NodeType::VSLIDER, args: [name, init, min, max, step]))
896
+ end
897
+
898
+ def nentry(name, init:, min:, max:, step: 1)
899
+ DSP.new(Node.new(type: NodeType::NENTRY, args: [name, init, min, max, step]))
900
+ end
901
+
902
+ def button(name)
903
+ DSP.new(Node.new(type: NodeType::BUTTON, args: [name]))
904
+ end
905
+
906
+ def checkbox(name)
907
+ DSP.new(Node.new(type: NodeType::CHECKBOX, args: [name]))
908
+ end
909
+
910
+ def hgroup(name, content = nil, &block)
911
+ content = block.call if block_given?
912
+ raise ArgumentError, "hgroup requires content or a block" if content.nil?
913
+ content = to_dsp(content)
914
+ DSP.new(Node.new(type: NodeType::HGROUP, args: [name], inputs: [content.node], channels: content.node.channels))
915
+ end
916
+
917
+ def vgroup(name, content = nil, &block)
918
+ content = block.call if block_given?
919
+ raise ArgumentError, "vgroup requires content or a block" if content.nil?
920
+ content = to_dsp(content)
921
+ DSP.new(Node.new(type: NodeType::VGROUP, args: [name], inputs: [content.node], channels: content.node.channels))
922
+ end
923
+
924
+ def tgroup(name, content = nil, &block)
925
+ content = block.call if block_given?
926
+ raise ArgumentError, "tgroup requires content or a block" if content.nil?
927
+ content = to_dsp(content)
928
+ DSP.new(Node.new(type: NodeType::TGROUP, args: [name], inputs: [content.node], channels: content.node.channels))
929
+ end
930
+
931
+ # =========================================================================
932
+ # ITERATION
933
+ # =========================================================================
934
+
935
+ # Parallel iteration: par(i, n, expr)
936
+ # @param count [Integer] Number of iterations
937
+ # @yield [DSP] Block receiving iteration index as DSP param
938
+ # @example fpar(4) { |i| osc(i * 100) }
939
+ # @example fpar(4) { osc(it * 100) } # Ruby 3.4+ implicit it
940
+ # @return [DSP]
941
+ def fpar(count, &block)
942
+ raise ArgumentError, "fpar requires a block" unless block_given?
943
+ var = block.parameters.first&.last || :it
944
+ DSP.new(Node.new(type: NodeType::FPAR, args: [var, count, block], channels: count))
945
+ end
946
+
947
+ # Sequential iteration: seq(i, n, expr)
948
+ # @param count [Integer] Number of iterations
949
+ # @yield [DSP] Block receiving iteration index as DSP param
950
+ # @return [DSP]
951
+ def fseq(count, &block)
952
+ raise ArgumentError, "fseq requires a block" unless block_given?
953
+ var = block.parameters.first&.last || :it
954
+ DSP.new(Node.new(type: NodeType::FSEQ, args: [var, count, block]))
955
+ end
956
+
957
+ # Summation iteration: sum(i, n, expr)
958
+ # @param count [Integer] Number of iterations
959
+ # @yield [DSP] Block receiving iteration index as DSP param
960
+ # @return [DSP]
961
+ def fsum(count, &block)
962
+ raise ArgumentError, "fsum requires a block" unless block_given?
963
+ var = block.parameters.first&.last || :it
964
+ DSP.new(Node.new(type: NodeType::FSUM, args: [var, count, block]))
965
+ end
966
+
967
+ # Product iteration: prod(i, n, expr)
968
+ # @param count [Integer] Number of iterations
969
+ # @yield [DSP] Block receiving iteration index as DSP param
970
+ # @return [DSP]
971
+ def fprod(count, &block)
972
+ raise ArgumentError, "fprod requires a block" unless block_given?
973
+ var = block.parameters.first&.last || :it
974
+ DSP.new(Node.new(type: NodeType::FPROD, args: [var, count, block]))
975
+ end
976
+
977
+ # =========================================================================
978
+ # LAMBDA
979
+ # =========================================================================
980
+
981
+ # Lambda expression: \(x).(body)
982
+ # @param params [Array<Symbol>] Parameter names
983
+ # @yield Block that receives parameters and returns body expression
984
+ # @return [DSP]
985
+ def flambda(*params, &block)
986
+ raise ArgumentError, "flambda requires a block" unless block_given?
987
+ DSP.new(Node.new(type: NodeType::LAMBDA, args: [params, block]))
988
+ end
989
+
990
+ # Parameter reference within a lambda
991
+ # @param name [Symbol] Parameter name
992
+ # @return [DSP]
993
+ def param(name)
994
+ DSP.new(Node.new(type: NodeType::PARAM, args: [name]))
995
+ end
996
+
997
+ # =========================================================================
998
+ # TABLES
999
+ # =========================================================================
1000
+
1001
+ # Read-only table: rdtable(n, init, ridx)
1002
+ # @param size [Integer, DSP] Table size
1003
+ # @param init [DSP] Initialization signal
1004
+ # @param ridx [DSP] Read index
1005
+ # @return [DSP]
1006
+ def rdtable(size, init, ridx)
1007
+ size = to_dsp(size)
1008
+ init = to_dsp(init)
1009
+ ridx = to_dsp(ridx)
1010
+ DSP.new(Node.new(type: NodeType::RDTABLE, inputs: [size.node, init.node, ridx.node]))
1011
+ end
1012
+
1013
+ # Read/write table: rwtable(n, init, widx, wsig, ridx)
1014
+ # @param size [Integer, DSP] Table size
1015
+ # @param init [DSP] Initialization signal
1016
+ # @param widx [DSP] Write index
1017
+ # @param wsig [DSP] Write signal
1018
+ # @param ridx [DSP] Read index
1019
+ # @return [DSP]
1020
+ def rwtable(size, init, widx, wsig, ridx)
1021
+ size = to_dsp(size)
1022
+ init = to_dsp(init)
1023
+ widx = to_dsp(widx)
1024
+ wsig = to_dsp(wsig)
1025
+ ridx = to_dsp(ridx)
1026
+ DSP.new(Node.new(type: NodeType::RWTABLE, inputs: [size.node, init.node, widx.node, wsig.node, ridx.node]))
1027
+ end
1028
+
1029
+ # Waveform constant table: waveform{v1, v2, ...}
1030
+ # @param values [Array<Numeric>] Table values
1031
+ # @return [DSP]
1032
+ def waveform(*values)
1033
+ DSP.new(Node.new(type: NodeType::WAVEFORM, args: values))
1034
+ end
1035
+
1036
+ # =========================================================================
1037
+ # ADDITIONAL ROUTING
1038
+ # =========================================================================
1039
+
1040
+ # Route signals: route(ins, outs, connections)
1041
+ # @param ins [Integer] Number of inputs
1042
+ # @param outs [Integer] Number of outputs
1043
+ # @param connections [Array<Array<Integer>>] Connection pairs [[from, to], ...]
1044
+ # @return [DSP]
1045
+ def route(ins, outs, connections)
1046
+ DSP.new(Node.new(type: NodeType::ROUTE, args: [ins, outs, connections], channels: outs))
1047
+ end
1048
+
1049
+ # 3-way selector: select3(sel, a, b, c)
1050
+ # @param sel [DSP] Selector (0, 1, or 2)
1051
+ # @param a [DSP] First signal
1052
+ # @param b [DSP] Second signal
1053
+ # @param c [DSP] Third signal
1054
+ # @return [DSP]
1055
+ def select3(sel, a, b, c)
1056
+ sel = to_dsp(sel)
1057
+ a = to_dsp(a)
1058
+ b = to_dsp(b)
1059
+ c = to_dsp(c)
1060
+ DSP.new(Node.new(type: NodeType::SELECT3, inputs: [sel.node, a.node, b.node, c.node], channels: a.node.channels))
1061
+ end
1062
+
1063
+ # =========================================================================
1064
+ # UTILITY
1065
+ # =========================================================================
1066
+
1067
+ def wire
1068
+ DSP.new(Node.new(type: NodeType::WIRE))
1069
+ end
1070
+
1071
+ def cut
1072
+ DSP.new(Node.new(type: NodeType::CUT, channels: 0))
1073
+ end
1074
+
1075
+ def mem
1076
+ DSP.new(Node.new(type: NodeType::MEM))
1077
+ end
1078
+
1079
+ def literal(expr, channels: 1)
1080
+ DSP.new(Node.new(type: NodeType::LITERAL, args: [expr], channels: channels))
1081
+ end
1082
+
1083
+ # Wrap a numeric value as a DSP node (constant signal)
1084
+ # Useful when composing with >> which would otherwise be Ruby's bit-shift
1085
+ def num(value)
1086
+ DSP.new(Node.new(type: NodeType::LITERAL, args: [value.to_s]))
1087
+ end
1088
+
1089
+ # =========================================================================
1090
+ # CONSTANTS
1091
+ # =========================================================================
1092
+
1093
+ def sr
1094
+ DSP.new(Node.new(type: NodeType::SR))
1095
+ end
1096
+
1097
+ def pi
1098
+ DSP.new(Node.new(type: NodeType::PI))
1099
+ end
1100
+
1101
+ def tempo
1102
+ DSP.new(Node.new(type: NodeType::TEMPO))
1103
+ end
1104
+
1105
+ # =========================================================================
1106
+ # ANTIALIASING (aa.)
1107
+ # =========================================================================
1108
+
1109
+ def aa_tanh1
1110
+ DSP.new(Node.new(type: NodeType::AA_TANH1))
1111
+ end
1112
+
1113
+ def aa_tanh2
1114
+ DSP.new(Node.new(type: NodeType::AA_TANH2))
1115
+ end
1116
+
1117
+ def aa_arctan
1118
+ DSP.new(Node.new(type: NodeType::AA_ARCTAN))
1119
+ end
1120
+
1121
+ def aa_softclip
1122
+ DSP.new(Node.new(type: NodeType::AA_SOFTCLIP))
1123
+ end
1124
+
1125
+ def aa_hardclip
1126
+ DSP.new(Node.new(type: NodeType::AA_HARDCLIP))
1127
+ end
1128
+
1129
+ def aa_parabolic
1130
+ DSP.new(Node.new(type: NodeType::AA_PARABOLIC))
1131
+ end
1132
+
1133
+ def aa_sin
1134
+ DSP.new(Node.new(type: NodeType::AA_SIN))
1135
+ end
1136
+
1137
+ def aa_cubic1
1138
+ DSP.new(Node.new(type: NodeType::AA_CUBIC1))
1139
+ end
1140
+
1141
+ def aa_cubic2
1142
+ DSP.new(Node.new(type: NodeType::AA_CUBIC2))
1143
+ end
1144
+
1145
+ # Analyzers (an.)
1146
+ def amp_follower(t)
1147
+ DSP.new(Node.new(type: NodeType::AMP_FOLLOWER, args: [t]))
1148
+ end
1149
+
1150
+ def amp_follower_ar(attack, release)
1151
+ DSP.new(Node.new(type: NodeType::AMP_FOLLOWER_AR, args: [attack, release]))
1152
+ end
1153
+
1154
+ def amp_follower_ud(up, down)
1155
+ DSP.new(Node.new(type: NodeType::AMP_FOLLOWER_UD, args: [up, down]))
1156
+ end
1157
+
1158
+ def rms_envelope_rect(period)
1159
+ DSP.new(Node.new(type: NodeType::RMS_ENVELOPE_RECT, args: [period]))
1160
+ end
1161
+
1162
+ def rms_envelope_tau(tau)
1163
+ DSP.new(Node.new(type: NodeType::RMS_ENVELOPE_TAU, args: [tau]))
1164
+ end
1165
+
1166
+ def abs_envelope_rect(period)
1167
+ DSP.new(Node.new(type: NodeType::ABS_ENVELOPE_RECT, args: [period]))
1168
+ end
1169
+
1170
+ def abs_envelope_tau(tau)
1171
+ DSP.new(Node.new(type: NodeType::ABS_ENVELOPE_TAU, args: [tau]))
1172
+ end
1173
+
1174
+ def ms_envelope_rect(period)
1175
+ DSP.new(Node.new(type: NodeType::MS_ENVELOPE_RECT, args: [period]))
1176
+ end
1177
+
1178
+ def ms_envelope_tau(tau)
1179
+ DSP.new(Node.new(type: NodeType::MS_ENVELOPE_TAU, args: [tau]))
1180
+ end
1181
+
1182
+ def peak_envelope(t)
1183
+ DSP.new(Node.new(type: NodeType::PEAK_ENVELOPE, args: [t]))
1184
+ end
1185
+
1186
+ # Effects (ef.)
1187
+ def cubicnl(drive, offset)
1188
+ DSP.new(Node.new(type: NodeType::CUBICNL, args: [drive, offset]))
1189
+ end
1190
+
1191
+ def gate_mono(thresh, attack, hold, release)
1192
+ DSP.new(Node.new(type: NodeType::GATE_MONO, args: [thresh, attack, hold, release]))
1193
+ end
1194
+
1195
+ def gate_stereo(thresh, attack, hold, release)
1196
+ DSP.new(Node.new(type: NodeType::GATE_STEREO, args: [thresh, attack, hold, release], channels: 2))
1197
+ end
1198
+
1199
+ def ef_compressor_mono(ratio, thresh, attack, release)
1200
+ DSP.new(Node.new(type: NodeType::EF_COMPRESSOR_MONO, args: [ratio, thresh, attack, release]))
1201
+ end
1202
+
1203
+ def ef_compressor_stereo(ratio, thresh, attack, release)
1204
+ DSP.new(Node.new(type: NodeType::EF_COMPRESSOR_STEREO, args: [ratio, thresh, attack, release], channels: 2))
1205
+ end
1206
+
1207
+ def ef_limiter_1176_mono
1208
+ DSP.new(Node.new(type: NodeType::EF_LIMITER_1176_MONO))
1209
+ end
1210
+
1211
+ def ef_limiter_1176_stereo
1212
+ DSP.new(Node.new(type: NodeType::EF_LIMITER_1176_STEREO, channels: 2))
1213
+ end
1214
+
1215
+ def echo(maxdel, del, fb)
1216
+ DSP.new(Node.new(type: NodeType::ECHO, args: [maxdel, del, fb]))
1217
+ end
1218
+
1219
+ def transpose(w, x, s)
1220
+ DSP.new(Node.new(type: NodeType::TRANSPOSE, args: [w, x, s]))
1221
+ end
1222
+
1223
+ def flanger_mono(dmax, delay_freq, level, feedback, invert)
1224
+ DSP.new(Node.new(type: NodeType::FLANGER_MONO, args: [dmax, delay_freq, level, feedback, invert]))
1225
+ end
1226
+
1227
+ def flanger_stereo(dmax, delay_freq, level, feedback, invert, lfsr, lfsf)
1228
+ DSP.new(Node.new(type: NodeType::FLANGER_STEREO, args: [dmax, delay_freq, level, feedback, invert, lfsr, lfsf], channels: 2))
1229
+ end
1230
+
1231
+ def phaser2_mono(nstages, freq, speed, depth, feedback, spread)
1232
+ DSP.new(Node.new(type: NodeType::PHASER2_MONO, args: [nstages, freq, speed, depth, feedback, spread]))
1233
+ end
1234
+
1235
+ def phaser2_stereo(nstages, freq, speed, depth, feedback, spread)
1236
+ DSP.new(Node.new(type: NodeType::PHASER2_STEREO, args: [nstages, freq, speed, depth, feedback, spread], channels: 2))
1237
+ end
1238
+
1239
+ def wah4(fr)
1240
+ DSP.new(Node.new(type: NodeType::WAH4, args: [fr]))
1241
+ end
1242
+
1243
+ def auto_wah(level)
1244
+ DSP.new(Node.new(type: NodeType::AUTO_WAH, args: [level]))
1245
+ end
1246
+
1247
+ def crybaby(wah)
1248
+ DSP.new(Node.new(type: NodeType::CRYBABY, args: [wah]))
1249
+ end
1250
+
1251
+ def vocoder(bands, range)
1252
+ DSP.new(Node.new(type: NodeType::VOCODER, args: [bands, range]))
1253
+ end
1254
+
1255
+ def speakerbp(flo, fhi)
1256
+ DSP.new(Node.new(type: NodeType::SPEAKERBP, args: [flo, fhi]))
1257
+ end
1258
+
1259
+ def dry_wet_mixer(mix)
1260
+ DSP.new(Node.new(type: NodeType::DRY_WET_MIXER, args: [mix]))
1261
+ end
1262
+
1263
+ def dry_wet_mixer_cp(mix)
1264
+ DSP.new(Node.new(type: NodeType::DRY_WET_MIXER_CP, args: [mix]))
1265
+ end
1266
+
1267
+ # Constant aliases for those who prefer the capitalized look
1268
+ SR = DSP.new(Node.new(type: NodeType::SR))
1269
+ PI = DSP.new(Node.new(type: NodeType::PI))
1270
+ TEMPO = DSP.new(Node.new(type: NodeType::TEMPO))
1271
+ end
1272
+
1273
+ # Program-level metadata for Faust declarations
1274
+ class Program
1275
+ attr_reader :process, :declarations, :imports
1276
+
1277
+ def initialize(process = nil, &block)
1278
+ @declarations = {}
1279
+ @imports = ["stdfaust.lib"]
1280
+ if block_given?
1281
+ extend DSL
1282
+ @process = instance_eval(&block)
1283
+ else
1284
+ @process = process
1285
+ end
1286
+ end
1287
+
1288
+ def declare(key, value)
1289
+ @declarations[key] = value
1290
+ self
1291
+ end
1292
+
1293
+ def import(lib)
1294
+ @imports << lib unless @imports.include?(lib)
1295
+ self
1296
+ end
1297
+ end
1298
+ end
1299
+
1300
+ # Numeric extensions for audio conversions
1301
+ # These return DSP nodes that can be used in signal chains
1302
+ class Numeric
1303
+ # MIDI note number to Hz
1304
+ # 60.midi => ba.midikey2hz(60)
1305
+ def midi
1306
+ Ruby2Faust::DSL.midi2hz(self)
1307
+ end
1308
+
1309
+ # dB to linear gain
1310
+ # -6.db => ba.db2linear(-6)
1311
+ def db
1312
+ Ruby2Faust::DSL.db2linear(self)
1313
+ end
1314
+
1315
+ # Seconds to samples
1316
+ # 0.1.sec => ba.sec2samp(0.1)
1317
+ def sec
1318
+ Ruby2Faust::DSL.sec2samp(self)
1319
+ end
1320
+
1321
+ # Milliseconds to samples
1322
+ # 100.ms => ba.sec2samp(0.1)
1323
+ def ms
1324
+ Ruby2Faust::DSL.sec2samp(self / 1000.0)
1325
+ end
1326
+
1327
+ # Hz (pass-through for clarity)
1328
+ # 440.hz => 440
1329
+ def hz
1330
+ Ruby2Faust::DSL.literal(self.to_s)
1331
+ end
1332
+ end