package openfl.media; #if !flash import haxe.Int64; import openfl.events.Event; import openfl.events.EventDispatcher; #if lime import lime.media.AudioSource; import lime.media.openal.AL; #end /** The SoundChannel class controls a sound in an application. Every sound is assigned to a sound channel, and the application can have multiple sound channels that are mixed together. The SoundChannel class contains a `stop()` method, properties for monitoring the amplitude (volume) of the channel, and a property for assigning a SoundTransform object to the channel. @event soundComplete Dispatched when a sound has finished playing. @see [Playing sounds](https://books.openfl.org/openfl-developers-guide/working-with-sound/playing-sounds.html) @see `openfl.media.Sound` **/ #if !openfl_debug @:fileXml('tags="haxe,release"') @:noDebug #end #if lime_cffi @:access(lime._internal.backend.native.NativeAudioSource) @:access(lime.media.AudioSource) #end @:access(openfl.media.Sound) @:access(openfl.media.SoundMixer) @:final @:keep class SoundChannel extends EventDispatcher { /** The current amplitude(volume) of the left channel, from 0(silent) to 1 (full amplitude). **/ public var leftPeak(get, null):Float; /** The current amplitude(volume) of the right channel, from 0(silent) to 1 (full amplitude). **/ public var rightPeak(get, null):Float; /** When the sound is playing, the `position` property indicates in milliseconds the current point that is being played in the sound file. When the sound is stopped or paused, the `position` property indicates the last point that was played in the sound file. A common use case is to save the value of the `position` property when the sound is stopped. You can resume the sound later by restarting it from that saved position. If the sound is looped, `position` is reset to 0 at the beginning of each loop. **/ public var position(get, set):Float; /** The SoundTransform object assigned to the sound channel. A SoundTransform object includes properties for setting volume, panning, left speaker assignment, and right speaker assignment. **/ public var soundTransform(get, set):SoundTransform; /** self explanatory */ public var loopTime(get, set):Float; public var endTime(get, set):Null; public var pitch(get, set):Float; public var loops(get, set):Int; @:noCompletion private var __sound:Sound; @:noCompletion private var __isValid:Bool; @:noCompletion private var __soundTransform:SoundTransform; @:noCompletion private var __lastPeakTime:Float; @:noCompletion private var __leftPeak:Float; @:noCompletion private var __rightPeak:Float; #if lime @:noCompletion private var __source:AudioSource; @:noCompletion private var __audioSource(get, set):AudioSource; // forward??? compatibility?? #end #if openfljs @:noCompletion private static function __init__() { untyped Object.defineProperties(SoundChannel.prototype, { "position": { get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_position (); }"), set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_position (v); }") }, "soundTransform": { get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_soundTransform (); }"), set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_soundTransform (v); }") }, }); } #end @:noCompletion private function new(source:#if lime AudioSource #else Dynamic #end = null, soundTransform:SoundTransform = null):Void { super(this); if (soundTransform != null) __soundTransform = soundTransform; else __soundTransform = new SoundTransform(); __initAudioSource(source); SoundMixer.__registerSoundChannel(this); } /** Stops the sound playing in the channel. **/ public function stop():Void { SoundMixer.__unregisterSoundChannel(this); if (!__isValid) return; #if lime __source.stop(); #end __dispose(); } @:noCompletion private function __dispose():Void { if (!__isValid) return; #if lime __source.onComplete.remove(source_onComplete); __source.dispose(); __source = null; #end __isValid = false; } @:noCompletion private function __updateTransform():Void { this.soundTransform = soundTransform; } @:noCompletion private function __updatePeaks(time:Float):Bool { if (Math.abs(time - __lastPeakTime) < 8) return false; __lastPeakTime = time; #if !macro if (!__isValid) return false; var buffer = __source.buffer; var wordSize = buffer.bitsPerSample >> 3, byteSize = 1 << (buffer.bitsPerSample - 1); var pos = Math.floor(time * buffer.sampleRate / 1000 * buffer.channels * wordSize); var leftMin = 0, leftMax = 0, rightMin = 0, rightMax = 0, size = 0, buf; #if lime_cffi var backend = __source.__backend, i = 0; if (backend.streamed) { size = backend.bufferLengths[i = backend.bufferLengths.length - backend.requestBuffers]; buf = backend.bufferDatas[i].buffer; pos -= Math.floor(backend.bufferTimes[i] * buffer.sampleRate * buffer.channels * wordSize); while (pos > size) { if (++i >= backend.bufferLengths.length) return false; pos -= size; buf = backend.bufferDatas[i].buffer; size = backend.bufferLengths[i]; } } else #end { buf = buffer.data #if !js .buffer #end; size = #if js buf.byteLength #else buf.length #end; } var s = Math.floor(Math.min(buffer.sampleRate / 80, 512)), c = 0, b; pos -= pos % (buffer.channels * wordSize); while (s > 0) { b = funkin.backend.utils.AudioAnalyzer.getByte(buf, pos, wordSize); if (c % 2 == 0) ((b > leftMax) ? (leftMax = b) : (if ((b = -b) > leftMin) (leftMin = b))); else ((b > rightMax) ? (rightMax = b) : (if ((b = -b) > rightMin) (rightMin = b))); if ((pos += wordSize) >= size) #if lime_cffi { if (!backend.streamed || ++i >= backend.bufferLengths.length) break; pos = 0; buf = backend.bufferDatas[i].buffer; size = backend.bufferLengths[i]; } #else break; #end if (++c > buffer.channels) { c = 0; s--; } } if (buffer.channels == 1) __rightPeak = (__leftPeak = (leftMax + leftMin) / byteSize); else { __leftPeak = (leftMax + leftMin) / byteSize; __rightPeak = (rightMax + rightMin) / byteSize; } #end return true; } @:noCompletion private function __initAudioSource(source:#if lime AudioSource #else Dynamic #end):Void { #if lime __source = source; if (__source == null) { return; } __source.onComplete.add(source_onComplete); __isValid = true; __source.play(); #end } // Get & Set Methods @:noCompletion private function get_position():Float { if (!__isValid) return 0; #if lime return __source.currentTime + __source.offset; #else return 0; #end } @:noCompletion private function set_position(value:Float):Float { if (!__isValid) return 0; #if lime __source.currentTime = value - __source.offset; #end return value; } @:noCompletion private function get_soundTransform():SoundTransform { return __soundTransform.clone(); } @:noCompletion private function set_soundTransform(value:SoundTransform):SoundTransform { if (value != null) { __soundTransform.pan = value.pan; __soundTransform.volume = value.volume; var pan = SoundMixer.__soundTransform.pan + __soundTransform.pan; if (pan < -1) pan = -1; if (pan > 1) pan = 1; var volume = SoundMixer.__soundTransform.volume * __soundTransform.volume; if (__isValid) { #if lime // TODO: implement SoundTransform.leftToRight, etc. with Native setAngles? __source.gain = volume; __source.pan = pan; return value; #end } } return value; } @:noCompletion private function get_pitch():Float { if (!__isValid) return 1; #if lime return __source.pitch; #else return 0; #end } @:noCompletion private function set_pitch(value:Float):Float { if (!__isValid) return 1; #if lime return __source.pitch = value; #else return 0; #end } @:noCompletion private function get_loopTime():Float { if (!__isValid) return -1; #if lime return __source.loopTime; #else return -1; #end } @:noCompletion private function set_loopTime(value:Float):Float { if (!__isValid) return -1; #if lime return __source.loopTime = value; #else return -1; #end } @:noCompletion private function get_endTime():Null { if (!__isValid) return null; #if lime return __source.length; #else return null; #end } @:noCompletion private function set_endTime(value:Null):Null { if (!__isValid) return null; #if lime return __source.length = value; #else return null; #end } @:noCompletion private function get_loops():Int { if (!__isValid) return 0; #if lime return __source.loops; #else return 0; #end } @:noCompletion private function set_loops(value:Int):Int { if (!__isValid) return 0; #if lime return __source.loops = value; #else return 0; #end } @:noCompletion private function get_leftPeak():Float { __updatePeaks(get_position()); return __leftPeak * (soundTransform == null ? 1 : soundTransform.volume); } @:noCompletion private function get_rightPeak():Float { __updatePeaks(get_position()); return __rightPeak * (soundTransform == null ? 1 : soundTransform.volume); } // Event Handlers @:noCompletion private function source_onComplete():Void { SoundMixer.__unregisterSoundChannel(this); __dispose(); dispatchEvent(new Event(Event.SOUND_COMPLETE)); } @:noCompletion private function get___audioSource():AudioSource return __source; @:noCompletion private function set___audioSource(source:AudioSource):AudioSource return __source = source; } #else typedef SoundChannel = flash.media.SoundChannel; #end