NativeChannel.hx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package hxd.snd;
  2. #if hlopenal
  3. import openal.AL;
  4. import hxd.snd.Manager;
  5. import hxd.snd.Driver;
  6. @:access(hxd.snd.Manager)
  7. private class ALChannel {
  8. static var nativeUpdate : haxe.MainLoop.MainEvent;
  9. static var nativeChannels : Array<ALChannel>;
  10. static function updateChannels() {
  11. var i = 0;
  12. // Should ensure ordering if it was removed during update?
  13. for ( chn in nativeChannels ) chn.onUpdate();
  14. }
  15. var manager : Manager;
  16. var update : haxe.MainLoop.MainEvent;
  17. var native : NativeChannel;
  18. var samples : Int;
  19. var driver : Driver;
  20. var buffers : Array<BufferHandle>;
  21. var bufPos : Int;
  22. var src : SourceHandle;
  23. var fbuf : haxe.io.Bytes;
  24. var ibuf : haxe.io.Bytes;
  25. public function new(samples, native) {
  26. if ( nativeUpdate == null ) {
  27. nativeUpdate = haxe.MainLoop.add(updateChannels);
  28. #if (haxe_ver >= 4) nativeUpdate.isBlocking = false; #end
  29. nativeChannels = [];
  30. }
  31. this.native = native;
  32. this.samples = samples;
  33. this.manager = Manager.get();
  34. this.driver = manager.driver;
  35. buffers = [driver.createBuffer(), driver.createBuffer()];
  36. src = driver.createSource();
  37. bufPos = 0;
  38. // AL.sourcef(src,AL.PITCH,1.0);
  39. // AL.sourcef(src,AL.GAIN,1.0);
  40. fbuf = haxe.io.Bytes.alloc( samples<<3 );
  41. ibuf = haxe.io.Bytes.alloc( samples<<2 );
  42. for ( b in buffers )
  43. onSample(b);
  44. forcePlay();
  45. nativeChannels.push(this);
  46. }
  47. public function stop() {
  48. if ( src != null ) {
  49. nativeChannels.remove(this);
  50. driver.stopSource(src);
  51. driver.destroySource(src);
  52. for (buf in buffers)
  53. driver.destroyBuffer(buf);
  54. src = null;
  55. buffers = null;
  56. }
  57. }
  58. @:noDebug function onSample( buf : BufferHandle ) {
  59. @:privateAccess native.onSample(haxe.io.Float32Array.fromBytes(fbuf));
  60. // Convert Float32 to Int16
  61. for ( i in 0...samples << 1 ) {
  62. var v = Std.int(fbuf.getFloat(i << 2) * 0x7FFF);
  63. ibuf.set( i<<1, v );
  64. ibuf.set( (i<<1) + 1, v>>>8 );
  65. }
  66. driver.setBufferData(buf, ibuf, ibuf.length, I16, 2, Manager.STREAM_BUFFER_SAMPLE_COUNT);
  67. driver.queueBuffer(src, buf, 0, false);
  68. }
  69. inline function forcePlay() {
  70. if (!src.playing) driver.playSource(src);
  71. }
  72. function onUpdate(){
  73. var cnt = driver.getProcessedBuffers(src);
  74. while (cnt > 0)
  75. {
  76. cnt--;
  77. var buf = buffers[bufPos];
  78. driver.unqueueBuffer(src, buf);
  79. onSample(buf);
  80. forcePlay();
  81. if (++bufPos == buffers.length) bufPos = 0;
  82. }
  83. }
  84. }
  85. #end
  86. class NativeChannel {
  87. #if flash
  88. var snd : flash.media.Sound;
  89. var channel : flash.media.SoundChannel;
  90. #elseif js
  91. // Avoid excessive buffer allocation when playing many sounds.
  92. // bufferSamples is constant and never change at runtime, so it's safe to use general pool.
  93. static var bufferPool : Array<haxe.io.Float32Array> = new Array();
  94. var front : js.html.audio.AudioBuffer;
  95. var back : js.html.audio.AudioBuffer;
  96. var current : js.html.audio.AudioBufferSourceNode;
  97. var queued : js.html.audio.AudioBufferSourceNode;
  98. var time : Float; // Mandatory for proper buffer sync, otherwise produces gaps in playback due to innacurate timings.
  99. var tmpBuffer : haxe.io.Float32Array;
  100. var gain : js.html.audio.GainNode;
  101. #elseif hlopenal
  102. var channel : ALChannel;
  103. #end
  104. public var bufferSamples(default, null) : Int;
  105. public function new( bufferSamples : Int ) {
  106. this.bufferSamples = bufferSamples;
  107. #if flash
  108. snd = new flash.media.Sound();
  109. snd.addEventListener(flash.events.SampleDataEvent.SAMPLE_DATA, onFlashSample);
  110. channel = snd.play(0, 0x7FFFFFFF);
  111. #elseif js
  112. var ctx = hxd.snd.webaudio.Context.get();
  113. var rate = Std.int(ctx.sampleRate);
  114. front = hxd.snd.webaudio.Context.getBuffer(2, bufferSamples, rate);
  115. back = hxd.snd.webaudio.Context.getBuffer(2, bufferSamples, rate);
  116. if ( bufferPool.length > 0 ) tmpBuffer = bufferPool.pop();
  117. else tmpBuffer = new haxe.io.Float32Array(bufferSamples * 2);
  118. gain = hxd.snd.webaudio.Context.getGain();
  119. gain.connect(hxd.snd.webaudio.Context.destination);
  120. fill(front);
  121. fill(back);
  122. current = ctx.createBufferSource();
  123. current.buffer = front;
  124. current.addEventListener("ended", swap);
  125. current.connect(gain);
  126. queued = ctx.createBufferSource();
  127. queued.buffer = back;
  128. queued.addEventListener("ended", swap);
  129. queued.connect(gain);
  130. var currTime : Float = ctx.currentTime;
  131. current.start(currTime);
  132. time = currTime + front.duration;
  133. queued.start(time);
  134. #elseif hlopenal
  135. channel = new ALChannel(bufferSamples, this);
  136. #end
  137. }
  138. #if flash
  139. function onFlashSample( event : flash.events.SampleDataEvent ) {
  140. var buf = event.data;
  141. buf.length = bufferSamples * 2 * 4;
  142. buf.position = 0;
  143. onSample(haxe.io.Float32Array.fromBytes(haxe.io.Bytes.ofData(buf)));
  144. buf.position = bufferSamples * 2 * 4;
  145. }
  146. #end
  147. #if js
  148. function swap( event : js.html.Event ) {
  149. var tmp = front;
  150. front = back;
  151. back = tmp;
  152. fill(tmp);
  153. current.removeEventListener("ended", swap);
  154. // current.disconnect(); // Should not be required as it's a one-shot object by design.
  155. current = queued;
  156. var ctx = hxd.snd.webaudio.Context.get();
  157. queued = ctx.createBufferSource();
  158. queued.buffer = tmp;
  159. queued.addEventListener("ended", swap);
  160. queued.connect(gain);
  161. time += front.duration;
  162. queued.start(time);
  163. }
  164. inline function fill( buffer : js.html.audio.AudioBuffer ) {
  165. onSample(tmpBuffer);
  166. // split the channels and copy to output
  167. var r = 0;
  168. var left = buffer.getChannelData(0);
  169. var right = buffer.getChannelData(1);
  170. for ( i in 0...bufferSamples )
  171. {
  172. left[i] = tmpBuffer[r++];
  173. right[i] = tmpBuffer[r++];
  174. }
  175. }
  176. #end
  177. function onSample( out : haxe.io.Float32Array ) {
  178. }
  179. public function stop() {
  180. #if flash
  181. if( channel != null ) {
  182. channel.stop();
  183. channel = null;
  184. }
  185. #elseif js
  186. if ( front != null ) {
  187. current.removeEventListener("ended", swap);
  188. current.stop();
  189. current.disconnect();
  190. current = null;
  191. queued.removeEventListener("ended", swap);
  192. queued.disconnect();
  193. queued.stop();
  194. queued = null;
  195. gain.disconnect();
  196. hxd.snd.webaudio.Context.putGain(gain);
  197. gain = null;
  198. hxd.snd.webaudio.Context.putBuffer(front);
  199. hxd.snd.webaudio.Context.putBuffer(back);
  200. bufferPool.push(tmpBuffer);
  201. tmpBuffer = null;
  202. }
  203. #elseif hlopenal
  204. if( channel != null ) {
  205. channel.stop();
  206. channel = null;
  207. }
  208. #end
  209. }
  210. }