-
Notifications
You must be signed in to change notification settings - Fork 0
/
filters.lua
387 lines (306 loc) · 8.65 KB
/
filters.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
---
--- @module applause
---
--
-- General-purpose IIR filters:
-- These are direct translations of ChucK's LPF, HPF, BPF and BRF
-- ugens which are in turn adapted from SuperCollider 3.
-- See also https://chuck.stanford.edu/doc/program/ugen_full.html#LPF
--
--- De-denormalize function adapted from ChucK.
-- Not quite sure why this is needed - properly to make the
-- IIR filters numerically more stable.
local function ddn(f)
return f >= 0 and (f > 1e-15 and f < 1e15 and f or 0) or
(f < -1e-15 and f > -1e15 and f or 0)
end
--- Low Pass Filter streams.
-- @type LPFStream
-- @local
LPFStream = DeriveClass(MuxableStream)
function LPFStream:muxableCtor(stream, freq)
self.stream = stream
self.freq_stream = freq
end
function LPFStream:gtick()
local a0, b1, b2
local y1, y2 = 0, 0
-- some cached constants
local radians_per_sample = (2*math.pi)/samplerate
local sqrt2 = math.sqrt(2)
-- some cached math table lookups
local tan = math.tan
local tick = self.stream:gtick()
local freq_tick = self.freq_stream:gtick()
local cur_freq = nil
return function()
local sample = tick()
local freq = freq_tick()
if sample == nil or freq == nil then
-- don't filter if we run out of frequency samples
return sample
elseif freq ~= cur_freq then
-- calculate filter coefficients
-- avoid recalculation for constant frequencies
cur_freq = freq
local pfreq = cur_freq * radians_per_sample * 0.5
local C = 1/tan(pfreq)
local C2 = C*C
local sqrt2C = C * sqrt2
a0 = 1/(1 + sqrt2C + C2)
b1 = -2.0 * (1.0 - C2) * a0
b2 = -(1.0 - sqrt2C + C2) * a0
end
local y0 = sample + b1*y1 + b2*y2
local result = a0 * (y0 + 2*y1 + y2)
y2 = ddn(y1)
y1 = ddn(y0)
return result
end
end
function LPFStream:len()
return self.stream:len()
end
--- Apply Low Pass Filter to stream.
-- This is a resonant filter (2nd order Butterworth).
-- @within Class Stream
-- @StreamableNumber freq Cutoff frequency.
-- @treturn Stream
function Stream:LPF(freq)
return LPFStream:new(self, freq)
end
HPFStream = DeriveClass(MuxableStream)
function HPFStream:muxableCtor(stream, freq)
self.stream = stream
self.freq_stream = freq
end
function HPFStream:gtick()
local a0, b1, b2
local y1, y2 = 0, 0
-- some cached constants
local radians_per_sample = (2*math.pi)/samplerate
local sqrt2 = math.sqrt(2)
-- some cached math table lookups
local tan = math.tan
local tick = self.stream:gtick()
local freq_tick = self.freq_stream:gtick()
local cur_freq = nil
-- NOTE: Very similar to LPFStream.gtick()
-- Can we factor out the similarity without sacrificing
-- too much performance?
return function()
local sample = tick()
local freq = freq_tick()
if sample == nil or freq == nil then
-- don't filter if we run out of frequency samples
return sample
elseif freq ~= cur_freq then
-- calculate filter coefficients
-- avoid recalculation for constant frequencies
cur_freq = freq
local pfreq = cur_freq * radians_per_sample * 0.5
local C = tan(pfreq)
local C2 = C*C
local sqrt2C = C * sqrt2
a0 = 1/(1 + sqrt2C + C2)
b1 = 2.0 * (1.0 - C2) * a0
b2 = -(1.0 - sqrt2C + C2) * a0
end
local y0 = sample + b1*y1 + b2*y2
local result = a0 * (y0 - 2*y1 + y2)
y2 = ddn(y1)
y1 = ddn(y0)
return result
end
end
function HPFStream:len()
return self.stream:len()
end
--- Apply High Pass Filter to stream.
-- This is a resonant filter (2nd order Butterworth).
-- @within Class Stream
-- @StreamableNumber freq Cutoff frequency.
-- @treturn Stream
function Stream:HPF(freq)
return HPFStream:new(self, freq)
end
BPFStream = DeriveClass(MuxableStream)
function BPFStream:muxableCtor(stream, freq, quality)
self.stream = stream
self.freq_stream = freq
self.quality_stream = quality
end
function BPFStream:gtick()
local a0, b1, b2
local y1, y2 = 0, 0
-- some cached constants
local radians_per_sample = (2*math.pi)/samplerate
local sqrt2 = math.sqrt(2)
-- some cached math table lookups
local tan = math.tan
local cos = math.cos
local tick = self.stream:gtick()
local freq_tick = self.freq_stream:gtick()
local quality_tick = self.quality_stream:gtick()
local cur_freq, cur_quality
return function()
local sample = tick()
local freq = freq_tick()
local quality = quality_tick()
if sample == nil or freq == nil or quality == nil then
-- don't filter if we run out of frequency samples
return sample
elseif freq ~= cur_freq or quality ~= cur_quality then
-- calculate filter coefficients
-- avoid recalculation for constant frequencies
-- and quality factors
cur_freq = freq
cur_quality = quality
local pfreq = cur_freq * radians_per_sample
local pbw = 1 / cur_quality*pfreq*0.5
local C = 1/tan(pbw)
local D = 2*cos(pfreq);
a0 = 1/(1 + C)
b1 = C*D*a0
b2 = (1 - C)*a0
end
local y0 = sample + b1*y1 + b2*y2
local result = a0 * (y0 - y2)
y2 = ddn(y1)
y1 = ddn(y0)
return result
end
end
function BPFStream:len()
return self.stream:len()
end
--- Apply Band Pass Filter to stream (2nd order Butterworth).
-- @within Class Stream
-- @StreamableNumber freq Center frequency.
-- @StreamableNumber quality
-- The quality factor, indirectly proportional to the passband width.
-- @treturn Stream
function Stream:BPF(freq, quality)
return BPFStream:new(self, freq, quality)
end
BRFStream = DeriveClass(MuxableStream)
function BRFStream:muxableCtor(stream, freq, quality)
self.stream = stream
self.freq_stream = freq
self.quality_stream = quality
end
function BRFStream:gtick()
local a0, b1, b2
local y1, y2 = 0, 0
-- some cached constants
local radians_per_sample = (2*math.pi)/samplerate
local sqrt2 = math.sqrt(2)
-- some cached math table lookups
local tan = math.tan
local cos = math.cos
local tick = self.stream:gtick()
local freq_tick = self.freq_stream:gtick()
local quality_tick = self.quality_stream:gtick()
local cur_freq, cur_quality
-- NOTE: Very similar to BPFStream.gtick()
return function()
local sample = tick()
local freq = freq_tick()
local quality = quality_tick()
if sample == nil or freq == nil or quality == nil then
-- don't filter if we run out of frequency samples
return sample
elseif freq ~= cur_freq then
-- calculate filter coefficients
-- avoid recalculation for constant frequencies
-- and quality factors
cur_freq = freq
cur_quality = quality
local pfreq = cur_freq * radians_per_sample
local pbw = 1 / cur_quality*pfreq*0.5
local C = tan(pbw)
local D = 2*cos(pfreq);
a0 = 1/(1 + C)
b1 = -D*a0
b2 = (1 - C)*a0
end
local y0 = sample - b1*y1 - b2*y2
local result = a0 * (y0 + y2) + b1*y1
y2 = ddn(y1)
y1 = ddn(y0)
return result
end
end
function BRFStream:len()
return self.stream:len()
end
--- Apply Band Reject Filter to stream (2nd order Butterworth).
-- @within Class Stream
-- @StreamableNumber freq Center frequency.
-- @StreamableNumber quality
-- The quality factor, indirectly proportional to the rejectband width.
-- @treturn Stream
function Stream:BRF(freq, quality)
return BRFStream:new(self, freq, quality)
end
--[==[
--
-- Non-working FIR filters (FIXME)
--
-- Normalized Sinc function
local function Sinc(x)
return x == 0 and 1 or
math.sin(2*math.pi*x)/(2*math.pi*x)
end
local function Hamming(n, window)
local alpha = 0.54
return alpha - (1-alpha)*math.cos((2*math.pi*n)/(window-1))
end
local function Blackman(n, window)
local alpha = 0.16
return (1-alpha)/2 -
0.5*math.cos((2*math.pi*n)/(window-1)) +
alpha*0.5*math.cos((4*math.pi*n)/(window-1))
end
FIRStream = DeriveClass(Stream)
function FIRStream:ctor(stream, freq_stream)
self.stream = tostream(stream)
self.freq_stream = tostream(freq_stream)
end
function FIRStream:gtick()
local window = {}
-- window size (max. 1024 samples)
-- this is the max. latency introduced by the filter
-- since the window must be filled before we can generate
-- (filtered) samples
local window_size = math.min(1024, self.stream:len())
local window_p = window_size-1
local accu = 0
local blackman = {}
for i = 1, window_size do blackman[i] = Blackman(i-1, window_size) end
local tick = self.stream:gtick()
local freq_tick = self.freq_stream:gtick()
return function()
-- fill buffer (initial)
while #window < window_size-1 do
table.insert(window, tick())
end
window[window_p+1] = tick()
window_p = (window_p + 1) % window_size
local period = freq_tick()/samplerate
local sample = 0
local i = window_p
repeat
-- FIXME
sample = sample + window[(i % window_size)+1] *
Sinc((i-window_p - window_size/2)/period) *
blackman[i-window_p+1]
i = i + 1
until (i % window_size) == window_p
return sample
end
end
function FIRStream:len()
return self.stream:len()
end
]==]