-
Notifications
You must be signed in to change notification settings - Fork 1
/
panap.bqn
251 lines (218 loc) · 10.1 KB
/
panap.bqn
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
# Stereo panning
o ← ≠◶⟨•Import∘"options.bqn", ⊑⟩ •args
mix‿fil ← o •Import¨ "mix.bqn"‿"filter.bqn"
⟨Cos⟩ ← •math
# The goal of stereo panning is to make sounds appear to come from
# different directions. A well-panned sound for music will have the
# following three features:
#
# 1. The high frequencies (and preferably only the high frequencies) are
# louder on the near side.
# 2. The low frequencies begin slightly earlier on the near side.
# 3. It still sounds good when the channels are combined.
#
# Requirements 1 and 2 come from physics: these are properties acquired by
# sounds as they travel around a human head, and they are the features the
# brain uses to identify their source. Requirement 3 comes from the fact
# that music is sometimes played in mono and should still have acceptable
# quality when mixed this way.
#
# Requirement 1 causes no problems and is simple to implement by moving part
# of the far channel to the near side (that is, subtracting a fraction of
# that channel, possibly with a high-pass filter, from the far side and
# adding it to the near side). Requirements 2 and 3, however, are in direct
# conflict. The obvious solution to 2 is to delay the far channel; however
# doing so will cause comb filtering when channels are re-combined.
#
# Typically the answer is to drop one of the requirements. Acceptable stereo
# localization can still be obtained without any delays, thus dropping
# requirement 2. And panning with a delay, or more generally applying a
# head-related transfer function (HRTF), yields the best possible
# localization at the expense of a weak mono mix.
#
# However, it is possible to compromise, obtaining excellent (but not
# perfect) stereo and good mono. The key is to realize that only the lower
# frequencies must be delayed, a feat which can be performed with all-pass
# filters. For delays under 1/2000 of a second we can combine a forwards
# filter with a backwards filter to delay a wide range of frequencies and then
# undelay those over 2000Hz, since those frequencies are not used to detect
# delay. This leaves a signal largely without artifacts--the only issue is
# a small amount of attenuation at frequencies near the cutoff when the two
# channels are combined.
# Pan with all-pass filters. Arguments are the same as pan
# Delays the input by slightly more than 60 samples.
PanIR ← {
AP ← fil.AllPass
near ← 0.5>𝕨
off ← | 1-˜2×𝕨
𝕩 ↩ (-⊸≍ off × 5000 fil.Lp1 ⊏)⊸+ 𝕩
a‿b ← Get_panap_coeff Shift_coeff off×25
near ⌽ (b⊸AP⌾⌽ a⊸AP)⌾⊏ 𝕩
}
Window ← (2÷˜1+Cos π×1↓↕⊸÷6)⊸(⊣ ⌽⊸mix.Fadefront mix.Fadeback) # Tukey window
PanAP ⇐ Window∘PanIR⍟(≠⟜0.5)⟜(≍˜60=↕180)⊸mix.Reverb
# Empirically determined good coefficients for Panap
# y is a single parameter giving the delay for low frequencies.
Shift_coeff ← {
f ← 732 + 79000 ÷ 1.6 ⋆˜ 10 + 𝕩
rs ← 1.097‿¯2.054‿2.16 {+⟜(𝕩⊸×)´𝕨} 1e4 ÷˜ f
𝕩‿rs‿f
}
# Data for Shift_coeff
# Values of rs which make the third derivative of the phase transfer at
# zero equal to zero. (rs × 1000) is given.
# frequency of max phase difference
# 2000 1750 1500 1250 1100 1000
# s 5 782 804 827 852 868 878
# h 10 816 829 845 864 876 885
# i 15 849 857 866 878 887 894
# f 20 875 879 885 893 899 904
# t 25 895 897 901 906 910 913
#
# "Good" shift, frequency, and rs values.
# Frequency is determined from the formula in Shift_coeff.
# In Shift_coeff, rs is modelled as quadratic w.r.t frequency.
# ˜0 2716 0.698
# 0.1 2685 0.701
# 1 2436 0.725
# 2.5 2121 0.759
# 5 1769 0.802
# 10 1387 0.853
# 15 1190 0.882
# 20 1074 0.900
# 25 1000 0.913
# 30 948 0.924
# =========================================================
# Argument is ⟨number of samples to shift, magnitude, stop frequency⟩.
# Magnitude should be a real number strictly between 0 and 1,
# and controls how pointy the end result is in phase space.
# Returns two all-pass filter inputs matching the specifications.
#
# The polynomials here should probably be treated as black magic, but
# a derivation from the constraints is included below anyway.
Get_panap_coeff ← { 𝕊 n‿rr‿f:
PM ← +´¨+⌜○↕○≠⊔○⥊×⌜ # Multiply polynomials
r ← טrr
c1‿c2 ← 2×Cos¨ 1‿2 × 2×π×f÷o.freq
q ← (r-1)×2÷n
poly ← +´ ⟨
4×⟨1+r+q,¯2⟩ PM (⟨r,1+r,0⟩×⟨0,6,0⟩+c2-2×c1) + ⟨1+טr,0,4⟩×1-c1
⟨1+r,¯2⟩ PM˜⊸PM (2+c1)×⟨1+r,-c1⟩
⟩
P ← {+⟜(𝕩⊸×)´poly}
v ← P¨ bnd←0‿rr
! 0≥×´v
s ← -×⊑v
re ← ⊑ (s×v) {u←s×P m←2÷˜+´𝕩⋄𝕊⍟(1e¯15<-˜´∘⊢)´u‿m((0≤u)⊑⌈‿⌊)¨𝕨‿𝕩} bnd
sv ← (ט ÷ (4×q) + ×⟜((3-r)+2×re)) 1+r-2×re
GetZ ← ⊢ ⋈ √∘-⟜(ט) # 𝕩 is real part and 𝕨 is square magnitude
(GetZ○⊑ ⋈ GetZ○(⊑+sv×+´))´ ⟨r‿¯1,re‿1⟩
}
# ---------------------------------------------------------
" # Derivation
# Transfer function for an all-pass filter: argument is z,
# and rs and re are (ט|) and (2÷˜+⟜+) of the complex parameter
H = (1 + (rs×ט) - 2×re⊸×) ÷ (ט + rs - 2×re⊸×)
# Derivative with respect to z
Hp = (2 × (re-˜rs⊸×) - H×(1-re)) ÷ (ט+rs-2×re⊸×)
= (2 × (× rs-H) - re×1-˜H) ÷ (ט+rs-2×re⊸×)
# Definition of the phase transfer function T
(⋆⍳T ω) = (H ⋆⍳ω)
# Derivative in terms of H and its derivative Hp
⍳(⋆⍳T(ω))×Tp(ω) = Hp(⋆⍳ω)×⍳⋆⍳ω
Tp(ω) = Hp(⋆⍳ω) × ⋆⍳ω-T(ω)
= Hp(z) × z ÷ H(z)
= (2×z × (z×rs-H) - re×H-1) ÷ (1 + (rs×טs) - 2×re×z)
# Simplified at 1 and ¯1 (0 and maximum frequency)
H(1) = 1
T(0) = 0
Tp(0) = (2×rs-1) ÷ (1+rs-2×re)
H(¯1) = 1
T(π) = 0
Tp(π) = (2×rs-1) ÷ (1+rs+2×re)
# For a delay
H(z) = z⋆-n
T(ω) = -n×ω
Tp(ω) = -n
# Constraints
# We refer to the forward and backwards filters with
# postfixes of 1 and 2.
Tp1(0) = Tp2(0) - n
Tp1(π) ≃ Tp2(π)
Tp1(f) = Tp2(f)
# In a symmetric notation: [a,b] = (a1÷b1) - (a2÷b2)
# where an and bn are the values for the nth filter.
# z2 = טz
(-n÷2)= [rs-1 , 1+rs-2×re]
0 = [rs-1 , 1+rs+2×re]
0 = [((z×rs-H) - re×H-1) , (1 + (z2×rs) - (2×z×re))]
# Useful identity
# Define (reC = re2 - re1) and (rsC = rs2 - rs1)
rs1×re2 - rs2×re1 = (rs1×re1 + rs1×reC) - (rs1×re1 + rsC×re1)
= rs1×reC - rsC×re1
# Second constraint, where [12] indicates the value with 1 and 2 swapped
((rs1-1) × 1+rs2+2×re2) = [12]
((rs1 × 1+2×re2) - (rs2+2×re2)) = [12]
(rs1 + re1 + rs1×re2) = [12]
0 = rsC + reC + (re1×rsC - reC×rs1)
= (rsC × re1+1) - (reC × rs1-1)
(reC ÷ re1+1) = (rsC ÷ rs1-1)
# Thus, we can define s so that
reC = s×re1+1
rsC = s×rs1-1
rs1×re2 - rs2×re1 = rs1×reC - rsC×re1
= s × (rs1×re1+1) - (re1×rs1-1)
= s × rs1 + re1
# Cross-multiply first constraint and subtract second
(rs2-rs1) = (n÷8)×(1+rs1-2×re1)×(1+rs2-2×re2)
(s×rs1-1) = (n÷8)×(1+rs1-2×re1)×((1+rs1-2×re1) + s×(rs1-1)-2×(re1+1))
(s × (rs1-1)×(8÷n)) = (ט 1+rs1-2×re1) - s×(1+rs1-2×re1)×(rs1-˜3+2×re1)
s = (ט 1+rs1-2×re1) ÷ (((rs1-1)×(8÷n)) + (1+rs1-2×re1)×(rs1-˜3+2×re1))
# Third constraint
# Value of Tp, dropping factor of 2×z
((z×rs1-h1) + re1×h1-1) × (1 + (z2×rs2) - (2×z×re2)) = [12]
(re1 -˜ z×rs1 + h1×re1-z) × (1 + (z2×rs2) - (2×z×re2)) = [12]
((z×rs1) - (re1 + z2×rs1×re2)) + (h1×re1-z)×(1 + (z2×rs2) - (2×z×re2)) = [12]
((z×rs1) - (re1 + z2×rs1×re2)) + (h1×h2×re1-z)×(rs2 + z2 - 2×z×re2) = [12]
((z×rs1) + (z2×rs2×re1) - re1) + h1×h2×((rs2×re1) + (z×rs1) - z2×re1) = [12]
((z×rsC) + (z2×(rs1×re2 - rs2×re1)) - reC) + h1×h2×((rs1×re2 - rs2×re1) + (z×rsC) - z2×reC) = 0
# Divide by s
((z×rs1-1) + (z2×(rs1+re1)) - re1+1) + h1×h2×((rs1+re1) + (z×rs1-1) - z2×re1+1) = 0
((rs1×z2+z) + (re1×z2-1) - z+1) + h1×h2×((rs1×z+1) + (re1×1-z2) - z2+z) = 0
# Divide by z+1
((rs1×z) + (re1×z-1) - 1) + h1×h2×(rs1 + (re1×1-z) - z) = 0
h1×h2 = ((rs1×z) + (re1×z-1) - 1) ÷ ((-rs1) + (re1×z-1) + z)
= 1 + (z+1)×(1-rs1) ÷ (rs1 + (re1×1-z) - z)
# Value of h2 in terms of re1 and rs1
# With these and the previous definition, we obtain a 4th-order
# polynomial in re1 and rs1.
a = 2×z×re1, r = rs1
h1 = ((1 + (z2×r) - a) ) ÷ ((z2 + r - a) )
h2 = ((1 + (z2×r) - a) + s×(((z2×r) - a) - (z×z+2))) ÷ ((z2 + r - a) + s×((r - a) - (1+2×z)))
# Algebra
h2 = 1 + ((r-1)×(z2-1)×(1+s)) ÷ ((-a×1+s) + (z2 + r) + s×(r - (1+2×z)))
= 1 + (r-1)×(z2-1) ÷ (((ט1+z)÷1+s) + (r - (1+2×z)) - a)
= 1 + (r-1)×(z2-1) ÷ ((z2+r-a) - (ט1+z)×s÷1+s)
h1 = 1 + (r-1)×(z2-1) ÷ (z2+r-a)
s÷1+s = (ט1+r-2×e) ÷ (4 × ((r-1)×(2÷n)) + 1+r-2×e)
= (ט1+r-2×e) ÷ (4 × q + 1+r-2×e)
# Put each of our values in a regular form, defining k, a=b+d, b, c
h2 = (1+k÷a) = 1 + (1-r)×(1-z2) ÷ ((z2+r-2×z×e) - (ט1+z)×s÷1+s)
h1 = (1+k÷b) = 1 + (1-r)×(1-z2) ÷ (z2+r-2×z×e)
h1×h2 = (1+k÷c) = 1 + (1-r)×(1-z2) ÷ (1-z)×((r-z) + (1-z)×e)
# Simplify the overall form
(1+k÷a)×(1+k÷b) = (1+k÷c)
c×(a+k)×(b+k) = ab×(c+k) # Cross-multiply
c×(a+b+k) = ab # Subtract abc
c×k+b = a×b-c
c×k+b = (1-z)×((r-z)+(1-z)×e) × ((1-r)×(1-z2))+z2+r-2×z×e
= (1-z)×((r-z)+(1-z)×e) × (1+z2×r)-2×z×e
a×b-c = ((z2+r-2×z×e) - (ט1+z)×s÷1+s)×((z2+r-2×z×e)-(1-z)×((r-z) + (1-z)×e))
= ((z2+r-2×z×e) - (ט1+z)×s÷1+s)×((z×1+r) - e×1+z2)
q←(r-1)×(2÷n)
((1-z)×⟨r-z,1-z⟩PM⟨1+z2×r,-2×z⟩PM⟨4×q+1+r,¯8⟩) + ⟨z×1+r,-1+z2⟩ PM (PM˜(1+z)×⟨1+r,¯2⟩) - ⟨z2+r,-2×z⟩PM⟨4×q+1+r,¯8⟩
# Group q
{(4×⟨q+1+r,¯2⟩PM(𝕩PM⟨z2+r,-2×z⟩)-˜(1-z)×⟨r-z,1-z⟩PM⟨1+z2×r,-2×z⟩) + 𝕩 PM PM˜(1+z)×⟨1+r,¯2⟩}⟨z×1+r,-1+z2⟩
# Expand; divide by z2; simplify
zd←z+÷z ⋄ ze←z2+÷z2
(4×⟨q+1+r,¯2⟩PM(⟨r,1+r,0⟩×⟨0,6,0⟩+ze-2×zd)+⟨1+טr,0,4⟩×1-zd) + (PM˜⟨1+r,¯2⟩)PM(2+zd)×⟨1+r,-zd⟩"