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 +4 -4
- data/README.md +11 -5
- data/bin/faust2ruby +3 -1
- data/faust2ruby.md +58 -370
- data/lib/faust2ruby/ruby_generator.rb +65 -30
- data/lib/ruby2faust/dsl.rb +82 -22
- data/lib/ruby2faust/emitter.rb +141 -41
- data/lib/ruby2faust/ir.rb +2 -1
- data/lib/ruby2faust/version.rb +1 -1
- data/lib/ruby2faust.rb +8 -3
- data/ruby2faust.md +63 -27
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '093f5e9746f4614c8b4085708ccd9b2bf092d06c3a50f5ff08fec837a86ff978'
|
|
4
|
+
data.tar.gz: 4f1262a4cf6d6e9357f9315729b9628b123034c5092867b9a48f78850acef31f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
27
|
+
## Quick Examples
|
|
28
28
|
|
|
29
29
|
```ruby
|
|
30
30
|
require 'ruby2faust'
|
|
31
31
|
|
|
32
32
|
code = Ruby2Faust.generate do
|
|
33
|
-
|
|
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
|
-
#
|
|
38
|
-
#
|
|
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)
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
# => "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
74
|
+
osc(440) | osc(880)
|
|
120
75
|
```
|
|
121
76
|
|
|
122
|
-
### Feedback
|
|
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
|
-
|
|
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(
|
|
92
|
+
fpar(4) { |i| osc(i * 100) }
|
|
144
93
|
```
|
|
145
94
|
|
|
146
|
-
##
|
|
95
|
+
## Mapping Overview
|
|
147
96
|
|
|
148
|
-
### Composition
|
|
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
|
-
###
|
|
108
|
+
### Numeric Extensions
|
|
159
109
|
|
|
160
|
-
|
|
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
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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 '
|
|
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
|
-
|
|
144
|
+
ruby_expr = Faust2Ruby.to_ruby(faust_input, expression_only: true)
|
|
261
145
|
|
|
262
|
-
# Ruby → Faust
|
|
263
146
|
include Ruby2Faust::DSL
|
|
264
|
-
process = eval(
|
|
147
|
+
process = eval(ruby_expr)
|
|
265
148
|
faust_output = Ruby2Faust::Emitter.program(process)
|
|
266
149
|
```
|
|
267
150
|
|
|
268
|
-
##
|
|
151
|
+
## Advanced Features
|
|
269
152
|
|
|
270
|
-
|
|
153
|
+
### With Clauses
|
|
154
|
+
|
|
155
|
+
Local definitions are converted to Ruby lambdas:
|
|
271
156
|
|
|
272
|
-
**Input (Faust):**
|
|
273
157
|
```faust
|
|
274
|
-
myDSP =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
+
fcase(0 => 1, 1 => 2) { |n| (n * 2) }
|
|
417
182
|
```
|
|
418
183
|
|
|
419
|
-
The
|
|
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
|
-
###
|
|
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
|
-
|
|
433
|
-
|
|
189
|
+
halfGain = *(0.5);
|
|
190
|
+
process = osc(440) : halfGain;
|
|
434
191
|
```
|
|
435
|
-
|
|
436
|
-
**Output (Ruby):**
|
|
437
192
|
```ruby
|
|
438
|
-
|
|
193
|
+
halfGain = gain(0.5)
|
|
194
|
+
process = osc(440) >> halfGain
|
|
439
195
|
```
|
|
440
196
|
|
|
441
|
-
|
|
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
|
-
**
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
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
|