Before anyone asks why vs cam, the code is public, plus i helped most of the audio backend rewrite in the mod too. :P - Static sounds can now also seamlessly loop with AL_SOFT_loop_points extension - Panning stereo sounds is now possible with AL_EXT_STEREO_ANGLES extension (although this was already implemented before) - Improved streaming sounds, should fix most of the lagspike issues - Streaming sounds should no longer regenrate if it's in the nearest timestamp (not for reversing though) ! You still can't access FlxSound.latency because of the new procedures that's not available in lime If there's any crashes with this pls report an issue asap.
401 lines
9.6 KiB
Haxe
401 lines
9.6 KiB
Haxe
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<Float>;
|
|
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.queuedBuffers];
|
|
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<Float>
|
|
{
|
|
if (!__isValid) return null;
|
|
|
|
#if lime
|
|
return __source.length;
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
|
|
@:noCompletion private function set_endTime(value:Null<Float>):Null<Float>
|
|
{
|
|
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
|