🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

391 lines
10 KiB

  1. #include "config.h"
  2. #include "nfc.h"
  3. #include <algorithm>
  4. #include "alMain.h"
  5. /* Near-field control filters are the basis for handling the near-field effect.
  6. * The near-field effect is a bass-boost present in the directional components
  7. * of a recorded signal, created as a result of the wavefront curvature (itself
  8. * a function of sound distance). Proper reproduction dictates this be
  9. * compensated for using a bass-cut given the playback speaker distance, to
  10. * avoid excessive bass in the playback.
  11. *
  12. * For real-time rendered audio, emulating the near-field effect based on the
  13. * sound source's distance, and subsequently compensating for it at output
  14. * based on the speaker distances, can create a more realistic perception of
  15. * sound distance beyond a simple 1/r attenuation.
  16. *
  17. * These filters do just that. Each one applies a low-shelf filter, created as
  18. * the combination of a bass-boost for a given sound source distance (near-
  19. * field emulation) along with a bass-cut for a given control/speaker distance
  20. * (near-field compensation).
  21. *
  22. * Note that it is necessary to apply a cut along with the boost, since the
  23. * boost alone is unstable in higher-order ambisonics as it causes an infinite
  24. * DC gain (even first-order ambisonics requires there to be no DC offset for
  25. * the boost to work). Consequently, ambisonics requires a control parameter to
  26. * be used to avoid an unstable boost-only filter. NFC-HOA defines this control
  27. * as a reference delay, calculated with:
  28. *
  29. * reference_delay = control_distance / speed_of_sound
  30. *
  31. * This means w0 (for input) or w1 (for output) should be set to:
  32. *
  33. * wN = 1 / (reference_delay * sample_rate)
  34. *
  35. * when dealing with NFC-HOA content. For FOA input content, which does not
  36. * specify a reference_delay variable, w0 should be set to 0 to apply only
  37. * near-field compensation for output. It's important that w1 be a finite,
  38. * positive, non-0 value or else the bass-boost will become unstable again.
  39. * Also, w0 should not be too large compared to w1, to avoid excessively loud
  40. * low frequencies.
  41. */
  42. namespace {
  43. constexpr float B[5][4] = {
  44. { 0.0f },
  45. { 1.0f },
  46. { 3.0f, 3.0f },
  47. { 3.6778f, 6.4595f, 2.3222f },
  48. { 4.2076f, 11.4877f, 5.7924f, 9.1401f }
  49. };
  50. NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
  51. {
  52. NfcFilter1 nfc{};
  53. float b_00, g_0;
  54. float r;
  55. nfc.base_gain = 1.0f;
  56. nfc.gain = 1.0f;
  57. /* Calculate bass-boost coefficients. */
  58. r = 0.5f * w0;
  59. b_00 = B[1][0] * r;
  60. g_0 = 1.0f + b_00;
  61. nfc.gain *= g_0;
  62. nfc.b1 = 2.0f * b_00 / g_0;
  63. /* Calculate bass-cut coefficients. */
  64. r = 0.5f * w1;
  65. b_00 = B[1][0] * r;
  66. g_0 = 1.0f + b_00;
  67. nfc.base_gain /= g_0;
  68. nfc.gain /= g_0;
  69. nfc.a1 = 2.0f * b_00 / g_0;
  70. return nfc;
  71. }
  72. void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
  73. {
  74. const float r{0.5f * w0};
  75. const float b_00{B[1][0] * r};
  76. const float g_0{1.0f + b_00};
  77. nfc->gain = nfc->base_gain * g_0;
  78. nfc->b1 = 2.0f * b_00 / g_0;
  79. }
  80. NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
  81. {
  82. NfcFilter2 nfc{};
  83. float b_10, b_11, g_1;
  84. float r;
  85. nfc.base_gain = 1.0f;
  86. nfc.gain = 1.0f;
  87. /* Calculate bass-boost coefficients. */
  88. r = 0.5f * w0;
  89. b_10 = B[2][0] * r;
  90. b_11 = B[2][1] * r * r;
  91. g_1 = 1.0f + b_10 + b_11;
  92. nfc.gain *= g_1;
  93. nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  94. nfc.b2 = 4.0f * b_11 / g_1;
  95. /* Calculate bass-cut coefficients. */
  96. r = 0.5f * w1;
  97. b_10 = B[2][0] * r;
  98. b_11 = B[2][1] * r * r;
  99. g_1 = 1.0f + b_10 + b_11;
  100. nfc.base_gain /= g_1;
  101. nfc.gain /= g_1;
  102. nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  103. nfc.a2 = 4.0f * b_11 / g_1;
  104. return nfc;
  105. }
  106. void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
  107. {
  108. const float r{0.5f * w0};
  109. const float b_10{B[2][0] * r};
  110. const float b_11{B[2][1] * r * r};
  111. const float g_1{1.0f + b_10 + b_11};
  112. nfc->gain = nfc->base_gain * g_1;
  113. nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  114. nfc->b2 = 4.0f * b_11 / g_1;
  115. }
  116. NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
  117. {
  118. NfcFilter3 nfc{};
  119. float b_10, b_11, g_1;
  120. float b_00, g_0;
  121. float r;
  122. nfc.base_gain = 1.0f;
  123. nfc.gain = 1.0f;
  124. /* Calculate bass-boost coefficients. */
  125. r = 0.5f * w0;
  126. b_10 = B[3][0] * r;
  127. b_11 = B[3][1] * r * r;
  128. b_00 = B[3][2] * r;
  129. g_1 = 1.0f + b_10 + b_11;
  130. g_0 = 1.0f + b_00;
  131. nfc.gain *= g_1 * g_0;
  132. nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  133. nfc.b2 = 4.0f * b_11 / g_1;
  134. nfc.b3 = 2.0f * b_00 / g_0;
  135. /* Calculate bass-cut coefficients. */
  136. r = 0.5f * w1;
  137. b_10 = B[3][0] * r;
  138. b_11 = B[3][1] * r * r;
  139. b_00 = B[3][2] * r;
  140. g_1 = 1.0f + b_10 + b_11;
  141. g_0 = 1.0f + b_00;
  142. nfc.base_gain /= g_1 * g_0;
  143. nfc.gain /= g_1 * g_0;
  144. nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  145. nfc.a2 = 4.0f * b_11 / g_1;
  146. nfc.a3 = 2.0f * b_00 / g_0;
  147. return nfc;
  148. }
  149. void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
  150. {
  151. const float r{0.5f * w0};
  152. const float b_10{B[3][0] * r};
  153. const float b_11{B[3][1] * r * r};
  154. const float b_00{B[3][2] * r};
  155. const float g_1{1.0f + b_10 + b_11};
  156. const float g_0{1.0f + b_00};
  157. nfc->gain = nfc->base_gain * g_1 * g_0;
  158. nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  159. nfc->b2 = 4.0f * b_11 / g_1;
  160. nfc->b3 = 2.0f * b_00 / g_0;
  161. }
  162. NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
  163. {
  164. NfcFilter4 nfc{};
  165. float b_10, b_11, g_1;
  166. float b_00, b_01, g_0;
  167. float r;
  168. nfc.base_gain = 1.0f;
  169. nfc.gain = 1.0f;
  170. /* Calculate bass-boost coefficients. */
  171. r = 0.5f * w0;
  172. b_10 = B[4][0] * r;
  173. b_11 = B[4][1] * r * r;
  174. b_00 = B[4][2] * r;
  175. b_01 = B[4][3] * r * r;
  176. g_1 = 1.0f + b_10 + b_11;
  177. g_0 = 1.0f + b_00 + b_01;
  178. nfc.gain *= g_1 * g_0;
  179. nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  180. nfc.b2 = 4.0f * b_11 / g_1;
  181. nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
  182. nfc.b4 = 4.0f * b_01 / g_0;
  183. /* Calculate bass-cut coefficients. */
  184. r = 0.5f * w1;
  185. b_10 = B[4][0] * r;
  186. b_11 = B[4][1] * r * r;
  187. b_00 = B[4][2] * r;
  188. b_01 = B[4][3] * r * r;
  189. g_1 = 1.0f + b_10 + b_11;
  190. g_0 = 1.0f + b_00 + b_01;
  191. nfc.base_gain /= g_1 * g_0;
  192. nfc.gain /= g_1 * g_0;
  193. nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  194. nfc.a2 = 4.0f * b_11 / g_1;
  195. nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
  196. nfc.a4 = 4.0f * b_01 / g_0;
  197. return nfc;
  198. }
  199. void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
  200. {
  201. const float r{0.5f * w0};
  202. const float b_10{B[4][0] * r};
  203. const float b_11{B[4][1] * r * r};
  204. const float b_00{B[4][2] * r};
  205. const float b_01{B[4][3] * r * r};
  206. const float g_1{1.0f + b_10 + b_11};
  207. const float g_0{1.0f + b_00 + b_01};
  208. nfc->gain = nfc->base_gain * g_1 * g_0;
  209. nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
  210. nfc->b2 = 4.0f * b_11 / g_1;
  211. nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
  212. nfc->b4 = 4.0f * b_01 / g_0;
  213. }
  214. } // namespace
  215. void NfcFilter::init(const float w1) noexcept
  216. {
  217. first = NfcFilterCreate1(0.0f, w1);
  218. second = NfcFilterCreate2(0.0f, w1);
  219. third = NfcFilterCreate3(0.0f, w1);
  220. fourth = NfcFilterCreate4(0.0f, w1);
  221. }
  222. void NfcFilter::adjust(const float w0) noexcept
  223. {
  224. NfcFilterAdjust1(&first, w0);
  225. NfcFilterAdjust2(&second, w0);
  226. NfcFilterAdjust3(&third, w0);
  227. NfcFilterAdjust4(&fourth, w0);
  228. }
  229. void NfcFilter::process1(float *RESTRICT dst, const float *RESTRICT src, const int count)
  230. {
  231. ASSUME(count > 0);
  232. const float gain{first.gain};
  233. const float b1{first.b1};
  234. const float a1{first.a1};
  235. float z1{first.z[0]};
  236. auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float
  237. {
  238. const float y{in*gain - a1*z1};
  239. const float out{y + b1*z1};
  240. z1 += y;
  241. return out;
  242. };
  243. std::transform(src, src+count, dst, proc_sample);
  244. first.z[0] = z1;
  245. }
  246. void NfcFilter::process2(float *RESTRICT dst, const float *RESTRICT src, const int count)
  247. {
  248. ASSUME(count > 0);
  249. const float gain{second.gain};
  250. const float b1{second.b1};
  251. const float b2{second.b2};
  252. const float a1{second.a1};
  253. const float a2{second.a2};
  254. float z1{second.z[0]};
  255. float z2{second.z[1]};
  256. auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float
  257. {
  258. const float y{in*gain - a1*z1 - a2*z2};
  259. const float out{y + b1*z1 + b2*z2};
  260. z2 += z1;
  261. z1 += y;
  262. return out;
  263. };
  264. std::transform(src, src+count, dst, proc_sample);
  265. second.z[0] = z1;
  266. second.z[1] = z2;
  267. }
  268. void NfcFilter::process3(float *RESTRICT dst, const float *RESTRICT src, const int count)
  269. {
  270. ASSUME(count > 0);
  271. const float gain{third.gain};
  272. const float b1{third.b1};
  273. const float b2{third.b2};
  274. const float b3{third.b3};
  275. const float a1{third.a1};
  276. const float a2{third.a2};
  277. const float a3{third.a3};
  278. float z1{third.z[0]};
  279. float z2{third.z[1]};
  280. float z3{third.z[2]};
  281. auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float
  282. {
  283. float y{in*gain - a1*z1 - a2*z2};
  284. float out{y + b1*z1 + b2*z2};
  285. z2 += z1;
  286. z1 += y;
  287. y = out - a3*z3;
  288. out = y + b3*z3;
  289. z3 += y;
  290. return out;
  291. };
  292. std::transform(src, src+count, dst, proc_sample);
  293. third.z[0] = z1;
  294. third.z[1] = z2;
  295. third.z[2] = z3;
  296. }
  297. void NfcFilter::process4(float *RESTRICT dst, const float *RESTRICT src, const int count)
  298. {
  299. ASSUME(count > 0);
  300. const float gain{fourth.gain};
  301. const float b1{fourth.b1};
  302. const float b2{fourth.b2};
  303. const float b3{fourth.b3};
  304. const float b4{fourth.b4};
  305. const float a1{fourth.a1};
  306. const float a2{fourth.a2};
  307. const float a3{fourth.a3};
  308. const float a4{fourth.a4};
  309. float z1{fourth.z[0]};
  310. float z2{fourth.z[1]};
  311. float z3{fourth.z[2]};
  312. float z4{fourth.z[3]};
  313. auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float
  314. {
  315. float y{in*gain - a1*z1 - a2*z2};
  316. float out{y + b1*z1 + b2*z2};
  317. z2 += z1;
  318. z1 += y;
  319. y = out - a3*z3 - a4*z4;
  320. out = y + b3*z3 + b4*z4;
  321. z4 += z3;
  322. z3 += y;
  323. return out;
  324. };
  325. std::transform(src, src+count, dst, proc_sample);
  326. fourth.z[0] = z1;
  327. fourth.z[1] = z2;
  328. fourth.z[2] = z3;
  329. fourth.z[3] = z4;
  330. }