speaker.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. "use strict";
  2. /** @const */
  3. var DAC_QUEUE_RESERVE = 0.2;
  4. /** @const */
  5. var AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000;
  6. /**
  7. * @constructor
  8. * @param {!BusConnector} bus
  9. */
  10. function SpeakerAdapter(bus)
  11. {
  12. if(typeof window === "undefined")
  13. {
  14. return;
  15. }
  16. if(!window.AudioContext && !window["webkitAudioContext"])
  17. {
  18. console.warn("Web browser doesn't support Web Audio API");
  19. return;
  20. }
  21. var SpeakerDAC = window.AudioWorklet ? SpeakerWorkletDAC : SpeakerBufferSourceDAC;
  22. /** @const */
  23. this.bus = bus;
  24. /** @const */
  25. this.audio_context = window.AudioContext ? new AudioContext() : new webkitAudioContext();
  26. /** @const */
  27. this.mixer = new SpeakerMixer(bus, this.audio_context);
  28. /** @const */
  29. this.pcspeaker = new PCSpeaker(bus, this.audio_context, this.mixer);
  30. /** @const */
  31. this.dac = new SpeakerDAC(bus, this.audio_context, this.mixer);
  32. this.pcspeaker.start();
  33. bus.register("emulator-stopped", function()
  34. {
  35. this.audio_context.suspend();
  36. }, this);
  37. bus.register("emulator-started", function()
  38. {
  39. this.audio_context.resume();
  40. }, this);
  41. bus.register("speaker-confirm-initialized", function()
  42. {
  43. bus.send("speaker-has-initialized");
  44. }, this);
  45. bus.send("speaker-has-initialized");
  46. }
  47. SpeakerAdapter.prototype.destroy = function()
  48. {
  49. this.audio_context && this.audio_context.close();
  50. this.dac && this.dac.node_processor && this.dac.node_processor.port.close();
  51. };
  52. /**
  53. * @constructor
  54. * @param {!BusConnector} bus
  55. * @param {!AudioContext} audio_context
  56. */
  57. function SpeakerMixer(bus, audio_context)
  58. {
  59. /** @const */
  60. this.audio_context = audio_context;
  61. this.sources = new Map();
  62. // States
  63. this.volume_both = 1;
  64. this.volume_left = 1;
  65. this.volume_right = 1;
  66. this.gain_left = 1;
  67. this.gain_right = 1;
  68. // Nodes
  69. // TODO: Find / calibrate / verify the filter frequencies
  70. this.node_treble_left = this.audio_context.createBiquadFilter();
  71. this.node_treble_right = this.audio_context.createBiquadFilter();
  72. this.node_treble_left.type = "highshelf";
  73. this.node_treble_right.type = "highshelf";
  74. this.node_treble_left.frequency.setValueAtTime(2000, this.audio_context.currentTime);
  75. this.node_treble_right.frequency.setValueAtTime(2000, this.audio_context.currentTime);
  76. this.node_bass_left = this.audio_context.createBiquadFilter();
  77. this.node_bass_right = this.audio_context.createBiquadFilter();
  78. this.node_bass_left.type = "lowshelf";
  79. this.node_bass_right.type = "lowshelf";
  80. this.node_bass_left.frequency.setValueAtTime(200, this.audio_context.currentTime);
  81. this.node_bass_right.frequency.setValueAtTime(200, this.audio_context.currentTime);
  82. this.node_gain_left = this.audio_context.createGain();
  83. this.node_gain_right = this.audio_context.createGain();
  84. this.node_merger = this.audio_context.createChannelMerger(2);
  85. // Graph
  86. this.input_left = this.node_treble_left;
  87. this.input_right = this.node_treble_right;
  88. this.node_treble_left.connect(this.node_bass_left);
  89. this.node_bass_left.connect(this.node_gain_left);
  90. this.node_gain_left.connect(this.node_merger, 0, 0);
  91. this.node_treble_right.connect(this.node_bass_right);
  92. this.node_bass_right.connect(this.node_gain_right);
  93. this.node_gain_right.connect(this.node_merger, 0, 1);
  94. this.node_merger.connect(this.audio_context.destination);
  95. // Interface
  96. bus.register("mixer-connect", function(data)
  97. {
  98. var source_id = data[0];
  99. var channel = data[1];
  100. this.connect_source(source_id, channel);
  101. }, this);
  102. bus.register("mixer-disconnect", function(data)
  103. {
  104. var source_id = data[0];
  105. var channel = data[1];
  106. this.disconnect_source(source_id, channel);
  107. }, this);
  108. bus.register("mixer-volume", function(data)
  109. {
  110. var source_id = data[0];
  111. var channel = data[1];
  112. var decibels = data[2];
  113. var gain = Math.pow(10, decibels / 20);
  114. var source = source_id === MIXER_SRC_MASTER ? this : this.sources.get(source_id);
  115. if(source === undefined)
  116. {
  117. dbg_assert(false, "Mixer set volume - cannot set volume for undefined source: " + source_id);
  118. return;
  119. }
  120. source.set_volume(gain, channel);
  121. }, this);
  122. bus.register("mixer-gain-left", function(/** number */ decibels)
  123. {
  124. this.gain_left = Math.pow(10, decibels / 20);
  125. this.update();
  126. }, this);
  127. bus.register("mixer-gain-right", function(/** number */ decibels)
  128. {
  129. this.gain_right = Math.pow(10, decibels / 20);
  130. this.update();
  131. }, this);
  132. function create_gain_handler(audio_node)
  133. {
  134. return function(decibels)
  135. {
  136. audio_node.gain.setValueAtTime(decibels, this.audio_context.currentTime);
  137. };
  138. }
  139. bus.register("mixer-treble-left", create_gain_handler(this.node_treble_left), this);
  140. bus.register("mixer-treble-right", create_gain_handler(this.node_treble_right), this);
  141. bus.register("mixer-bass-left", create_gain_handler(this.node_bass_left), this);
  142. bus.register("mixer-bass-right", create_gain_handler(this.node_bass_right), this);
  143. }
  144. /**
  145. * @param {!AudioNode} source_node
  146. * @param {number} source_id
  147. * @return {SpeakerMixerSource}
  148. */
  149. SpeakerMixer.prototype.add_source = function(source_node, source_id)
  150. {
  151. var source = new SpeakerMixerSource(
  152. this.audio_context,
  153. source_node,
  154. this.input_left,
  155. this.input_right
  156. );
  157. dbg_assert(!this.sources.has(source_id), "Mixer add source - overwritting source: " + source_id);
  158. this.sources.set(source_id, source);
  159. return source;
  160. };
  161. /**
  162. * @param {number} source_id
  163. * @param {number=} channel
  164. */
  165. SpeakerMixer.prototype.connect_source = function(source_id, channel)
  166. {
  167. var source = this.sources.get(source_id);
  168. if(source === undefined)
  169. {
  170. dbg_assert(false, "Mixer connect - cannot connect undefined source: " + source_id);
  171. return;
  172. }
  173. source.connect(channel);
  174. };
  175. /**
  176. * @param {number} source_id
  177. * @param {number=} channel
  178. */
  179. SpeakerMixer.prototype.disconnect_source = function(source_id, channel)
  180. {
  181. var source = this.sources.get(source_id);
  182. if(source === undefined)
  183. {
  184. dbg_assert(false, "Mixer disconnect - cannot disconnect undefined source: " + source_id);
  185. return;
  186. }
  187. source.disconnect(channel);
  188. };
  189. /**
  190. * @param {number} value
  191. * @param {number=} channel
  192. */
  193. SpeakerMixer.prototype.set_volume = function(value, channel)
  194. {
  195. if(channel === undefined)
  196. {
  197. channel = MIXER_CHANNEL_BOTH;
  198. }
  199. switch(channel)
  200. {
  201. case MIXER_CHANNEL_LEFT:
  202. this.volume_left = value;
  203. break;
  204. case MIXER_CHANNEL_RIGHT:
  205. this.volume_right = value;
  206. break;
  207. case MIXER_CHANNEL_BOTH:
  208. this.volume_both = value;
  209. break;
  210. default:
  211. dbg_assert(false, "Mixer set master volume - unknown channel: " + channel);
  212. return;
  213. }
  214. this.update();
  215. };
  216. SpeakerMixer.prototype.update = function()
  217. {
  218. var net_gain_left = this.volume_both * this.volume_left * this.gain_left;
  219. var net_gain_right = this.volume_both * this.volume_right * this.gain_right;
  220. this.node_gain_left.gain.setValueAtTime(net_gain_left, this.audio_context.currentTime);
  221. this.node_gain_right.gain.setValueAtTime(net_gain_right, this.audio_context.currentTime);
  222. };
  223. /**
  224. * @constructor
  225. * @param {!AudioContext} audio_context
  226. * @param {!AudioNode} source_node
  227. * @param {!AudioNode} destination_left
  228. * @param {!AudioNode} destination_right
  229. */
  230. function SpeakerMixerSource(audio_context, source_node, destination_left, destination_right)
  231. {
  232. /** @const */
  233. this.audio_context = audio_context;
  234. // States
  235. this.connected_left = true;
  236. this.connected_right = true;
  237. this.gain_hidden = 1;
  238. this.volume_both = 1;
  239. this.volume_left = 1;
  240. this.volume_right = 1;
  241. // Nodes
  242. this.node_splitter = audio_context.createChannelSplitter(2);
  243. this.node_gain_left = audio_context.createGain();
  244. this.node_gain_right = audio_context.createGain();
  245. // Graph
  246. source_node.connect(this.node_splitter);
  247. this.node_splitter.connect(this.node_gain_left, 0);
  248. this.node_gain_left.connect(destination_left);
  249. this.node_splitter.connect(this.node_gain_right, 1);
  250. this.node_gain_right.connect(destination_right);
  251. }
  252. SpeakerMixerSource.prototype.update = function()
  253. {
  254. var net_gain_left = this.connected_left * this.gain_hidden * this.volume_both * this.volume_left;
  255. var net_gain_right = this.connected_right * this.gain_hidden * this.volume_both * this.volume_right;
  256. this.node_gain_left.gain.setValueAtTime(net_gain_left, this.audio_context.currentTime);
  257. this.node_gain_right.gain.setValueAtTime(net_gain_right, this.audio_context.currentTime);
  258. };
  259. /** @param {number=} channel */
  260. SpeakerMixerSource.prototype.connect = function(channel)
  261. {
  262. var both = !channel || channel === MIXER_CHANNEL_BOTH;
  263. if(both || channel === MIXER_CHANNEL_LEFT)
  264. {
  265. this.connected_left = true;
  266. }
  267. if(both || channel === MIXER_CHANNEL_RIGHT)
  268. {
  269. this.connected_right = true;
  270. }
  271. this.update();
  272. };
  273. /** @param {number=} channel */
  274. SpeakerMixerSource.prototype.disconnect = function(channel)
  275. {
  276. var both = !channel || channel === MIXER_CHANNEL_BOTH;
  277. if(both || channel === MIXER_CHANNEL_LEFT)
  278. {
  279. this.connected_left = false;
  280. }
  281. if(both || channel === MIXER_CHANNEL_RIGHT)
  282. {
  283. this.connected_right = false;
  284. }
  285. this.update();
  286. };
  287. /**
  288. * @param {number} value
  289. * @param {number=} channel
  290. */
  291. SpeakerMixerSource.prototype.set_volume = function(value, channel)
  292. {
  293. if(channel === undefined)
  294. {
  295. channel = MIXER_CHANNEL_BOTH;
  296. }
  297. switch(channel)
  298. {
  299. case MIXER_CHANNEL_LEFT:
  300. this.volume_left = value;
  301. break;
  302. case MIXER_CHANNEL_RIGHT:
  303. this.volume_right = value;
  304. break;
  305. case MIXER_CHANNEL_BOTH:
  306. this.volume_both = value;
  307. break;
  308. default:
  309. dbg_assert(false, "Mixer set volume - unknown channel: " + channel);
  310. return;
  311. }
  312. this.update();
  313. };
  314. SpeakerMixerSource.prototype.set_gain_hidden = function(value)
  315. {
  316. this.gain_hidden = value;
  317. };
  318. /**
  319. * @constructor
  320. * @param {!BusConnector} bus
  321. * @param {!AudioContext} audio_context
  322. * @param {!SpeakerMixer} mixer
  323. */
  324. function PCSpeaker(bus, audio_context, mixer)
  325. {
  326. // Nodes
  327. this.node_oscillator = audio_context.createOscillator();
  328. this.node_oscillator.type = "square";
  329. this.node_oscillator.frequency.setValueAtTime(440, audio_context.currentTime);
  330. // Interface
  331. this.mixer_connection = mixer.add_source(this.node_oscillator, MIXER_SRC_PCSPEAKER);
  332. this.mixer_connection.disconnect();
  333. bus.register("pcspeaker-enable", function()
  334. {
  335. mixer.connect_source(MIXER_SRC_PCSPEAKER);
  336. }, this);
  337. bus.register("pcspeaker-disable", function()
  338. {
  339. mixer.disconnect_source(MIXER_SRC_PCSPEAKER);
  340. }, this);
  341. bus.register("pcspeaker-update", function(data)
  342. {
  343. var counter_mode = data[0];
  344. var counter_reload = data[1];
  345. var frequency = 0;
  346. var beep_enabled = counter_mode === 3;
  347. if(beep_enabled)
  348. {
  349. frequency = OSCILLATOR_FREQ * 1000 / counter_reload;
  350. frequency = Math.min(frequency, this.node_oscillator.frequency.maxValue);
  351. frequency = Math.max(frequency, 0);
  352. }
  353. this.node_oscillator.frequency.setValueAtTime(frequency, audio_context.currentTime);
  354. }, this);
  355. }
  356. PCSpeaker.prototype.start = function()
  357. {
  358. this.node_oscillator.start();
  359. };
  360. /**
  361. * @constructor
  362. * @param {!BusConnector} bus
  363. * @param {!AudioContext} audio_context
  364. * @param {!SpeakerMixer} mixer
  365. */
  366. function SpeakerWorkletDAC(bus, audio_context, mixer)
  367. {
  368. /** @const */
  369. this.bus = bus;
  370. /** @const */
  371. this.audio_context = audio_context;
  372. // State
  373. this.enabled = false;
  374. this.sampling_rate = 48000;
  375. // Worklet
  376. function worklet()
  377. {
  378. /** @const */
  379. var RENDER_QUANTUM = 128;
  380. /** @const */
  381. var MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM;
  382. /** @const */
  383. var QUEUE_RESERVE = 1024;
  384. function sinc(x)
  385. {
  386. if(x === 0) return 1;
  387. x *= Math.PI;
  388. return Math.sin(x) / x;
  389. }
  390. var EMPTY_BUFFER =
  391. [
  392. new Float32Array(MINIMUM_BUFFER_SIZE),
  393. new Float32Array(MINIMUM_BUFFER_SIZE),
  394. ];
  395. /**
  396. * @constructor
  397. * @extends AudioWorkletProcessor
  398. */
  399. function DACProcessor()
  400. {
  401. var self = Reflect.construct(AudioWorkletProcessor, [], DACProcessor);
  402. // Params
  403. self.kernel_size = 3;
  404. // States
  405. // Buffers waiting for their turn to be consumed
  406. self.queue_data = new Array(1024);
  407. self.queue_start = 0;
  408. self.queue_end = 0;
  409. self.queue_length = 0;
  410. self.queue_size = self.queue_data.length;
  411. self.queued_samples = 0;
  412. // Buffers being actively consumed
  413. /** @type{Array<Float32Array>} */
  414. self.source_buffer_previous = EMPTY_BUFFER;
  415. /** @type{Array<Float32Array>} */
  416. self.source_buffer_current = EMPTY_BUFFER;
  417. // Ratio of alienland sample rate to homeland sample rate.
  418. self.source_samples_per_destination = 1.0;
  419. // Integer representing the position of the first destination sample
  420. // for the current block, relative to source_buffer_current.
  421. self.source_block_start = 0;
  422. // Real number representing the position of the current destination
  423. // sample relative to source_buffer_current, since source_block_start.
  424. self.source_time = 0.0;
  425. // Same as source_time but rounded down to an index.
  426. self.source_offset = 0;
  427. // Interface
  428. self.port.onmessage = (event) =>
  429. {
  430. switch(event.data.type)
  431. {
  432. case "queue":
  433. self.queue_push(event.data.value);
  434. break;
  435. case "sampling-rate":
  436. self.source_samples_per_destination = event.data.value / sampleRate;
  437. break;
  438. }
  439. };
  440. return self;
  441. }
  442. Reflect.setPrototypeOf(DACProcessor.prototype, AudioWorkletProcessor.prototype);
  443. Reflect.setPrototypeOf(DACProcessor, AudioWorkletProcessor);
  444. DACProcessor.prototype["process"] =
  445. DACProcessor.prototype.process = function(inputs, outputs, parameters)
  446. {
  447. for(var i = 0; i < outputs[0][0].length; i++)
  448. {
  449. // Lanczos resampling
  450. var sum0 = 0;
  451. var sum1 = 0;
  452. var start = this.source_offset - this.kernel_size + 1;
  453. var end = this.source_offset + this.kernel_size;
  454. for(var j = start; j <= end; j++)
  455. {
  456. var convolute_index = this.source_block_start + j;
  457. sum0 += this.get_sample(convolute_index, 0) * this.kernel(this.source_time - j);
  458. sum1 += this.get_sample(convolute_index, 1) * this.kernel(this.source_time - j);
  459. }
  460. if(isNaN(sum0) || isNaN(sum1))
  461. {
  462. // NaN values cause entire audio graph to cease functioning.
  463. sum0 = sum1 = 0;
  464. this.dbg_log("ERROR: NaN values! Ignoring for now.");
  465. }
  466. outputs[0][0][i] = sum0;
  467. outputs[0][1][i] = sum1;
  468. this.source_time += this.source_samples_per_destination;
  469. this.source_offset = Math.floor(this.source_time);
  470. }
  471. // +2 to safeguard against rounding variations
  472. var samples_needed_per_block = this.source_offset;
  473. samples_needed_per_block += this.kernel_size + 2;
  474. this.source_time -= this.source_offset;
  475. this.source_block_start += this.source_offset;
  476. this.source_offset = 0;
  477. // Note: This needs to be done after source_block_start is updated.
  478. this.ensure_enough_data(samples_needed_per_block);
  479. return true;
  480. };
  481. DACProcessor.prototype.kernel = function(x)
  482. {
  483. return sinc(x) * sinc(x / this.kernel_size);
  484. };
  485. DACProcessor.prototype.get_sample = function(index, channel)
  486. {
  487. if(index < 0)
  488. {
  489. // -ve index represents previous buffer
  490. // <-------|
  491. // [Previous buffer][Current buffer]
  492. index += this.source_buffer_previous[0].length;
  493. return this.source_buffer_previous[channel][index];
  494. }
  495. else
  496. {
  497. return this.source_buffer_current[channel][index];
  498. }
  499. };
  500. DACProcessor.prototype.ensure_enough_data = function(needed)
  501. {
  502. var current_length = this.source_buffer_current[0].length;
  503. var remaining = current_length - this.source_block_start;
  504. if(remaining < needed)
  505. {
  506. this.prepare_next_buffer();
  507. this.source_block_start -= current_length;
  508. }
  509. };
  510. DACProcessor.prototype.prepare_next_buffer = function()
  511. {
  512. if(this.queued_samples < MINIMUM_BUFFER_SIZE && this.queue_length)
  513. {
  514. this.dbg_log("Not enough samples - should not happen during midway of playback");
  515. }
  516. this.source_buffer_previous = this.source_buffer_current;
  517. this.source_buffer_current = this.queue_shift();
  518. var sample_count = this.source_buffer_current[0].length;
  519. if(sample_count < MINIMUM_BUFFER_SIZE)
  520. {
  521. // Unfortunately, this single buffer is too small :(
  522. var queue_pos = this.queue_start;
  523. var buffer_count = 0;
  524. // Figure out how many small buffers to combine.
  525. while(sample_count < MINIMUM_BUFFER_SIZE && buffer_count < this.queue_length)
  526. {
  527. sample_count += this.queue_data[queue_pos][0].length;
  528. queue_pos = queue_pos + 1 & this.queue_size - 1;
  529. buffer_count++;
  530. }
  531. // Note: if not enough buffers, this will be end-padded with zeros:
  532. var new_big_buffer_size = Math.max(sample_count, MINIMUM_BUFFER_SIZE);
  533. var new_big_buffer =
  534. [
  535. new Float32Array(new_big_buffer_size),
  536. new Float32Array(new_big_buffer_size),
  537. ];
  538. // Copy the first, already-shifted, small buffer into the new buffer.
  539. new_big_buffer[0].set(this.source_buffer_current[0]);
  540. new_big_buffer[1].set(this.source_buffer_current[1]);
  541. var new_big_buffer_pos = this.source_buffer_current[0].length;
  542. // Copy the rest.
  543. for(var i = 0; i < buffer_count; i++)
  544. {
  545. var small_buffer = this.queue_shift();
  546. new_big_buffer[0].set(small_buffer[0], new_big_buffer_pos);
  547. new_big_buffer[1].set(small_buffer[1], new_big_buffer_pos);
  548. new_big_buffer_pos += small_buffer[0].length;
  549. }
  550. // Pretend that everything's just fine.
  551. this.source_buffer_current = new_big_buffer;
  552. }
  553. this.pump();
  554. };
  555. DACProcessor.prototype.pump = function()
  556. {
  557. if(this.queued_samples / this.source_samples_per_destination < QUEUE_RESERVE)
  558. {
  559. this.port.postMessage(
  560. {
  561. type: "pump",
  562. });
  563. }
  564. };
  565. DACProcessor.prototype.queue_push = function(item)
  566. {
  567. if(this.queue_length < this.queue_size)
  568. {
  569. this.queue_data[this.queue_end] = item;
  570. this.queue_end = this.queue_end + 1 & this.queue_size - 1;
  571. this.queue_length++;
  572. this.queued_samples += item[0].length;
  573. this.pump();
  574. }
  575. };
  576. DACProcessor.prototype.queue_shift = function()
  577. {
  578. if(!this.queue_length)
  579. {
  580. return EMPTY_BUFFER;
  581. }
  582. var item = this.queue_data[this.queue_start];
  583. this.queue_data[this.queue_start] = null;
  584. this.queue_start = this.queue_start + 1 & this.queue_size - 1;
  585. this.queue_length--;
  586. this.queued_samples -= item[0].length;
  587. return item;
  588. };
  589. DACProcessor.prototype.dbg_log = function(message)
  590. {
  591. if(DEBUG)
  592. {
  593. this.port.postMessage(
  594. {
  595. type: "debug-log",
  596. value: message,
  597. });
  598. }
  599. };
  600. registerProcessor("dac-processor", DACProcessor);
  601. }
  602. var worklet_string = worklet.toString();
  603. var worklet_code_start = worklet_string.indexOf("{") + 1;
  604. var worklet_code_end = worklet_string.lastIndexOf("}");
  605. var worklet_code = worklet_string.substring(worklet_code_start, worklet_code_end);
  606. if(DEBUG)
  607. {
  608. worklet_code = "var DEBUG = true;\n" + worklet_code;
  609. }
  610. var worklet_blob = new Blob([worklet_code], { type: "application/javascript" });
  611. var worklet_url = URL.createObjectURL(worklet_blob);
  612. /** @type {AudioWorkletNode} */
  613. this.node_processor = null;
  614. // Placeholder pass-through node to connect to, when worklet node is not ready yet.
  615. this.node_output = this.audio_context.createGain();
  616. this.audio_context
  617. .audioWorklet
  618. .addModule(worklet_url)
  619. .then(() =>
  620. {
  621. URL.revokeObjectURL(worklet_url);
  622. this.node_processor = new AudioWorkletNode(this.audio_context, "dac-processor",
  623. {
  624. numberOfInputs: 0,
  625. numberOfOutputs: 1,
  626. outputChannelCount: [2],
  627. parameterData: {},
  628. processorOptions: {},
  629. });
  630. this.node_processor.port.postMessage(
  631. {
  632. type: "sampling-rate",
  633. value: this.sampling_rate,
  634. });
  635. this.node_processor.port.onmessage = (event) =>
  636. {
  637. switch(event.data.type)
  638. {
  639. case "pump":
  640. this.pump();
  641. break;
  642. case "debug-log":
  643. dbg_log("SpeakerWorkletDAC - Worklet: " + event.data.value);
  644. break;
  645. }
  646. };
  647. // Graph
  648. this.node_processor.connect(this.node_output);
  649. });
  650. // Interface
  651. this.mixer_connection = mixer.add_source(this.node_output, MIXER_SRC_DAC);
  652. this.mixer_connection.set_gain_hidden(3);
  653. bus.register("dac-send-data", function(data)
  654. {
  655. this.queue(data);
  656. }, this);
  657. bus.register("dac-enable", function(enabled)
  658. {
  659. this.enabled = true;
  660. }, this);
  661. bus.register("dac-disable", function()
  662. {
  663. this.enabled = false;
  664. }, this);
  665. bus.register("dac-tell-sampling-rate", function(/** number */ rate)
  666. {
  667. dbg_assert(rate > 0, "Sampling rate should be nonzero");
  668. this.sampling_rate = rate;
  669. if(!this.node_processor)
  670. {
  671. return;
  672. }
  673. this.node_processor.port.postMessage(
  674. {
  675. type: "sampling-rate",
  676. value: rate,
  677. });
  678. }, this);
  679. if(DEBUG)
  680. {
  681. this.debugger = new SpeakerDACDebugger(this.audio_context, this.node_output);
  682. }
  683. }
  684. SpeakerWorkletDAC.prototype.queue = function(data)
  685. {
  686. if(!this.node_processor)
  687. {
  688. return;
  689. }
  690. if(DEBUG)
  691. {
  692. this.debugger.push_queued_data(data);
  693. }
  694. this.node_processor.port.postMessage(
  695. {
  696. type: "queue",
  697. value: data,
  698. }, [data[0].buffer, data[1].buffer]);
  699. };
  700. SpeakerWorkletDAC.prototype.pump = function()
  701. {
  702. if(!this.enabled)
  703. {
  704. return;
  705. }
  706. this.bus.send("dac-request-data");
  707. };
  708. /**
  709. * @constructor
  710. * @param {!BusConnector} bus
  711. * @param {!AudioContext} audio_context
  712. * @param {!SpeakerMixer} mixer
  713. */
  714. function SpeakerBufferSourceDAC(bus, audio_context, mixer)
  715. {
  716. /** @const */
  717. this.bus = bus;
  718. /** @const */
  719. this.audio_context = audio_context;
  720. // States
  721. this.enabled = false;
  722. this.sampling_rate = 22050;
  723. this.buffered_time = 0;
  724. this.rate_ratio = 1;
  725. // Nodes
  726. this.node_lowpass = this.audio_context.createBiquadFilter();
  727. this.node_lowpass.type = "lowpass";
  728. // Interface
  729. this.node_output = this.node_lowpass;
  730. this.mixer_connection = mixer.add_source(this.node_output, MIXER_SRC_DAC);
  731. this.mixer_connection.set_gain_hidden(3);
  732. bus.register("dac-send-data", function(data)
  733. {
  734. this.queue(data);
  735. }, this);
  736. bus.register("dac-enable", function(enabled)
  737. {
  738. this.enabled = true;
  739. this.pump();
  740. }, this);
  741. bus.register("dac-disable", function()
  742. {
  743. this.enabled = false;
  744. }, this);
  745. bus.register("dac-tell-sampling-rate", function(/** number */ rate)
  746. {
  747. dbg_assert(rate > 0, "Sampling rate should be nonzero");
  748. this.sampling_rate = rate;
  749. this.rate_ratio = Math.ceil(AUDIOBUFFER_MINIMUM_SAMPLING_RATE / rate);
  750. this.node_lowpass.frequency.setValueAtTime(rate / 2, this.audio_context.currentTime);
  751. }, this);
  752. if(DEBUG)
  753. {
  754. this.debugger = new SpeakerDACDebugger(this.audio_context, this.node_output);
  755. }
  756. }
  757. SpeakerBufferSourceDAC.prototype.queue = function(data)
  758. {
  759. if(DEBUG)
  760. {
  761. this.debugger.push_queued_data(data);
  762. }
  763. var sample_count = data[0].length;
  764. var block_duration = sample_count / this.sampling_rate;
  765. var buffer;
  766. if(this.rate_ratio > 1)
  767. {
  768. var new_sample_count = sample_count * this.rate_ratio;
  769. var new_sampling_rate = this.sampling_rate * this.rate_ratio;
  770. buffer = this.audio_context.createBuffer(2, new_sample_count, new_sampling_rate);
  771. var buffer_data0 = buffer.getChannelData(0);
  772. var buffer_data1 = buffer.getChannelData(1);
  773. var buffer_index = 0;
  774. for(var i = 0; i < sample_count; i++)
  775. {
  776. for(var j = 0; j < this.rate_ratio; j++, buffer_index++)
  777. {
  778. buffer_data0[buffer_index] = data[0][i];
  779. buffer_data1[buffer_index] = data[1][i];
  780. }
  781. }
  782. }
  783. else
  784. {
  785. // Allocating new AudioBuffer every block
  786. // - Memory profiles show insignificant improvements if recycling old buffers.
  787. buffer = this.audio_context.createBuffer(2, sample_count, this.sampling_rate);
  788. if(buffer.copyToChannel)
  789. {
  790. buffer.copyToChannel(data[0], 0);
  791. buffer.copyToChannel(data[1], 1);
  792. }
  793. else
  794. {
  795. // Safari doesn't support copyToChannel yet. See #286
  796. buffer.getChannelData(0).set(data[0]);
  797. buffer.getChannelData(1).set(data[1]);
  798. }
  799. }
  800. var source = this.audio_context.createBufferSource();
  801. source.buffer = buffer;
  802. source.connect(this.node_lowpass);
  803. source.addEventListener("ended", this.pump.bind(this));
  804. var current_time = this.audio_context.currentTime;
  805. if(this.buffered_time < current_time)
  806. {
  807. dbg_log("Speaker DAC - Creating/Recreating reserve - shouldn't occur frequently during playback");
  808. // Schedule pump() to queue evenly, starting from current time
  809. this.buffered_time = current_time;
  810. var target_silence_duration = DAC_QUEUE_RESERVE - block_duration;
  811. var current_silence_duration = 0;
  812. while(current_silence_duration <= target_silence_duration)
  813. {
  814. current_silence_duration += block_duration;
  815. this.buffered_time += block_duration;
  816. setTimeout(() => this.pump(), current_silence_duration * 1000);
  817. }
  818. }
  819. source.start(this.buffered_time);
  820. this.buffered_time += block_duration;
  821. // Chase the schedule - ensure reserve is full
  822. setTimeout(() => this.pump(), 0);
  823. };
  824. SpeakerBufferSourceDAC.prototype.pump = function()
  825. {
  826. if(!this.enabled)
  827. {
  828. return;
  829. }
  830. if(this.buffered_time - this.audio_context.currentTime > DAC_QUEUE_RESERVE)
  831. {
  832. return;
  833. }
  834. this.bus.send("dac-request-data");
  835. };
  836. /**
  837. * @constructor
  838. */
  839. function SpeakerDACDebugger(audio_context, source_node)
  840. {
  841. /** @const */
  842. this.audio_context = audio_context;
  843. /** @const */
  844. this.node_source = source_node;
  845. this.node_processor = null;
  846. this.node_gain = this.audio_context.createGain();
  847. this.node_gain.gain.setValueAtTime(0, this.audio_context.currentTime);
  848. this.node_gain.connect(this.audio_context.destination);
  849. this.is_active = false;
  850. this.queued_history = [];
  851. this.output_history = [];
  852. this.queued = [[], []];
  853. this.output = [[], []];
  854. }
  855. /** @suppress {deprecated} */
  856. SpeakerDACDebugger.prototype.start = function(duration_ms)
  857. {
  858. this.is_active = true;
  859. this.queued = [[], []];
  860. this.output = [[], []];
  861. this.queued_history.push(this.queued);
  862. this.output_history.push(this.output);
  863. this.node_processor = this.audio_context.createScriptProcessor(1024, 2, 2);
  864. this.node_processor.onaudioprocess = (event) =>
  865. {
  866. this.output[0].push(event.inputBuffer.getChannelData(0).slice());
  867. this.output[1].push(event.inputBuffer.getChannelData(1).slice());
  868. };
  869. this.node_source.connect(this.node_processor);
  870. this.node_processor.connect(this.node_gain);
  871. setTimeout(() =>
  872. {
  873. this.stop();
  874. }, duration_ms);
  875. };
  876. SpeakerDACDebugger.prototype.stop = function()
  877. {
  878. this.is_active = false;
  879. this.node_source.disconnect(this.node_processor);
  880. this.node_processor.disconnect();
  881. this.node_processor = null;
  882. };
  883. SpeakerDACDebugger.prototype.push_queued_data = function(data)
  884. {
  885. if(this.is_active)
  886. {
  887. this.queued[0].push(data[0].slice());
  888. this.queued[1].push(data[1].slice());
  889. }
  890. };
  891. // Useful for Audacity imports
  892. SpeakerDACDebugger.prototype.download_txt = function(history_id, channel)
  893. {
  894. var txt = this.output_history[history_id][channel]
  895. .map((v) => v.join(" "))
  896. .join(" ");
  897. dump_file(txt, "dacdata.txt");
  898. };
  899. // Useful for general plotting
  900. SpeakerDACDebugger.prototype.download_csv = function(history_id)
  901. {
  902. var buffers = this.output_history[history_id];
  903. var csv_rows = [];
  904. for(var buffer_id = 0; buffer_id < buffers[0].length; buffer_id++)
  905. {
  906. for(var i = 0; i < buffers[0][buffer_id].length; i++)
  907. {
  908. csv_rows.push(`${buffers[0][buffer_id][i]},${buffers[1][buffer_id][i]}`);
  909. }
  910. }
  911. dump_file(csv_rows.join("\n"), "dacdata.csv");
  912. };