library_godot_audio.js 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267
  1. /**************************************************************************/
  2. /* library_godot_audio.js */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. /**
  31. * @typedef { "disabled" | "forward" | "backward" | "pingpong" } LoopMode
  32. */
  33. /**
  34. * @typedef {{
  35. * id: string
  36. * audioBuffer: AudioBuffer
  37. * }} SampleParams
  38. * @typedef {{
  39. * numberOfChannels?: number
  40. * sampleRate?: number
  41. * loopMode?: LoopMode
  42. * loopBegin?: number
  43. * loopEnd?: number
  44. * }} SampleOptions
  45. */
  46. /**
  47. * Represents a sample, memory-wise.
  48. * @class
  49. */
  50. class Sample {
  51. /**
  52. * Returns a `Sample`.
  53. * @param {string} id Id of the `Sample` to get.
  54. * @returns {Sample}
  55. * @throws {ReferenceError} When no `Sample` is found
  56. */
  57. static getSample(id) {
  58. if (!GodotAudio.samples.has(id)) {
  59. throw new ReferenceError(`Could not find sample "${id}"`);
  60. }
  61. return GodotAudio.samples.get(id);
  62. }
  63. /**
  64. * Returns a `Sample` or `null`, if it doesn't exist.
  65. * @param {string} id Id of the `Sample` to get.
  66. * @returns {Sample?}
  67. */
  68. static getSampleOrNull(id) {
  69. return GodotAudio.samples.get(id) ?? null;
  70. }
  71. /**
  72. * Creates a `Sample` based on the params. Will register it to the
  73. * `GodotAudio.samples` registry.
  74. * @param {SampleParams} params Base params
  75. * @param {SampleOptions | undefined} options Optional params.
  76. * @returns {Sample}
  77. */
  78. static create(params, options = {}) {
  79. const sample = new GodotAudio.Sample(params, options);
  80. GodotAudio.samples.set(params.id, sample);
  81. return sample;
  82. }
  83. /**
  84. * Deletes a `Sample` based on the id.
  85. * @param {string} id `Sample` id to delete
  86. * @returns {void}
  87. */
  88. static delete(id) {
  89. GodotAudio.samples.delete(id);
  90. }
  91. /**
  92. * `Sample` constructor.
  93. * @param {SampleParams} params Base params
  94. * @param {SampleOptions | undefined} options Optional params.
  95. */
  96. constructor(params, options = {}) {
  97. /** @type {string} */
  98. this.id = params.id;
  99. /** @type {AudioBuffer} */
  100. this._audioBuffer = null;
  101. /** @type {number} */
  102. this.numberOfChannels = options.numberOfChannels ?? 2;
  103. /** @type {number} */
  104. this.sampleRate = options.sampleRate ?? 44100;
  105. /** @type {LoopMode} */
  106. this.loopMode = options.loopMode ?? 'disabled';
  107. /** @type {number} */
  108. this.loopBegin = options.loopBegin ?? 0;
  109. /** @type {number} */
  110. this.loopEnd = options.loopEnd ?? 0;
  111. this.setAudioBuffer(params.audioBuffer);
  112. }
  113. /**
  114. * Gets the audio buffer of the sample.
  115. * @returns {AudioBuffer}
  116. */
  117. getAudioBuffer() {
  118. return this._duplicateAudioBuffer();
  119. }
  120. /**
  121. * Sets the audio buffer of the sample.
  122. * @param {AudioBuffer} val The audio buffer to set.
  123. * @returns {void}
  124. */
  125. setAudioBuffer(val) {
  126. this._audioBuffer = val;
  127. }
  128. /**
  129. * Clears the current sample.
  130. * @returns {void}
  131. */
  132. clear() {
  133. this.setAudioBuffer(null);
  134. GodotAudio.Sample.delete(this.id);
  135. }
  136. /**
  137. * Returns a duplicate of the stored audio buffer.
  138. * @returns {AudioBuffer}
  139. */
  140. _duplicateAudioBuffer() {
  141. if (this._audioBuffer == null) {
  142. throw new Error('couldn\'t duplicate a null audioBuffer');
  143. }
  144. /** @type {Array<Float32Array>} */
  145. const channels = new Array(this._audioBuffer.numberOfChannels);
  146. for (let i = 0; i < this._audioBuffer.numberOfChannels; i++) {
  147. const channel = new Float32Array(this._audioBuffer.getChannelData(i));
  148. channels[i] = channel;
  149. }
  150. const buffer = GodotAudio.ctx.createBuffer(
  151. this.numberOfChannels,
  152. this._audioBuffer.length,
  153. this._audioBuffer.sampleRate
  154. );
  155. for (let i = 0; i < channels.length; i++) {
  156. buffer.copyToChannel(channels[i], i, 0);
  157. }
  158. return buffer;
  159. }
  160. }
  161. /**
  162. * Represents a `SampleNode` linked to a `Bus`.
  163. * @class
  164. */
  165. class SampleNodeBus {
  166. /**
  167. * Creates a new `SampleNodeBus`.
  168. * @param {Bus} bus The bus related to the new `SampleNodeBus`.
  169. * @returns {SampleNodeBus}
  170. */
  171. static create(bus) {
  172. return new GodotAudio.SampleNodeBus(bus);
  173. }
  174. /**
  175. * `SampleNodeBus` constructor.
  176. * @param {Bus} bus The bus related to the new `SampleNodeBus`.
  177. */
  178. constructor(bus) {
  179. const NUMBER_OF_WEB_CHANNELS = 6;
  180. /** @type {Bus} */
  181. this._bus = bus;
  182. /** @type {ChannelSplitterNode} */
  183. this._channelSplitter = GodotAudio.ctx.createChannelSplitter(NUMBER_OF_WEB_CHANNELS);
  184. /** @type {GainNode} */
  185. this._l = GodotAudio.ctx.createGain();
  186. /** @type {GainNode} */
  187. this._r = GodotAudio.ctx.createGain();
  188. /** @type {GainNode} */
  189. this._sl = GodotAudio.ctx.createGain();
  190. /** @type {GainNode} */
  191. this._sr = GodotAudio.ctx.createGain();
  192. /** @type {GainNode} */
  193. this._c = GodotAudio.ctx.createGain();
  194. /** @type {GainNode} */
  195. this._lfe = GodotAudio.ctx.createGain();
  196. /** @type {ChannelMergerNode} */
  197. this._channelMerger = GodotAudio.ctx.createChannelMerger(NUMBER_OF_WEB_CHANNELS);
  198. this._channelSplitter
  199. .connect(this._l, GodotAudio.WebChannel.CHANNEL_L)
  200. .connect(
  201. this._channelMerger,
  202. GodotAudio.WebChannel.CHANNEL_L,
  203. GodotAudio.WebChannel.CHANNEL_L
  204. );
  205. this._channelSplitter
  206. .connect(this._r, GodotAudio.WebChannel.CHANNEL_R)
  207. .connect(
  208. this._channelMerger,
  209. GodotAudio.WebChannel.CHANNEL_L,
  210. GodotAudio.WebChannel.CHANNEL_R
  211. );
  212. this._channelSplitter
  213. .connect(this._sl, GodotAudio.WebChannel.CHANNEL_SL)
  214. .connect(
  215. this._channelMerger,
  216. GodotAudio.WebChannel.CHANNEL_L,
  217. GodotAudio.WebChannel.CHANNEL_SL
  218. );
  219. this._channelSplitter
  220. .connect(this._sr, GodotAudio.WebChannel.CHANNEL_SR)
  221. .connect(
  222. this._channelMerger,
  223. GodotAudio.WebChannel.CHANNEL_L,
  224. GodotAudio.WebChannel.CHANNEL_SR
  225. );
  226. this._channelSplitter
  227. .connect(this._c, GodotAudio.WebChannel.CHANNEL_C)
  228. .connect(
  229. this._channelMerger,
  230. GodotAudio.WebChannel.CHANNEL_L,
  231. GodotAudio.WebChannel.CHANNEL_C
  232. );
  233. this._channelSplitter
  234. .connect(this._lfe, GodotAudio.WebChannel.CHANNEL_L)
  235. .connect(
  236. this._channelMerger,
  237. GodotAudio.WebChannel.CHANNEL_L,
  238. GodotAudio.WebChannel.CHANNEL_LFE
  239. );
  240. this._channelMerger.connect(this._bus.getInputNode());
  241. }
  242. /**
  243. * Returns the input node.
  244. * @returns {AudioNode}
  245. */
  246. getInputNode() {
  247. return this._channelSplitter;
  248. }
  249. /**
  250. * Returns the output node.
  251. * @returns {AudioNode}
  252. */
  253. getOutputNode() {
  254. return this._channelMerger;
  255. }
  256. /**
  257. * Sets the volume for each (split) channel.
  258. * @param {Float32Array} volume Volume array from the engine for each channel.
  259. * @returns {void}
  260. */
  261. setVolume(volume) {
  262. if (volume.length !== GodotAudio.MAX_VOLUME_CHANNELS) {
  263. throw new Error(
  264. `Volume length isn't "${GodotAudio.MAX_VOLUME_CHANNELS}", is ${volume.length} instead`
  265. );
  266. }
  267. this._l.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_L] ?? 0;
  268. this._r.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_R] ?? 0;
  269. this._sl.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_SL] ?? 0;
  270. this._sr.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_SR] ?? 0;
  271. this._c.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_C] ?? 0;
  272. this._lfe.gain.value = volume[GodotAudio.GodotChannel.CHANNEL_LFE] ?? 0;
  273. }
  274. /**
  275. * Clears the current `SampleNodeBus` instance.
  276. * @returns {void}
  277. */
  278. clear() {
  279. this._bus = null;
  280. this._channelSplitter.disconnect();
  281. this._channelSplitter = null;
  282. this._l.disconnect();
  283. this._l = null;
  284. this._r.disconnect();
  285. this._r = null;
  286. this._sl.disconnect();
  287. this._sl = null;
  288. this._sr.disconnect();
  289. this._sr = null;
  290. this._c.disconnect();
  291. this._c = null;
  292. this._lfe.disconnect();
  293. this._lfe = null;
  294. this._channelMerger.disconnect();
  295. this._channelMerger = null;
  296. }
  297. }
  298. /**
  299. * @typedef {{
  300. * id: string
  301. * streamObjectId: string
  302. * busIndex: number
  303. * }} SampleNodeParams
  304. * @typedef {{
  305. * offset?: number
  306. * playbackRate?: number
  307. * startTime?: number
  308. * pitchScale?: number
  309. * loopMode?: LoopMode
  310. * volume?: Float32Array
  311. * start?: boolean
  312. * }} SampleNodeOptions
  313. */
  314. /**
  315. * Represents an `AudioNode` of a `Sample`.
  316. * @class
  317. */
  318. class SampleNode {
  319. /**
  320. * Returns a `SampleNode`.
  321. * @param {string} id Id of the `SampleNode`.
  322. * @returns {SampleNode}
  323. * @throws {ReferenceError} When no `SampleNode` is not found
  324. */
  325. static getSampleNode(id) {
  326. if (!GodotAudio.sampleNodes.has(id)) {
  327. throw new ReferenceError(`Could not find sample node "${id}"`);
  328. }
  329. return GodotAudio.sampleNodes.get(id);
  330. }
  331. /**
  332. * Returns a `SampleNode`, returns null if not found.
  333. * @param {string} id Id of the SampleNode.
  334. * @returns {SampleNode?}
  335. */
  336. static getSampleNodeOrNull(id) {
  337. return GodotAudio.sampleNodes.get(id) ?? null;
  338. }
  339. /**
  340. * Stops a `SampleNode` by id.
  341. * @param {string} id Id of the `SampleNode` to stop.
  342. * @returns {void}
  343. */
  344. static stopSampleNode(id) {
  345. const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(id);
  346. if (sampleNode == null) {
  347. return;
  348. }
  349. sampleNode.stop();
  350. }
  351. /**
  352. * Pauses the `SampleNode` by id.
  353. * @param {string} id Id of the `SampleNode` to pause.
  354. * @param {boolean} enable State of the pause
  355. * @returns {void}
  356. */
  357. static pauseSampleNode(id, enable) {
  358. const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(id);
  359. if (sampleNode == null) {
  360. return;
  361. }
  362. sampleNode.pause(enable);
  363. }
  364. /**
  365. * Creates a `SampleNode` based on the params. Will register the `SampleNode` to
  366. * the `GodotAudio.sampleNodes` regisery.
  367. * @param {SampleNodeParams} params Base params.
  368. * @param {SampleNodeOptions | undefined} options Optional params.
  369. * @returns {SampleNode}
  370. */
  371. static create(params, options = {}) {
  372. const sampleNode = new GodotAudio.SampleNode(params, options);
  373. GodotAudio.sampleNodes.set(params.id, sampleNode);
  374. return sampleNode;
  375. }
  376. /**
  377. * Deletes a `SampleNode` based on the id.
  378. * @param {string} id Id of the `SampleNode` to delete.
  379. * @returns {void}
  380. */
  381. static delete(id) {
  382. GodotAudio.sampleNodes.delete(id);
  383. }
  384. /**
  385. * @param {SampleNodeParams} params Base params
  386. * @param {SampleNodeOptions | undefined} options Optional params.
  387. */
  388. constructor(params, options = {}) {
  389. /** @type {string} */
  390. this.id = params.id;
  391. /** @type {string} */
  392. this.streamObjectId = params.streamObjectId;
  393. /** @type {number} */
  394. this.offset = options.offset ?? 0;
  395. /** @type {number} */
  396. this._playbackPosition = options.offset;
  397. /** @type {number} */
  398. this.startTime = options.startTime ?? 0;
  399. /** @type {boolean} */
  400. this.isPaused = false;
  401. /** @type {boolean} */
  402. this.isStarted = false;
  403. /** @type {boolean} */
  404. this.isCanceled = false;
  405. /** @type {number} */
  406. this.pauseTime = 0;
  407. /** @type {number} */
  408. this._playbackRate = 44100;
  409. /** @type {LoopMode} */
  410. this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
  411. /** @type {number} */
  412. this._pitchScale = options.pitchScale ?? 1;
  413. /** @type {number} */
  414. this._sourceStartTime = 0;
  415. /** @type {Map<Bus, SampleNodeBus>} */
  416. this._sampleNodeBuses = new Map();
  417. /** @type {AudioBufferSourceNode | null} */
  418. this._source = GodotAudio.ctx.createBufferSource();
  419. this._onended = null;
  420. /** @type {AudioWorkletNode | null} */
  421. this._positionWorklet = null;
  422. this.setPlaybackRate(options.playbackRate ?? 44100);
  423. this._source.buffer = this.getSample().getAudioBuffer();
  424. this._addEndedListener();
  425. const bus = GodotAudio.Bus.getBus(params.busIndex);
  426. const sampleNodeBus = this.getSampleNodeBus(bus);
  427. sampleNodeBus.setVolume(options.volume);
  428. this.connectPositionWorklet(options.start).catch((err) => {
  429. const newErr = new Error('Failed to create PositionWorklet.');
  430. newErr.cause = err;
  431. GodotRuntime.error(newErr);
  432. });
  433. }
  434. /**
  435. * Gets the playback rate.
  436. * @returns {number}
  437. */
  438. getPlaybackRate() {
  439. return this._playbackRate;
  440. }
  441. /**
  442. * Gets the playback position.
  443. * @returns {number}
  444. */
  445. getPlaybackPosition() {
  446. return this._playbackPosition;
  447. }
  448. /**
  449. * Sets the playback rate.
  450. * @param {number} val Value to set.
  451. * @returns {void}
  452. */
  453. setPlaybackRate(val) {
  454. this._playbackRate = val;
  455. this._syncPlaybackRate();
  456. }
  457. /**
  458. * Gets the pitch scale.
  459. * @returns {number}
  460. */
  461. getPitchScale() {
  462. return this._pitchScale;
  463. }
  464. /**
  465. * Sets the pitch scale.
  466. * @param {number} val Value to set.
  467. * @returns {void}
  468. */
  469. setPitchScale(val) {
  470. this._pitchScale = val;
  471. this._syncPlaybackRate();
  472. }
  473. /**
  474. * Returns the linked `Sample`.
  475. * @returns {Sample}
  476. */
  477. getSample() {
  478. return GodotAudio.Sample.getSample(this.streamObjectId);
  479. }
  480. /**
  481. * Returns the output node.
  482. * @returns {AudioNode}
  483. */
  484. getOutputNode() {
  485. return this._source;
  486. }
  487. /**
  488. * Starts the `SampleNode`.
  489. * @returns {void}
  490. */
  491. start() {
  492. if (this.isStarted) {
  493. return;
  494. }
  495. this._resetSourceStartTime();
  496. this._source.start(this.startTime, this.offset);
  497. this.isStarted = true;
  498. }
  499. /**
  500. * Stops the `SampleNode`.
  501. * @returns {void}
  502. */
  503. stop() {
  504. this.clear();
  505. }
  506. /**
  507. * Restarts the `SampleNode`.
  508. */
  509. restart() {
  510. this.isPaused = false;
  511. this.pauseTime = 0;
  512. this._resetSourceStartTime();
  513. this._restart();
  514. }
  515. /**
  516. * Pauses the `SampleNode`.
  517. * @param {boolean} [enable=true] State of the pause.
  518. * @returns {void}
  519. */
  520. pause(enable = true) {
  521. if (enable) {
  522. this._pause();
  523. return;
  524. }
  525. this._unpause();
  526. }
  527. /**
  528. * Connects an AudioNode to the output node of this `SampleNode`.
  529. * @param {AudioNode} node AudioNode to connect.
  530. * @returns {void}
  531. */
  532. connect(node) {
  533. return this.getOutputNode().connect(node);
  534. }
  535. /**
  536. * Sets the volumes of the `SampleNode` for each buses passed in parameters.
  537. * @param {Array<Bus>} buses
  538. * @param {Float32Array} volumes
  539. */
  540. setVolumes(buses, volumes) {
  541. for (let busIdx = 0; busIdx < buses.length; busIdx++) {
  542. const sampleNodeBus = this.getSampleNodeBus(buses[busIdx]);
  543. sampleNodeBus.setVolume(
  544. volumes.slice(
  545. busIdx * GodotAudio.MAX_VOLUME_CHANNELS,
  546. (busIdx * GodotAudio.MAX_VOLUME_CHANNELS) + GodotAudio.MAX_VOLUME_CHANNELS
  547. )
  548. );
  549. }
  550. }
  551. /**
  552. * Returns the SampleNodeBus based on the bus in parameters.
  553. * @param {Bus} bus Bus to get the SampleNodeBus from.
  554. * @returns {SampleNodeBus}
  555. */
  556. getSampleNodeBus(bus) {
  557. if (!this._sampleNodeBuses.has(bus)) {
  558. const sampleNodeBus = GodotAudio.SampleNodeBus.create(bus);
  559. this._sampleNodeBuses.set(bus, sampleNodeBus);
  560. this._source.connect(sampleNodeBus.getInputNode());
  561. }
  562. return this._sampleNodeBuses.get(bus);
  563. }
  564. /**
  565. * Sets up and connects the source to the GodotPositionReportingProcessor
  566. * If the worklet module is not loaded in, it will be added
  567. */
  568. async connectPositionWorklet(start) {
  569. await GodotAudio.audioPositionWorkletPromise;
  570. if (this.isCanceled) {
  571. return;
  572. }
  573. this._source.connect(this.getPositionWorklet());
  574. if (start) {
  575. this.start();
  576. }
  577. }
  578. /**
  579. * Get a AudioWorkletProcessor
  580. * @returns {AudioWorkletNode}
  581. */
  582. getPositionWorklet() {
  583. if (this._positionWorklet != null) {
  584. return this._positionWorklet;
  585. }
  586. this._positionWorklet = new AudioWorkletNode(
  587. GodotAudio.ctx,
  588. 'godot-position-reporting-processor'
  589. );
  590. this._positionWorklet.port.onmessage = (event) => {
  591. switch (event.data['type']) {
  592. case 'position':
  593. this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset;
  594. break;
  595. default:
  596. // Do nothing.
  597. }
  598. };
  599. return this._positionWorklet;
  600. }
  601. /**
  602. * Clears the `SampleNode`.
  603. * @returns {void}
  604. */
  605. clear() {
  606. this.isCanceled = true;
  607. this.isPaused = false;
  608. this.pauseTime = 0;
  609. if (this._source != null) {
  610. this._source.removeEventListener('ended', this._onended);
  611. this._onended = null;
  612. if (this.isStarted) {
  613. this._source.stop();
  614. }
  615. this._source.disconnect();
  616. this._source = null;
  617. }
  618. for (const sampleNodeBus of this._sampleNodeBuses.values()) {
  619. sampleNodeBus.clear();
  620. }
  621. this._sampleNodeBuses.clear();
  622. if (this._positionWorklet) {
  623. this._positionWorklet.disconnect();
  624. this._positionWorklet.port.onmessage = null;
  625. this._positionWorklet.port.postMessage({ type: 'ended' });
  626. this._positionWorklet = null;
  627. }
  628. GodotAudio.SampleNode.delete(this.id);
  629. }
  630. /**
  631. * Resets the source start time
  632. * @returns {void}
  633. */
  634. _resetSourceStartTime() {
  635. this._sourceStartTime = GodotAudio.ctx.currentTime;
  636. }
  637. /**
  638. * Syncs the `AudioNode` playback rate based on the `SampleNode` playback rate and pitch scale.
  639. * @returns {void}
  640. */
  641. _syncPlaybackRate() {
  642. this._source.playbackRate.value = this.getPlaybackRate() * this.getPitchScale();
  643. }
  644. /**
  645. * Restarts the `SampleNode`.
  646. * Honors `isPaused` and `pauseTime`.
  647. * @returns {void}
  648. */
  649. _restart() {
  650. if (this._source != null) {
  651. this._source.disconnect();
  652. }
  653. this._source = GodotAudio.ctx.createBufferSource();
  654. this._source.buffer = this.getSample().getAudioBuffer();
  655. // Make sure that we connect the new source to the sample node bus.
  656. for (const sampleNodeBus of this._sampleNodeBuses.values()) {
  657. this.connect(sampleNodeBus.getInputNode());
  658. }
  659. this._addEndedListener();
  660. const pauseTime = this.isPaused
  661. ? this.pauseTime
  662. : 0;
  663. if (this._positionWorklet != null) {
  664. this._positionWorklet.port.postMessage({ type: 'clear' });
  665. this._source.connect(this._positionWorklet);
  666. }
  667. this._source.start(this.startTime, this.offset + pauseTime);
  668. this.isStarted = true;
  669. }
  670. /**
  671. * Pauses the `SampleNode`.
  672. * @returns {void}
  673. */
  674. _pause() {
  675. if (!this.isStarted) {
  676. return;
  677. }
  678. this.isPaused = true;
  679. this.pauseTime = (GodotAudio.ctx.currentTime - this._sourceStartTime) / this.getPlaybackRate();
  680. this._source.stop();
  681. }
  682. /**
  683. * Unpauses the `SampleNode`.
  684. * @returns {void}
  685. */
  686. _unpause() {
  687. this._restart();
  688. this.isPaused = false;
  689. this.pauseTime = 0;
  690. }
  691. /**
  692. * Adds an "ended" listener to the source node to repeat it if necessary.
  693. * @returns {void}
  694. */
  695. _addEndedListener() {
  696. if (this._onended != null) {
  697. this._source.removeEventListener('ended', this._onended);
  698. }
  699. /** @type {SampleNode} */
  700. // eslint-disable-next-line consistent-this
  701. const self = this;
  702. this._onended = (_) => {
  703. if (self.isPaused) {
  704. return;
  705. }
  706. switch (self.getSample().loopMode) {
  707. case 'disabled': {
  708. const id = this.id;
  709. self.stop();
  710. if (GodotAudio.sampleFinishedCallback != null) {
  711. const idCharPtr = GodotRuntime.allocString(id);
  712. GodotAudio.sampleFinishedCallback(idCharPtr);
  713. GodotRuntime.free(idCharPtr);
  714. }
  715. } break;
  716. case 'forward':
  717. case 'backward':
  718. self.restart();
  719. break;
  720. default:
  721. // do nothing
  722. }
  723. };
  724. this._source.addEventListener('ended', this._onended);
  725. }
  726. }
  727. /**
  728. * Collection of nodes to represents a Godot Engine audio bus.
  729. * @class
  730. */
  731. class Bus {
  732. /**
  733. * Returns the number of registered buses.
  734. * @returns {number}
  735. */
  736. static getCount() {
  737. return GodotAudio.buses.length;
  738. }
  739. /**
  740. * Sets the number of registered buses.
  741. * Will delete buses if lower than the current number.
  742. * @param {number} val Count of registered buses.
  743. * @returns {void}
  744. */
  745. static setCount(val) {
  746. const buses = GodotAudio.buses;
  747. if (val === buses.length) {
  748. return;
  749. }
  750. if (val < buses.length) {
  751. // TODO: what to do with nodes connected to the deleted buses?
  752. const deletedBuses = buses.slice(val);
  753. for (let i = 0; i < deletedBuses.length; i++) {
  754. const deletedBus = deletedBuses[i];
  755. deletedBus.clear();
  756. }
  757. GodotAudio.buses = buses.slice(0, val);
  758. return;
  759. }
  760. for (let i = GodotAudio.buses.length; i < val; i++) {
  761. GodotAudio.Bus.create();
  762. }
  763. }
  764. /**
  765. * Returns a `Bus` based on it's index number.
  766. * @param {number} index
  767. * @returns {Bus}
  768. * @throws {ReferenceError} If the index value is outside the registry.
  769. */
  770. static getBus(index) {
  771. if (index < 0 || index >= GodotAudio.buses.length) {
  772. throw new ReferenceError(`invalid bus index "${index}"`);
  773. }
  774. return GodotAudio.buses[index];
  775. }
  776. /**
  777. * Returns a `Bus` based on it's index number. Returns null if it doesn't exist.
  778. * @param {number} index
  779. * @returns {Bus?}
  780. */
  781. static getBusOrNull(index) {
  782. if (index < 0 || index >= GodotAudio.buses.length) {
  783. return null;
  784. }
  785. return GodotAudio.buses[index];
  786. }
  787. /**
  788. * Move a bus from an index to another.
  789. * @param {number} fromIndex From index
  790. * @param {number} toIndex To index
  791. * @returns {void}
  792. */
  793. static move(fromIndex, toIndex) {
  794. const movedBus = GodotAudio.Bus.getBusOrNull(fromIndex);
  795. if (movedBus == null) {
  796. return;
  797. }
  798. const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex);
  799. // Inserts at index.
  800. buses.splice(toIndex - 1, 0, movedBus);
  801. GodotAudio.buses = buses;
  802. }
  803. /**
  804. * Adds a new bus at the specified index.
  805. * @param {number} index Index to add a new bus.
  806. * @returns {void}
  807. */
  808. static addAt(index) {
  809. const newBus = GodotAudio.Bus.create();
  810. if (index !== newBus.getId()) {
  811. GodotAudio.Bus.move(newBus.getId(), index);
  812. }
  813. }
  814. /**
  815. * Creates a `Bus` and registers it.
  816. * @returns {Bus}
  817. */
  818. static create() {
  819. const newBus = new GodotAudio.Bus();
  820. const isFirstBus = GodotAudio.buses.length === 0;
  821. GodotAudio.buses.push(newBus);
  822. if (isFirstBus) {
  823. newBus.setSend(null);
  824. } else {
  825. newBus.setSend(GodotAudio.Bus.getBus(0));
  826. }
  827. return newBus;
  828. }
  829. /**
  830. * `Bus` constructor.
  831. */
  832. constructor() {
  833. /** @type {Set<SampleNode>} */
  834. this._sampleNodes = new Set();
  835. /** @type {boolean} */
  836. this.isSolo = false;
  837. /** @type {Bus?} */
  838. this._send = null;
  839. /** @type {GainNode} */
  840. this._gainNode = GodotAudio.ctx.createGain();
  841. /** @type {GainNode} */
  842. this._soloNode = GodotAudio.ctx.createGain();
  843. /** @type {GainNode} */
  844. this._muteNode = GodotAudio.ctx.createGain();
  845. this._gainNode
  846. .connect(this._soloNode)
  847. .connect(this._muteNode);
  848. }
  849. /**
  850. * Returns the current id of the bus (its index).
  851. * @returns {number}
  852. */
  853. getId() {
  854. return GodotAudio.buses.indexOf(this);
  855. }
  856. /**
  857. * Returns the bus volume db value.
  858. * @returns {number}
  859. */
  860. getVolumeDb() {
  861. return GodotAudio.linear_to_db(this._gainNode.gain.value);
  862. }
  863. /**
  864. * Sets the bus volume db value.
  865. * @param {number} val Value to set
  866. * @returns {void}
  867. */
  868. setVolumeDb(val) {
  869. const linear = GodotAudio.db_to_linear(val);
  870. if (isFinite(linear)) {
  871. this._gainNode.gain.value = linear;
  872. }
  873. }
  874. /**
  875. * Returns the "send" bus.
  876. * If null, this bus sends its contents directly to the output.
  877. * If not null, this bus sends its contents to another bus.
  878. * @returns {Bus?}
  879. */
  880. getSend() {
  881. return this._send;
  882. }
  883. /**
  884. * Sets the "send" bus.
  885. * If null, this bus sends its contents directly to the output.
  886. * If not null, this bus sends its contents to another bus.
  887. *
  888. * **Note:** if null, `getId()` must be equal to 0. Otherwise, it will throw.
  889. * @param {Bus?} val
  890. * @returns {void}
  891. * @throws {Error} When val is `null` and `getId()` isn't equal to 0
  892. */
  893. setSend(val) {
  894. this._send = val;
  895. if (val == null) {
  896. if (this.getId() == 0) {
  897. this.getOutputNode().connect(GodotAudio.ctx.destination);
  898. return;
  899. }
  900. throw new Error(
  901. `Cannot send to "${val}" without the bus being at index 0 (current index: ${this.getId()})`
  902. );
  903. }
  904. this.connect(val);
  905. }
  906. /**
  907. * Returns the input node of the bus.
  908. * @returns {AudioNode}
  909. */
  910. getInputNode() {
  911. return this._gainNode;
  912. }
  913. /**
  914. * Returns the output node of the bus.
  915. * @returns {AudioNode}
  916. */
  917. getOutputNode() {
  918. return this._muteNode;
  919. }
  920. /**
  921. * Sets the mute status of the bus.
  922. * @param {boolean} enable
  923. */
  924. mute(enable) {
  925. this._muteNode.gain.value = enable ? 0 : 1;
  926. }
  927. /**
  928. * Sets the solo status of the bus.
  929. * @param {boolean} enable
  930. */
  931. solo(enable) {
  932. if (this.isSolo === enable) {
  933. return;
  934. }
  935. if (enable) {
  936. if (GodotAudio.busSolo != null && GodotAudio.busSolo !== this) {
  937. GodotAudio.busSolo._disableSolo();
  938. }
  939. this._enableSolo();
  940. return;
  941. }
  942. this._disableSolo();
  943. }
  944. /**
  945. * Wrapper to simply add a sample node to the bus.
  946. * @param {SampleNode} sampleNode `SampleNode` to remove
  947. * @returns {void}
  948. */
  949. addSampleNode(sampleNode) {
  950. this._sampleNodes.add(sampleNode);
  951. sampleNode.getOutputNode().connect(this.getInputNode());
  952. }
  953. /**
  954. * Wrapper to simply remove a sample node from the bus.
  955. * @param {SampleNode} sampleNode `SampleNode` to remove
  956. * @returns {void}
  957. */
  958. removeSampleNode(sampleNode) {
  959. this._sampleNodes.delete(sampleNode);
  960. sampleNode.getOutputNode().disconnect();
  961. }
  962. /**
  963. * Wrapper to simply connect to another bus.
  964. * @param {Bus} bus
  965. * @returns {void}
  966. */
  967. connect(bus) {
  968. if (bus == null) {
  969. throw new Error('cannot connect to null bus');
  970. }
  971. this.getOutputNode().disconnect();
  972. this.getOutputNode().connect(bus.getInputNode());
  973. return bus;
  974. }
  975. /**
  976. * Clears the current bus.
  977. * @returns {void}
  978. */
  979. clear() {
  980. GodotAudio.buses = GodotAudio.buses.filter((v) => v !== this);
  981. }
  982. _syncSampleNodes() {
  983. const sampleNodes = Array.from(this._sampleNodes);
  984. for (let i = 0; i < sampleNodes.length; i++) {
  985. const sampleNode = sampleNodes[i];
  986. sampleNode.getOutputNode().disconnect();
  987. sampleNode.getOutputNode().connect(this.getInputNode());
  988. }
  989. }
  990. /**
  991. * Process to enable solo.
  992. * @returns {void}
  993. */
  994. _enableSolo() {
  995. this.isSolo = true;
  996. GodotAudio.busSolo = this;
  997. this._soloNode.gain.value = 1;
  998. const otherBuses = GodotAudio.buses.filter(
  999. (otherBus) => otherBus !== this
  1000. );
  1001. for (let i = 0; i < otherBuses.length; i++) {
  1002. const otherBus = otherBuses[i];
  1003. otherBus._soloNode.gain.value = 0;
  1004. }
  1005. }
  1006. /**
  1007. * Process to disable solo.
  1008. * @returns {void}
  1009. */
  1010. _disableSolo() {
  1011. this.isSolo = false;
  1012. GodotAudio.busSolo = null;
  1013. this._soloNode.gain.value = 1;
  1014. const otherBuses = GodotAudio.buses.filter(
  1015. (otherBus) => otherBus !== this
  1016. );
  1017. for (let i = 0; i < otherBuses.length; i++) {
  1018. const otherBus = otherBuses[i];
  1019. otherBus._soloNode.gain.value = 1;
  1020. }
  1021. }
  1022. }
  1023. const _GodotAudio = {
  1024. $GodotAudio__deps: ['$GodotRuntime', '$GodotOS'],
  1025. $GodotAudio: {
  1026. /**
  1027. * Max number of volume channels.
  1028. */
  1029. MAX_VOLUME_CHANNELS: 8,
  1030. /**
  1031. * Represents the index of each sound channel relative to the engine.
  1032. */
  1033. GodotChannel: Object.freeze({
  1034. CHANNEL_L: 0,
  1035. CHANNEL_R: 1,
  1036. CHANNEL_C: 3,
  1037. CHANNEL_LFE: 4,
  1038. CHANNEL_RL: 5,
  1039. CHANNEL_RR: 6,
  1040. CHANNEL_SL: 7,
  1041. CHANNEL_SR: 8,
  1042. }),
  1043. /**
  1044. * Represents the index of each sound channel relative to the Web Audio API.
  1045. */
  1046. WebChannel: Object.freeze({
  1047. CHANNEL_L: 0,
  1048. CHANNEL_R: 1,
  1049. CHANNEL_SL: 2,
  1050. CHANNEL_SR: 3,
  1051. CHANNEL_C: 4,
  1052. CHANNEL_LFE: 5,
  1053. }),
  1054. // `Sample` class
  1055. /**
  1056. * Registry of `Sample`s.
  1057. * @type {Map<string, Sample>}
  1058. */
  1059. samples: null,
  1060. Sample,
  1061. // `SampleNodeBus` class
  1062. SampleNodeBus,
  1063. // `SampleNode` class
  1064. /**
  1065. * Registry of `SampleNode`s.
  1066. * @type {Map<string, SampleNode>}
  1067. */
  1068. sampleNodes: null,
  1069. SampleNode,
  1070. // `Bus` class
  1071. /**
  1072. * Registry of `Bus`es.
  1073. * @type {Array<Bus>}
  1074. */
  1075. buses: null,
  1076. /**
  1077. * Reference to the current bus in solo mode.
  1078. * @type {Bus | null}
  1079. */
  1080. busSolo: null,
  1081. Bus,
  1082. /**
  1083. * Callback to signal that a sample has finished.
  1084. * @type {(playbackObjectIdPtr: number) => void | null}
  1085. */
  1086. sampleFinishedCallback: null,
  1087. /** @type {AudioContext} */
  1088. ctx: null,
  1089. input: null,
  1090. driver: null,
  1091. interval: 0,
  1092. /** @type {Promise} */
  1093. audioPositionWorkletPromise: null,
  1094. /**
  1095. * Converts linear volume to Db.
  1096. * @param {number} linear Linear value to convert.
  1097. * @returns {number}
  1098. */
  1099. linear_to_db: function (linear) {
  1100. // eslint-disable-next-line no-loss-of-precision
  1101. return Math.log(linear) * 8.6858896380650365530225783783321;
  1102. },
  1103. /**
  1104. * Converts Db volume to linear.
  1105. * @param {number} db Db value to convert.
  1106. * @returns {number}
  1107. */
  1108. db_to_linear: function (db) {
  1109. // eslint-disable-next-line no-loss-of-precision
  1110. return Math.exp(db * 0.11512925464970228420089957273422);
  1111. },
  1112. init: function (mix_rate, latency, onstatechange, onlatencyupdate) {
  1113. // Initialize classes static values.
  1114. GodotAudio.samples = new Map();
  1115. GodotAudio.sampleNodes = new Map();
  1116. GodotAudio.buses = [];
  1117. GodotAudio.busSolo = null;
  1118. const opts = {};
  1119. // If mix_rate is 0, let the browser choose.
  1120. if (mix_rate) {
  1121. GodotAudio.sampleRate = mix_rate;
  1122. opts['sampleRate'] = mix_rate;
  1123. }
  1124. // Do not specify, leave 'interactive' for good performance.
  1125. // opts['latencyHint'] = latency / 1000;
  1126. const ctx = new (window.AudioContext || window.webkitAudioContext)(opts);
  1127. GodotAudio.ctx = ctx;
  1128. ctx.onstatechange = function () {
  1129. let state = 0;
  1130. switch (ctx.state) {
  1131. case 'suspended':
  1132. state = 0;
  1133. break;
  1134. case 'running':
  1135. state = 1;
  1136. break;
  1137. case 'closed':
  1138. state = 2;
  1139. break;
  1140. default:
  1141. // Do nothing.
  1142. }
  1143. onstatechange(state);
  1144. };
  1145. ctx.onstatechange(); // Immediately notify state.
  1146. // Update computed latency
  1147. GodotAudio.interval = setInterval(function () {
  1148. let computed_latency = 0;
  1149. if (ctx.baseLatency) {
  1150. computed_latency += GodotAudio.ctx.baseLatency;
  1151. }
  1152. if (ctx.outputLatency) {
  1153. computed_latency += GodotAudio.ctx.outputLatency;
  1154. }
  1155. onlatencyupdate(computed_latency);
  1156. }, 1000);
  1157. GodotOS.atexit(GodotAudio.close_async);
  1158. const path = GodotConfig.locate_file('godot.audio.position.worklet.js');
  1159. GodotAudio.audioPositionWorkletPromise = ctx.audioWorklet.addModule(path);
  1160. return ctx.destination.channelCount;
  1161. },
  1162. create_input: function (callback) {
  1163. if (GodotAudio.input) {
  1164. return 0; // Already started.
  1165. }
  1166. function gotMediaInput(stream) {
  1167. try {
  1168. GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream);
  1169. callback(GodotAudio.input);
  1170. } catch (e) {
  1171. GodotRuntime.error('Failed creating input.', e);
  1172. }
  1173. }
  1174. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  1175. navigator.mediaDevices.getUserMedia({
  1176. 'audio': true,
  1177. }).then(gotMediaInput, function (e) {
  1178. GodotRuntime.error('Error getting user media.', e);
  1179. });
  1180. } else {
  1181. if (!navigator.getUserMedia) {
  1182. navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
  1183. }
  1184. if (!navigator.getUserMedia) {
  1185. GodotRuntime.error('getUserMedia not available.');
  1186. return 1;
  1187. }
  1188. navigator.getUserMedia({
  1189. 'audio': true,
  1190. }, gotMediaInput, function (e) {
  1191. GodotRuntime.print(e);
  1192. });
  1193. }
  1194. return 0;
  1195. },
  1196. close_async: function (resolve, reject) {
  1197. const ctx = GodotAudio.ctx;
  1198. GodotAudio.ctx = null;
  1199. // Audio was not initialized.
  1200. if (!ctx) {
  1201. resolve();
  1202. return;
  1203. }
  1204. // Remove latency callback
  1205. if (GodotAudio.interval) {
  1206. clearInterval(GodotAudio.interval);
  1207. GodotAudio.interval = 0;
  1208. }
  1209. // Disconnect input, if it was started.
  1210. if (GodotAudio.input) {
  1211. GodotAudio.input.disconnect();
  1212. GodotAudio.input = null;
  1213. }
  1214. // Disconnect output
  1215. let closed = Promise.resolve();
  1216. if (GodotAudio.driver) {
  1217. closed = GodotAudio.driver.close();
  1218. }
  1219. closed.then(function () {
  1220. return ctx.close();
  1221. }).then(function () {
  1222. ctx.onstatechange = null;
  1223. resolve();
  1224. }).catch(function (e) {
  1225. ctx.onstatechange = null;
  1226. GodotRuntime.error('Error closing AudioContext', e);
  1227. resolve();
  1228. });
  1229. },
  1230. /**
  1231. * Triggered when a sample node needs to start.
  1232. * @param {string} playbackObjectId The unique id of the sample playback
  1233. * @param {string} streamObjectId The unique id of the stream
  1234. * @param {number} busIndex Index of the bus currently binded to the sample playback
  1235. * @param {SampleNodeOptions | undefined} startOptions Optional params.
  1236. * @returns {void}
  1237. */
  1238. start_sample: function (
  1239. playbackObjectId,
  1240. streamObjectId,
  1241. busIndex,
  1242. startOptions
  1243. ) {
  1244. GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
  1245. GodotAudio.SampleNode.create(
  1246. {
  1247. busIndex,
  1248. id: playbackObjectId,
  1249. streamObjectId,
  1250. },
  1251. startOptions
  1252. );
  1253. },
  1254. /**
  1255. * Triggered when a sample node needs to be stopped.
  1256. * @param {string} playbackObjectId Id of the sample playback
  1257. * @returns {void}
  1258. */
  1259. stop_sample: function (playbackObjectId) {
  1260. GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
  1261. },
  1262. /**
  1263. * Triggered when a sample node needs to be paused or unpaused.
  1264. * @param {string} playbackObjectId Id of the sample playback
  1265. * @param {boolean} pause State of the pause
  1266. * @returns {void}
  1267. */
  1268. sample_set_pause: function (playbackObjectId, pause) {
  1269. GodotAudio.SampleNode.pauseSampleNode(playbackObjectId, pause);
  1270. },
  1271. /**
  1272. * Triggered when a sample node needs its pitch scale to be updated.
  1273. * @param {string} playbackObjectId Id of the sample playback
  1274. * @param {number} pitchScale Pitch scale of the sample playback
  1275. * @returns {void}
  1276. */
  1277. update_sample_pitch_scale: function (playbackObjectId, pitchScale) {
  1278. const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
  1279. if (sampleNode == null) {
  1280. return;
  1281. }
  1282. sampleNode.setPitchScale(pitchScale);
  1283. },
  1284. /**
  1285. * Triggered when a sample node volumes need to be updated.
  1286. * @param {string} playbackObjectId Id of the sample playback
  1287. * @param {Array<number>} busIndexes Indexes of the buses that need to be updated
  1288. * @param {Float32Array} volumes Array of the volumes
  1289. * @returns {void}
  1290. */
  1291. sample_set_volumes_linear: function (playbackObjectId, busIndexes, volumes) {
  1292. const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
  1293. if (sampleNode == null) {
  1294. return;
  1295. }
  1296. const buses = busIndexes.map((busIndex) => GodotAudio.Bus.getBus(busIndex));
  1297. sampleNode.setVolumes(buses, volumes);
  1298. },
  1299. /**
  1300. * Triggered when the bus count changes.
  1301. * @param {number} count Number of buses
  1302. * @returns {void}
  1303. */
  1304. set_sample_bus_count: function (count) {
  1305. GodotAudio.Bus.setCount(count);
  1306. },
  1307. /**
  1308. * Triggered when a bus needs to be removed.
  1309. * @param {number} index Bus index
  1310. * @returns {void}
  1311. */
  1312. remove_sample_bus: function (index) {
  1313. const bus = GodotAudio.Bus.getBusOrNull(index);
  1314. if (bus == null) {
  1315. return;
  1316. }
  1317. bus.clear();
  1318. },
  1319. /**
  1320. * Triggered when a bus needs to be at the desired position.
  1321. * @param {number} atPos Position to add the bus
  1322. * @returns {void}
  1323. */
  1324. add_sample_bus: function (atPos) {
  1325. GodotAudio.Bus.addAt(atPos);
  1326. },
  1327. /**
  1328. * Triggered when a bus needs to be moved.
  1329. * @param {number} busIndex Index of the bus to move
  1330. * @param {number} toPos Index of the new position of the bus
  1331. * @returns {void}
  1332. */
  1333. move_sample_bus: function (busIndex, toPos) {
  1334. GodotAudio.Bus.move(busIndex, toPos);
  1335. },
  1336. /**
  1337. * Triggered when the "send" value of a bus changes.
  1338. * @param {number} busIndex Index of the bus to update the "send" value
  1339. * @param {number} sendIndex Index of the bus that is the new "send"
  1340. * @returns {void}
  1341. */
  1342. set_sample_bus_send: function (busIndex, sendIndex) {
  1343. const bus = GodotAudio.Bus.getBusOrNull(busIndex);
  1344. if (bus == null) {
  1345. // Cannot send from an invalid bus.
  1346. return;
  1347. }
  1348. let targetBus = GodotAudio.Bus.getBusOrNull(sendIndex);
  1349. if (targetBus == null) {
  1350. // Send to master.
  1351. targetBus = GodotAudio.Bus.getBus(0);
  1352. }
  1353. bus.setSend(targetBus);
  1354. },
  1355. /**
  1356. * Triggered when a bus needs its volume db to be updated.
  1357. * @param {number} busIndex Index of the bus to update its volume db
  1358. * @param {number} volumeDb Volume of the bus
  1359. * @returns {void}
  1360. */
  1361. set_sample_bus_volume_db: function (busIndex, volumeDb) {
  1362. const bus = GodotAudio.Bus.getBusOrNull(busIndex);
  1363. if (bus == null) {
  1364. return;
  1365. }
  1366. bus.setVolumeDb(volumeDb);
  1367. },
  1368. /**
  1369. * Triggered when a bus needs to update its solo status
  1370. * @param {number} busIndex Index of the bus to update its solo status
  1371. * @param {boolean} enable Status of the solo
  1372. * @returns {void}
  1373. */
  1374. set_sample_bus_solo: function (busIndex, enable) {
  1375. const bus = GodotAudio.Bus.getBusOrNull(busIndex);
  1376. if (bus == null) {
  1377. return;
  1378. }
  1379. bus.solo(enable);
  1380. },
  1381. /**
  1382. * Triggered when a bus needs to update its mute status
  1383. * @param {number} busIndex Index of the bus to update its mute status
  1384. * @param {boolean} enable Status of the mute
  1385. * @returns {void}
  1386. */
  1387. set_sample_bus_mute: function (busIndex, enable) {
  1388. const bus = GodotAudio.Bus.getBusOrNull(busIndex);
  1389. if (bus == null) {
  1390. return;
  1391. }
  1392. bus.mute(enable);
  1393. },
  1394. },
  1395. godot_audio_is_available__sig: 'i',
  1396. godot_audio_is_available__proxy: 'sync',
  1397. godot_audio_is_available: function () {
  1398. if (!(window.AudioContext || window.webkitAudioContext)) {
  1399. return 0;
  1400. }
  1401. return 1;
  1402. },
  1403. godot_audio_has_worklet__proxy: 'sync',
  1404. godot_audio_has_worklet__sig: 'i',
  1405. godot_audio_has_worklet: function () {
  1406. return GodotAudio.ctx && GodotAudio.ctx.audioWorklet ? 1 : 0;
  1407. },
  1408. godot_audio_has_script_processor__proxy: 'sync',
  1409. godot_audio_has_script_processor__sig: 'i',
  1410. godot_audio_has_script_processor: function () {
  1411. return GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor ? 1 : 0;
  1412. },
  1413. godot_audio_init__proxy: 'sync',
  1414. godot_audio_init__sig: 'iiiii',
  1415. godot_audio_init: function (
  1416. p_mix_rate,
  1417. p_latency,
  1418. p_state_change,
  1419. p_latency_update
  1420. ) {
  1421. const statechange = GodotRuntime.get_func(p_state_change);
  1422. const latencyupdate = GodotRuntime.get_func(p_latency_update);
  1423. const mix_rate = GodotRuntime.getHeapValue(p_mix_rate, 'i32');
  1424. const channels = GodotAudio.init(
  1425. mix_rate,
  1426. p_latency,
  1427. statechange,
  1428. latencyupdate
  1429. );
  1430. GodotRuntime.setHeapValue(p_mix_rate, GodotAudio.ctx.sampleRate, 'i32');
  1431. return channels;
  1432. },
  1433. godot_audio_resume__proxy: 'sync',
  1434. godot_audio_resume__sig: 'v',
  1435. godot_audio_resume: function () {
  1436. if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
  1437. GodotAudio.ctx.resume();
  1438. }
  1439. },
  1440. godot_audio_input_start__proxy: 'sync',
  1441. godot_audio_input_start__sig: 'i',
  1442. godot_audio_input_start: function () {
  1443. return GodotAudio.create_input(function (input) {
  1444. input.connect(GodotAudio.driver.get_node());
  1445. });
  1446. },
  1447. godot_audio_input_stop__proxy: 'sync',
  1448. godot_audio_input_stop__sig: 'v',
  1449. godot_audio_input_stop: function () {
  1450. if (GodotAudio.input) {
  1451. const tracks = GodotAudio.input['mediaStream']['getTracks']();
  1452. for (let i = 0; i < tracks.length; i++) {
  1453. tracks[i]['stop']();
  1454. }
  1455. GodotAudio.input.disconnect();
  1456. GodotAudio.input = null;
  1457. }
  1458. },
  1459. godot_audio_sample_stream_is_registered__proxy: 'sync',
  1460. godot_audio_sample_stream_is_registered__sig: 'ii',
  1461. /**
  1462. * Returns if the sample stream is registered
  1463. * @param {number} streamObjectIdStrPtr Pointer of the streamObjectId
  1464. * @returns {number}
  1465. */
  1466. godot_audio_sample_stream_is_registered: function (streamObjectIdStrPtr) {
  1467. const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
  1468. return Number(GodotAudio.Sample.getSampleOrNull(streamObjectId) != null);
  1469. },
  1470. godot_audio_sample_register_stream__proxy: 'sync',
  1471. godot_audio_sample_register_stream__sig: 'viiiiiii',
  1472. /**
  1473. * Registers a stream.
  1474. * @param {number} streamObjectIdStrPtr StreamObjectId pointer
  1475. * @param {number} framesPtr Frames pointer
  1476. * @param {number} framesTotal Frames total value
  1477. * @param {number} loopModeStrPtr Loop mode pointer
  1478. * @param {number} loopBegin Loop begin value
  1479. * @param {number} loopEnd Loop end value
  1480. * @returns {void}
  1481. */
  1482. godot_audio_sample_register_stream: function (
  1483. streamObjectIdStrPtr,
  1484. framesPtr,
  1485. framesTotal,
  1486. loopModeStrPtr,
  1487. loopBegin,
  1488. loopEnd
  1489. ) {
  1490. const BYTES_PER_FLOAT32 = 4;
  1491. const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
  1492. const loopMode = GodotRuntime.parseString(loopModeStrPtr);
  1493. const numberOfChannels = 2;
  1494. const sampleRate = GodotAudio.ctx.sampleRate;
  1495. /** @type {Float32Array} */
  1496. const subLeft = GodotRuntime.heapSub(HEAPF32, framesPtr, framesTotal);
  1497. /** @type {Float32Array} */
  1498. const subRight = GodotRuntime.heapSub(
  1499. HEAPF32,
  1500. framesPtr + framesTotal * BYTES_PER_FLOAT32,
  1501. framesTotal
  1502. );
  1503. const audioBuffer = GodotAudio.ctx.createBuffer(
  1504. numberOfChannels,
  1505. framesTotal,
  1506. sampleRate
  1507. );
  1508. audioBuffer.copyToChannel(new Float32Array(subLeft), 0, 0);
  1509. audioBuffer.copyToChannel(new Float32Array(subRight), 1, 0);
  1510. GodotAudio.Sample.create(
  1511. {
  1512. id: streamObjectId,
  1513. audioBuffer,
  1514. },
  1515. {
  1516. loopBegin,
  1517. loopEnd,
  1518. loopMode,
  1519. numberOfChannels,
  1520. sampleRate,
  1521. }
  1522. );
  1523. },
  1524. godot_audio_sample_unregister_stream__proxy: 'sync',
  1525. godot_audio_sample_unregister_stream__sig: 'vi',
  1526. /**
  1527. * Unregisters a stream.
  1528. * @param {number} streamObjectIdStrPtr StreamObjectId pointer
  1529. * @returns {void}
  1530. */
  1531. godot_audio_sample_unregister_stream: function (streamObjectIdStrPtr) {
  1532. const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
  1533. const sample = GodotAudio.Sample.getSampleOrNull(streamObjectId);
  1534. if (sample != null) {
  1535. sample.clear();
  1536. }
  1537. },
  1538. godot_audio_sample_start__proxy: 'sync',
  1539. godot_audio_sample_start__sig: 'viiiifi',
  1540. /**
  1541. * Starts a sample.
  1542. * @param {number} playbackObjectIdStrPtr Playback object id pointer
  1543. * @param {number} streamObjectIdStrPtr Stream object id pointer
  1544. * @param {number} busIndex Bus index
  1545. * @param {number} offset Sample offset
  1546. * @param {number} pitchScale Pitch scale
  1547. * @param {number} volumePtr Volume pointer
  1548. * @returns {void}
  1549. */
  1550. godot_audio_sample_start: function (
  1551. playbackObjectIdStrPtr,
  1552. streamObjectIdStrPtr,
  1553. busIndex,
  1554. offset,
  1555. pitchScale,
  1556. volumePtr
  1557. ) {
  1558. /** @type {string} */
  1559. const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
  1560. /** @type {string} */
  1561. const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
  1562. /** @type {Float32Array} */
  1563. const volume = GodotRuntime.heapSub(HEAPF32, volumePtr, 8);
  1564. /** @type {SampleNodeOptions} */
  1565. const startOptions = {
  1566. offset,
  1567. volume,
  1568. playbackRate: 1,
  1569. pitchScale,
  1570. start: true,
  1571. };
  1572. GodotAudio.start_sample(
  1573. playbackObjectId,
  1574. streamObjectId,
  1575. busIndex,
  1576. startOptions
  1577. );
  1578. },
  1579. godot_audio_sample_stop__proxy: 'sync',
  1580. godot_audio_sample_stop__sig: 'vi',
  1581. /**
  1582. * Stops a sample from playing.
  1583. * @param {number} playbackObjectIdStrPtr Playback object id pointer
  1584. * @returns {void}
  1585. */
  1586. godot_audio_sample_stop: function (playbackObjectIdStrPtr) {
  1587. const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
  1588. GodotAudio.stop_sample(playbackObjectId);
  1589. },
  1590. godot_audio_sample_set_pause__proxy: 'sync',
  1591. godot_audio_sample_set_pause__sig: 'vii',
  1592. /**
  1593. * Sets the pause state of a sample.
  1594. * @param {number} playbackObjectIdStrPtr Playback object id pointer
  1595. * @param {number} pause Pause state
  1596. */
  1597. godot_audio_sample_set_pause: function (playbackObjectIdStrPtr, pause) {
  1598. const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
  1599. GodotAudio.sample_set_pause(playbackObjectId, Boolean(pause));
  1600. },
  1601. godot_audio_sample_is_active__proxy: 'sync',
  1602. godot_audio_sample_is_active__sig: 'ii',
  1603. /**
  1604. * Returns if the sample is active.
  1605. * @param {number} playbackObjectIdStrPtr Playback object id pointer
  1606. * @returns {number}
  1607. */
  1608. godot_audio_sample_is_active: function (playbackObjectIdStrPtr) {
  1609. const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
  1610. return Number(GodotAudio.sampleNodes.has(playbackObjectId));
  1611. },
  1612. godot_audio_get_sample_playback_position__proxy: 'sync',
  1613. godot_audio_get_sample_playback_position__sig: 'di',
  1614. /**
  1615. * Returns the position of the playback position.
  1616. * @param {number} playbackObjectIdStrPtr Playback object id pointer
  1617. * @returns {number}
  1618. */
  1619. godot_audio_get_sample_playback_position: function (playbackObjectIdStrPtr) {
  1620. const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
  1621. const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
  1622. if (sampleNode == null) {
  1623. return 0;
  1624. }
  1625. return sampleNode.getPlaybackPosition();
  1626. },
  1627. godot_audio_sample_update_pitch_scale__proxy: 'sync',
  1628. godot_audio_sample_update_pitch_scale__sig: 'vii',
  1629. /**
  1630. * Updates the pitch scale of a sample.
  1631. * @param {number} playbackObjectIdStrPtr Playback object id pointer
  1632. * @param {number} pitchScale Pitch scale value
  1633. * @returns {void}
  1634. */
  1635. godot_audio_sample_update_pitch_scale: function (
  1636. playbackObjectIdStrPtr,
  1637. pitchScale
  1638. ) {
  1639. const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
  1640. GodotAudio.update_sample_pitch_scale(playbackObjectId, pitchScale);
  1641. },
  1642. godot_audio_sample_set_volumes_linear__proxy: 'sync',
  1643. godot_audio_sample_set_volumes_linear__sig: 'vii',
  1644. /**
  1645. * Sets the volumes linear of each mentioned bus for the sample.
  1646. * @param {number} playbackObjectIdStrPtr Playback object id pointer
  1647. * @param {number} busesPtr Buses array pointer
  1648. * @param {number} busesSize Buses array size
  1649. * @param {number} volumesPtr Volumes array pointer
  1650. * @param {number} volumesSize Volumes array size
  1651. * @returns {void}
  1652. */
  1653. godot_audio_sample_set_volumes_linear: function (
  1654. playbackObjectIdStrPtr,
  1655. busesPtr,
  1656. busesSize,
  1657. volumesPtr,
  1658. volumesSize
  1659. ) {
  1660. /** @type {string} */
  1661. const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
  1662. /** @type {Uint32Array} */
  1663. const buses = GodotRuntime.heapSub(HEAP32, busesPtr, busesSize);
  1664. /** @type {Float32Array} */
  1665. const volumes = GodotRuntime.heapSub(HEAPF32, volumesPtr, volumesSize);
  1666. GodotAudio.sample_set_volumes_linear(
  1667. playbackObjectId,
  1668. Array.from(buses),
  1669. volumes
  1670. );
  1671. },
  1672. godot_audio_sample_bus_set_count__proxy: 'sync',
  1673. godot_audio_sample_bus_set_count__sig: 'vi',
  1674. /**
  1675. * Sets the bus count.
  1676. * @param {number} count Bus count
  1677. * @returns {void}
  1678. */
  1679. godot_audio_sample_bus_set_count: function (count) {
  1680. GodotAudio.set_sample_bus_count(count);
  1681. },
  1682. godot_audio_sample_bus_remove__proxy: 'sync',
  1683. godot_audio_sample_bus_remove__sig: 'vi',
  1684. /**
  1685. * Removes a bus.
  1686. * @param {number} index Index of the bus to remove
  1687. * @returns {void}
  1688. */
  1689. godot_audio_sample_bus_remove: function (index) {
  1690. GodotAudio.remove_sample_bus(index);
  1691. },
  1692. godot_audio_sample_bus_add__proxy: 'sync',
  1693. godot_audio_sample_bus_add__sig: 'vi',
  1694. /**
  1695. * Adds a bus at the defined position.
  1696. * @param {number} atPos Position to add the bus
  1697. * @returns {void}
  1698. */
  1699. godot_audio_sample_bus_add: function (atPos) {
  1700. GodotAudio.add_sample_bus(atPos);
  1701. },
  1702. godot_audio_sample_bus_move__proxy: 'sync',
  1703. godot_audio_sample_bus_move__sig: 'vii',
  1704. /**
  1705. * Moves the bus from a position to another.
  1706. * @param {number} fromPos Position of the bus to move
  1707. * @param {number} toPos Final position of the bus
  1708. * @returns {void}
  1709. */
  1710. godot_audio_sample_bus_move: function (fromPos, toPos) {
  1711. GodotAudio.move_sample_bus(fromPos, toPos);
  1712. },
  1713. godot_audio_sample_bus_set_send__proxy: 'sync',
  1714. godot_audio_sample_bus_set_send__sig: 'vii',
  1715. /**
  1716. * Sets the "send" of a bus.
  1717. * @param {number} bus Position of the bus to set the send
  1718. * @param {number} sendIndex Position of the "send" bus
  1719. * @returns {void}
  1720. */
  1721. godot_audio_sample_bus_set_send: function (bus, sendIndex) {
  1722. GodotAudio.set_sample_bus_send(bus, sendIndex);
  1723. },
  1724. godot_audio_sample_bus_set_volume_db__proxy: 'sync',
  1725. godot_audio_sample_bus_set_volume_db__sig: 'vii',
  1726. /**
  1727. * Sets the volume db of a bus.
  1728. * @param {number} bus Position of the bus to set the volume db
  1729. * @param {number} volumeDb Volume db to set
  1730. * @returns {void}
  1731. */
  1732. godot_audio_sample_bus_set_volume_db: function (bus, volumeDb) {
  1733. GodotAudio.set_sample_bus_volume_db(bus, volumeDb);
  1734. },
  1735. godot_audio_sample_bus_set_solo__proxy: 'sync',
  1736. godot_audio_sample_bus_set_solo__sig: 'vii',
  1737. /**
  1738. * Sets the state of solo for a bus
  1739. * @param {number} bus Position of the bus to set the solo state
  1740. * @param {number} enable State of the solo
  1741. * @returns {void}
  1742. */
  1743. godot_audio_sample_bus_set_solo: function (bus, enable) {
  1744. GodotAudio.set_sample_bus_solo(bus, Boolean(enable));
  1745. },
  1746. godot_audio_sample_bus_set_mute__proxy: 'sync',
  1747. godot_audio_sample_bus_set_mute__sig: 'vii',
  1748. /**
  1749. * Sets the state of mute for a bus
  1750. * @param {number} bus Position of the bus to set the mute state
  1751. * @param {number} enable State of the mute
  1752. * @returns {void}
  1753. */
  1754. godot_audio_sample_bus_set_mute: function (bus, enable) {
  1755. GodotAudio.set_sample_bus_mute(bus, Boolean(enable));
  1756. },
  1757. godot_audio_sample_set_finished_callback__proxy: 'sync',
  1758. godot_audio_sample_set_finished_callback__sig: 'vi',
  1759. /**
  1760. * Sets the finished callback
  1761. * @param {Number} callbackPtr Finished callback pointer
  1762. * @returns {void}
  1763. */
  1764. godot_audio_sample_set_finished_callback: function (callbackPtr) {
  1765. GodotAudio.sampleFinishedCallback = GodotRuntime.get_func(callbackPtr);
  1766. },
  1767. };
  1768. autoAddDeps(_GodotAudio, '$GodotAudio');
  1769. mergeInto(LibraryManager.library, _GodotAudio);
  1770. /**
  1771. * The AudioWorklet API driver, used when threads are available.
  1772. */
  1773. const GodotAudioWorklet = {
  1774. $GodotAudioWorklet__deps: ['$GodotAudio', '$GodotConfig'],
  1775. $GodotAudioWorklet: {
  1776. promise: null,
  1777. worklet: null,
  1778. ring_buffer: null,
  1779. create: function (channels) {
  1780. const path = GodotConfig.locate_file('godot.audio.worklet.js');
  1781. GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet
  1782. .addModule(path)
  1783. .then(function () {
  1784. GodotAudioWorklet.worklet = new AudioWorkletNode(
  1785. GodotAudio.ctx,
  1786. 'godot-processor',
  1787. {
  1788. outputChannelCount: [channels],
  1789. }
  1790. );
  1791. return Promise.resolve();
  1792. });
  1793. GodotAudio.driver = GodotAudioWorklet;
  1794. },
  1795. start: function (in_buf, out_buf, state) {
  1796. GodotAudioWorklet.promise.then(function () {
  1797. const node = GodotAudioWorklet.worklet;
  1798. node.connect(GodotAudio.ctx.destination);
  1799. node.port.postMessage({
  1800. 'cmd': 'start',
  1801. 'data': [state, in_buf, out_buf],
  1802. });
  1803. node.port.onmessage = function (event) {
  1804. GodotRuntime.error(event.data);
  1805. };
  1806. });
  1807. },
  1808. start_no_threads: function (
  1809. p_out_buf,
  1810. p_out_size,
  1811. out_callback,
  1812. p_in_buf,
  1813. p_in_size,
  1814. in_callback
  1815. ) {
  1816. function RingBuffer() {
  1817. let wpos = 0;
  1818. let rpos = 0;
  1819. let pending_samples = 0;
  1820. const wbuf = new Float32Array(p_out_size);
  1821. function send(port) {
  1822. if (pending_samples === 0) {
  1823. return;
  1824. }
  1825. const buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
  1826. const size = buffer.length;
  1827. const tot_sent = pending_samples;
  1828. out_callback(wpos, pending_samples);
  1829. if (wpos + pending_samples >= size) {
  1830. const high = size - wpos;
  1831. wbuf.set(buffer.subarray(wpos, size));
  1832. pending_samples -= high;
  1833. wpos = 0;
  1834. }
  1835. if (pending_samples > 0) {
  1836. wbuf.set(
  1837. buffer.subarray(wpos, wpos + pending_samples),
  1838. tot_sent - pending_samples
  1839. );
  1840. }
  1841. port.postMessage({ 'cmd': 'chunk', 'data': wbuf.subarray(0, tot_sent) });
  1842. wpos += pending_samples;
  1843. pending_samples = 0;
  1844. }
  1845. this.receive = function (recv_buf) {
  1846. const buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
  1847. const from = rpos;
  1848. let to_write = recv_buf.length;
  1849. let high = 0;
  1850. if (rpos + to_write >= p_in_size) {
  1851. high = p_in_size - rpos;
  1852. buffer.set(recv_buf.subarray(0, high), rpos);
  1853. to_write -= high;
  1854. rpos = 0;
  1855. }
  1856. if (to_write) {
  1857. buffer.set(recv_buf.subarray(high, to_write), rpos);
  1858. }
  1859. in_callback(from, recv_buf.length);
  1860. rpos += to_write;
  1861. };
  1862. this.consumed = function (size, port) {
  1863. pending_samples += size;
  1864. send(port);
  1865. };
  1866. }
  1867. GodotAudioWorklet.ring_buffer = new RingBuffer();
  1868. GodotAudioWorklet.promise.then(function () {
  1869. const node = GodotAudioWorklet.worklet;
  1870. const buffer = GodotRuntime.heapSlice(HEAPF32, p_out_buf, p_out_size);
  1871. node.connect(GodotAudio.ctx.destination);
  1872. node.port.postMessage({
  1873. 'cmd': 'start_nothreads',
  1874. 'data': [buffer, p_in_size],
  1875. });
  1876. node.port.onmessage = function (event) {
  1877. if (!GodotAudioWorklet.worklet) {
  1878. return;
  1879. }
  1880. if (event.data['cmd'] === 'read') {
  1881. const read = event.data['data'];
  1882. GodotAudioWorklet.ring_buffer.consumed(
  1883. read,
  1884. GodotAudioWorklet.worklet.port
  1885. );
  1886. } else if (event.data['cmd'] === 'input') {
  1887. const buf = event.data['data'];
  1888. if (buf.length > p_in_size) {
  1889. GodotRuntime.error('Input chunk is too big');
  1890. return;
  1891. }
  1892. GodotAudioWorklet.ring_buffer.receive(buf);
  1893. } else {
  1894. GodotRuntime.error(event.data);
  1895. }
  1896. };
  1897. });
  1898. },
  1899. get_node: function () {
  1900. return GodotAudioWorklet.worklet;
  1901. },
  1902. close: function () {
  1903. return new Promise(function (resolve, reject) {
  1904. if (GodotAudioWorklet.promise === null) {
  1905. return;
  1906. }
  1907. const p = GodotAudioWorklet.promise;
  1908. p.then(function () {
  1909. GodotAudioWorklet.worklet.port.postMessage({
  1910. 'cmd': 'stop',
  1911. 'data': null,
  1912. });
  1913. GodotAudioWorklet.worklet.disconnect();
  1914. GodotAudioWorklet.worklet.port.onmessage = null;
  1915. GodotAudioWorklet.worklet = null;
  1916. GodotAudioWorklet.promise = null;
  1917. resolve();
  1918. }).catch(function (err) {
  1919. // Aborted?
  1920. GodotRuntime.error(err);
  1921. });
  1922. });
  1923. },
  1924. },
  1925. godot_audio_worklet_create__proxy: 'sync',
  1926. godot_audio_worklet_create__sig: 'ii',
  1927. godot_audio_worklet_create: function (channels) {
  1928. try {
  1929. GodotAudioWorklet.create(channels);
  1930. } catch (e) {
  1931. GodotRuntime.error('Error starting AudioDriverWorklet', e);
  1932. return 1;
  1933. }
  1934. return 0;
  1935. },
  1936. godot_audio_worklet_start__proxy: 'sync',
  1937. godot_audio_worklet_start__sig: 'viiiii',
  1938. godot_audio_worklet_start: function (
  1939. p_in_buf,
  1940. p_in_size,
  1941. p_out_buf,
  1942. p_out_size,
  1943. p_state
  1944. ) {
  1945. const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
  1946. const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
  1947. const state = GodotRuntime.heapSub(HEAP32, p_state, 4);
  1948. GodotAudioWorklet.start(in_buffer, out_buffer, state);
  1949. },
  1950. godot_audio_worklet_start_no_threads__proxy: 'sync',
  1951. godot_audio_worklet_start_no_threads__sig: 'viiiiii',
  1952. godot_audio_worklet_start_no_threads: function (
  1953. p_out_buf,
  1954. p_out_size,
  1955. p_out_callback,
  1956. p_in_buf,
  1957. p_in_size,
  1958. p_in_callback
  1959. ) {
  1960. const out_callback = GodotRuntime.get_func(p_out_callback);
  1961. const in_callback = GodotRuntime.get_func(p_in_callback);
  1962. GodotAudioWorklet.start_no_threads(
  1963. p_out_buf,
  1964. p_out_size,
  1965. out_callback,
  1966. p_in_buf,
  1967. p_in_size,
  1968. in_callback
  1969. );
  1970. },
  1971. godot_audio_worklet_state_wait__sig: 'iiii',
  1972. godot_audio_worklet_state_wait: function (
  1973. p_state,
  1974. p_idx,
  1975. p_expected,
  1976. p_timeout
  1977. ) {
  1978. Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
  1979. return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
  1980. },
  1981. godot_audio_worklet_state_add__sig: 'iiii',
  1982. godot_audio_worklet_state_add: function (p_state, p_idx, p_value) {
  1983. return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value);
  1984. },
  1985. godot_audio_worklet_state_get__sig: 'iii',
  1986. godot_audio_worklet_state_get: function (p_state, p_idx) {
  1987. return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
  1988. },
  1989. };
  1990. autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
  1991. mergeInto(LibraryManager.library, GodotAudioWorklet);
  1992. /*
  1993. * The ScriptProcessorNode API, used as a fallback if AudioWorklet is not available.
  1994. */
  1995. const GodotAudioScript = {
  1996. $GodotAudioScript__deps: ['$GodotAudio'],
  1997. $GodotAudioScript: {
  1998. script: null,
  1999. create: function (buffer_length, channel_count) {
  2000. GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(
  2001. buffer_length,
  2002. 2,
  2003. channel_count
  2004. );
  2005. GodotAudio.driver = GodotAudioScript;
  2006. return GodotAudioScript.script.bufferSize;
  2007. },
  2008. start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) {
  2009. GodotAudioScript.script.onaudioprocess = function (event) {
  2010. // Read input
  2011. const inb = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
  2012. const input = event.inputBuffer;
  2013. if (GodotAudio.input) {
  2014. const inlen = input.getChannelData(0).length;
  2015. for (let ch = 0; ch < 2; ch++) {
  2016. const data = input.getChannelData(ch);
  2017. for (let s = 0; s < inlen; s++) {
  2018. inb[s * 2 + ch] = data[s];
  2019. }
  2020. }
  2021. }
  2022. // Let Godot process the input/output.
  2023. onprocess();
  2024. // Write the output.
  2025. const outb = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
  2026. const output = event.outputBuffer;
  2027. const channels = output.numberOfChannels;
  2028. for (let ch = 0; ch < channels; ch++) {
  2029. const data = output.getChannelData(ch);
  2030. // Loop through samples and assign computed values.
  2031. for (let sample = 0; sample < data.length; sample++) {
  2032. data[sample] = outb[sample * channels + ch];
  2033. }
  2034. }
  2035. };
  2036. GodotAudioScript.script.connect(GodotAudio.ctx.destination);
  2037. },
  2038. get_node: function () {
  2039. return GodotAudioScript.script;
  2040. },
  2041. close: function () {
  2042. return new Promise(function (resolve, reject) {
  2043. GodotAudioScript.script.disconnect();
  2044. GodotAudioScript.script.onaudioprocess = null;
  2045. GodotAudioScript.script = null;
  2046. resolve();
  2047. });
  2048. },
  2049. },
  2050. godot_audio_script_create__proxy: 'sync',
  2051. godot_audio_script_create__sig: 'iii',
  2052. godot_audio_script_create: function (buffer_length, channel_count) {
  2053. const buf_len = GodotRuntime.getHeapValue(buffer_length, 'i32');
  2054. try {
  2055. const out_len = GodotAudioScript.create(buf_len, channel_count);
  2056. GodotRuntime.setHeapValue(buffer_length, out_len, 'i32');
  2057. } catch (e) {
  2058. GodotRuntime.error('Error starting AudioDriverScriptProcessor', e);
  2059. return 1;
  2060. }
  2061. return 0;
  2062. },
  2063. godot_audio_script_start__proxy: 'sync',
  2064. godot_audio_script_start__sig: 'viiiii',
  2065. godot_audio_script_start: function (
  2066. p_in_buf,
  2067. p_in_size,
  2068. p_out_buf,
  2069. p_out_size,
  2070. p_cb
  2071. ) {
  2072. const onprocess = GodotRuntime.get_func(p_cb);
  2073. GodotAudioScript.start(
  2074. p_in_buf,
  2075. p_in_size,
  2076. p_out_buf,
  2077. p_out_size,
  2078. onprocess
  2079. );
  2080. },
  2081. };
  2082. autoAddDeps(GodotAudioScript, '$GodotAudioScript');
  2083. mergeInto(LibraryManager.library, GodotAudioScript);