synth_blocks 1.0.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,275 @@
1
+ module SynthBlocks
2
+ module Fx
3
+ # Direct port from GVerb
4
+ # Source: https://github.com/swh/lv2/blob/master/gverb/gverbdsp.c
5
+ # (and other files from that repo)
6
+ #
7
+ # Here's the original (c) notice from https://github.com/swh/lv2/blob/master/gverb/gverbdsp.c
8
+ #
9
+ # Copyright (C) 1999 Juhana Sadeharju
10
+ # kouhia at nic.funet.fi
11
+ # This program is free software; you can redistribute it and/or modify
12
+ # it under the terms of the GNU General Public License as published by
13
+ # the Free Software Foundation; either version 2 of the License, or
14
+ # (at your option) any later version.
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program; if not, write to the Free Software
21
+ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
+ #
23
+
24
+ require 'prime'
25
+
26
+ class FixedDelay # :nodoc:
27
+ def initialize(size)
28
+ @size = size
29
+ @buf = Array.new(size)
30
+ @idx = 0
31
+ @buf = @buf.map { |e| 0.0 }
32
+ end
33
+
34
+ def read(n)
35
+ i = (@idx - n + @size) % @size;
36
+ @buf[i]
37
+ end
38
+
39
+ def write(x)
40
+ @buf[@idx] = x
41
+ @idx = (@idx + 1) % @size
42
+ end
43
+ end
44
+
45
+ class Damper # :nodoc:
46
+ def initialize(damping)
47
+ @damping = damping
48
+ @delay = 0.0
49
+ end
50
+
51
+ def run(x)
52
+ y = x * (1.0-@damping) + @delay * @damping;
53
+ @delay = y
54
+ y
55
+ end
56
+ end
57
+
58
+ class Diffuser # :nodoc:
59
+ def initialize(size, coeff)
60
+ @size = size.floor
61
+ @coeff = coeff
62
+ @idx = 0
63
+ @buf = Array.new(@size)
64
+ @buf = @buf.map { |e| 0.0 }
65
+ end
66
+
67
+ def run(x)
68
+ w = x - @buf[@idx] * @coeff;
69
+ y = @buf[@idx] + w * @coeff;
70
+ @buf[@idx] = w
71
+ @idx = (@idx + 1) % @size;
72
+ y
73
+ end
74
+ end
75
+ ##
76
+ # GVerb is a relatively simple reverb implementation
77
+ class GVerb
78
+ FDNORDER = 4 # :nodoc:
79
+
80
+
81
+ ##
82
+ # Create new GVerb instance
83
+ #
84
+ # max_room_size is the maximum room size you'll use
85
+ #
86
+ # room_size is the current room size
87
+ #
88
+ def initialize(srate, max_room_size: 120.0, room_size: 50.0, rev_time: 2.0, damping: 0.3, spread: 15.0, input_bandwidth: 1.5, early_level: 0.8, tail_level: 0.5, mix: 0.2)
89
+ @rate = srate
90
+ @damping = damping
91
+ @max_room_size = max_room_size
92
+ @room_size = room_size
93
+ @rev_time = rev_time
94
+ @early_level = early_level
95
+ @tail_level = tail_level
96
+ @mix = mix
97
+ @max_delay = @rate * @max_room_size / 340.0
98
+ @largest_delay = @rate * @room_size / 340.0
99
+ @input_bandwidth = input_bandwidth;
100
+ @input_damper = Damper.new(1.0 - @input_bandwidth)
101
+
102
+
103
+ @fdndels = FDNORDER.times.map do |i|
104
+ FixedDelay.new(@max_delay + 1000)
105
+ end
106
+ @fdngains = Array.new(FDNORDER)
107
+ @fdnlens = Array.new(FDNORDER)
108
+
109
+ @fdndamps = FDNORDER.times.map do |i|
110
+ Damper.new(@damping)
111
+ end
112
+
113
+ ga = 60.0;
114
+ gt = @rev_time;
115
+ ga = 10.0 ** (-ga / 20.0)
116
+ n = @rate * gt
117
+ @alpha = ga ** (1.0 / n)
118
+ gb = 0.0;
119
+ FDNORDER.times do |i|
120
+ gb = 1.000000*@largest_delay if (i == 0)
121
+ gb = 0.816490*@largest_delay if (i == 1)
122
+ gb = 0.707100*@largest_delay if (i == 2)
123
+ gb = 0.632450*@largest_delay if (i == 3)
124
+
125
+ @fdnlens[i] = nearest_prime(gb, 0.5);
126
+ @fdnlens[i] = gb.round;
127
+ @fdngains[i] = -(@alpha ** @fdnlens[i])
128
+ end
129
+
130
+ @d = Array.new(FDNORDER)
131
+ @u = Array.new(FDNORDER)
132
+ @f = Array.new(FDNORDER)
133
+
134
+ # DIFFUSER SECTION
135
+
136
+ diffscale = @fdnlens[3].to_f/(210+159+562+410);
137
+ spread1 = spread.to_f
138
+ spread2 = 3.0*spread
139
+
140
+ b = 210
141
+ r = 0.125541
142
+ a = spread1*r
143
+ c = 210+159+a
144
+ cc = c-b
145
+ r = 0.854046
146
+ a = spread2*r
147
+ d = 210+159+562+a
148
+ dd = d-c
149
+ e = 1341-d
150
+
151
+ @ldifs = [
152
+ Diffuser.new((diffscale*b),0.75),
153
+ Diffuser.new((diffscale*cc),0.75),
154
+ Diffuser.new((diffscale*dd),0.625),
155
+ Diffuser.new((diffscale*e),0.625)
156
+ ]
157
+
158
+ b = 210
159
+ r = -0.568366
160
+ a = spread1*r
161
+ c = 210+159+a
162
+ cc = c-b
163
+ r = -0.126815;
164
+ a = spread2*r
165
+ d = 210+159+562+a
166
+ dd = d-c
167
+ e = 1341-d
168
+
169
+ @rdifs = [
170
+ Diffuser.new((diffscale*b),0.75),
171
+ Diffuser.new((diffscale*cc),0.75),
172
+ Diffuser.new((diffscale*dd),0.625),
173
+ Diffuser.new((diffscale*e),0.625)
174
+ ]
175
+
176
+
177
+ # Tapped delay section */
178
+
179
+ @tapdelay = FixedDelay.new(44000)
180
+ @taps = Array.new(FDNORDER)
181
+ @tapgains = Array.new(FDNORDER)
182
+
183
+ @taps[0] = 5+0.410*@largest_delay
184
+ @taps[1] = 5+0.300*@largest_delay
185
+ @taps[2] = 5+0.155*@largest_delay
186
+ @taps[3] = 5+0.000*@largest_delay
187
+
188
+ FDNORDER.times do |i|
189
+ @tapgains[i] = @alpha ** @taps[i]
190
+ end
191
+ end
192
+
193
+
194
+ ##
195
+ # runs a value through the reverb, returns the reverberated signal
196
+ # mixed with the original.
197
+ def run(x)
198
+ if x.nan? || x.abs > 100000.0
199
+ x = 0.0
200
+ end
201
+
202
+ z = @input_damper.run(x)
203
+ z = @ldifs[0].run(z)
204
+ FDNORDER.times do |i|
205
+ @u[i] = @tapgains[i] * @tapdelay.read(@taps[i])
206
+ end
207
+
208
+ @tapdelay.write(z)
209
+
210
+ FDNORDER.times do |i|
211
+ @d[i] = @fdndamps[i].run(@fdngains[i] * @fdndels[i].read(@fdnlens[i]))
212
+ end
213
+
214
+ sum = 0.0
215
+ sign = 1.0
216
+ FDNORDER.times do |i|
217
+ sum += sign * (@tail_level * @d[i] + @early_level * @u[i])
218
+ sign = -sign
219
+ end
220
+
221
+ sum += x* @early_level
222
+
223
+ lsum = sum
224
+ # rsum = sum
225
+
226
+ @f = fdn_matrix(@d)
227
+
228
+ FDNORDER.times do |i|
229
+ @fdndels[i].write(@u[i] + @f[i])
230
+ end
231
+
232
+ lsum = @ldifs[1].run(lsum)
233
+ lsum = @ldifs[2].run(lsum)
234
+ lsum = @ldifs[3].run(lsum)
235
+
236
+ # rsum = @rdifs[1].run(rsum)
237
+ # rsum = @rdifs[2].run(rsum)
238
+ # rsum = @rdifs[3].run(rsum)
239
+
240
+ lsum = x * (1.0 - @mix) + lsum * @mix
241
+ # rsum = x * (1.0 - mix) + rsum * mix
242
+ return lsum
243
+ end
244
+
245
+
246
+ private
247
+
248
+ def nearest_prime(n_f, rerror)
249
+ n = n_f.to_i
250
+ return n if Prime.prime?(n)
251
+ # assume n is large enough and n*rerror enough smaller than n */
252
+ bound = n*rerror;
253
+ 1.upto(bound) do |k|
254
+ return n+k if Prime.prime?(n+k)
255
+ return n-k if Prime.prime?(n-k)
256
+ end
257
+ return -1
258
+ end
259
+
260
+ def fdn_matrix(a)
261
+ b = Array.new(FDNORDER)
262
+ dl0 = a[0]
263
+ dl1 = a[1]
264
+ dl2 = a[2]
265
+ dl3 = a[3]
266
+
267
+ b[0] = 0.5*(dl0 + dl1 - dl2 - dl3);
268
+ b[1] = 0.5*(dl0 - dl1 - dl2 + dl3);
269
+ b[2] = 0.5*(-dl0 + dl1 - dl2 + dl3);
270
+ b[3] = 0.5*(dl0 + dl1 + dl2 + dl3);
271
+ b
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,15 @@
1
+ module SynthBlocks
2
+ module Fx
3
+ ##
4
+ # Simple soft limiter
5
+ # Taken from https://github.com/pichenettes/stmlib/blob/448babb082dfe7b0a1ffbf0b349eefde64691b49/dsp/dsp.h#L97
6
+ class Limiter
7
+
8
+ ##
9
+ # run limiter
10
+ def run(x)
11
+ x * (27.0 + x * x) / (27.0 + 9.0 * x * x)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module SynthBlocks
2
+ module Fx
3
+ ##
4
+ # waveshaper, source http://www.musicdsp.org/en/latest/Effects/41-waveshaper.html
5
+ # amount can go from 1 to ... oo
6
+ # the higher a the stronger is the distortion
7
+ class Waveshaper
8
+ ##
9
+ # Waveshaper amount
10
+ attr_reader :amount
11
+ ##
12
+ # Create waveshaper instance
13
+ # [amount] Amount can be from 0 to oo
14
+ def initialize(amount)
15
+ @amount = amount
16
+ end
17
+ ##
18
+ # run waveshaper
19
+ def run(input)
20
+ input * (input.abs + amount) / (input ** 2 + (amount - 1) * input.abs + 1)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,106 @@
1
+ require 'synth_blocks/core/sound'
2
+
3
+ module SynthBlocks
4
+ module Mixer
5
+ ##
6
+ # Emulation of a mixer channel on a mixing desk
7
+ # has a built in EQ, compressor and ducker, can run an arbitrary
8
+ # number of insert effects and send channels
9
+ class MixerChannel < SynthBlocks::Core::Sound
10
+ ##
11
+ # These params can be automated
12
+ LIVE_PARAMS = [:volume, :eq_low_gain, :eq_high_gain, :eq_mid_gain]
13
+ attr_accessor :preset # :nodoc:
14
+
15
+ def live_params # :nodoc:
16
+ LIVE_PARAMS
17
+ end
18
+
19
+ ##
20
+ # - source - Source sound generator
21
+ # - insert_effects - Array of effects instances
22
+ # - sends - Array of send values
23
+ # === Parameters
24
+ # - volume - channel volume
25
+ # - eq_low_freq, eq_high_freq - shelving frequencies for equalizer
26
+ # - eq_low_gain, eq_mid_gain, eq_high_gain - Equalizer gains per band
27
+ # - comp_threshold, comp_ratio, comp_attack, comp_release - Compressor params
28
+ # - duck - Duck amount (0-1)
29
+ # - duck attack, duck_release - Ducker envelope params in s
30
+ def initialize(srate, source, insert_effects: [], sends: [], preset: {})
31
+ @source = source
32
+ @insert_effects = insert_effects
33
+ @sends = sends
34
+ @preset = {
35
+ volume: 0.2,
36
+ eq_low_freq: 880,
37
+ eq_high_freq: 5000,
38
+ eq_low_gain: 1.0,
39
+ eq_mid_gain: 1.0,
40
+ eq_high_gain: 1.0,
41
+ comp_threshold: -50.0,
42
+ comp_ratio: 0.4,
43
+ comp_attack: 80.0,
44
+ comp_release: 200.0,
45
+ duck: 0.0,
46
+ duck_attack: 0.01,
47
+ duck_release: 0.5
48
+ }.merge(preset)
49
+
50
+ super(srate)
51
+ @ducks = []
52
+ @duck_env = SynthBlocks::Mod::Envelope.new(@preset[:duck_attack], @preset[:duck_release])
53
+ @eq = SynthBlocks::Fx::Eq.new(srate, lowfreq: @preset[:eq_low_freq], highfreq: @preset[:eq_high_freq])
54
+ @compressor = SynthBlocks::Fx::Compressor.new(srate, attack: @preset[:comp_attack], release: @preset[:comp_release], ratio: @preset[:comp_ratio], threshold: @preset[:comp_threshold])
55
+ update_live_params(0)
56
+ end
57
+
58
+ ##
59
+ # Schedule ducking at time t (in seconds)
60
+ def duck(t)
61
+ @ducks << t
62
+ @ducks.sort
63
+ end
64
+
65
+ ##
66
+ # returns send portion of output signal for send index
67
+ def send(index)
68
+ @output * (@sends[index] || 0.0)
69
+ end
70
+
71
+ ##
72
+ # runs channel
73
+ def run(offset)
74
+ t = time(offset)
75
+ update_live_params(t)
76
+ out = @eq.run(@source.run(offset))
77
+ @insert_effects.each do |effect|
78
+ out = effect.run(out)
79
+ end
80
+ if @preset[:duck] != 0.0
81
+ duck = current_duck(t)
82
+ if duck
83
+ local_duck = t - duck
84
+ out = out * (1.0 - @preset[:duck] * @duck_env.run(local_duck))
85
+ end
86
+ end
87
+ out = @compressor.run(out)
88
+ @output = out * @preset[:volume]
89
+ end
90
+
91
+ private
92
+
93
+ def update_live_params(t)
94
+ @eq.low_gain = get(:eq_low_gain, t)
95
+ @eq.mid_gain = get(:eq_low_gain, t)
96
+ @eq.high_gain = get(:eq_low_gain, t)
97
+ end
98
+
99
+ def current_duck(t)
100
+ past = @ducks.select {|duck| duck < t}
101
+ return past.last unless past.empty?
102
+ nil
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,25 @@
1
+ require 'synth_blocks/mixer/mixer_channel'
2
+
3
+ module SynthBlocks
4
+ module Mixer
5
+ ##t
6
+ # Channel subclass specifically for SendChannels
7
+ class SendChannel < MixerChannel
8
+ ##
9
+ # creates new send channel. See MixerChannel#new for parameters
10
+ def initialize(srate, insert_effects: [], sends: [], preset: {})
11
+ super(srate, nil, insert_effects: insert_effects, sends: sends, preset: preset)
12
+ end
13
+
14
+ ##
15
+ # run the send channel
16
+ def run(offset, input)
17
+ out = @eq.run(input)
18
+ @insert_effects.each do |effect|
19
+ out = effect.run(out)
20
+ end
21
+ @output = out * @preset[:volume]
22
+ end
23
+ end
24
+ end
25
+ end