Source: addons/mixer.js

  1. import register from "../util/register.js";
  2. import m_ctl_fact from "../extern/controls.js";
  3. import m_print_fact from "../intern/print.js";
  4. import m_scenes_fact from "../extern/scenes.js";
  5. import m_screen_fact from "../extern/screen.js";
  6. import m_sfx_fact from "../extern/sfx.js";
  7. import m_util_fact from "../extern/util.js";
  8. /**
  9. * Audio mixer add-on.
  10. * Implements volume faders, positional params, parametric equalizers per
  11. * channel and volume fader and compressor to the master section.
  12. * @module mixer
  13. */
  14. function Mixer(ns, exports) {
  15. var m_ctl = m_ctl_fact(ns);
  16. var m_print = m_print_fact(ns);
  17. var m_scenes = m_scenes_fact(ns);
  18. var m_screen = m_screen_fact(ns);
  19. var m_sfx = m_sfx_fact(ns);
  20. var m_util = m_util_fact(ns);
  21. var TIMER_SLOW_PERIOD = 0.15;
  22. var TIMER_FAST_PERIOD = 0.05;
  23. var MIXER_CONTROLS_MANIFOLD = ["SWITCH_STRIP", "SWITCH_STRIP_HOLD",
  24. "SWITCH_PARAM", "INC_DEC", "INC_DEC_HOLD", "MUTE_SOLO"];
  25. var _mixer_strips = [];
  26. var _active_strip = 0;
  27. var _filter_freq_arr = null;
  28. var _filter_mag_arr = null;
  29. var _filter_phase_arr = null;
  30. /**
  31. * Enable mixer controls.
  32. */
  33. exports.enable_mixer_controls = function() {
  34. init();
  35. // switch mixer strip
  36. var key_num_left = m_ctl.create_keyboard_sensor(m_ctl.KEY_NUM4);
  37. var key_num_right = m_ctl.create_keyboard_sensor(m_ctl.KEY_NUM6);
  38. // switch mixer param
  39. var key_num_up = m_ctl.create_keyboard_sensor(m_ctl.KEY_NUM8);
  40. var key_num_down = m_ctl.create_keyboard_sensor(m_ctl.KEY_NUM2);
  41. // change mixer param value
  42. var key_num_add = m_ctl.create_keyboard_sensor(m_ctl.KEY_ADD);
  43. var key_num_sub = m_ctl.create_keyboard_sensor(m_ctl.KEY_SUB);
  44. // mute-solo
  45. var key_num_home = m_ctl.create_keyboard_sensor(m_ctl.KEY_NUM7);
  46. var key_num_pgup = m_ctl.create_keyboard_sensor(m_ctl.KEY_NUM9);
  47. var timer_slow = m_ctl.create_timer_sensor(TIMER_SLOW_PERIOD, true);
  48. var timer_fast = m_ctl.create_timer_sensor(TIMER_FAST_PERIOD, true);
  49. var switch_strip_keys = [key_num_left, key_num_right, timer_slow];
  50. var switch_strip_logic = function(s) {
  51. return (s[0] || s[1]);
  52. }
  53. var switch_strip_logic_hold = function(s) {
  54. return ((s[0] || s[1]) && s[2]);
  55. }
  56. var switch_spk_cb = function(obj, id, pulse) {
  57. if (pulse == 1) {
  58. var dir = Boolean(m_ctl.get_sensor_value(obj, id, 0)) ? -1 : 1;
  59. switch_strip(dir);
  60. if (id == "SWITCH_STRIP")
  61. m_ctl.reset_timer_sensor(obj, id, 2, TIMER_SLOW_PERIOD + 0.3);
  62. else
  63. m_ctl.reset_timer_sensor(obj, id, 2, TIMER_SLOW_PERIOD);
  64. } else {
  65. // hold on some time
  66. m_ctl.reset_timer_sensor(obj, id, 2, 10);
  67. }
  68. }
  69. m_ctl.create_sensor_manifold(null, "SWITCH_STRIP", m_ctl.CT_TRIGGER,
  70. switch_strip_keys, switch_strip_logic, switch_spk_cb);
  71. m_ctl.create_sensor_manifold(null, "SWITCH_STRIP_HOLD", m_ctl.CT_SHOT,
  72. switch_strip_keys, switch_strip_logic_hold, switch_spk_cb);
  73. var switch_param_keys = [key_num_up, key_num_down];
  74. var switch_param_logic = function(s) {
  75. return (s[0] || s[1]);
  76. }
  77. var switch_param_cb = function(obj, id, pulse) {
  78. var dir = Boolean(m_ctl.get_sensor_value(obj, id, 0)) ? -1 : 1;
  79. switch_param(dir);
  80. }
  81. m_ctl.create_sensor_manifold(null, "SWITCH_PARAM", m_ctl.CT_SHOT,
  82. switch_param_keys, switch_param_logic, switch_param_cb);
  83. var inc_dec_keys = [key_num_sub, key_num_add, timer_fast];
  84. var inc_dec_cb = function(obj, id, pulse) {
  85. if (pulse == 1) {
  86. var dir = Boolean(m_ctl.get_sensor_value(obj, id, 0)) ? -1 : 1;
  87. param_inc_dec(dir);
  88. if (id == "INC_DEC")
  89. m_ctl.reset_timer_sensor(obj, id, 2, TIMER_FAST_PERIOD + 0.3);
  90. else
  91. m_ctl.reset_timer_sensor(obj, id, 2, TIMER_FAST_PERIOD);
  92. } else {
  93. // hold on some time
  94. m_ctl.reset_timer_sensor(obj, id, 2, 10);
  95. }
  96. }
  97. var inc_dec_logic = function(s) {
  98. return (s[0] || s[1]);
  99. }
  100. var inc_dec_logic_hold = function(s) {
  101. return ((s[0] || s[1]) && s[2]);
  102. }
  103. m_ctl.create_sensor_manifold(null, "INC_DEC", m_ctl.CT_TRIGGER,
  104. inc_dec_keys, inc_dec_logic, inc_dec_cb);
  105. m_ctl.create_sensor_manifold(null, "INC_DEC_HOLD", m_ctl.CT_SHOT,
  106. inc_dec_keys, inc_dec_logic_hold, inc_dec_cb);
  107. var mute_solo_keys = [key_num_home, key_num_pgup];
  108. var mute_solo_cb = function(obj, id, pulse) {
  109. var mute_solo = Boolean(m_ctl.get_sensor_value(obj, id, 0));
  110. if (mute_solo)
  111. switch_mute();
  112. else
  113. switch_solo();
  114. }
  115. var mute_solo_logic = function(s) {
  116. return (s[0] || s[1]);
  117. }
  118. m_ctl.create_sensor_manifold(null, "MUTE_SOLO", m_ctl.CT_SHOT,
  119. mute_solo_keys, mute_solo_logic, mute_solo_cb);
  120. var elapsed = m_ctl.create_elapsed_sensor();
  121. m_ctl.create_sensor_manifold(null, "MIXER_DRAW", m_ctl.CT_CONTINUOUS,
  122. [elapsed], null, function() {draw()});
  123. var timer = m_ctl.create_timer_sensor(1, true);
  124. m_ctl.create_sensor_manifold(null, "MIXER_UPDATE", m_ctl.CT_TRIGGER,
  125. [timer], null, function() {
  126. var strip_range = active_strip_range();
  127. for (var i = strip_range[0]; i <= strip_range[1]; i++)
  128. update_strip_params(_mixer_strips[i]);
  129. });
  130. }
  131. /**
  132. * Disable mixer controls.
  133. */
  134. exports.disable_mixer_controls = function() {
  135. for (var i = 0; MIXER_CONTROLS_MANIFOLD.length; i++)
  136. m_ctl.remove_sensor_manifold(null, MIXER_CONTROLS_MANIFOLD[i]);
  137. }
  138. /**
  139. * Initialize mixer
  140. */
  141. function init() {
  142. _mixer_strips.length = 0;
  143. _active_strip = 0;
  144. _filter_freq_arr = gen_freq_arr(100);
  145. _filter_mag_arr = new Float32Array(_filter_freq_arr.length);
  146. _filter_phase_arr = new Float32Array(_filter_freq_arr.length);
  147. var speakers = m_sfx.get_speaker_objects();
  148. if (!speakers.length)
  149. return;
  150. _mixer_strips.push(create_master_strip());
  151. for (var i = 0; i < speakers.length; i++) {
  152. var spk = speakers[i];
  153. _mixer_strips.push(create_speaker_strip(spk));
  154. }
  155. // special strips first, then by name
  156. _mixer_strips.sort(function(a,b) {
  157. if (a.id == "MASTER")
  158. return -1;
  159. else if (b.id == "MASTER")
  160. return 1;
  161. else if (a.id == "COMPRESSOR")
  162. return -1;
  163. else if (b.id == "COMPRESSOR")
  164. return 1;
  165. else if (a.id.toUpperCase() < b.id.toUpperCase())
  166. return -1;
  167. else if (a.id.toUpperCase() > b.id.toUpperCase())
  168. return 1;
  169. else
  170. return 0;
  171. });
  172. }
  173. function gen_freq_arr(steps) {
  174. var FMIN = 20;
  175. var FMAX = 20000;
  176. var freq_arr = new Float32Array(steps);
  177. var freq_base = FMAX/FMIN;
  178. var freq_pow = 0;
  179. for (var i = 0; i < steps; i++) {
  180. freq_arr[i] = FMIN * Math.pow(freq_base, freq_pow);
  181. freq_pow += 1 / (steps - 1);
  182. }
  183. return freq_arr;
  184. }
  185. function create_master_strip() {
  186. var strip = init_strip("MASTER");
  187. var cparams = m_sfx.get_compressor_params();
  188. if (cparams) {
  189. strip.params.push(["THRESHOLD", cparams["threshold"], -100, 0, 100, false]);
  190. strip.params.push(["KNEE", cparams["knee"], 0, 40, 40, false]);
  191. strip.params.push(["RATIO", cparams["ratio"], 1, 20, 20, false]);
  192. strip.params.push(["ATTACK", cparams["attack"], 0, 1, 1000, false]);
  193. strip.params.push(["RELEASE", cparams["release"], 0, 1, 1000, false]);
  194. }
  195. strip.params.push(["VOLUME", m_sfx.get_volume(null), 0, 1, 50, false]);
  196. strip.mute = m_sfx.is_muted(null) ? 1 : 0;
  197. strip.solo = -1;
  198. return strip;
  199. }
  200. function init_strip(id) {
  201. return {
  202. id : id,
  203. params : [],
  204. active_param : 0,
  205. mute: -1,
  206. solo: -1,
  207. speaker: null
  208. }
  209. }
  210. function create_speaker_strip(spk) {
  211. var strip = init_strip(m_scenes.get_object_name(spk));
  212. strip.mute = m_sfx.is_muted(spk) ? 1 : 0;
  213. strip.solo = 0;
  214. strip.speaker = spk;
  215. return strip;
  216. }
  217. function switch_strip(dir) {
  218. if (!_mixer_strips.length)
  219. return;
  220. if (dir == 1 && _active_strip < (_mixer_strips.length - 1)) {
  221. _active_strip++;
  222. } else if (dir == -1 && _active_strip > 0) {
  223. _active_strip--;
  224. }
  225. var strip_range = active_strip_range();
  226. for (var i = strip_range[0]; i <= strip_range[1]; i++)
  227. update_strip_params(_mixer_strips[i]);
  228. }
  229. function update_strip_params(strip) {
  230. if (!strip.speaker)
  231. return;
  232. // cleanup
  233. strip.params.length = 0;
  234. var pparams = m_sfx.get_positional_params(strip.speaker);
  235. if (pparams) {
  236. strip.params.push(["DIST_REF", pparams["dist_ref"], 0, 1000, 10000, false]);
  237. strip.params.push(["ATTENUATION", pparams["attenuation"], 0, 50, 1000, false]);
  238. strip.params.push(["DIST_MAX", pparams["dist_max"], 0, 10000, 10000, false]);
  239. }
  240. var fparams = m_sfx.get_filter_params(strip.speaker);
  241. if (fparams) {
  242. strip.params.push(["EQ_FREQ", fparams["freq"], 20, 20000, 100, true]);
  243. strip.params.push(["EQ_Q", fparams["Q"], 0, 10, 100, false]);
  244. strip.params.push(["EQ_GAIN", fparams["gain"], -70, 30, 100, false]);
  245. }
  246. strip.params.push(["VOLUME", m_sfx.get_volume(strip.speaker), 0, 1, 50, false]);
  247. // handle params decrease
  248. m_util.clamp(strip.active_param, 0, strip.params.length - 1);
  249. }
  250. function switch_param(dir) {
  251. var strip = _mixer_strips[_active_strip];
  252. if (!strip)
  253. return;
  254. if (dir == 1 && strip.active_param < (strip.params.length - 1)) {
  255. strip.active_param++;
  256. } else if (dir == -1 && strip.active_param > 0) {
  257. strip.active_param--;
  258. }
  259. }
  260. function param_inc_dec(dir) {
  261. var strip = _mixer_strips[_active_strip];
  262. if (!strip)
  263. return;
  264. var param = strip.params[strip.active_param];
  265. if (param[5])
  266. param[1] *= Math.pow(param[3] / param[2], dir / param[4]);
  267. else
  268. param[1] += dir * ((param[3] - param[2]) / param[4]);
  269. param[1] = m_util.clamp(param[1], param[2], param[3]);
  270. switch (param[0]) {
  271. case "VOLUME":
  272. if (strip.id != "MASTER")
  273. m_sfx.set_volume(strip.speaker, param[1]);
  274. else
  275. m_sfx.set_volume(null, param[1]);
  276. break;
  277. case "DIST_REF":
  278. var pparams = m_sfx.get_positional_params(strip.speaker);
  279. pparams["dist_ref"] = param[1];
  280. m_sfx.set_positional_params(strip.speaker, pparams);
  281. break;
  282. case "ATTENUATION":
  283. var pparams = m_sfx.get_positional_params(strip.speaker);
  284. pparams["attenuation"] = param[1];
  285. m_sfx.set_positional_params(strip.speaker, pparams);
  286. break;
  287. case "DIST_MAX":
  288. var pparams = m_sfx.get_positional_params(strip.speaker);
  289. pparams["dist_max"] = param[1];
  290. m_sfx.set_positional_params(strip.speaker, pparams);
  291. break;
  292. case "EQ_FREQ":
  293. var fparams = m_sfx.get_filter_params(strip.speaker);
  294. fparams["freq"] = param[1];
  295. m_sfx.set_filter_params(strip.speaker, fparams);
  296. break;
  297. case "EQ_Q":
  298. var fparams = m_sfx.get_filter_params(strip.speaker);
  299. fparams["Q"] = param[1];
  300. m_sfx.set_filter_params(strip.speaker, fparams);
  301. break;
  302. case "EQ_GAIN":
  303. var fparams = m_sfx.get_filter_params(strip.speaker);
  304. fparams["gain"] = param[1];
  305. m_sfx.set_filter_params(strip.speaker, fparams);
  306. break;
  307. case "THRESHOLD":
  308. var cparams = m_sfx.get_compressor_params();
  309. cparams["threshold"] = param[1];
  310. m_sfx.set_compressor_params(cparams);
  311. break;
  312. case "KNEE":
  313. var cparams = m_sfx.get_compressor_params();
  314. cparams["knee"] = param[1];
  315. m_sfx.set_compressor_params(cparams);
  316. break;
  317. case "RATIO":
  318. var cparams = m_sfx.get_compressor_params();
  319. cparams["ratio"] = param[1];
  320. m_sfx.set_compressor_params(cparams);
  321. break;
  322. case "ATTACK":
  323. var cparams = m_sfx.get_compressor_params();
  324. cparams["attack"] = param[1];
  325. m_sfx.set_compressor_params(cparams);
  326. break;
  327. case "RELEASE":
  328. var cparams = m_sfx.get_compressor_params();
  329. cparams["release"] = param[1];
  330. m_sfx.set_compressor_params(cparams);
  331. break;
  332. default:
  333. m_print.error("Unknown strip param");
  334. break;
  335. }
  336. }
  337. function switch_mute() {
  338. var strip = _mixer_strips[_active_strip];
  339. if (!strip)
  340. return;
  341. if (strip.mute >= 0) {
  342. flip_strip_mute(strip);
  343. }
  344. }
  345. function flip_strip_mute(strip) {
  346. var id = strip.id;
  347. if (id != "MASTER") {
  348. if (strip.mute == 0) {
  349. strip.mute = 1;
  350. m_sfx.mute(strip.speaker, true);
  351. if (strip.solo == 1) {
  352. strip.solo = 0;
  353. if (!is_other_solo(strip))
  354. unmute_other(strip);
  355. }
  356. } else {
  357. strip.mute = 0;
  358. if (!is_other_solo(strip))
  359. m_sfx.mute(strip.speaker, false);
  360. }
  361. } else {
  362. if (strip.mute == 0) {
  363. strip.mute = 1;
  364. m_sfx.mute(null, true);
  365. } else {
  366. strip.mute = 0;
  367. m_sfx.mute(null, false);
  368. }
  369. }
  370. }
  371. function is_other_solo(strip) {
  372. for (var i = 0; i < _mixer_strips.length; i++) {
  373. var strip_i = _mixer_strips[i];
  374. if (strip_i != strip && strip_i.solo == 1)
  375. return true;
  376. }
  377. return false;
  378. }
  379. function mute_other(strip) {
  380. for (var i = 0; i < _mixer_strips.length; i++) {
  381. var strip_i = _mixer_strips[i];
  382. if (strip_i != strip && strip_i.mute == 0 && strip_i.solo == 0)
  383. m_sfx.mute(strip_i.speaker, true);
  384. }
  385. }
  386. function unmute_other(strip) {
  387. for (var i = 0; i < _mixer_strips.length; i++) {
  388. var strip_i = _mixer_strips[i];
  389. if (strip_i != strip && strip_i.mute == 0 && strip_i.solo == 0)
  390. m_sfx.mute(strip_i.speaker, false);
  391. }
  392. }
  393. function switch_solo() {
  394. var strip = _mixer_strips[_active_strip];
  395. if (!strip)
  396. return;
  397. if (strip.solo >= 0) {
  398. flip_strip_solo(strip);
  399. }
  400. }
  401. function flip_strip_solo(strip) {
  402. if (strip.solo == 0) {
  403. strip.solo = 1;
  404. m_sfx.mute(strip.speaker, false);
  405. // flip muted current
  406. if (strip.mute == 1)
  407. strip.mute = 0;
  408. mute_other(strip);
  409. } else {
  410. strip.solo = 0;
  411. if (is_other_solo(strip))
  412. m_sfx.mute(strip.speaker, true);
  413. else
  414. unmute_other(strip);
  415. }
  416. }
  417. function draw() {
  418. if (!_mixer_strips[_active_strip])
  419. return;
  420. var strip_range = active_strip_range();
  421. for (var i = strip_range[0]; i <= strip_range[1]; i++) {
  422. var strip = _mixer_strips[i];
  423. m_screen.draw_mixer_strip(strip.id, i == _active_strip, i % 8,
  424. strip.params, strip.active_param, strip.mute, strip.solo);
  425. if (strip.speaker && m_sfx.get_filter_params(strip.speaker)) {
  426. m_sfx.get_filter_freq_response(strip.speaker, _filter_freq_arr,
  427. _filter_mag_arr, _filter_phase_arr);
  428. for (var j = 0; j < _filter_mag_arr.length; j++) {
  429. var mag = _filter_mag_arr[j];
  430. // log10()
  431. _filter_mag_arr[j] = 20 * Math.log(mag) / Math.LN10;
  432. }
  433. m_screen.plot_array("EQ", i % 8, _filter_mag_arr, 20, 20000, -10, 10);
  434. }
  435. }
  436. }
  437. function active_strip_range() {
  438. if (_mixer_strips.length == 0)
  439. return [0, -1];
  440. var strip_low = Math.floor(_active_strip / 8) * 8;
  441. var strip_high = strip_low + 7;
  442. strip_low = m_util.clamp(strip_low, 0, _mixer_strips.length-1);
  443. strip_high = m_util.clamp(strip_high, 0, _mixer_strips.length-1);
  444. return [strip_low, strip_high];
  445. }
  446. }
  447. var mixer_factory = register("mixer", Mixer);
  448. export default mixer_factory;