Source: modules/filter.js

Source: modules/filter.js

  1. /*
  2. * This file is part of Toolkit.
  3. *
  4. * Toolkit is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 3 of the License, or (at your option) any later version.
  8. *
  9. * Toolkit is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General
  15. * Public License along with this program; if not, write to the
  16. * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  17. * Boston, MA 02110-1301 USA
  18. */
  19. "use strict";
  20. (function(w, TK){
  21. /* These formulae for 'standard' biquad filter coefficients are
  22. * from
  23. * "Cookbook formulae for audio EQ biquad filter coefficients"
  24. * by Robert Bristow-Johnson.
  25. *
  26. */
  27. function Null(O) {
  28. /* this biquad does not do anything */
  29. return {
  30. b0: 1,
  31. b1: 1,
  32. b2: 1,
  33. a0: 1,
  34. a1: 1,
  35. a2: 1,
  36. sample_rate: O.sample_rate,
  37. };
  38. }
  39. function LowShelf(O) {
  40. var cos = Math.cos,
  41. sqrt = Math.sqrt,
  42. A = Math.pow(10, O.gain / 40),
  43. w0 = 2*Math.PI*O.freq/O.sample_rate,
  44. alpha = Math.sin(w0)/(2*O.q);
  45. return {
  46. b0: A*( (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha ),
  47. b1: 2*A*( (A-1) - (A+1)*cos(w0) ),
  48. b2: A*( (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha ),
  49. a0: (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha,
  50. a1: -2*( (A-1) + (A+1)*cos(w0) ),
  51. a2: (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha,
  52. sample_rate: O.sample_rate,
  53. };
  54. }
  55. function HighShelf(O) {
  56. var cos = Math.cos;
  57. var sqrt = Math.sqrt;
  58. var A = Math.pow(10, O.gain / 40);
  59. var w0 = 2*Math.PI*O.freq/O.sample_rate;
  60. var alpha = Math.sin(w0)/(2*O.q);
  61. return {
  62. b0: A*( (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha ),
  63. b1: -2*A*( (A-1) + (A+1)*cos(w0) ),
  64. b2: A*( (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha ),
  65. a0: (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha,
  66. a1: 2*( (A-1) - (A+1)*cos(w0) ),
  67. a2: (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha,
  68. sample_rate: O.sample_rate,
  69. };
  70. }
  71. function Peaking(O) {
  72. var cos = Math.cos;
  73. var A = Math.pow(10, O.gain / 40);
  74. var w0 = 2*Math.PI*O.freq/O.sample_rate;
  75. var alpha = Math.sin(w0)/(2*O.q);
  76. return {
  77. b0: 1 + alpha*A,
  78. b1: -2*cos(w0),
  79. b2: 1 - alpha*A,
  80. a0: 1 + alpha/A,
  81. a1: -2*cos(w0),
  82. a2: 1 - alpha/A,
  83. sample_rate: O.sample_rate,
  84. };
  85. }
  86. function Notch(O) {
  87. var cos = Math.cos;
  88. var w0 = 2*Math.PI*O.freq/O.sample_rate;
  89. var alpha = Math.sin(w0)/(2*O.q);
  90. return {
  91. b0: 1,
  92. b1: -2*cos(w0),
  93. b2: 1,
  94. a0: 1 + alpha,
  95. a1: -2*cos(w0),
  96. a2: 1 - alpha,
  97. sample_rate: O.sample_rate,
  98. };
  99. }
  100. /* This is a standard lowpass filter with transfer function
  101. * H(s) = 1/(1+s)
  102. */
  103. function LowPass1(O) {
  104. var w0 = 2*Math.PI*O.freq/O.sample_rate,
  105. s0 = Math.sin(w0),
  106. c0 = Math.cos(w0);
  107. return {
  108. b0: 1-c0,
  109. b1: 2*(1-c0),
  110. b2: 1-c0,
  111. a0: 1 - c0 + s0,
  112. a1: 2*(1-c0),
  113. a2: 1 - c0 - s0,
  114. sample_rate: O.sample_rate,
  115. };
  116. }
  117. function LowPass2(O) {
  118. var cos = Math.cos;
  119. var w0 = 2*Math.PI*O.freq/O.sample_rate;
  120. var alpha = Math.sin(w0)/(2*O.q);
  121. return {
  122. b0: (1 - cos(w0))/2,
  123. b1: 1 - cos(w0),
  124. b2: (1 - cos(w0))/2,
  125. a0: 1 + alpha,
  126. a1: -2*cos(w0),
  127. a2: 1 - alpha,
  128. sample_rate: O.sample_rate,
  129. };
  130. }
  131. function LowPass4(O) {
  132. O = LowPass2(O);
  133. O.factor = 2;
  134. return O;
  135. }
  136. /* This is a standard highpass filter with transfer function
  137. * H(s) = s/(1+s)
  138. */
  139. function HighPass1(O) {
  140. var w0 = 2*Math.PI*O.freq/O.sample_rate,
  141. s0 = Math.sin(w0),
  142. c0 = Math.cos(w0);
  143. return {
  144. b0: s0,
  145. b1: 0,
  146. b2: -s0,
  147. a0: 1 - c0 + s0,
  148. a1: 2*(1-c0),
  149. a2: 1 - c0 - s0,
  150. sample_rate: O.sample_rate,
  151. };
  152. }
  153. function HighPass2(O) {
  154. var cos = Math.cos;
  155. var w0 = 2*Math.PI*O.freq/O.sample_rate;
  156. var alpha = Math.sin(w0)/(2*O.q);
  157. return {
  158. b0: (1 + cos(w0))/2,
  159. b1: -(1 + cos(w0)),
  160. b2: (1 + cos(w0))/2,
  161. a0: 1 + alpha,
  162. a1: -2*cos(w0),
  163. a2: 1 - alpha,
  164. sample_rate: O.sample_rate,
  165. };
  166. }
  167. function HighPass4(O) {
  168. O = HighPass2(O);
  169. O.factor = 2;
  170. return O;
  171. }
  172. var Filters = {
  173. Null: Null,
  174. LowShelf: LowShelf,
  175. HighShelf: HighShelf,
  176. Peaking: Peaking,
  177. Notch: Notch,
  178. LowPass1: LowPass1,
  179. LowPass2: LowPass2,
  180. LowPass4: LowPass4,
  181. HighPass1: HighPass1,
  182. HighPass2: HighPass2,
  183. HighPass4: HighPass4,
  184. };
  185. var standard_biquads = {
  186. "null": BiquadFilter(Null),
  187. "low-shelf": BiquadFilter(LowShelf),
  188. "high-shelf": BiquadFilter(HighShelf),
  189. parametric: BiquadFilter(Peaking),
  190. notch: BiquadFilter(Notch),
  191. lowpass1: BiquadFilter(LowPass1),
  192. lowpass2: BiquadFilter(LowPass2),
  193. lowpass3: BiquadFilter(LowPass1, LowPass2),
  194. lowpass4: BiquadFilter(LowPass4),
  195. highpass1: BiquadFilter(HighPass1),
  196. highpass2: BiquadFilter(HighPass2),
  197. highpass3: BiquadFilter(HighPass1, HighPass2),
  198. highpass4: BiquadFilter(HighPass4),
  199. };
  200. var NullModule = { freq2gain: function(f) { return 0.0; } };
  201. function BilinearModule(w, O) {
  202. var log = Math.log;
  203. var sin = Math.sin;
  204. var LN10_10 = (O.factor||1.0) * 10/Math.LN10;
  205. var PI = +(Math.PI/O.sample_rate);
  206. var Ra = +((O.a0 + O.a1) * (O.a0 + O.a1) / 4);
  207. var Rb = +((O.b0 + O.b1) * (O.b0 + O.b1) / 4);
  208. var Ya = +(O.a1 * O.a0);
  209. var Yb = +(O.b1 * O.b0);
  210. if (Ra === Rb && Ya === Yb) return NullModule;
  211. function freq2gain(f) {
  212. f = +f;
  213. var S = +sin(PI * f)
  214. S *= S;
  215. return LN10_10 * log( (Rb - S * Yb) /
  216. (Ra - S * Ya) );
  217. }
  218. return { freq2gain: freq2gain };
  219. }
  220. function BiquadModule(w, O) {
  221. var log = Math.log;
  222. var sin = Math.sin;
  223. var LN10_10 = (O.factor||1.0) * 10/Math.LN10;
  224. var PI = +(Math.PI/O.sample_rate);
  225. var Ra = +((O.a0 + O.a1 + O.a2) * (O.a0 + O.a1 + O.a2) / 4);
  226. var Rb = +((O.b0 + O.b1 + O.b2) * (O.b0 + O.b1 + O.b2) / 4);
  227. var Xa = +(4 * O.a0 * O.a2);
  228. var Ya = +(O.a1 * (O.a0 + O.a2));
  229. var Xb = +(4 * O.b0 * O.b2);
  230. var Yb = +(O.b1 * (O.b0 + O.b2));
  231. if (Ra === Rb && Ya === Yb && Xa === Xb) return NullModule;
  232. function freq2gain(f) {
  233. f = +f;
  234. var S = +sin(PI * f)
  235. S *= S;
  236. return LN10_10 * log( (Rb - S * (Xb * (1 - S) + Yb)) /
  237. (Ra - S * (Xa * (1 - S) + Ya)) );
  238. }
  239. return { freq2gain: freq2gain };
  240. }
  241. function BiquadFilter1(trafo) {
  242. function factory(stdlib, O) {
  243. return BiquadModule(stdlib, trafo(O));
  244. }
  245. return factory;
  246. }
  247. function BiquadFilterN(trafos) {
  248. function factory(stdlib, O) {
  249. var A = new Array(trafos.length);
  250. var i;
  251. for (i = 0; i < trafos.length; i ++) {
  252. A[i] = BiquadModule(stdlib, trafos[i](O)).freq2gain;
  253. }
  254. return {
  255. freq2gain: function(f) {
  256. var ret = 0.0;
  257. var i;
  258. for (i = 0; i < A.length; i++) {
  259. ret += A[i](f);
  260. }
  261. return ret;
  262. }
  263. };
  264. }
  265. return factory;
  266. }
  267. function BiquadFilter() {
  268. if (arguments.length === 1) return BiquadFilter1(arguments[0]);
  269. return BiquadFilterN.call(this, Array.prototype.slice.call(arguments));
  270. }
  271. TK.BiquadFilter = BiquadFilter;
  272. function reset() {
  273. this.freq2gain = null;
  274. /**
  275. * Is fired when a filters drawing function is reset.
  276. *
  277. * @event TK.Filter#reset
  278. */
  279. this.fire_event("reset");
  280. }
  281. TK.Filter = TK.class({
  282. /**
  283. * TK.Filter provides the math for calculating a gain from
  284. * a given frequency for different types of biquad filters.
  285. *
  286. * @param {Object} [options={ }] - An object containing initial options.
  287. *
  288. * @property {Stgring|Function} [options.type="parametric"] - The type of the filter. Possible values are
  289. * <code>parametric</code>, <code>notch</code>, <code>low-shelf</code>,
  290. * <code>high-shelf</code>, <code>lowpass[n]</code> or <code>highpass[n]</code>.
  291. * @property {Number} [options.freq=1000] - The initial frequency.
  292. * @property {Number} [options.gain=0] - The initial gain.
  293. * @property {Number} [options.q=1] - The initial Q of the filter.
  294. * @property {Number} [options.sample_rate=44100] - The sample rate.
  295. *
  296. * @mixin TK.Filter
  297. *
  298. * @extends TK.Base
  299. *
  300. * @mixes TK.AudioMath
  301. * @mixes TK.Notes
  302. */
  303. /**
  304. * Returns the gain for a given frequency
  305. *
  306. * @method TK.Filter#freq2gain
  307. *
  308. * @param {number} frequency - The frequency to calculate the gain for.
  309. *
  310. * @returns {number} gain - The gain at the given frequency.
  311. */
  312. _class: "Filter",
  313. Extends: TK.Base,
  314. _options: {
  315. type: "string|function",
  316. freq: "number",
  317. gain: "number",
  318. q: "number",
  319. sample_rate: "number",
  320. },
  321. options: {
  322. type: "parametric",
  323. freq: 1000,
  324. gain: 0,
  325. q: 1,
  326. sample_rate: 44100,
  327. },
  328. static_events: {
  329. set_freq: reset,
  330. set_type: reset,
  331. set_q: reset,
  332. set_gain: reset,
  333. initialized: reset,
  334. },
  335. create_freq2gain: function() {
  336. var O = this.options;
  337. var m;
  338. if (typeof(O.type) === "string") {
  339. m = standard_biquads[O.type];
  340. if (!m) {
  341. TK.error("Unknown standard filter: "+O.type);
  342. return;
  343. }
  344. } else if (typeof(O.type) === "function") {
  345. m = O.type;
  346. } else {
  347. TK.error("Unsupported option 'type'.");
  348. return;
  349. }
  350. this.freq2gain = m(window, O).freq2gain;
  351. },
  352. get_freq2gain: function() {
  353. if (this.freq2gain === null) this.create_freq2gain();
  354. return this.freq2gain;
  355. },
  356. reset: reset,
  357. });
  358. TK.Filters = Filters;
  359. })(this, this.TK);