FlxSound & internal rewrite & Streamed Music
This commit is contained in:
@@ -0,0 +1,955 @@
|
||||
package flixel.sound;
|
||||
|
||||
import lime.media.AudioBuffer;
|
||||
import lime.media.AudioSource;
|
||||
import lime.media.openal.AL;
|
||||
#if lime_vorbis
|
||||
import lime.media.vorbis.VorbisFile;
|
||||
#end
|
||||
|
||||
import openfl.Assets;
|
||||
import openfl.events.IEventDispatcher;
|
||||
import openfl.events.Event;
|
||||
import openfl.media.Sound;
|
||||
import openfl.media.SoundChannel;
|
||||
import openfl.media.SoundTransform;
|
||||
import openfl.media.SoundMixer;
|
||||
import openfl.net.URLRequest;
|
||||
import openfl.utils.AssetType;
|
||||
#if flash11
|
||||
import openfl.utils.ByteArray;
|
||||
#end
|
||||
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.util.FlxStringUtil;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxG;
|
||||
|
||||
@:access(flixel.FlxGame)
|
||||
class FlxSound extends FlxBasic {
|
||||
/**
|
||||
* The x position of this sound in world coordinates.
|
||||
* Only really matters if you are doing proximity/panning stuff.
|
||||
*/
|
||||
public var x:Float;
|
||||
|
||||
/**
|
||||
* The y position of this sound in world coordinates.
|
||||
* Only really matters if you are doing proximity/panning stuff.
|
||||
*/
|
||||
public var y:Float;
|
||||
|
||||
/**
|
||||
* Whether or not this sound should be automatically destroyed when you switch states.
|
||||
*/
|
||||
public var persist:Bool;
|
||||
|
||||
/**
|
||||
* Whether or not the sound is currently playing.
|
||||
*/
|
||||
public var playing(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Set volume to a value between 0 and 1 to change how this sound is.
|
||||
*/
|
||||
public var volume(get, set):Float;
|
||||
|
||||
/**
|
||||
* Whether to make this sound muted or not.
|
||||
*/
|
||||
public var muted(default, set):Bool;
|
||||
|
||||
#if FLX_PITCH
|
||||
/**
|
||||
* Set pitch, which also alters the playback speed. Default is 1.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public var pitch(get, set):Float;
|
||||
|
||||
/**
|
||||
* Alters the pitch of the sound depends on the current FlxG.timeScale. Default is true.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var timeScaleBased:Bool;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Pan amount. -1 = full left, 1 = full right. Proximity based panning overrides this.
|
||||
*/
|
||||
public var pan(get, set):Float;
|
||||
|
||||
/**
|
||||
* The position in runtime of the music playback in milliseconds.
|
||||
* If set while paused, changes only come into effect after a `resume()` call.
|
||||
*/
|
||||
public var time(get, set):Float;
|
||||
|
||||
/**
|
||||
* The offset for this FlxSound.
|
||||
* Useful for just generally offsetting this sound without affecting time.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var offset(get, set):Float;
|
||||
|
||||
/**
|
||||
* The length of the sound in milliseconds.
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public var length(get, never):Float;
|
||||
|
||||
/**
|
||||
* The latency of the sound in milliseconds.
|
||||
* @since raltyMod
|
||||
*/
|
||||
//public var latency(get, never):Float;
|
||||
|
||||
/**
|
||||
* Whether or not this sound should loop.
|
||||
*/
|
||||
public var looped(default, set):Bool;
|
||||
|
||||
/**
|
||||
* In case of looping, the point (in milliseconds) from where to restart the sound when it loops back
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public var loopTime(default, set):Float;
|
||||
|
||||
/**
|
||||
* At which point to stop playing the sound, in milliseconds.
|
||||
* If not set / `null`, the sound completes normally.
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public var endTime(default, set):Null<Float>;
|
||||
|
||||
/**
|
||||
* The sound's "target" (for proximity and panning).
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var target:Null<FlxObject>;
|
||||
|
||||
/**
|
||||
* The maximum effective radius of this sound (for proximity and panning).
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var radius:Float;
|
||||
|
||||
/**
|
||||
* Whether the proximity alters the pan or not
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var proximityPan:Bool;
|
||||
|
||||
/**
|
||||
* Stores for how much channels are in the loaded sound.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var channels(get, never):Int;
|
||||
|
||||
/**
|
||||
* Whether or not this sound is stereo instead of mono.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var stereo(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Whether or not this sound is loaded yet.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var loaded(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Whether or not this sound is streamed, only vorbis though.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var streamed(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Stores the average wave amplitude of both stereo channels.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var amplitude(get, never):Float;
|
||||
|
||||
/**
|
||||
* Just the amplitude of the left stereo channel
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var amplitudeLeft(get, never):Float;
|
||||
|
||||
/**
|
||||
* Just the amplitude of the right stereo channel
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var amplitudeRight(get, never):Float;
|
||||
|
||||
/**
|
||||
* The ID3 song name. Defaults to null. Currently only works for MP3 streamed sounds.
|
||||
*/
|
||||
public var name(default, null):String;
|
||||
|
||||
/**
|
||||
* The ID3 artist name. Defaults to null. Currently only works for MP3 streamed sounds.
|
||||
*/
|
||||
public var artist(default, null):String;
|
||||
|
||||
/**
|
||||
* Whether to call `destroy()` when the sound has finished playing.
|
||||
*/
|
||||
public var autoDestroy:Bool;
|
||||
|
||||
/**
|
||||
* Signal that is dispatched on sound complete.
|
||||
*/
|
||||
public final onFinish:FlxSignal;
|
||||
|
||||
/**
|
||||
* Tracker for sound complete callback. If assigned, will be called
|
||||
* each time when sound reaches its end.
|
||||
*/
|
||||
//@:deprecated("`FlxSound.onComplete` is deprecated! Use `FlxSound.onFinish` instead.")
|
||||
public var onComplete:Void->Void;
|
||||
|
||||
/**
|
||||
* The sound group this sound belongs to
|
||||
*/
|
||||
public var group(default, set):FlxSoundGroup;
|
||||
|
||||
/**
|
||||
* Stores the sound lime AudioBuffer if exists.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var buffer(get, never):AudioBuffer;
|
||||
|
||||
#if lime_vorbis
|
||||
/**
|
||||
* Stores the sound VorbisFile if exists.
|
||||
* @since raltyMod
|
||||
*/
|
||||
public var vorbis(get, never):VorbisFile;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The tween used to fade this sound's volume in and out (set via `fadeIn()` and `fadeOut()`)
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public var fadeTween:FlxTween;
|
||||
|
||||
@:allow(flixel.system.frontEnds.SoundFrontEnd.load) var _sound:Sound;
|
||||
var _transform:SoundTransform;
|
||||
var _channel:SoundChannel;
|
||||
var _source:AudioSource;
|
||||
var _paused:Bool;
|
||||
var _volume:Float;
|
||||
var _volumeAdjust:Float;
|
||||
var _time:Float;
|
||||
var _offset:Float;
|
||||
var _timeInterpolation:Float;
|
||||
var _lastTime:Null<Float>; // FlxG.game.getTicks(), in MS
|
||||
var _length:Float;
|
||||
#if FLX_PITCH
|
||||
var _pitch:Float;
|
||||
var _timeScaleAdjust:Float;
|
||||
var _realPitch:Float;
|
||||
#end
|
||||
var _amplitudeLeft:Float;
|
||||
var _amplitudeRight:Float;
|
||||
var _amplitudeUpdate:Bool;
|
||||
var _amplitudeTime:Float;
|
||||
var _alreadyPaused:Null<Bool>;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
onFinish = new FlxSignal();
|
||||
revive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this FlxSound properties.
|
||||
*
|
||||
* @param clean Whether if this FlxSound also needs to be cleaned up too.
|
||||
*/
|
||||
public function reset():Void {
|
||||
if (_source != null) stop();
|
||||
x = y = 0;
|
||||
|
||||
muted = false;
|
||||
looped = false;
|
||||
loopTime = 0;
|
||||
endTime = null;
|
||||
autoDestroy = false;
|
||||
visible = false;
|
||||
target = null;
|
||||
radius = 0;
|
||||
proximityPan = true;
|
||||
onComplete = null;
|
||||
timeScaleBased = true;
|
||||
|
||||
_alreadyPaused = null;
|
||||
_cameras = null;
|
||||
_lastTime = null;
|
||||
_paused = false;
|
||||
_length = _time = 0;
|
||||
_volume = _volumeAdjust = 1;
|
||||
_amplitudeLeft = _amplitudeRight = 0;
|
||||
_amplitudeUpdate = true;
|
||||
#if FLX_PITCH _pitch = _realPitch = _timeScaleAdjust = 1; #end
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal cleanup function for cleaning up this FlxSound.
|
||||
* @since raltyMod
|
||||
*/
|
||||
function cleanup(destroySound:Bool, resetPosition:Bool = true) @:privateAccess {
|
||||
active = false;
|
||||
_lastTime = null;
|
||||
|
||||
if (_channel != null) {
|
||||
_channel.removeEventListener(Event.SOUND_COMPLETE, stopped);
|
||||
}
|
||||
|
||||
if (destroySound) {
|
||||
onFinish.removeAll();
|
||||
|
||||
if (group != null) group.remove(this);
|
||||
|
||||
if (_channel != null) {
|
||||
_channel.stop();
|
||||
_channel = null;
|
||||
}
|
||||
if (_source != null) _source.dispose();
|
||||
_source = null;
|
||||
_sound = null;
|
||||
|
||||
reset();
|
||||
}
|
||||
else if (_channel != null && _channel.__isValid) {
|
||||
if (resetPosition) {
|
||||
_source.stop();
|
||||
|
||||
_time = 0;
|
||||
_paused = false;
|
||||
}
|
||||
else if (!_paused) {
|
||||
get_time();
|
||||
_source.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles fade out, fade in, panning, proximity, and amplitude operations each frame.
|
||||
*/
|
||||
override function update(elapsed:Float):Void {
|
||||
if (!playing) return;
|
||||
|
||||
var timeScaleTarget = timeScaleBased ? FlxG.timeScale : 1.0;
|
||||
if (_timeScaleAdjust != timeScaleTarget) {
|
||||
_timeScaleAdjust = timeScaleTarget;
|
||||
pitch = _pitch;
|
||||
if (_channel == null) return;
|
||||
}
|
||||
|
||||
_amplitudeUpdate = true;
|
||||
|
||||
var radialMultiplier = 1.0;
|
||||
|
||||
// Distance-based volume control (TODO for Ralty: REDO THIS)
|
||||
if (target != null) {
|
||||
var targetPosition = target.getPosition();
|
||||
radialMultiplier = targetPosition.distanceTo(FlxPoint.weak(x, y)) / radius;
|
||||
targetPosition.put();
|
||||
radialMultiplier = 1 - FlxMath.bound(radialMultiplier, 0, 1);
|
||||
|
||||
if (proximityPan && _transform != null) {
|
||||
var d:Float = (x - target.x) / radius;
|
||||
_transform.pan = FlxMath.bound(d, -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
_volumeAdjust = radialMultiplier;
|
||||
updateTransform();
|
||||
}
|
||||
|
||||
override function revive() {
|
||||
reset();
|
||||
super.revive();
|
||||
}
|
||||
|
||||
override function kill() {
|
||||
super.kill();
|
||||
reset();
|
||||
}
|
||||
|
||||
override function destroy() {
|
||||
super.destroy();
|
||||
cleanup(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* One of the main setup functions for sounds, this function loads a sound from an embedded MP3.
|
||||
*
|
||||
* @param embeddedSound An embedded Class object representing an MP3 file.
|
||||
* @param looped Whether or not this sound should loop endlessly.
|
||||
* @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing.
|
||||
* Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default.
|
||||
* @param onComplete Called when the sound finished playing
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function loadEmbedded(embeddedSound:FlxSoundAsset, looped = false, autoDestroy = false, ?onComplete:Void->Void):FlxSound {
|
||||
if (!exists || embeddedSound == null) return this;
|
||||
cleanup(true);
|
||||
|
||||
if ((embeddedSound is Sound)) _sound = embeddedSound;
|
||||
else if ((embeddedSound is Class)) _sound = Type.createInstance(embeddedSound, []);
|
||||
else if ((embeddedSound is String)) {
|
||||
if (Assets.exists(embeddedSound, AssetType.MUSIC) && this == FlxG.sound.music)
|
||||
_sound = Assets.getMusic(embeddedSound);
|
||||
else if (Assets.exists(embeddedSound, AssetType.SOUND))
|
||||
_sound = Assets.getSound(embeddedSound);
|
||||
else
|
||||
FlxG.log.error('Could not find a Sound asset with an ID of \'$embeddedSound\'.');
|
||||
}
|
||||
|
||||
return init(looped, autoDestroy, onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* One of the main setup functions for sounds, this function loads a sound from a URL.
|
||||
*
|
||||
* @param soundURL A string representing the URL of the MP3 file you want to play.
|
||||
* @param looped Whether or not this sound should loop endlessly.
|
||||
* @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing.
|
||||
* Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default.
|
||||
* @param onComplete Called when the sound finished playing
|
||||
* @param onLoad Called when the sound finished loading.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function loadStream(soundURL:String, looped = false, autoDestroy = false, ?onComplete:Void->Void, ?onLoad:Void->Void):FlxSound {
|
||||
if (!exists) return this;
|
||||
cleanup(true);
|
||||
|
||||
_sound = new Sound();
|
||||
_sound.addEventListener(Event.ID3, gotID3);
|
||||
var loadCallback:Event->Void = null;
|
||||
loadCallback = function(e:Event)
|
||||
{
|
||||
(e.target : IEventDispatcher).removeEventListener(e.type, loadCallback);
|
||||
// Check if the sound was destroyed before calling. Weak ref doesn't guarantee GC.
|
||||
if (_sound == e.target)
|
||||
{
|
||||
_length = _sound.length;
|
||||
if (onLoad != null)
|
||||
onLoad();
|
||||
}
|
||||
}
|
||||
// Use a weak reference so this can be garbage collected if destroyed before loading.
|
||||
_sound.addEventListener(Event.COMPLETE, loadCallback, false, 0, true);
|
||||
_sound.load(new URLRequest(soundURL));
|
||||
|
||||
return init(looped, autoDestroy, onComplete);
|
||||
}
|
||||
|
||||
#if flash11
|
||||
/**
|
||||
* One of the main setup functions for sounds, this function loads a sound from a ByteArray.
|
||||
*
|
||||
* @param bytes A ByteArray object.
|
||||
* @param looped Whether or not this sound should loop endlessly.
|
||||
* @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing.
|
||||
* Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function loadByteArray(bytes:ByteArray, looped = false, autoDestroy = false, ?onComplete:Void->Void):FlxSound {
|
||||
if (!exists) return this;
|
||||
cleanup(true);
|
||||
|
||||
_sound = new Sound();
|
||||
_sound.addEventListener(Event.ID3, gotID3);
|
||||
_sound.loadCompressedDataFromByteArray(bytes, bytes.length);
|
||||
|
||||
return init(looped, autoDestroy, onComplete);
|
||||
}
|
||||
#end
|
||||
|
||||
function init(?looped:Null<Bool>, ?autoDestroy:Null<Bool>, ?onComplete:Null<Void->Void>):FlxSound {
|
||||
if (looped != null) this.looped = looped;
|
||||
if (autoDestroy != null) this.autoDestroy = autoDestroy;
|
||||
if (onComplete != null) this.onComplete = onComplete; // DEPRECATED
|
||||
|
||||
if (_sound != null) makeChannel();
|
||||
else _length = 0;
|
||||
|
||||
endTime = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call inbetween init function if a sound asset does exists.
|
||||
* @since raltyMod
|
||||
*/
|
||||
function makeChannel() @:privateAccess {
|
||||
if (_source != null) _source.dispose();
|
||||
|
||||
if (_channel == null) (_source = (_channel = new SoundChannel(null)).__source = new AudioSource(_sound.__buffer)).gain = 0;
|
||||
else {
|
||||
(_source = new AudioSource(_sound.__buffer)).gain = 0;
|
||||
_channel.__dispose();
|
||||
_channel.__source = _source;
|
||||
SoundMixer.__registerSoundChannel(_channel);
|
||||
}
|
||||
if (_transform == null) _transform = new SoundTransform();
|
||||
_transform.pan = 0;
|
||||
_length = _source.length;
|
||||
|
||||
_source.onComplete.add(_channel.source_onComplete);
|
||||
_source.onLoop.add(_channel.source_onLoop);
|
||||
_channel.__soundTransform = _transform;
|
||||
_channel.__isValid = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call after adjusting the volume to update the sound channel's settings.
|
||||
*/
|
||||
@:allow(flixel.sound.FlxSoundGroup)
|
||||
function updateTransform() {
|
||||
if (_transform == null) {
|
||||
_transform = new SoundTransform();
|
||||
pan = pan;
|
||||
}
|
||||
_transform.volume = #if FLX_SOUND_SYSTEM ((FlxG.sound.muted || muted) ? 0 : 1) * FlxG.sound.volume * #end
|
||||
(group != null ? group.volume : 1) * _volume * _volumeAdjust;
|
||||
|
||||
if (_channel != null) _channel.soundTransform = _transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function if you want this sound's volume to change
|
||||
* based on distance from a particular FlxObject.
|
||||
*
|
||||
* @param X The X position of the sound.
|
||||
* @param Y The Y position of the sound.
|
||||
* @param TargetObject The object you want to track.
|
||||
* @param Radius The maximum distance this sound can travel.
|
||||
* @param Pan Whether panning should be used in addition to the volume changes.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function proximity(x = 0.0, y = 0.0, ?targetObject:FlxObject, ?radius:Float, pan = true):FlxSound {
|
||||
setPosition(x, y);
|
||||
if (targetObject != null) this.target = targetObject;
|
||||
if (radius != null) this.radius = radius;
|
||||
proximityPan = pan;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to set the coordinates of this object.
|
||||
* Sound positioning is used in conjunction with proximity/panning.
|
||||
*
|
||||
* @param x The new x position
|
||||
* @param y The new y position
|
||||
*/
|
||||
public inline function setPosition(x = 0.0, y = 0.0):Void {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function to play the sound - also works on paused sounds.
|
||||
*
|
||||
* @param forceRestart Whether to start the sound over or not.
|
||||
* Default value is false, meaning if the sound is already playing or was
|
||||
* paused when you call play(), it will continue playing from its current
|
||||
* position, NOT start again from the beginning.
|
||||
* @param startTime At which point to start playing the sound, in milliseconds.
|
||||
* @param endTime At which point to stop playing the sound, in milliseconds.
|
||||
* If not set / `null`, the sound completes normally.
|
||||
*/
|
||||
public function play(forceRestart = false, startTime = 0.0, ?endTime:Null<Float>):FlxSound {
|
||||
if (!exists) return this;
|
||||
|
||||
if (forceRestart) cleanup(false, true);
|
||||
else if (playing) return this;
|
||||
|
||||
if (endTime != null) this.endTime = endTime;
|
||||
if (_paused) resume();
|
||||
else startSound(startTime);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpause a sound. Only works on sounds that have been paused.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function resume():FlxSound {
|
||||
if (_paused) startSound(_time);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function to pause this sound.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function pause():FlxSound {
|
||||
if (!playing) return this;
|
||||
|
||||
cleanup(false, false);
|
||||
_paused = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function to stop this sound.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function stop():FlxSound {
|
||||
cleanup(autoDestroy, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that tweens this sound's volume.
|
||||
*
|
||||
* @param duration The amount of time the fade-out operation should take.
|
||||
* @param to The volume to tween to, 0 by default.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function fadeOut(duration = 1.0, to = 0.0, ?onComplete:FlxTween->Void):FlxSound {
|
||||
if (fadeTween != null) fadeTween.cancel();
|
||||
fadeTween = FlxTween.num(volume, to, duration, {onComplete: onComplete}, volumeTween);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that tweens this sound's volume.
|
||||
* If the sound wasn't playing at all, it'll play before the tween starts.
|
||||
*
|
||||
* @param duration The amount of time the fade-in operation should take.
|
||||
* @param from The volume to tween from, 0 by default.
|
||||
* @param to The volume to tween to, 1 by default.
|
||||
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
|
||||
*/
|
||||
public function fadeIn(duration = 1.0, from = 0.0, to = 1.0, ?onComplete:FlxTween->Void):FlxSound {
|
||||
if (!playing) play();
|
||||
if (fadeTween != null) fadeTween.cancel();
|
||||
fadeTween = FlxTween.num(from, to, duration, {onComplete: onComplete}, volumeTween);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
function volumeTween(f:Float) volume = f;
|
||||
|
||||
/**
|
||||
* Returns the currently selected "real" volume of the sound (takes fades and proximity into account).
|
||||
*
|
||||
* @return The adjusted volume of the sound.
|
||||
*/
|
||||
public inline function getActualVolume():Float
|
||||
return _volume * _volumeAdjust;
|
||||
|
||||
#if FLX_PITCH
|
||||
/**
|
||||
* Returns the currently selected "real" pitch of the sound.
|
||||
*
|
||||
* @return The adjusted pitch of the sound.
|
||||
*/
|
||||
public inline function getActualPitch():Float
|
||||
return _realPitch;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Returns the actual time coming from the internal, can be used for detecting sync error.
|
||||
*
|
||||
* @return The actual time of the sound.
|
||||
*/
|
||||
public inline function getActualTime():Float {
|
||||
get_time();
|
||||
return _time;
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal helper function used to attempt to start playing
|
||||
* the sound and populate the _channel variable.
|
||||
*/
|
||||
var _tries = 0;
|
||||
function startSound(StartTime:Float) @:privateAccess {
|
||||
if (_sound == null) return;
|
||||
|
||||
_paused = false;
|
||||
_time = StartTime;
|
||||
_lastTime = FlxG.game.getTicks();
|
||||
if (_channel == null || !_channel.__isValid || _source.__backend == null || _source.__backend.disposed #if lime_openal || _source.__backend.handle == null #end)
|
||||
makeChannel();
|
||||
|
||||
if (_channel != null) {
|
||||
#if FLX_PITCH
|
||||
_timeScaleAdjust = timeScaleBased ? FlxG.timeScale : 1.0;
|
||||
_realPitch = -1.0;
|
||||
pitch = _pitch;
|
||||
#end
|
||||
|
||||
// TODO: fix the buffer loud beep for streaming sounds...
|
||||
#if lime_openal
|
||||
_source.__backend.setGain(0);
|
||||
#end
|
||||
|
||||
_channel.soundTransform = _transform;
|
||||
_channel.__lastPeakTime = 0;
|
||||
_channel.__leftPeak = 0;
|
||||
_channel.__rightPeak = 0;
|
||||
_channel.addEventListener(Event.SOUND_COMPLETE, stopped);
|
||||
_source.__backend.playing = true;
|
||||
_source.offset = 0;
|
||||
_source.currentTime = _time;
|
||||
|
||||
#if lime_openal
|
||||
if (_source.__backend.handle == null) return destroy();
|
||||
|
||||
var s = false;
|
||||
try {s = AL.getSourcei(_source.__backend.handle, AL.SOURCE_STATE) == AL.PLAYING;} catch(e) {}
|
||||
if (s) updateTransform();
|
||||
else {
|
||||
if (++_tries > 10) return;
|
||||
makeChannel();
|
||||
return startSound(StartTime);
|
||||
}
|
||||
|
||||
_tries = 0;
|
||||
#end
|
||||
|
||||
looped = looped;
|
||||
loopTime = loopTime;
|
||||
endTime = endTime;
|
||||
_amplitudeTime = -1;
|
||||
|
||||
active = true;
|
||||
}
|
||||
else {
|
||||
exists = false;
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
|
||||
function stopped(?_) {
|
||||
onFinish.dispatch();
|
||||
|
||||
if (onComplete != null) onComplete();
|
||||
|
||||
if (looped) {
|
||||
cleanup(false);
|
||||
play(false, loopTime - _offset, endTime - _offset);
|
||||
}
|
||||
else cleanup(autoDestroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal event handler for ID3 info (i.e. fetching the song name).
|
||||
*/
|
||||
function gotID3(_) {
|
||||
name = _sound.id3.songName;
|
||||
artist = _sound.id3.artist;
|
||||
_sound.removeEventListener(Event.ID3, gotID3);
|
||||
}
|
||||
|
||||
#if FLX_SOUND_SYSTEM
|
||||
@:allow(flixel.system.frontEnds.SoundFrontEnd)
|
||||
function onFocus() if (!_alreadyPaused) {
|
||||
resume();
|
||||
_alreadyPaused = null;
|
||||
}
|
||||
|
||||
@:allow(flixel.system.frontEnds.SoundFrontEnd)
|
||||
function onFocusLost() if (_alreadyPaused == null && !(_alreadyPaused = _paused)) pause();
|
||||
#end
|
||||
|
||||
function set_group(value:FlxSoundGroup):FlxSoundGroup {
|
||||
if (value != null) value.add(this);
|
||||
else group.remove(this);
|
||||
return group;
|
||||
}
|
||||
|
||||
inline function get_playing():Bool @:privateAccess
|
||||
return _channel != null && _channel.__isValid && _source.playing;
|
||||
|
||||
inline function get_volume():Float
|
||||
return _volume;
|
||||
|
||||
inline function set_volume(Volume:Float):Float {
|
||||
_volume = FlxMath.bound(Volume, 0, 4);
|
||||
updateTransform();
|
||||
return _volume;
|
||||
}
|
||||
|
||||
inline function set_muted(Muted:Bool):Bool {
|
||||
muted = Muted;
|
||||
updateTransform();
|
||||
return muted;
|
||||
}
|
||||
|
||||
inline function get_loaded():Bool
|
||||
return buffer != null;
|
||||
|
||||
inline function get_streamed():Bool
|
||||
return #if lime_vorbis vorbis != null #else false #end;
|
||||
|
||||
inline function get_buffer():AudioBuffer
|
||||
@:privateAccess return _sound != null ? _sound.__buffer : null;
|
||||
|
||||
#if lime_vorbis
|
||||
inline function get_vorbis():VorbisFile
|
||||
@:privateAccess return _sound != null ? _sound.__buffer.__srcVorbisFile : null;
|
||||
#end
|
||||
|
||||
function update_amplitude():Void @:privateAccess {
|
||||
if (_channel == null || _time == _amplitudeTime || !_amplitudeUpdate) return;
|
||||
_channel.__updatePeaks();
|
||||
|
||||
_amplitudeUpdate = false;
|
||||
_amplitudeLeft = _channel.__leftPeak;
|
||||
_amplitudeRight = _channel.__rightPeak;
|
||||
_amplitudeTime = _time;
|
||||
}
|
||||
|
||||
inline function get_amplitudeLeft():Float {
|
||||
update_amplitude();
|
||||
return _amplitudeLeft;
|
||||
}
|
||||
|
||||
inline function get_amplitudeRight():Float {
|
||||
update_amplitude();
|
||||
return _amplitudeRight;
|
||||
}
|
||||
|
||||
inline function get_amplitude():Float {
|
||||
update_amplitude();
|
||||
return if (stereo) (_amplitudeLeft + _amplitudeRight) * 0.5; else _amplitudeLeft;
|
||||
}
|
||||
|
||||
inline function get_channels():Int
|
||||
@:privateAccess return (buffer != null) ? buffer.channels : 0;
|
||||
|
||||
inline function get_stereo():Bool
|
||||
return channels > 1;
|
||||
|
||||
#if FLX_PITCH
|
||||
inline function get_pitch():Float
|
||||
return _pitch;
|
||||
|
||||
function set_pitch(v:Float):Float {
|
||||
var adjusted:Float = FlxMath.bound(v * _timeScaleAdjust, 0);
|
||||
if (_channel != null && _realPitch != adjusted) {
|
||||
if ((_channel.pitch = adjusted) > 0 && _realPitch <= 0) {
|
||||
_realPitch = adjusted;
|
||||
time = _time;
|
||||
}
|
||||
else {
|
||||
get_time();
|
||||
_realPitch = adjusted;
|
||||
}
|
||||
}
|
||||
return _pitch = FlxMath.bound(v, 0);
|
||||
}
|
||||
#end
|
||||
|
||||
function set_looped(v:Bool):Bool {
|
||||
if (playing) {
|
||||
if (v) _channel.loops = 999;
|
||||
else _channel.loops = 0;
|
||||
}
|
||||
return looped = v;
|
||||
}
|
||||
|
||||
function set_loopTime(v:Float):Float {
|
||||
if (playing) _channel.loopTime = v;
|
||||
return loopTime = v;
|
||||
}
|
||||
|
||||
function set_endTime(v:Null<Float>):Null<Float> {
|
||||
if (playing) {
|
||||
if (v != null && v > 0 && v < length) _channel.endTime = v;
|
||||
else _channel.endTime = null;
|
||||
}
|
||||
return endTime = v;
|
||||
}
|
||||
|
||||
inline function get_pan():Float return _transform.pan;
|
||||
inline function set_pan(pan:Float):Float return _transform.pan = pan;
|
||||
|
||||
inline function getFakeTime():Float {
|
||||
if (@:privateAccess _channel.__isValid && _source.playing && _realPitch > 0 && _lastTime != null)
|
||||
return _time + (FlxG.game.getTicks() - _lastTime) * _realPitch * _timeInterpolation;
|
||||
else
|
||||
return _time;
|
||||
}
|
||||
function get_time():Float {
|
||||
if (_channel == null) return _time;
|
||||
|
||||
final pos = _channel.position - _offset;
|
||||
if (!playing || _realPitch <= 0) {
|
||||
_lastTime = null;
|
||||
return _time = pos;
|
||||
}
|
||||
|
||||
final fakeTime = getFakeTime();
|
||||
if (pos != _time) {
|
||||
_lastTime = FlxG.game.getTicks();
|
||||
if ((_timeInterpolation = 1 - Math.min(fakeTime - pos, 1000) * 0.001) < 1 && _timeInterpolation > .9)
|
||||
return _time = fakeTime;
|
||||
else {
|
||||
_timeInterpolation = 1;
|
||||
return _time = pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
return fakeTime;
|
||||
}
|
||||
|
||||
function set_time(time:Float):Float @:privateAccess {
|
||||
time = FlxMath.bound(time, 0, length - 1);
|
||||
if (_channel != null && _realPitch > 0) {
|
||||
if (!_channel.__isValid) {
|
||||
cleanup(false, true);
|
||||
startSound(time);
|
||||
}
|
||||
else if (playing) {
|
||||
_source.offset = 0;
|
||||
_source.currentTime = time + _offset;
|
||||
}
|
||||
}
|
||||
|
||||
_lastTime = null;
|
||||
return _time = time;
|
||||
}
|
||||
|
||||
function get_offset():Float return _offset;
|
||||
function set_offset(offset:Float):Float {
|
||||
if (offset == _offset) return offset;
|
||||
_offset = offset;
|
||||
time = time;
|
||||
return offset;
|
||||
}
|
||||
|
||||
inline function get_length():Float return _length - _offset;
|
||||
|
||||
/*function get_latency():Float {
|
||||
if (_channel != null) return _source.latency;
|
||||
return 0;
|
||||
}*/
|
||||
|
||||
override function toString():String {
|
||||
return FlxStringUtil.getDebugString([
|
||||
LabelValuePair.weak("playing", playing),
|
||||
LabelValuePair.weak("time", _time),
|
||||
LabelValuePair.weak("length", length),
|
||||
LabelValuePair.weak("volume", volume),
|
||||
LabelValuePair.weak("pitch", pitch)
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -160,11 +160,11 @@ class Conductor
|
||||
|
||||
private static function __updateSongPos(elapsed:Float) {
|
||||
if (FlxG.sound.music == null || !FlxG.sound.music.playing) {
|
||||
lastSongPos = FlxG.sound.music != null ? FlxG.sound.music.time - songOffset : -songOffset;
|
||||
lastSongPos = FlxG.sound.music != null ? FlxG.sound.music.getActualTime() - songOffset : -songOffset;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSongPos != (lastSongPos = FlxG.sound.music.time - songOffset)) {
|
||||
if (lastSongPos != (lastSongPos = FlxG.sound.music.getActualTime() - songOffset)) {
|
||||
// update conductor
|
||||
songPosition = lastSongPos;
|
||||
} else {
|
||||
|
||||
@@ -812,10 +812,12 @@ class CoolUtil
|
||||
* @param music Music
|
||||
*/
|
||||
public static inline function setMusic(frontEnd:SoundFrontEnd, music:FlxSound) {
|
||||
if (frontEnd.music != null)
|
||||
@:privateAccess frontEnd.destroySound(frontEnd.music);
|
||||
if (frontEnd.music == music) return;
|
||||
|
||||
if (frontEnd.music != null) @:privateAccess frontEnd.destroySound(frontEnd.music);
|
||||
frontEnd.list.remove(music);
|
||||
frontEnd.music = music;
|
||||
frontEnd.defaultMusicGroup.add(frontEnd.music = music);
|
||||
music.persist = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -509,7 +509,7 @@ class PlayState extends MusicBeatState
|
||||
@:noCompletion @:dox(hide) private var _endSongCalled:Bool = false;
|
||||
|
||||
@:dox(hide)
|
||||
var __vocalOffsetViolation:Float = 0;
|
||||
var __vocalSyncTimer:Float = 0;
|
||||
|
||||
private function get_accuracy():Float {
|
||||
if (accuracyPressedNotes <= 0) return -1;
|
||||
@@ -999,21 +999,11 @@ class PlayState extends MusicBeatState
|
||||
|
||||
inst.onComplete = endSong;
|
||||
|
||||
if (!paused) {
|
||||
FlxG.sound.setMusic(inst);
|
||||
FlxG.sound.music.play();
|
||||
}
|
||||
vocals.play();
|
||||
var time = (chartingMode && Charter.startHere) ? Charter.startTime : 0;
|
||||
inst.time = vocals.time = time;
|
||||
|
||||
vocals.pause();
|
||||
inst.pause();
|
||||
for (strumLine in strumLines.members) {
|
||||
strumLine.vocals.play();
|
||||
strumLine.vocals.pause();
|
||||
}
|
||||
inst.time = vocals.time = (chartingMode && Charter.startHere) ? Charter.startTime : 0;
|
||||
for (strumLine in strumLines.members) {
|
||||
strumLine.vocals.time = vocals.time;
|
||||
strumLine.vocals.time = time;
|
||||
strumLine.vocals.play();
|
||||
}
|
||||
vocals.play();
|
||||
@@ -1028,15 +1018,14 @@ class PlayState extends MusicBeatState
|
||||
scripts.call("destroy");
|
||||
for(g in __cachedGraphics)
|
||||
g.useCount--;
|
||||
@:privateAccess
|
||||
for (strumLine in strumLines.members)
|
||||
FlxG.sound.destroySound(strumLine.vocals);
|
||||
super.destroy();
|
||||
scripts = FlxDestroyUtil.destroy(scripts);
|
||||
@:privateAccess {
|
||||
FlxG.sound.destroySound(inst);
|
||||
for (strumLine in strumLines.members) FlxG.sound.destroySound(strumLine.vocals);
|
||||
if (FlxG.sound.music != inst) FlxG.sound.destroySound(inst);
|
||||
FlxG.sound.destroySound(vocals);
|
||||
}
|
||||
scripts = FlxDestroyUtil.destroy(scripts);
|
||||
|
||||
super.destroy();
|
||||
|
||||
WindowUtils.resetAffixes();
|
||||
SaveWarning.reset();
|
||||
@@ -1079,15 +1068,14 @@ class PlayState extends MusicBeatState
|
||||
curSong = songData.meta.name.toLowerCase();
|
||||
curSongID = curSong.replace(" ", "-");
|
||||
|
||||
inst = FlxG.sound.load(Paths.inst(SONG.meta.name, difficulty));
|
||||
FlxG.sound.setMusic(inst = FlxG.sound.load(Assets.getMusic(Paths.inst(SONG.meta.name, difficulty))));
|
||||
if (SONG.meta.needsVoices != false && Assets.exists(Paths.voices(SONG.meta.name, difficulty))) // null or true
|
||||
vocals = FlxG.sound.load(Paths.voices(SONG.meta.name, difficulty));
|
||||
else
|
||||
vocals = new FlxSound();
|
||||
inst.group = FlxG.sound.defaultMusicGroup;
|
||||
vocals.group = FlxG.sound.defaultMusicGroup;
|
||||
|
||||
inst.persist = vocals.persist = false;
|
||||
vocals.group = FlxG.sound.defaultMusicGroup;
|
||||
vocals.persist = false;
|
||||
|
||||
generatedMusic = true;
|
||||
}
|
||||
@@ -1191,14 +1179,15 @@ class PlayState extends MusicBeatState
|
||||
vocals.pause();
|
||||
for (strumLine in strumLines.members) strumLine.vocals.pause();
|
||||
|
||||
FlxG.sound.music.play();
|
||||
Conductor.songPosition = FlxG.sound.music.time;
|
||||
vocals.time = Conductor.songPosition + Conductor.songOffset;
|
||||
var time = Conductor.songPosition + Conductor.songOffset;
|
||||
inst.time = vocals.time = time;
|
||||
for (strumLine in strumLines.members) {
|
||||
strumLine.vocals.time = vocals.time;
|
||||
strumLine.vocals.time = time;
|
||||
strumLine.vocals.play();
|
||||
}
|
||||
vocals.play();
|
||||
inst.play();
|
||||
|
||||
gameAndCharsCall("onVocalsResync");
|
||||
}
|
||||
|
||||
@@ -1333,22 +1322,19 @@ class PlayState extends MusicBeatState
|
||||
if (Conductor.songPosition >= 0)
|
||||
startSong();
|
||||
}
|
||||
} else if (FlxG.sound.music != null) {
|
||||
var instTime = FlxG.sound.music.time;
|
||||
var isOffsync:Bool = vocals.time != instTime;
|
||||
if(!isOffsync) {
|
||||
for(strumLine in strumLines.members) {
|
||||
if(strumLine.vocals.time != instTime) {
|
||||
isOffsync = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (FlxG.sound.music != null && (__vocalSyncTimer -= elapsed) < 0) {
|
||||
__vocalSyncTimer = 1;
|
||||
|
||||
var instTime = FlxG.sound.music.getActualTime();
|
||||
var isOffsync:Bool = vocals.loaded && Math.abs(instTime - vocals.getActualTime()) > 30;
|
||||
if (!isOffsync) {
|
||||
for (strumLine in strumLines.members) {
|
||||
if ((isOffsync = strumLine.vocals.loaded && Math.abs(instTime - strumLine.vocals.getActualTime()) > 30)) break;
|
||||
}
|
||||
}
|
||||
__vocalOffsetViolation = Math.max(0, __vocalOffsetViolation + (isOffsync ? elapsed : -elapsed / 2));
|
||||
if (__vocalOffsetViolation > Flags.VOCAL_OFFSET_VIOLATION_THRESHOLD) {
|
||||
resyncVocals();
|
||||
__vocalOffsetViolation = 0;
|
||||
}
|
||||
|
||||
if (isOffsync) resyncVocals();
|
||||
}
|
||||
|
||||
while(events.length > 0 && events.last().time <= Conductor.songPosition)
|
||||
|
||||
@@ -65,7 +65,7 @@ class PauseSubState extends MusicBeatSubstate
|
||||
|
||||
menuItems = event.options;
|
||||
|
||||
pauseMusic = FlxG.sound.load(Paths.music(event.music), 0, true);
|
||||
pauseMusic = FlxG.sound.load(Assets.getMusic(Paths.music(event.music)), 0, true);
|
||||
pauseMusic.persist = false;
|
||||
pauseMusic.group = FlxG.sound.defaultMusicGroup;
|
||||
pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2)));
|
||||
|
||||
@@ -2,6 +2,7 @@ package lime._internal.backend.html5;
|
||||
|
||||
import lime.math.Vector4;
|
||||
import lime.media.AudioSource;
|
||||
import lime.media.AudioManager;
|
||||
|
||||
@:access(lime.media.AudioBuffer)
|
||||
class HTML5AudioSource
|
||||
@@ -9,7 +10,7 @@ class HTML5AudioSource
|
||||
private var completed:Bool;
|
||||
private var gain:Float;
|
||||
private var id:Int;
|
||||
private var length:Float;
|
||||
private var length:Null<Float>;
|
||||
private var loops:Int;
|
||||
private var parent:AudioSource;
|
||||
private var playing:Bool;
|
||||
@@ -24,7 +25,9 @@ class HTML5AudioSource
|
||||
position = new Vector4();
|
||||
}
|
||||
|
||||
public function dispose():Void {}
|
||||
public function dispose():Void {
|
||||
stop();
|
||||
}
|
||||
|
||||
public function init():Void {}
|
||||
|
||||
@@ -98,8 +101,9 @@ class HTML5AudioSource
|
||||
{
|
||||
loops--;
|
||||
stop();
|
||||
// currentTime = 0;
|
||||
if (loopTime != null && loopTime > 0) setCurrentTime(loopTime);
|
||||
play();
|
||||
parent.onLoop.dispatch();
|
||||
return;
|
||||
}
|
||||
else if (parent.buffer != null && parent.buffer.__srcHowl != null)
|
||||
@@ -128,7 +132,7 @@ class HTML5AudioSource
|
||||
}
|
||||
else if (parent.buffer != null && parent.buffer.__srcHowl != null)
|
||||
{
|
||||
var time = (parent.buffer.__srcHowl.seek(id) * 1000) - parent.offset;
|
||||
var time = parent.buffer.__srcHowl.seek(id) * 1000.0 - parent.offset;
|
||||
if (time < 0) return 0;
|
||||
return time;
|
||||
}
|
||||
@@ -137,6 +141,20 @@ class HTML5AudioSource
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getLatency():Float
|
||||
{
|
||||
var ctx = AudioManager.context.web;
|
||||
if (ctx != null)
|
||||
{
|
||||
var baseLatency:Float = untyped ctx.baseLatency != null ? untyped ctx.baseLatency : 0;
|
||||
var outputLatency:Float = untyped ctx.outputLatency != null ? untyped ctx.outputLatency : 0;
|
||||
|
||||
return (baseLatency + outputLatency) * 1000;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setCurrentTime(value:Float):Float
|
||||
{
|
||||
#if lime_howlerjs
|
||||
@@ -172,7 +190,7 @@ class HTML5AudioSource
|
||||
return gain = value;
|
||||
}
|
||||
|
||||
public function getLength():Float
|
||||
public function getLength():Null<Float>
|
||||
{
|
||||
if (length != 0)
|
||||
{
|
||||
@@ -182,14 +200,14 @@ class HTML5AudioSource
|
||||
#if lime_howlerjs
|
||||
if (parent.buffer != null && parent.buffer.__srcHowl != null)
|
||||
{
|
||||
return parent.buffer.__srcHowl.duration() * 1000;
|
||||
return parent.buffer.__srcHowl.duration() * 1000.0;
|
||||
}
|
||||
#end
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setLength(value:Float):Float
|
||||
public function setLength(value:Null<Float>):Null<Float>
|
||||
{
|
||||
return length = value;
|
||||
}
|
||||
@@ -204,6 +222,14 @@ class HTML5AudioSource
|
||||
return loops = value;
|
||||
}
|
||||
|
||||
public function getLoopTime():Float {
|
||||
return loopTime;
|
||||
}
|
||||
|
||||
public function setLoopTime(value:Float):Float {
|
||||
return loopTime = value;
|
||||
}
|
||||
|
||||
public function getPitch():Float
|
||||
{
|
||||
#if lime_howlerjs
|
||||
@@ -218,9 +244,11 @@ class HTML5AudioSource
|
||||
#if lime_howlerjs
|
||||
parent.buffer.__srcHowl.rate(value);
|
||||
#end
|
||||
|
||||
return getPitch();
|
||||
}
|
||||
|
||||
|
||||
public function getPosition():Vector4
|
||||
{
|
||||
#if lime_howlerjs
|
||||
@@ -250,4 +278,4 @@ class HTML5AudioSource
|
||||
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,236 +1,193 @@
|
||||
package lime._internal.backend.native;
|
||||
|
||||
import haxe.Int64;
|
||||
import haxe.Timer;
|
||||
import lime.math.Vector4;
|
||||
import haxe.Int64;
|
||||
|
||||
import lime.media.openal.AL;
|
||||
import lime.media.openal.ALBuffer;
|
||||
import lime.media.openal.ALSource;
|
||||
import lime.media.vorbis.VorbisFile;
|
||||
import lime.media.AudioManager;
|
||||
|
||||
import lime.math.Vector4;
|
||||
import lime.media.AudioBuffer;
|
||||
import lime.media.AudioSource;
|
||||
import lime.utils.UInt8Array;
|
||||
import lime.utils.ArrayBufferView;
|
||||
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
@:access(lime.media.AudioBuffer)
|
||||
class NativeAudioSource
|
||||
{
|
||||
@:access(lime.utils.ArrayBufferView)
|
||||
@:access(lime.media.vorbis.VorbisFile)
|
||||
class NativeAudioSource {
|
||||
public static var initBuffers:Array<NativeAudioSource> = [];
|
||||
|
||||
private static var STREAM_BUFFER_SIZE = 48000;
|
||||
#if (native_audio_buffers && !macro)
|
||||
private static var STREAM_NUM_BUFFERS = Std.parseInt(haxe.macro.Compiler.getDefine("native_audio_buffers"));
|
||||
#else
|
||||
private static var STREAM_NUM_BUFFERS = 3;
|
||||
#end
|
||||
private static var STREAM_TIMER_FREQUENCY = 100;
|
||||
private static final STREAM_BUFFER_SIZE:Int = 0x8000;
|
||||
private static final STREAM_NUM_BUFFERS:Int = 5;
|
||||
private static final STREAM_TIMER_FREQUENCY:Int = 100;
|
||||
|
||||
//#if (lime >= "8.3.0")
|
||||
//private static var hasALSoftLatencyExt:Null<Bool>;
|
||||
//#end
|
||||
|
||||
private var buffers:Array<ALBuffer>;
|
||||
private var bufferDatas:Array<ArrayBufferView>;
|
||||
private var bufferTimeBlocks:Array<Float>;
|
||||
private var completed:Bool;
|
||||
private var dataLength:Int;
|
||||
private var format:Int;
|
||||
private var handle:ALSource;
|
||||
private var bufferLoops:Int;
|
||||
private var queuedBuffers:Int;
|
||||
private var requestBuffers:Int;
|
||||
|
||||
private var length:Null<Float>;
|
||||
private var loops:Int;
|
||||
private var parent:AudioSource;
|
||||
private var loopTime:Null<Float>;
|
||||
private var playing:Bool;
|
||||
private var loops:Int;
|
||||
private var position:Vector4;
|
||||
private var samples:Float;
|
||||
|
||||
private var bitsPerSample:Int;
|
||||
private var format:Int;
|
||||
private var dataLength:Int64;
|
||||
private var samples:Int64;
|
||||
private var completed:Bool;
|
||||
private var stream:Bool;
|
||||
private var streamTimer:Timer;
|
||||
|
||||
private var handle:ALSource;
|
||||
private var parent:AudioSource;
|
||||
private var timer:Timer;
|
||||
private var streamTimer:Timer;
|
||||
private var disposed:Bool;
|
||||
private var safeEnd:Bool;
|
||||
|
||||
private var pcmTotal:Int64;
|
||||
private var sampleRate:Int;
|
||||
|
||||
var disposed:Bool = false;
|
||||
|
||||
public function new(parent:AudioSource)
|
||||
{
|
||||
public function new(parent:AudioSource) {
|
||||
this.parent = parent;
|
||||
|
||||
position = new Vector4();
|
||||
}
|
||||
|
||||
public function dispose():Void
|
||||
{
|
||||
if (handle != null)
|
||||
{
|
||||
stop();
|
||||
public function dispose():Void {
|
||||
disposed = true;
|
||||
stop();
|
||||
|
||||
if (handle != null) {
|
||||
AL.sourcei(handle, AL.BUFFER, null);
|
||||
AL.deleteSource(handle);
|
||||
if (buffers != null)
|
||||
{
|
||||
for (buffer in buffers)
|
||||
{
|
||||
AL.deleteBuffer(buffer);
|
||||
}
|
||||
buffers = null;
|
||||
}
|
||||
handle = null;
|
||||
}
|
||||
disposed = true;
|
||||
handle = null;
|
||||
|
||||
if (buffers != null) AL.deleteBuffers(buffers);
|
||||
buffers = null;
|
||||
|
||||
initBuffers.remove(this);
|
||||
}
|
||||
|
||||
public function init():Void
|
||||
{
|
||||
dataLength = 0;
|
||||
format = 0;
|
||||
public function init():Void {
|
||||
//#if (lime >= "8.3.0")
|
||||
//if (hasALSoftLatencyExt == null) hasALSoftLatencyExt = AL.isExtensionPresent("AL_SOFT_source_latency");
|
||||
//#end
|
||||
|
||||
if (parent.buffer.channels == 1)
|
||||
{
|
||||
if (parent.buffer.bitsPerSample == 8)
|
||||
{
|
||||
format = AL.FORMAT_MONO8;
|
||||
}
|
||||
else if (parent.buffer.bitsPerSample == 16)
|
||||
{
|
||||
format = AL.FORMAT_MONO16;
|
||||
}
|
||||
}
|
||||
else if (parent.buffer.channels == 2)
|
||||
{
|
||||
if (parent.buffer.bitsPerSample == 8)
|
||||
{
|
||||
format = AL.FORMAT_STEREO8;
|
||||
}
|
||||
else if (parent.buffer.bitsPerSample == 16)
|
||||
{
|
||||
format = AL.FORMAT_STEREO16;
|
||||
}
|
||||
}
|
||||
if (handle != null) return;
|
||||
|
||||
if (parent.buffer.__srcVorbisFile != null)
|
||||
{
|
||||
stream = true;
|
||||
var buffer = parent.buffer;
|
||||
if (disposed = (handle = AL.createSource()) == null) return;
|
||||
|
||||
var vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
pcmTotal = vorbisFile.pcmTotal();
|
||||
var info = vorbisFile.info();
|
||||
sampleRate = info.rate;
|
||||
var _dataLength = pcmTotal * Int64.ofInt(parent.buffer.channels) * (Int64.ofInt(parent.buffer.bitsPerSample) / Int64.ofInt(8));
|
||||
dataLength = Int64.toInt(_dataLength);
|
||||
AL.sourcef(handle, AL.MAX_GAIN, 10);
|
||||
bufferLoops = 0;
|
||||
|
||||
buffers = new Array();
|
||||
bufferTimeBlocks = new Array();
|
||||
bitsPerSample = buffer.bitsPerSample;
|
||||
if (buffer.channels > 1) format = bitsPerSample == 8 ? AL.FORMAT_STEREO8 : AL.FORMAT_STEREO16;
|
||||
else format = bitsPerSample == 8 ? AL.FORMAT_MONO8 : AL.FORMAT_MONO16;
|
||||
|
||||
for (i in 0...STREAM_NUM_BUFFERS)
|
||||
{
|
||||
var vorbisFile = buffer.__srcVorbisFile;
|
||||
if (stream = vorbisFile != null) {
|
||||
dataLength = (samples = vorbisFile.pcmTotal()) * buffer.channels * (Int64.ofInt(bitsPerSample) / 8);
|
||||
buffers = [];
|
||||
bufferDatas = [];
|
||||
bufferTimeBlocks = [];
|
||||
|
||||
var constructor = bitsPerSample == 8 ? Int8 : Int16;
|
||||
for (i in 0...STREAM_NUM_BUFFERS) {
|
||||
buffers.push(AL.createBuffer());
|
||||
bufferDatas.push(new ArrayBufferView(STREAM_BUFFER_SIZE, constructor));
|
||||
bufferTimeBlocks.push(0);
|
||||
}
|
||||
|
||||
handle = AL.createSource();
|
||||
|
||||
samples = Int64.toInt(pcmTotal);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent.buffer.__srcBuffer == null)
|
||||
{
|
||||
parent.buffer.__srcBuffer = AL.createBuffer();
|
||||
else {
|
||||
samples = ((dataLength = buffer.data.length) * 8) / (buffer.channels * bitsPerSample);
|
||||
|
||||
if (parent.buffer.__srcBuffer != null)
|
||||
{
|
||||
AL.bufferData(parent.buffer.__srcBuffer, format, parent.buffer.data, parent.buffer.data.length, parent.buffer.sampleRate);
|
||||
}
|
||||
}
|
||||
if (buffer.__srcBuffer == null && (buffer.__srcBuffer = AL.createBuffer()) != null)
|
||||
AL.bufferData(buffer.__srcBuffer, format, buffer.data, buffer.data.length, buffer.sampleRate);
|
||||
|
||||
dataLength = parent.buffer.data.length;
|
||||
|
||||
handle = AL.createSource();
|
||||
|
||||
if (handle != null)
|
||||
{
|
||||
AL.sourcei(handle, AL.BUFFER, parent.buffer.__srcBuffer);
|
||||
}
|
||||
|
||||
samples = Int64.toInt((Int64.make(0, dataLength) * Int64.ofInt(8)) / Int64.ofInt(parent.buffer.channels * parent.buffer.bitsPerSample));
|
||||
AL.sourcei(handle, AL.BUFFER, buffer.__srcBuffer);
|
||||
}
|
||||
|
||||
initBuffers.push(this);
|
||||
|
||||
if (dataLength == 0) {
|
||||
trace('NativeAudioSource Bug! dataLength is 0');
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public function play():Void
|
||||
{
|
||||
if (disposed) {
|
||||
trace("TRIED TO PLAY DISPOSED?? WTFFF");
|
||||
trace(haxe.CallStack.toString(haxe.CallStack.callStack()));
|
||||
}
|
||||
if (playing || handle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public function play():Void {
|
||||
if (playing || disposed) return;
|
||||
|
||||
playing = true;
|
||||
|
||||
if (stream)
|
||||
{
|
||||
setCurrentTime(getCurrentTime());
|
||||
|
||||
streamTimer = new Timer(STREAM_TIMER_FREQUENCY);
|
||||
streamTimer.run = streamTimer_onRun;
|
||||
}
|
||||
else
|
||||
{
|
||||
var time = completed ? 0 : getCurrentTime();
|
||||
|
||||
setCurrentTime(time);
|
||||
}
|
||||
if (completed) setCurrentTime(0);
|
||||
else setCurrentTime(getCurrentTime());
|
||||
}
|
||||
|
||||
public function pause():Void
|
||||
{
|
||||
public function pause():Void {
|
||||
if (!(disposed = handle == null)) AL.sourcePause(handle);
|
||||
|
||||
playing = false;
|
||||
|
||||
if (handle == null) return;
|
||||
AL.sourcePause(handle);
|
||||
|
||||
if (streamTimer != null)
|
||||
{
|
||||
streamTimer.stop();
|
||||
}
|
||||
|
||||
if (timer != null)
|
||||
{
|
||||
timer.stop();
|
||||
}
|
||||
stopStreamTimer();
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
private function readVorbisFileBuffer(vorbisFile:VorbisFile, length:Int):UInt8Array
|
||||
{
|
||||
#if lime_vorbis
|
||||
var buffer = new UInt8Array(length);
|
||||
var read = 0, total = 0, readMax;
|
||||
|
||||
for (i in 0...STREAM_NUM_BUFFERS-1)
|
||||
{
|
||||
bufferTimeBlocks[i] = bufferTimeBlocks[i + 1];
|
||||
public function stop():Void {
|
||||
if (!(disposed = handle == null)) {
|
||||
if (AL.getSourcei(handle, AL.SOURCE_STATE) != AL.STOPPED) AL.sourceStop(handle);
|
||||
if (stream) AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED) + AL.getSourcei(handle, AL.BUFFERS_PROCESSED));
|
||||
}
|
||||
bufferTimeBlocks[STREAM_NUM_BUFFERS-1] = vorbisFile.timeTell();
|
||||
|
||||
while (total < length)
|
||||
{
|
||||
readMax = 4096;
|
||||
requestBuffers = queuedBuffers = bufferLoops = 0;
|
||||
playing = false;
|
||||
stopStreamTimer();
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
if (readMax > length - total)
|
||||
{
|
||||
readMax = length - total;
|
||||
private function complete():Void {
|
||||
stop();
|
||||
|
||||
completed = true;
|
||||
parent.onComplete.dispatch();
|
||||
}
|
||||
|
||||
private function readVorbisFileBuffer(vorbisFile:VorbisFile, max:Int):ArrayBufferView {
|
||||
#if lime_vorbis
|
||||
var id = STREAM_NUM_BUFFERS - requestBuffers, read = STREAM_NUM_BUFFERS - 1, total = 0, readMax;
|
||||
var buffer = bufferDatas[id];
|
||||
|
||||
for (i in id...read) {
|
||||
bufferTimeBlocks[i] = bufferTimeBlocks[i + 1];
|
||||
bufferDatas[i] = bufferDatas[i + 1];
|
||||
}
|
||||
bufferTimeBlocks[read] = vorbisFile.timeTell();
|
||||
bufferDatas[read] = buffer;
|
||||
queuedBuffers = requestBuffers;
|
||||
|
||||
while (total < STREAM_BUFFER_SIZE) {
|
||||
if ((readMax = 4096) > (read = max - total)) readMax = read;
|
||||
if (vorbisFile.handle == null) break;
|
||||
if (readMax > 0 && (read = vorbisFile.read(buffer.buffer, total, readMax)) > 0) total += read;
|
||||
else if (safeEnd = (loops > bufferLoops)) {
|
||||
if (readMax == 4096) continue;
|
||||
bufferLoops++; vorbisFile.timeSeek((loopTime != null ? Math.max(0, loopTime / 1000) : 0) + parent.offset / 1000);
|
||||
if ((max = (dataLength - (vorbisFile.pcmTell() * (Int64.ofInt(bitsPerSample) / 8) * parent.buffer.channels)).low) > STREAM_BUFFER_SIZE)
|
||||
max = STREAM_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
read = vorbisFile.read(buffer.buffer, total, readMax);
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
total += read;
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
buffer.buffer.fill(total, STREAM_BUFFER_SIZE - total - 1, 0);
|
||||
resetTimer((getLength() - getCurrentTime()) / getPitch());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -241,344 +198,281 @@ class NativeAudioSource
|
||||
#end
|
||||
}
|
||||
|
||||
private function refillBuffers(buffers:Array<ALBuffer> = null):Void
|
||||
{
|
||||
private function fillBuffers(buffers:Array<ALBuffer>):Void {
|
||||
#if lime_vorbis
|
||||
var vorbisFile = null;
|
||||
var position = 0;
|
||||
if (parent == null || parent.buffer == null) return dispose();
|
||||
if (handle == null || buffers.length < 1) return;
|
||||
|
||||
if (buffers == null)
|
||||
{
|
||||
var buffersProcessed:Int = AL.getSourcei(handle, AL.BUFFERS_PROCESSED);
|
||||
final buffer = parent.buffer, vorbisFile = buffer.__srcVorbisFile;
|
||||
final actualDataRate = (Int64.ofInt(bitsPerSample) / 8) * buffer.channels;
|
||||
var position = vorbisFile.pcmTell() * actualDataRate, length = getLengthSamples() * actualDataRate, trackLoops = bufferLoops;
|
||||
if (position >= length && safeEnd) return;
|
||||
|
||||
if (buffersProcessed > 0)
|
||||
{
|
||||
vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
position = Int64.toInt(vorbisFile.pcmTell());
|
||||
var sampleRate = buffer.sampleRate, numBuffers = 0, data, size:Int64;
|
||||
for (buffer in buffers) {
|
||||
if ((size = length - position) > STREAM_BUFFER_SIZE) size = Int64.ofInt(STREAM_BUFFER_SIZE);
|
||||
data = readVorbisFileBuffer(vorbisFile, Int64.toInt(size));
|
||||
|
||||
if (position < dataLength)
|
||||
{
|
||||
buffers = AL.sourceUnqueueBuffers(handle, buffersProcessed);
|
||||
}
|
||||
if (disposed) return;
|
||||
AL.bufferData(buffer, format, data, STREAM_BUFFER_SIZE, sampleRate);
|
||||
numBuffers++;
|
||||
|
||||
if (safeEnd) break;
|
||||
else if (bufferLoops != trackLoops) {
|
||||
position = vorbisFile.pcmTell() * actualDataRate;
|
||||
trackLoops = bufferLoops;
|
||||
}
|
||||
}
|
||||
AL.sourceQueueBuffers(handle, numBuffers, buffers);
|
||||
|
||||
if (buffers != null)
|
||||
{
|
||||
if (vorbisFile == null)
|
||||
{
|
||||
vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
position = Int64.toInt(vorbisFile.pcmTell());
|
||||
}
|
||||
|
||||
var numBuffers = 0;
|
||||
var data;
|
||||
|
||||
for (buffer in buffers)
|
||||
{
|
||||
if (dataLength - position >= STREAM_BUFFER_SIZE)
|
||||
{
|
||||
data = readVorbisFileBuffer(vorbisFile, STREAM_BUFFER_SIZE);
|
||||
AL.bufferData(buffer, format, data, data.length, parent.buffer.sampleRate);
|
||||
position += STREAM_BUFFER_SIZE;
|
||||
numBuffers++;
|
||||
}
|
||||
else if (position < dataLength)
|
||||
{
|
||||
data = readVorbisFileBuffer(vorbisFile, dataLength - position);
|
||||
AL.bufferData(buffer, format, data, data.length, parent.buffer.sampleRate);
|
||||
numBuffers++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AL.sourceQueueBuffers(handle, numBuffers, buffers);
|
||||
|
||||
// OpenAL can unexpectedly stop playback if the buffers run out
|
||||
// of data, which typically happens if an operation (such as
|
||||
// resizing a window) freezes the main thread.
|
||||
// If AL is supposed to be playing but isn't, restart it here.
|
||||
if (playing && handle != null && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED)
|
||||
{
|
||||
AL.sourcePlay(handle);
|
||||
if (AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) {
|
||||
if (bufferLoops > 0) { // TODO: remove this once buffer loop stutters fixed
|
||||
setCurrentTime(getCurrentTime());
|
||||
return;
|
||||
}
|
||||
AL.sourcePlay(handle);
|
||||
resetTimer(Std.int((getLength() - getCurrentTime()) / getPitch()));
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public function stop():Void
|
||||
{
|
||||
if (playing && handle != null && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING)
|
||||
{
|
||||
AL.sourceStop(handle);
|
||||
private function streamRun():Void {
|
||||
#if lime_vorbis
|
||||
if (disposed = (handle == null || buffers == null)) return dispose();
|
||||
|
||||
var vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
if (vorbisFile == null) return dispose();
|
||||
|
||||
try {
|
||||
var processed = AL.getSourcei(handle, AL.BUFFERS_PROCESSED);
|
||||
if (processed > 0 && playing) {
|
||||
fillBuffers(AL.sourceUnqueueBuffers(handle, processed));
|
||||
if ((!safeEnd || loops > 0) && queuedBuffers < STREAM_NUM_BUFFERS && playing) {
|
||||
/*if (requestBuffers >= STREAM_NUM_BUFFERS) {
|
||||
AL.sourceUnqueueBuffers(handle, queuedBuffers);
|
||||
setCurrentTime((bufferTimeBlocks[STREAM_NUM_BUFFERS - queuedBuffers] +
|
||||
(STREAM_BUFFER_SIZE / ((bitsPerSample / 8) * parent.buffer.channels) / parent.buffer.sampleRate)
|
||||
) * 1000 - parent.offset);
|
||||
}
|
||||
else*/fillBuffers([buffers[++requestBuffers - 1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playing = false;
|
||||
|
||||
if (streamTimer != null)
|
||||
{
|
||||
streamTimer.stop();
|
||||
}
|
||||
|
||||
if (timer != null)
|
||||
{
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
setCurrentTime(0);
|
||||
catch(e) trace(e);
|
||||
#end
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
private function streamTimer_onRun():Void
|
||||
{
|
||||
refillBuffers();
|
||||
// Timers
|
||||
inline function stopStreamTimer():Void if (streamTimer != null) streamTimer.stop();
|
||||
|
||||
private function resetStreamTimer():Void {
|
||||
stopStreamTimer();
|
||||
|
||||
#if lime_vorbis
|
||||
streamTimer = new Timer(STREAM_TIMER_FREQUENCY);
|
||||
streamTimer.run = streamRun;
|
||||
#end
|
||||
}
|
||||
|
||||
private function timer_onRun():Void
|
||||
{
|
||||
if (loops > 0)
|
||||
{
|
||||
playing = false;
|
||||
loops--;
|
||||
setCurrentTime(0);
|
||||
play();
|
||||
inline function stopTimer():Void if (timer != null) timer.stop();
|
||||
|
||||
private function resetTimer(timeRemaining:Float):Void {
|
||||
stopTimer();
|
||||
|
||||
/*if (timeRemaining <= 26) {
|
||||
timer_onRun();
|
||||
return;
|
||||
}*/
|
||||
timer = new Timer(timeRemaining);
|
||||
timer.run = timer_onRun;
|
||||
}
|
||||
|
||||
private function timer_onRun():Void {
|
||||
if (!safeEnd && bufferLoops <= 0) {
|
||||
#if lime_vorbis
|
||||
var ranOut = false;
|
||||
if (stream) {
|
||||
var vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
if (vorbisFile == null) return dispose();
|
||||
ranOut = vorbisFile.pcmTell() >= getLengthSamples() || queuedBuffers < 3;
|
||||
}
|
||||
|
||||
if (!ranOut)
|
||||
#end
|
||||
{
|
||||
var timeRemaining = (getLength() - getCurrentTime()) / getPitch();
|
||||
if (timeRemaining > 100 && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING) {
|
||||
resetTimer(timeRemaining);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
safeEnd = false;
|
||||
|
||||
if (loops <= 0) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
completed = true;
|
||||
parent.onComplete.dispatch();
|
||||
if (bufferLoops <= 0) {
|
||||
loops--;
|
||||
var loopTime = loopTime ?? 0;
|
||||
if (stream || loopTime > 0) setCurrentTime(loopTime);
|
||||
else if (!stream) {
|
||||
setLoops(loops);
|
||||
if (AL.getSourcei(handle, AL.SOURCE_STATE) != AL.PLAYING)
|
||||
setCurrentTime(0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
loops -= bufferLoops;
|
||||
bufferLoops = 0;
|
||||
}
|
||||
parent.onLoop.dispatch();
|
||||
}
|
||||
|
||||
// Get & Set Methods
|
||||
public function getCurrentTime():Float
|
||||
{
|
||||
if (completed)
|
||||
{
|
||||
return getLength();
|
||||
}
|
||||
else if (handle != null)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
var time = (Std.int(bufferTimeBlocks[0] * 1000) + Std.int(AL.getSourcef(handle, AL.SEC_OFFSET) * 1000)) - parent.offset;
|
||||
if (time < 0) return 0;
|
||||
return time;
|
||||
}
|
||||
else
|
||||
{
|
||||
var offset:Int = AL.getSourcei(handle, AL.BYTE_OFFSET);
|
||||
var ratio = (offset / dataLength);
|
||||
var totalSeconds = samples / parent.buffer.sampleRate;
|
||||
|
||||
var time = totalSeconds * ratio * 1000 - parent.offset;
|
||||
|
||||
// var time = Std.int (AL.getSourcef (handle, AL.SEC_OFFSET) * 1000) - parent.offset;
|
||||
if (time < 0) return 0;
|
||||
return time;
|
||||
public function getCurrentTime():Float {
|
||||
if (completed) return getLength();
|
||||
else if (!disposed) {
|
||||
var time = AL.getSourcef(handle, AL.SEC_OFFSET);
|
||||
if (stream) time += bufferTimeBlocks[STREAM_NUM_BUFFERS - queuedBuffers];
|
||||
time = time * 1000 - parent.offset;
|
||||
if (loops > 0 && time > getLength()) {
|
||||
var start = loopTime != null ? Math.max(0, loopTime + parent.offset) : parent.offset;
|
||||
return ((time - start) % (getLength() - start)) + start;
|
||||
}
|
||||
if (time > 0) return time;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setCurrentTime(value:Float):Float
|
||||
{
|
||||
// `setCurrentTime()` has side effects and is never safe to skip.
|
||||
/* if (value == getCurrentTime())
|
||||
{
|
||||
return value;
|
||||
} */
|
||||
public function setCurrentTime(value:Float):Float {
|
||||
if (disposed = (handle == null)) return value;
|
||||
|
||||
if (handle != null)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
AL.sourceStop(handle);
|
||||
var total = getRealLength();
|
||||
var time = Math.max(0, Math.min(total, value + parent.offset)), ratio = time / total;
|
||||
|
||||
parent.buffer.__srcVorbisFile.timeSeek((value + parent.offset) / 1000);
|
||||
AL.sourceUnqueueBuffers(handle, STREAM_NUM_BUFFERS);
|
||||
refillBuffers(buffers);
|
||||
if (stream) {
|
||||
// TODO: smooth setCurrentTime for stream (dont refill buffers again)
|
||||
|
||||
if (playing) AL.sourcePlay(handle);
|
||||
}
|
||||
else if (parent.buffer != null)
|
||||
{
|
||||
AL.sourceRewind(handle);
|
||||
|
||||
// AL.sourcef (handle, AL.SEC_OFFSET, (value + parent.offset) / 1000);
|
||||
|
||||
var secondOffset = (value + parent.offset) / 1000;
|
||||
var totalSeconds = samples / parent.buffer.sampleRate;
|
||||
|
||||
if (secondOffset < 0) secondOffset = 0;
|
||||
else if (secondOffset > totalSeconds) secondOffset = totalSeconds;
|
||||
|
||||
var ratio = (secondOffset / totalSeconds);
|
||||
var totalOffset = Std.int(dataLength * ratio);
|
||||
|
||||
AL.sourcei(handle, AL.BYTE_OFFSET, totalOffset);
|
||||
if (playing) AL.sourcePlay(handle);
|
||||
AL.sourceStop(handle);
|
||||
AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED));
|
||||
|
||||
#if lime_vorbis
|
||||
var vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
if (vorbisFile != null) {
|
||||
// var chunk = Std.int(Math.floor(getFloat(samples) * ratio / STREAM_BUFFER_SIZE) * STREAM_BUFFER_SIZE);
|
||||
vorbisFile.pcmSeek(Int64.fromFloat(getFloat(samples) * ratio));
|
||||
fillBuffers(buffers.slice(0, requestBuffers = queuedBuffers = 3));
|
||||
// AL.sourcei(handle, AL.SAMPLE_OFFSET, Std.int((samples * ratio) - chunk));
|
||||
if (playing) resetStreamTimer();
|
||||
}
|
||||
#end
|
||||
}
|
||||
else {
|
||||
AL.sourceRewind(handle);
|
||||
AL.sourcei(handle, AL.BYTE_OFFSET, Int64.fromFloat(getFloat(dataLength) * ratio));
|
||||
}
|
||||
|
||||
if (playing)
|
||||
{
|
||||
if (timer != null)
|
||||
{
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
var timeRemaining = (getLength() - value) / getPitch();
|
||||
|
||||
if (timeRemaining > 0)
|
||||
{
|
||||
completed = false;
|
||||
timer = new Timer(timeRemaining);
|
||||
timer.run = timer_onRun;
|
||||
}
|
||||
else
|
||||
{
|
||||
playing = false;
|
||||
completed = true;
|
||||
if (playing) {
|
||||
var timeRemaining = (getLength() - time) / getPitch();
|
||||
if (completed = timeRemaining <= 0) complete();
|
||||
else {
|
||||
AL.sourcePlay(handle);
|
||||
resetTimer(timeRemaining);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public function getGain():Float
|
||||
{
|
||||
if (handle != null)
|
||||
{
|
||||
return AL.getSourcef(handle, AL.GAIN);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
inline private function getLengthSamples():Int64 {
|
||||
return if (length == null) samples;
|
||||
else Int64.fromFloat((length + parent.offset) / 1000 * parent.buffer.sampleRate);
|
||||
}
|
||||
|
||||
inline private function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >>> 0);
|
||||
|
||||
inline private function getRealLength():Float return getFloat(samples) / parent.buffer.sampleRate * 1000;
|
||||
|
||||
public function getLength():Null<Float> {
|
||||
return if (length == null) getRealLength() - parent.offset;
|
||||
else length - parent.offset;
|
||||
}
|
||||
|
||||
public function setGain(value:Float):Float
|
||||
{
|
||||
if (handle != null)
|
||||
{
|
||||
AL.sourcef(handle, AL.GAIN, value);
|
||||
}
|
||||
public function setLength(value:Null<Float>):Null<Float> {
|
||||
if (value == length) return value;
|
||||
length = value;
|
||||
|
||||
if (playing) {
|
||||
var timeRemaining = ((getLength() - parent.offset) - getCurrentTime()) / getPitch();
|
||||
if (timeRemaining > 0) resetTimer(timeRemaining);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public function getLength():Float
|
||||
{
|
||||
if (length != null)
|
||||
{
|
||||
return length;
|
||||
public function getPitch():Float {
|
||||
return if (disposed) 1;
|
||||
else AL.getSourcef(handle, AL.PITCH);
|
||||
}
|
||||
|
||||
public function setPitch(value:Float):Float {
|
||||
if (disposed || value == AL.getSourcef(handle, AL.PITCH)) return value;
|
||||
if (playing) {
|
||||
var timeRemaining = (getLength() - getCurrentTime()) / value;
|
||||
if (timeRemaining > 0) resetTimer(timeRemaining);
|
||||
}
|
||||
|
||||
return (samples / parent.buffer.sampleRate * 1000) - parent.offset;
|
||||
AL.sourcef(handle, AL.PITCH, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public function setLength(value:Float):Float
|
||||
{
|
||||
if (playing && length != value)
|
||||
{
|
||||
if (timer != null)
|
||||
{
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
var timeRemaining = (value - getCurrentTime()) / getPitch();
|
||||
|
||||
if (timeRemaining > 0)
|
||||
{
|
||||
timer = new Timer(timeRemaining);
|
||||
timer.run = timer_onRun;
|
||||
}
|
||||
}
|
||||
|
||||
return length = value;
|
||||
public function getGain():Float {
|
||||
if (disposed) return 1;
|
||||
return AL.getSourcef(handle, AL.GAIN);
|
||||
}
|
||||
|
||||
public function getLoops():Int
|
||||
{
|
||||
return loops;
|
||||
public function setGain(value:Float):Float {
|
||||
if (!disposed) AL.sourcef(handle, AL.GAIN, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public function setLoops(value:Int):Int
|
||||
{
|
||||
inline public function getLoops():Int return loops;
|
||||
|
||||
inline public function setLoops(value:Int):Int {
|
||||
if (!stream && !disposed) AL.sourcei(handle, AL.LOOPING, (loopTime <= 0 && value > 0) ? AL.TRUE : AL.FALSE);
|
||||
return loops = value;
|
||||
}
|
||||
|
||||
public function getPitch():Float
|
||||
{
|
||||
if (handle != null)
|
||||
{
|
||||
return AL.getSourcef(handle, AL.PITCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
inline public function getLoopTime():Float return loopTime;
|
||||
|
||||
inline public function setLoopTime(value:Float):Float {
|
||||
if (!stream && !disposed) AL.sourcei(handle, AL.LOOPING, (value <= 0 && loops > 0) ? AL.TRUE : AL.FALSE);
|
||||
return loopTime = value;
|
||||
}
|
||||
|
||||
public function setPitch(value:Float):Float
|
||||
{
|
||||
if (playing && value != getPitch())
|
||||
{
|
||||
if (timer != null)
|
||||
{
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
var timeRemaining = (getLength() - getCurrentTime()) / value;
|
||||
|
||||
if (timeRemaining > 0)
|
||||
{
|
||||
timer = new Timer(timeRemaining);
|
||||
timer.run = timer_onRun;
|
||||
}
|
||||
public function getLatency():Float {
|
||||
/*#if (lime >= "8.3.0")
|
||||
if (hasALSoftLatencyExt) {
|
||||
final offsets = AL.getSourcedvSOFT(handle, AL.SEC_OFFSET_LATENCY_SOFT, 2);
|
||||
if (offsets != null) return offsets[1] * 1000;
|
||||
}
|
||||
|
||||
if (handle != null)
|
||||
{
|
||||
AL.sourcef(handle, AL.PITCH, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
#end*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getPosition():Vector4
|
||||
{
|
||||
if (handle != null)
|
||||
{
|
||||
#if !emscripten
|
||||
var value = AL.getSource3f(handle, AL.POSITION);
|
||||
position.x = value[0];
|
||||
position.y = value[1];
|
||||
position.z = value[2];
|
||||
#end
|
||||
}
|
||||
public function getPosition():Vector4 return position;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public function setPosition(value:Vector4):Vector4
|
||||
{
|
||||
public function setPosition(value:Vector4):Vector4 {
|
||||
position.x = value.x;
|
||||
position.y = value.y;
|
||||
position.z = value.z;
|
||||
position.w = value.w;
|
||||
|
||||
if (handle != null)
|
||||
{
|
||||
if (!disposed) {
|
||||
AL.distanceModel(AL.NONE);
|
||||
AL.source3f(handle, AL.POSITION, position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
package lime.media;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime._internal.format.Base64;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
import lime.media.openal.AL;
|
||||
import lime.media.openal.ALBuffer;
|
||||
import lime.media.vorbis.VorbisFile;
|
||||
import lime.net.HTTPRequest;
|
||||
import lime.utils.Log;
|
||||
import lime.utils.UInt8Array;
|
||||
#if lime_howlerjs
|
||||
import lime.media.howlerjs.Howl;
|
||||
#end
|
||||
#if (js && html5)
|
||||
import js.html.Audio;
|
||||
#elseif flash
|
||||
import flash.media.Sound;
|
||||
import flash.net.URLRequest;
|
||||
#end
|
||||
|
||||
@:access(lime._internal.backend.native.NativeCFFI)
|
||||
@:access(lime.utils.Assets)
|
||||
#if hl
|
||||
@:keep
|
||||
#end
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
|
||||
/**
|
||||
The `AudioBuffer` class represents a buffer of audio data that can be played back using an `AudioSource`.
|
||||
It supports a variety of audio formats and platforms, providing a consistent API for loading and managing audio data.
|
||||
|
||||
Depending on the platform, the audio backend may differ, but the class provides a unified interface for accessing
|
||||
audio data, whether it's stored in memory, loaded from a file, or streamed.
|
||||
|
||||
@see lime.media.AudioSource
|
||||
**/
|
||||
class AudioBuffer
|
||||
{
|
||||
/**
|
||||
The number of bits per sample in the audio data.
|
||||
**/
|
||||
public var bitsPerSample:Int;
|
||||
|
||||
/**
|
||||
The number of audio channels (e.g., 1 for mono, 2 for stereo).
|
||||
**/
|
||||
public var channels:Int;
|
||||
|
||||
/**
|
||||
The raw audio data stored as a `UInt8Array`.
|
||||
**/
|
||||
public var data:UInt8Array;
|
||||
|
||||
/**
|
||||
The sample rate of the audio data, in Hz.
|
||||
**/
|
||||
public var sampleRate:Int;
|
||||
|
||||
/**
|
||||
The source of the audio data. This can be an `Audio`, `Sound`, `Howl`, or other platform-specific object.
|
||||
**/
|
||||
public var src(get, set):Dynamic;
|
||||
|
||||
@:noCompletion private var __srcAudio:#if (js && html5) Audio #else Dynamic #end;
|
||||
@:noCompletion private var __srcBuffer:#if lime_cffi ALBuffer #else Dynamic #end;
|
||||
@:noCompletion private var __srcCustom:Dynamic;
|
||||
@:noCompletion private var __srcHowl:#if lime_howlerjs Howl #else Dynamic #end;
|
||||
@:noCompletion private var __srcSound:#if flash Sound #else Dynamic #end;
|
||||
@:noCompletion private var __srcVorbisFile:#if lime_vorbis VorbisFile #else Dynamic #end;
|
||||
|
||||
#if commonjs
|
||||
private static function __init__()
|
||||
{
|
||||
var p = untyped AudioBuffer.prototype;
|
||||
untyped Object.defineProperties(p,
|
||||
{
|
||||
"src": {get: p.get_src, set: p.set_src}
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
Creates a new, empty `AudioBuffer` instance.
|
||||
**/
|
||||
public function new() {}
|
||||
|
||||
/**
|
||||
Disposes of the resources used by this `AudioBuffer`, such as unloading any associated audio data.
|
||||
**/
|
||||
public function dispose():Void
|
||||
{
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
if (__srcHowl != null) __srcHowl.unload();
|
||||
__srcHowl = null;
|
||||
#end
|
||||
#if lime_cffi
|
||||
if (__srcBuffer != null) AL.deleteBuffer(__srcBuffer);
|
||||
__srcBuffer = null;
|
||||
#end
|
||||
#if lime_vorbis
|
||||
if (__srcVorbisFile != null) {
|
||||
__srcVorbisFile.clear();
|
||||
@:privateAccess __srcVorbisFile.handle = null;
|
||||
}
|
||||
__srcVorbisFile = null;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an `AudioBuffer` from a Base64-encoded string.
|
||||
|
||||
@param base64String The Base64-encoded audio data.
|
||||
@return An `AudioBuffer` instance with the decoded audio data.
|
||||
**/
|
||||
public static function fromBase64(base64String:String):AudioBuffer
|
||||
{
|
||||
if (base64String == null) return null;
|
||||
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
// if base64String doesn't contain codec data, add it.
|
||||
if (base64String.indexOf(",") == -1)
|
||||
{
|
||||
base64String = "data:" + __getCodec(Base64.decode(base64String)) + ";base64," + base64String;
|
||||
}
|
||||
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.src = new Howl({src: [base64String], html5: true, preload: false});
|
||||
return audioBuffer;
|
||||
#elseif (lime_cffi && !macro)
|
||||
#if !cs
|
||||
// if base64String contains codec data, strip it then decode it.
|
||||
var base64StringSplit = base64String.split(",");
|
||||
var base64StringNoEncoding = base64StringSplit[base64StringSplit.length - 1];
|
||||
var bytes:Bytes = Base64.decode(base64StringNoEncoding);
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.data = new UInt8Array(Bytes.alloc(0));
|
||||
|
||||
return NativeCFFI.lime_audio_load_bytes(bytes, audioBuffer);
|
||||
#else
|
||||
// if base64String contains codec data, strip it then decode it.
|
||||
var base64StringSplit = base64String.split(",");
|
||||
var base64StringNoEncoding = base64StringSplit[base64StringSplit.length - 1];
|
||||
var bytes:Bytes = Base64.decode(base64StringNoEncoding);
|
||||
var data:Dynamic = NativeCFFI.lime_audio_load_bytes(bytes, null);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.bitsPerSample = data.bitsPerSample;
|
||||
audioBuffer.channels = data.channels;
|
||||
audioBuffer.data = new UInt8Array(@:privateAccess new Bytes(data.data.length, data.data.b));
|
||||
audioBuffer.sampleRate = data.sampleRate;
|
||||
return audioBuffer;
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an `AudioBuffer` from a `Bytes` object.
|
||||
|
||||
@param bytes The `Bytes` object containing the audio data.
|
||||
@return An `AudioBuffer` instance with the decoded audio data.
|
||||
**/
|
||||
public static function fromBytes(bytes:Bytes):AudioBuffer
|
||||
{
|
||||
if (bytes == null) return null;
|
||||
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.src = new Howl({src: ["data:" + __getCodec(bytes) + ";base64," + Base64.encode(bytes)], html5: true, preload: false});
|
||||
|
||||
return audioBuffer;
|
||||
#elseif (lime_cffi && !macro)
|
||||
#if !cs
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.data = new UInt8Array(Bytes.alloc(0));
|
||||
|
||||
return NativeCFFI.lime_audio_load_bytes(bytes, audioBuffer);
|
||||
#else
|
||||
var data:Dynamic = NativeCFFI.lime_audio_load_bytes(bytes, null);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.bitsPerSample = data.bitsPerSample;
|
||||
audioBuffer.channels = data.channels;
|
||||
audioBuffer.data = new UInt8Array(@:privateAccess new Bytes(data.data.length, data.data.b));
|
||||
audioBuffer.sampleRate = data.sampleRate;
|
||||
return audioBuffer;
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an `AudioBuffer` from a file.
|
||||
|
||||
@param path The file path to the audio data.
|
||||
@return An `AudioBuffer` instance with the audio data loaded from the file.
|
||||
**/
|
||||
public static function fromFile(path:String #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer
|
||||
{
|
||||
if (path == null) return null;
|
||||
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
var audioBuffer = new AudioBuffer();
|
||||
|
||||
#if force_html5_audio
|
||||
audioBuffer.__srcHowl = new Howl({src: [path], html5: true, preload: false});
|
||||
#else
|
||||
audioBuffer.__srcHowl = new Howl({src: [path], html5: howlHtml5, preload: false});
|
||||
#end
|
||||
|
||||
return audioBuffer;
|
||||
#elseif flash
|
||||
switch (Path.extension(path))
|
||||
{
|
||||
case "ogg", "wav":
|
||||
return null;
|
||||
default:
|
||||
}
|
||||
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.__srcSound = new Sound(new URLRequest(path));
|
||||
return audioBuffer;
|
||||
#elseif (lime_cffi && !macro)
|
||||
#if !cs
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.data = new UInt8Array(Bytes.alloc(0));
|
||||
|
||||
//audioBuffer = NativeCFFI.lime_audio_load_file(path, audioBuffer);
|
||||
//if (audioBuffer != null) audioBuffer.initBuffer();
|
||||
//return audioBuffer;
|
||||
return NativeCFFI.lime_audio_load_file(path, audioBuffer);
|
||||
#else
|
||||
var data:Dynamic = NativeCFFI.lime_audio_load_file(path, null);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.bitsPerSample = data.bitsPerSample;
|
||||
audioBuffer.channels = data.channels;
|
||||
audioBuffer.data = new UInt8Array(@:privateAccess new Bytes(data.data.length, data.data.b));
|
||||
audioBuffer.sampleRate = data.sampleRate;
|
||||
//audioBuffer.initBuffer();
|
||||
return audioBuffer;
|
||||
}
|
||||
|
||||
return null;
|
||||
#end
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an `AudioBuffer` from an array of file paths.
|
||||
|
||||
@param paths An array of file paths to search for audio data.
|
||||
@return An `AudioBuffer` instance with the audio data loaded from the first valid file found.
|
||||
**/
|
||||
public static function fromFiles(paths:Array<String> #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer
|
||||
{
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
var audioBuffer = new AudioBuffer();
|
||||
|
||||
#if force_html5_audio
|
||||
audioBuffer.__srcHowl = new Howl({src: paths, html5: true, preload: false});
|
||||
#else
|
||||
audioBuffer.__srcHowl = new Howl({src: paths, html5: howlHtml5, preload: false});
|
||||
#end
|
||||
|
||||
return audioBuffer;
|
||||
#else
|
||||
var buffer = null;
|
||||
|
||||
for (path in paths)
|
||||
{
|
||||
buffer = AudioBuffer.fromFile(path);
|
||||
if (buffer != null) break;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an `AudioBuffer` from a `VorbisFile`.
|
||||
|
||||
@param vorbisFile The `VorbisFile` object containing the audio data.
|
||||
@return An `AudioBuffer` instance with the decoded audio data.
|
||||
**/
|
||||
#if lime_vorbis
|
||||
|
||||
public static function fromVorbisFile(vorbisFile:VorbisFile):AudioBuffer
|
||||
{
|
||||
if (vorbisFile == null) return null;
|
||||
|
||||
var info = vorbisFile.info();
|
||||
|
||||
var audioBuffer = new AudioBuffer();
|
||||
audioBuffer.channels = info.channels;
|
||||
audioBuffer.sampleRate = info.rate;
|
||||
audioBuffer.bitsPerSample = 16;
|
||||
audioBuffer.__srcVorbisFile = vorbisFile;
|
||||
|
||||
return audioBuffer;
|
||||
}
|
||||
#else
|
||||
public static function fromVorbisFile(vorbisFile:Dynamic):AudioBuffer
|
||||
{
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
Asynchronously loads an `AudioBuffer` from a file.
|
||||
|
||||
@param path The file path to the audio data.
|
||||
@return A `Future` that resolves to the loaded `AudioBuffer`.
|
||||
**/
|
||||
public static function loadFromFile(path:String):Future<AudioBuffer>
|
||||
{
|
||||
#if (flash || (js && html5))
|
||||
var promise = new Promise<AudioBuffer>();
|
||||
|
||||
var audioBuffer = AudioBuffer.fromFile(path);
|
||||
|
||||
if (audioBuffer != null)
|
||||
{
|
||||
#if flash
|
||||
audioBuffer.__srcSound.addEventListener(flash.events.Event.COMPLETE, function(event)
|
||||
{
|
||||
promise.complete(audioBuffer);
|
||||
});
|
||||
|
||||
audioBuffer.__srcSound.addEventListener(flash.events.ProgressEvent.PROGRESS, function(event)
|
||||
{
|
||||
promise.progress(Std.int(event.bytesLoaded), Std.int(event.bytesTotal));
|
||||
});
|
||||
|
||||
audioBuffer.__srcSound.addEventListener(flash.events.IOErrorEvent.IO_ERROR, promise.error);
|
||||
#elseif (js && html5 && lime_howlerjs)
|
||||
if (audioBuffer != null)
|
||||
{
|
||||
audioBuffer.__srcHowl.on("load", function()
|
||||
{
|
||||
promise.complete(audioBuffer);
|
||||
});
|
||||
|
||||
audioBuffer.__srcHowl.on("loaderror", function(id, msg)
|
||||
{
|
||||
promise.error(msg);
|
||||
});
|
||||
|
||||
audioBuffer.__srcHowl.load();
|
||||
}
|
||||
#else
|
||||
promise.complete(audioBuffer);
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.error(null);
|
||||
}
|
||||
|
||||
return promise.future;
|
||||
#else
|
||||
// TODO: Streaming
|
||||
|
||||
var request = new HTTPRequest<AudioBuffer>();
|
||||
return request.load(path).then(function(buffer)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
return Future.withValue(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
return cast Future.withError("");
|
||||
}
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously loads an `AudioBuffer` from multiple files.
|
||||
|
||||
@param paths An array of file paths to search for audio data.
|
||||
@return A `Future` that resolves to the loaded `AudioBuffer`.
|
||||
**/
|
||||
public static function loadFromFiles(paths:Array<String>):Future<AudioBuffer>
|
||||
{
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
var promise = new Promise<AudioBuffer>();
|
||||
|
||||
var audioBuffer = AudioBuffer.fromFiles(paths);
|
||||
|
||||
if (audioBuffer != null)
|
||||
{
|
||||
audioBuffer.__srcHowl.on("load", function()
|
||||
{
|
||||
promise.complete(audioBuffer);
|
||||
});
|
||||
|
||||
audioBuffer.__srcHowl.on("loaderror", function()
|
||||
{
|
||||
promise.error(null);
|
||||
});
|
||||
|
||||
audioBuffer.__srcHowl.load();
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.error(null);
|
||||
}
|
||||
|
||||
return promise.future;
|
||||
#else
|
||||
return new Future(fromFiles.bind(paths), true);
|
||||
#end
|
||||
}
|
||||
|
||||
private static function __getCodec(bytes:Bytes):String
|
||||
{
|
||||
var signature = bytes.getString(0, 4);
|
||||
|
||||
switch (signature)
|
||||
{
|
||||
case "OggS":
|
||||
return "audio/ogg";
|
||||
case "fLaC":
|
||||
return "audio/flac";
|
||||
case "RIFF" if (bytes.getString(8, 4) == "WAVE"):
|
||||
return "audio/wav";
|
||||
default:
|
||||
switch ([bytes.get(0), bytes.get(1), bytes.get(2)])
|
||||
{
|
||||
case [73, 68, 51] | [255, 251, _] | [255, 250, _] | [255, 243, _]: return "audio/mp3";
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
Log.error("Unsupported sound format");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get & Set Methods
|
||||
@:noCompletion private function get_src():Dynamic
|
||||
{
|
||||
#if (js && html5)
|
||||
#if lime_howlerjs
|
||||
return __srcHowl;
|
||||
#else
|
||||
return __srcAudio;
|
||||
#end
|
||||
#elseif flash
|
||||
return __srcSound;
|
||||
#elseif lime_vorbis
|
||||
return __srcVorbisFile;
|
||||
#else
|
||||
return __srcCustom;
|
||||
#end
|
||||
}
|
||||
|
||||
@:noCompletion private function set_src(value:Dynamic):Dynamic
|
||||
{
|
||||
#if (js && html5)
|
||||
#if lime_howlerjs
|
||||
return __srcHowl = value;
|
||||
#else
|
||||
return __srcAudio = value;
|
||||
#end
|
||||
#elseif flash
|
||||
return __srcSound = value;
|
||||
#elseif lime_vorbis
|
||||
return __srcVorbisFile = value;
|
||||
#else
|
||||
return __srcCustom = value;
|
||||
#end
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,98 @@
|
||||
package lime.media;
|
||||
|
||||
import lime.app.Event;
|
||||
import lime.media.openal.AL;
|
||||
import lime.media.openal.ALSource;
|
||||
import lime.math.Vector4;
|
||||
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
/**
|
||||
The `AudioSource` class provides a way to control audio playback in a Lime application.
|
||||
It allows for playing, pausing, and stopping audio, as well as controlling various
|
||||
audio properties such as gain, pitch, and looping.
|
||||
|
||||
Depending on the platform, the audio backend may vary, but the API remains consistent.
|
||||
|
||||
@see lime.media.AudioBuffer
|
||||
**/
|
||||
class AudioSource
|
||||
{
|
||||
/**
|
||||
An event that is dispatched when the audio playback is complete.
|
||||
**/
|
||||
public var onComplete = new Event<Void->Void>();
|
||||
|
||||
/**
|
||||
An event that is dispatched when the audio playback looped.
|
||||
**/
|
||||
public var onLoop = new Event<Void->Void>();
|
||||
|
||||
/**
|
||||
The `AudioBuffer` associated with this `AudioSource`.
|
||||
**/
|
||||
public var buffer:AudioBuffer;
|
||||
|
||||
/**
|
||||
An property if this 'AudioSource' is playing.
|
||||
**/
|
||||
public var playing(get, null):Bool;
|
||||
|
||||
/**
|
||||
The current playback position of the audio, in milliseconds.
|
||||
**/
|
||||
public var currentTime(get, set):Float;
|
||||
|
||||
/**
|
||||
The gain (volume) of the audio. A value of `1.0` represents the default volume.
|
||||
**/
|
||||
public var gain(get, set):Float;
|
||||
public var length(get, set):Float;
|
||||
|
||||
/**
|
||||
The length of the audio, in milliseconds.
|
||||
**/
|
||||
public var length(get, set):Null<Float>;
|
||||
|
||||
/**
|
||||
The number of times the audio will loop. A value of `0` means the audio will not loop.
|
||||
**/
|
||||
public var loops(get, set):Int;
|
||||
|
||||
/**
|
||||
In which audio playback time the audio will loop.
|
||||
**/
|
||||
public var loopTime(get, set):Float;
|
||||
|
||||
/**
|
||||
The pitch of the audio. A value of `1.0` represents the default pitch.
|
||||
**/
|
||||
public var pitch(get, set):Float;
|
||||
public var offset:Int;
|
||||
|
||||
/**
|
||||
The offset within the audio buffer to start playback, in samples.
|
||||
**/
|
||||
public var offset:Float;
|
||||
|
||||
/**
|
||||
The 3D position of the audio source, represented as a `Vector4`.
|
||||
**/
|
||||
public var position(get, set):Vector4;
|
||||
|
||||
/**
|
||||
The latency of the audio source.
|
||||
**/
|
||||
public var latency(get, never):Float;
|
||||
|
||||
@:noCompletion private var __backend:AudioSourceBackend;
|
||||
|
||||
public function new(buffer:AudioBuffer = null, offset:Int = 0, length:Null<Int> = null, loops:Int = 0)
|
||||
/**
|
||||
Creates a new `AudioSource` instance.
|
||||
@param buffer The `AudioBuffer` to associate with this `AudioSource`.
|
||||
@param offset The starting offset within the audio buffer, in samples.
|
||||
@param length The length of the audio to play, in milliseconds. If `null`, the full buffer is used.
|
||||
@param loops The number of times to loop the audio. `0` means no looping.
|
||||
**/
|
||||
public function new(buffer:AudioBuffer = null, offset:Float = 0, length:Null<Float> = null, loops:Int = 0)
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.offset = offset;
|
||||
@@ -35,104 +104,134 @@ class AudioSource
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
this.loops = loops;
|
||||
|
||||
if (buffer != null)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
this.loops = loops;
|
||||
}
|
||||
|
||||
public function dispose():Void
|
||||
/**
|
||||
Releases any resources used by this `AudioSource`.
|
||||
**/
|
||||
inline public function dispose():Void
|
||||
{
|
||||
__backend.dispose();
|
||||
}
|
||||
|
||||
@:noCompletion private function init():Void
|
||||
@:noCompletion inline private function init():Void
|
||||
{
|
||||
__backend.init();
|
||||
}
|
||||
|
||||
public function play():Void
|
||||
/**
|
||||
Starts or resumes audio playback.
|
||||
**/
|
||||
inline public function play():Void
|
||||
{
|
||||
__backend.play();
|
||||
}
|
||||
|
||||
public function pause():Void
|
||||
/**
|
||||
Pauses audio playback.
|
||||
**/
|
||||
inline public function pause():Void
|
||||
{
|
||||
__backend.pause();
|
||||
}
|
||||
|
||||
public function stop():Void
|
||||
/**
|
||||
Stops audio playback and resets the playback position to the beginning.
|
||||
**/
|
||||
inline public function stop():Void
|
||||
{
|
||||
__backend.stop();
|
||||
}
|
||||
|
||||
// Get & Set Methods
|
||||
@:noCompletion private function get_currentTime():Float
|
||||
@:noCompletion inline private function get_playing():Bool
|
||||
{
|
||||
@:privateAccess return __backend.playing;
|
||||
}
|
||||
|
||||
@:noCompletion inline private function get_currentTime():Float
|
||||
{
|
||||
return __backend.getCurrentTime();
|
||||
}
|
||||
|
||||
@:noCompletion private function set_currentTime(value:Float):Float
|
||||
@:noCompletion inline private function set_currentTime(value:Float):Float
|
||||
{
|
||||
return __backend.setCurrentTime(value);
|
||||
}
|
||||
|
||||
@:noCompletion private function get_gain():Float
|
||||
@:noCompletion inline private function get_gain():Float
|
||||
{
|
||||
return __backend.getGain();
|
||||
}
|
||||
|
||||
@:noCompletion private function set_gain(value:Float):Float
|
||||
@:noCompletion inline private function set_gain(value:Float):Float
|
||||
{
|
||||
return __backend.setGain(value);
|
||||
}
|
||||
|
||||
@:noCompletion private function get_length():Float
|
||||
@:noCompletion inline private function get_length():Null<Float>
|
||||
{
|
||||
return __backend.getLength();
|
||||
}
|
||||
|
||||
@:noCompletion private function set_length(value:Float):Float
|
||||
@:noCompletion inline private function set_length(value:Null<Float>):Null<Float>
|
||||
{
|
||||
return __backend.setLength(value);
|
||||
}
|
||||
|
||||
@:noCompletion private function get_loops():Int
|
||||
@:noCompletion inline private function get_loops():Int
|
||||
{
|
||||
return __backend.getLoops();
|
||||
}
|
||||
|
||||
@:noCompletion private function set_loops(value:Int):Int
|
||||
@:noCompletion inline private function set_loops(value:Int):Int
|
||||
{
|
||||
return __backend.setLoops(value);
|
||||
}
|
||||
|
||||
@:noCompletion private function get_pitch():Float
|
||||
@:noCompletion inline private function get_loopTime():Float
|
||||
{
|
||||
return __backend.getLoopTime();
|
||||
}
|
||||
|
||||
@:noCompletion inline private function set_loopTime(value:Float):Float
|
||||
{
|
||||
return __backend.setLoopTime(value);
|
||||
}
|
||||
|
||||
@:noCompletion inline private function get_pitch():Float
|
||||
{
|
||||
return __backend.getPitch();
|
||||
}
|
||||
|
||||
@:noCompletion private function set_pitch(value:Float):Float
|
||||
@:noCompletion inline private function set_pitch(value:Float):Float
|
||||
{
|
||||
return __backend.setPitch(value);
|
||||
}
|
||||
|
||||
@:noCompletion private function get_position():Vector4
|
||||
@:noCompletion inline private function get_position():Vector4
|
||||
{
|
||||
return __backend.getPosition();
|
||||
}
|
||||
|
||||
@:noCompletion private function set_position(value:Vector4):Vector4
|
||||
@:noCompletion inline private function set_position(value:Vector4):Vector4
|
||||
{
|
||||
return __backend.setPosition(value);
|
||||
}
|
||||
|
||||
@:noCompletion inline private function get_latency():Float
|
||||
{
|
||||
return __backend.getLatency();
|
||||
}
|
||||
}
|
||||
|
||||
#if flash
|
||||
@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.flash.FlashAudioSource;
|
||||
#elseif (js && html5)
|
||||
#if (js && html5)
|
||||
@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.html5.HTML5AudioSource;
|
||||
#else
|
||||
@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeAudioSource;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package openfl.media;
|
||||
|
||||
#if !flash
|
||||
import haxe.Int64;
|
||||
|
||||
import openfl.events.Event;
|
||||
import openfl.events.EventDispatcher;
|
||||
#if lime
|
||||
import lime.media.AudioSource;
|
||||
|
||||
#if lime_cffi import lime._internal.backend.native.NativeAudioSource; #end
|
||||
#if lime_vorbis import lime.media.openal.AL; #end
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -16,11 +21,19 @@ import lime.media.AudioSource;
|
||||
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 && 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
|
||||
{
|
||||
@@ -28,7 +41,13 @@ import lime.media.AudioSource;
|
||||
The current amplitude(volume) of the left channel, from 0(silent) to 1
|
||||
(full amplitude).
|
||||
**/
|
||||
public var leftPeak(default, null):Float;
|
||||
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
|
||||
@@ -45,12 +64,6 @@ import lime.media.AudioSource;
|
||||
**/
|
||||
public var position(get, set):Float;
|
||||
|
||||
/**
|
||||
The current amplitude(volume) of the right channel, from 0(silent) to 1
|
||||
(full amplitude).
|
||||
**/
|
||||
public var rightPeak(default, null):Float;
|
||||
|
||||
/**
|
||||
The SoundTransform object assigned to the sound channel. A SoundTransform
|
||||
object includes properties for setting volume, panning, left speaker
|
||||
@@ -58,13 +71,23 @@ import lime.media.AudioSource;
|
||||
**/
|
||||
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;
|
||||
@:noCompletion private var __audioSource(get, set):AudioSource; // forward??? compatibility??
|
||||
#end
|
||||
|
||||
#if openfljs
|
||||
@@ -87,28 +110,10 @@ import lime.media.AudioSource;
|
||||
{
|
||||
super(this);
|
||||
|
||||
leftPeak = 1;
|
||||
rightPeak = 1;
|
||||
if (soundTransform != null) __soundTransform = soundTransform;
|
||||
else __soundTransform = new SoundTransform();
|
||||
|
||||
if (soundTransform != null)
|
||||
{
|
||||
__soundTransform = soundTransform;
|
||||
}
|
||||
else
|
||||
{
|
||||
__soundTransform = new SoundTransform();
|
||||
}
|
||||
|
||||
#if lime
|
||||
if (source != null)
|
||||
{
|
||||
__source = source;
|
||||
__source.onComplete.add(source_onComplete);
|
||||
__isValid = true;
|
||||
|
||||
__source.play();
|
||||
}
|
||||
#end
|
||||
__initAudioSource(source);
|
||||
|
||||
SoundMixer.__registerSoundChannel(this);
|
||||
}
|
||||
@@ -134,6 +139,7 @@ import lime.media.AudioSource;
|
||||
|
||||
#if lime
|
||||
__source.onComplete.remove(source_onComplete);
|
||||
__source.onLoop.remove(source_onLoop);
|
||||
__source.dispose();
|
||||
__source = null;
|
||||
#end
|
||||
@@ -145,6 +151,105 @@ import lime.media.AudioSource;
|
||||
this.soundTransform = soundTransform;
|
||||
}
|
||||
|
||||
// hi i made these - raltyro
|
||||
// took me 8 months to figure this out
|
||||
@:noCompletion inline private static var scanSamples = 480;
|
||||
|
||||
#if (lime_cffi && !macro)
|
||||
@:noCompletion private function __checkUpdatePeaks(time:Float):Bool
|
||||
{
|
||||
if (Math.abs(__lastPeakTime - time) < Math.max(1, pitch * 8)) return false;
|
||||
__lastPeakTime = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if lime_vorbis
|
||||
@:noCompletion private function __updateVorbisPeaks():Void
|
||||
{
|
||||
if (__source.buffer == null || !__checkUpdatePeaks(position)) return;
|
||||
|
||||
var index = AL.getSourcei(__source.__backend.handle, AL.SAMPLE_OFFSET), bufferDatas = __source.__backend.bufferDatas;
|
||||
var bufferi = NativeAudioSource.STREAM_NUM_BUFFERS - __source.__backend.queuedBuffers, bytes = bufferDatas[bufferi].buffer;
|
||||
|
||||
var channels = __source.buffer.channels, todo = scanSamples, sample;
|
||||
var lfilled = false, rfilled = channels < 2;
|
||||
|
||||
while(todo > 0 && (!lfilled || !rfilled)) {
|
||||
if (index >= NativeAudioSource.STREAM_BUFFER_SIZE) {
|
||||
if ((bytes = bufferDatas[++bufferi].buffer) == null) break;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (!lfilled) {
|
||||
if ((sample = bytes.getUInt16(index * channels * 2) / 65535) > .5) sample = -(sample - 1);
|
||||
if (sample > __leftPeak) lfilled = (__leftPeak = sample) >= 1;
|
||||
}
|
||||
|
||||
if (!rfilled) {
|
||||
if ((sample = bytes.getUInt16(index * channels * 2 + 2) / 65535) > .5) sample = -(sample - 1);
|
||||
if (sample > __rightPeak) rfilled = (__rightPeak = sample) >= 1;
|
||||
}
|
||||
|
||||
index++;
|
||||
todo--;
|
||||
}
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
@:noCompletion private function __updatePeaks():Void
|
||||
{
|
||||
__leftPeak = __rightPeak = 0;
|
||||
|
||||
#if (lime_cffi && !macro)
|
||||
if (!__isValid) return;
|
||||
|
||||
#if lime_vorbis if (__source.__backend.stream) return __updateVorbisPeaks(); #end
|
||||
|
||||
var buffer = __source.buffer;
|
||||
if (buffer == null || buffer.data == null || !__checkUpdatePeaks(position)) return;
|
||||
|
||||
var index = Int64.make(0, AL.getSourcei(__source.__backend.handle, AL.SAMPLE_OFFSET));
|
||||
var bytes = buffer.data.buffer, length = __source.__backend.samples;
|
||||
if (index >= length) return;
|
||||
|
||||
var temp = index + scanSamples;
|
||||
if (temp < length) length = temp;
|
||||
|
||||
var channels = buffer.channels, sample;
|
||||
var lfilled = false, rfilled = channels < 2;
|
||||
|
||||
while(index < length && (!lfilled || !rfilled)) {
|
||||
if (!lfilled) {
|
||||
if ((sample = bytes.getUInt16(Int64.toInt(index * (channels * 2))) / 65535) > .5) sample = -(sample - 1);
|
||||
if (sample > __leftPeak) lfilled = (__leftPeak = sample) >= 1;
|
||||
}
|
||||
if (!rfilled) {
|
||||
if ((sample = bytes.getUInt16(Int64.toInt(index * (channels * 2) + 2)) / 65535) > .5) sample = -(sample - 1);
|
||||
if (sample > __rightPeak) rfilled = (__rightPeak = sample) >= 1;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@: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);
|
||||
__source.onLoop.add(source_onLoop);
|
||||
__isValid = true;
|
||||
|
||||
__source.play();
|
||||
#end
|
||||
}
|
||||
|
||||
// Get & Set Methods
|
||||
@:noCompletion private function get_position():Float
|
||||
{
|
||||
@@ -167,27 +272,6 @@ import lime.media.AudioSource;
|
||||
return value;
|
||||
}
|
||||
|
||||
@:noCompletion private function get_pitch():Float
|
||||
{
|
||||
if (!__isValid) return 1;
|
||||
|
||||
#if lime
|
||||
return __source.pitch;
|
||||
#else
|
||||
return 1;
|
||||
#end
|
||||
}
|
||||
|
||||
@:noCompletion private function set_pitch(value:Float):Float
|
||||
{
|
||||
if (!__isValid) return 1;
|
||||
|
||||
#if lime
|
||||
__source.pitch = value;
|
||||
#end
|
||||
return value;
|
||||
}
|
||||
|
||||
@:noCompletion private function get_soundTransform():SoundTransform
|
||||
{
|
||||
return __soundTransform.clone();
|
||||
@@ -203,7 +287,7 @@ import lime.media.AudioSource;
|
||||
var pan = SoundMixer.__soundTransform.pan + __soundTransform.pan;
|
||||
|
||||
if (pan < -1) pan = -1;
|
||||
else if (pan > 1) pan = 1;
|
||||
if (pan > 1) pan = 1;
|
||||
|
||||
var volume = SoundMixer.__soundTransform.volume * __soundTransform.volume;
|
||||
|
||||
@@ -225,6 +309,107 @@ import lime.media.AudioSource;
|
||||
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();
|
||||
return __leftPeak * (soundTransform == null ? 1 : soundTransform.volume);
|
||||
}
|
||||
|
||||
@:noCompletion private function get_rightPeak():Float
|
||||
{
|
||||
__updatePeaks();
|
||||
return __rightPeak * (soundTransform == null ? 1 : soundTransform.volume);
|
||||
}
|
||||
|
||||
|
||||
// Event Handlers
|
||||
@:noCompletion private function source_onComplete():Void
|
||||
{
|
||||
@@ -234,14 +419,14 @@ import lime.media.AudioSource;
|
||||
dispatchEvent(new Event(Event.SOUND_COMPLETE));
|
||||
}
|
||||
|
||||
function set___audioSource(value:AudioSource):AudioSource {
|
||||
return __source = value;
|
||||
@:noCompletion private function source_onLoop():Void
|
||||
{
|
||||
//dispatchEvent(new Event(Event.SOUND_LOOP));
|
||||
}
|
||||
|
||||
function get___audioSource():AudioSource {
|
||||
return __source;
|
||||
}
|
||||
@: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
|
||||
#end
|
||||
|
||||
Reference in New Issue
Block a user