diff --git a/building/alsoft.txt b/building/alsoft.txt index 2f928c99..58d8c74c 100644 --- a/building/alsoft.txt +++ b/building/alsoft.txt @@ -1,14 +1,17 @@ -[general] +[General] sample-type=float32 stereo-mode=speakers -stereo-encoding=panpot hrtf=false cf_level=0 -resampler=fast_bsinc24 -front-stablizer=false output-limiter=false +front-stabilizer=false volume-adjust=0 +period_size=441 +sources=512 +sends=64 +dither=false + [decoder] -hq-mode=false -distance-comp=false +hq-mode=true +distance-comp=true nfc=false \ No newline at end of file diff --git a/source/flixel/sound/FlxSound.hx b/source/flixel/sound/FlxSound.hx index 180744e1..f514b5f7 100644 --- a/source/flixel/sound/FlxSound.hx +++ b/source/flixel/sound/FlxSound.hx @@ -718,7 +718,7 @@ class FlxSound extends FlxBasic { _paused = false; _time = startTime; _lastTime = FlxG.game.getTicks(); - if (_channel == null || !_channel.__isValid || _source == null #if lime_cffi || _source.__backend.disposed || _source.__backend.handle == null #end) + if (_channel == null || !_channel.__isValid || _source == null #if lime_cffi || _source.__backend.disposed #end) makeChannel(); if (_channel != null) { @@ -939,19 +939,19 @@ class FlxSound extends FlxBasic { return _time = time; } - inline function get_offset():Float return _offset; - inline function set_offset(offset:Float):Float { + function get_offset():Float return _offset; + function set_offset(offset:Float):Float { if (_offset == (_offset = offset)) return offset; //time = time + _offset; return offset; } - inline function get_length():Float return _length - _offset; + function get_length():Float return _length - _offset; - /*function get_latency():Float { - if (_channel != null) return _source.latency; - return 0; - }*/ + //function get_latency():Float { + // if (_channel != null) return _source.latency; + // return 0; + //} override function toString():String { return FlxStringUtil.getDebugString([ diff --git a/source/funkin/backend/utils/AudioAnalyzer.hx b/source/funkin/backend/utils/AudioAnalyzer.hx index 174f148c..1a972ac0 100644 --- a/source/funkin/backend/utils/AudioAnalyzer.hx +++ b/source/funkin/backend/utils/AudioAnalyzer.hx @@ -20,8 +20,7 @@ typedef AudioAnalyzerCallback = Int->Int->Void; * An utility that analyze FlxSounds, * can be used to make waveform or real-time audio visualizer. * - * FlxSound.amplitude does work in CNE so if any case if your only checking for peak of current - * time, use that instead. + * FlxSound.amplitude works so if any case if your only checking for peak of current time, use that instead. */ final class AudioAnalyzer { /** @@ -296,7 +295,7 @@ final class AudioAnalyzer { __check(); } - function __check() if (sound.buffer != buffer) { + function __check() if (sound != null && sound.buffer != buffer) { byteSize = 1 << ((buffer = sound.buffer).bitsPerSample - 1); #if (lime_cffi && lime_vorbis) @@ -324,7 +323,7 @@ final class AudioAnalyzer { * @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended). * @return Output of levels/bars that ranges from 0 to 1. */ - public function getLevels(startPos:Float, ?volume:Float, barCount:Int, ?levels:Array, ?ratio:Float, ?minDb:Float, ?maxDb:Float, ?minFreq:Float, ?maxFreq:Float):Array + public function getLevels(?startPos:Float, ?volume:Float, barCount:Int, ?levels:Array, ?ratio:Float, ?minDb:Float, ?maxDb:Float, ?minFreq:Float, ?maxFreq:Float):Array return inline getLevelsFromFrequencies(__frequencies = getFrequencies(startPos, volume, __frequencies), buffer.sampleRate, barCount, levels, ratio, minDb, maxDb, minFreq, maxFreq); /** @@ -334,8 +333,8 @@ final class AudioAnalyzer { * @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional). * @return Output of frequencies. */ - public function getFrequencies(startPos:Float, ?volume:Float, ?frequencies:Array):Array - return inline getFrequenciesFromSamples(__freqSamples = getSamples(startPos, fftN, true, -1, volume, __freqSamples), fftN, useWindowingFFT, frequencies); + public function getFrequencies(?startPos:Float, ?volume:Float, ?frequencies:Array):Array + return inline getFrequenciesFromSamples(__freqSamples = getSamples(startPos != null ? startPos : sound.time, fftN, true, -1, volume, __freqSamples), fftN, useWindowingFFT, frequencies); /** * Analyzes an attached FlxSound from startPos to endPos in milliseconds to get the amplitudes. @@ -467,36 +466,37 @@ final class AudioAnalyzer { @:privateAccess return sound._source != null && sound._source.__backend != null && sound._source.__backend.playing; inline function __readStream(startPos:Float, endPos:Float, callback:AudioAnalyzerCallback):Float @:privateAccess { - var backend = sound._source.__backend; - var i = backend.bufferSizes.length - backend.queuedBuffers; - var time = backend.bufferTimes[i] * 1000; + final backend = sound._source.__backend; + // TODO: Wrap it with try until i figured it out an effective way to do this... + // So... sometimes it just uses the decoder even if it looks good?? please help var n = Math.floor((endPos - startPos) * __toBits); - if (startPos >= time && startPos < backend.bufferTimes[backend.bufferSizes.length - 1] * 1000) { - var pos = Math.floor((startPos - time) * __toBits), buf = backend.bufferDatas[i].buffer, size = backend.bufferSizes[i], c = 0; + var i = backend.bufferLengths.length - backend.queuedBuffers - 1, time:Float; + while (++i < backend.bufferLengths.length) if (startPos >= (time = backend.bufferTimes[i] * 1000)) { + var pos = Math.floor((startPos - time) * __toBits), buf = backend.bufferDatas[i].buffer, size = backend.bufferLengths[i], c = 0; while (pos >= size) { - if (++i >= backend.bufferSizes.length) { - n = 0; - break; - } + if (++i >= backend.bufferLengths.length) break; pos -= size; buf = backend.bufferDatas[i].buffer; - size = backend.bufferSizes[i]; + size = backend.bufferLengths[i]; } - pos -= pos % __sampleSize; + if (i >= backend.bufferLengths.length) break; + if ((pos -= pos % __sampleSize) < 0) pos = 0; n -= pos % __sampleSize; while (n > 0) { callback(getByte(buf, pos, __wordSize), c); if (++c > buffer.channels) c = 0; if ((pos += __wordSize) >= size) { - if (++i >= backend.bufferSizes.length) break; + if (++i >= backend.bufferLengths.length) break; pos = 0; buf = backend.bufferDatas[i].buffer; - size = backend.bufferSizes[i]; + size = backend.bufferLengths[i]; } n -= __wordSize; } + + break; } return endPos - (n / __toBits); diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 1a4e882b..76752ac4 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1587,13 +1587,11 @@ class PlayState extends MusicBeatState var camera:FlxCamera = event.params[1] == "camHUD" ? camHUD : camGame; camera.zoom += event.params[0]; case "Camera Bop": - if (Options.camZoomOnBeat) { - if (useCamZoomMult) { - camZoomingMult += event.params[0]; - } else { - FlxG.camera.zoom += event.params[0] * camZoomingStrength; - camHUD.zoom += event.params[0] * camZoomingStrength; - } + if (useCamZoomMult) { + camZoomingMult += event.params[0]; + } else { + FlxG.camera.zoom += event.params[0] * camZoomingStrength; + camHUD.zoom += event.params[0] * camZoomingStrength; } case "Camera Zoom": var cam = event.params[2] == "camHUD" ? camHUD : camGame; diff --git a/source/lime/_internal/backend/native/NativeAudioSource.hx b/source/lime/_internal/backend/native/NativeAudioSource.hx index d7cbdb51..c68c52d8 100644 --- a/source/lime/_internal/backend/native/NativeAudioSource.hx +++ b/source/lime/_internal/backend/native/NativeAudioSource.hx @@ -1,4 +1,3 @@ -// ALL REWRITTEN FROM SCRATCH!!!! -raltyro package lime._internal.backend.native; import haxe.Timer; @@ -11,225 +10,367 @@ import lime.media.openal.ALSource; #if lime_vorbis import lime.media.vorbis.Vorbis; import lime.media.vorbis.VorbisFile; +import lime.media.vorbis.VorbisInfo; #end import lime.math.Vector2; import lime.math.Vector4; import lime.media.AudioBuffer; import lime.media.AudioSource; +import lime.system.Endian; +import lime.system.System; +import lime.utils.ArrayBuffer; +import lime.utils.ArrayBufferView.TypedArrayType; import lime.utils.ArrayBufferView; #if !lime_debug @:fileXml('tags="haxe,release"') @:noDebug #end +@:access(haxe.Timer) @:access(lime.media.AudioBuffer) @:access(lime.utils.ArrayBufferView) class NativeAudioSource { - private static var STREAM_BUFFER_SAMPLES:Int = 0x4000; - private static var STREAM_MAX_BUFFERS:Int = 8; - private static var STREAM_TIMER_FREQUENCY:Int = 100; - private static var STREAM_BUFFER_FREQUENCY:Int = 3; + // Can hold up to 3 hours 44100 sampleRate audio, if you are into that, theorically. - private static var moreFormatsSupported:Null; - private static var stereoAnglesExtensionSupported:Null; - private static var loopPointsSupported:Null; // TODO: implement loop Points for looped static sources - private static var latencyExtensionSupported:Null; + public static var STREAM_BUFFER_SAMPLES:Int = 0x2000; // how much buffers will be generating every frequency (doesnt have to be pow of 2?). + public static var STREAM_MIN_BUFFERS:Int = 2; // how much buffers can a stream hold on minimum or starting. + public static var STREAM_MAX_BUFFERS:Int = 8; // how much limit of a buffers can be used for streamed audios, must be higher than minimum. + public static var STREAM_PROCESS_BUFFERS:Int = 2; // how much buffers can be processed in a frequency tick. + public static var STREAM_TIMER_CHECK_MS:Int = 100; // determines how milliseconds to update the buffers if available. + public static var MAX_POOL_BUFFERS:Int = 32; // how much buffers for the pool to hold. - private var parent:AudioSource; - private var disposed:Bool; - private var streamed:Bool; - private var playing:Bool; - private var completed:Bool; + public static var moreFormatsSupported:Null; + public static var loopPointsSupported:Null; + public static var stereoAnglesExtensionSupported:Null; + public static var latencyExtensionSupported:Null; - private var handle:ALSource; - private var buffers:Array; - private var unusedBuffers:Array; - private var timer:Timer; - private var format:Int; - private var samples:Float; + private static var bufferDataPool:Array = []; + private static var isBigEndian:Bool = System.endianness == Endian.BIG_ENDIAN; - private var streamTimer:Timer; - private var bufferDatas:Array; - private var bufferTimes:Array; - private var bufferSizes:Array; - private var bufferSize:Int; - private var requestBuffers:Int; - private var queuedBuffers:Int; - private var toLoop:Int; - private var streamEnded:Bool; - private var dataLength:Float; + public static function getALFormat(bitsPerSample:Int, channels:Int):Int { + if (moreFormatsSupported == null) moreFormatsSupported = AL.isExtensionPresent("AL_EXT_MCFORMATS"); - private var position:Vector4; - private var angles:Vector2; - private var anglesArray:Array; - private var length:Null; - private var loopTime:Null; - private var loops:Int; + // There was a code to also supports for X-Fi Renderer but, that kind of device is + // rare nowadays and none of sounds should have more than 24 bitsPerSample. + // https://github.com/kcat/openal-soft/issues/934 - public function new(parent:AudioSource) this.parent = parent; + if (channels > 2 && moreFormatsSupported) { + if (channels == 3) return bitsPerSample == 32 ? 0x1209 : (bitsPerSample == 16 ? 0x1208 : 0x1207); + else if (channels == 4) return bitsPerSample == 32 ? 0x1206 : (bitsPerSample == 16 ? 0x1205 : 0x1204); + else if (channels == 6) return bitsPerSample == 32 ? 0x120C : (bitsPerSample == 16 ? 0x120B : 0x120A); + else if (channels == 7) return bitsPerSample == 32 ? 0x120F : (bitsPerSample == 16 ? 0x120E : 0x120D); + else if (channels == 8) return bitsPerSample == 32 ? 0x1212 : (bitsPerSample == 16 ? 0x1211 : 0x1210); + else return AL.FORMAT_MONO8; + } + else if (bitsPerSample == 32 && moreFormatsSupported) return channels == 2 ? 0x1203 : 0x1202; + else if (channels == 2) return bitsPerSample == 16 ? AL.FORMAT_STEREO16 : AL.FORMAT_STEREO8; + else return bitsPerSample == 16 ? AL.FORMAT_MONO16 : AL.FORMAT_MONO8; + } + + private static function resetTimer(timer:Timer, time:Float, callback:Void->Void):Timer { + if (timer == null) (timer = new Timer(time)).run = callback; + else { + timer.mTime = time; + timer.mFireAt = Timer.getMS() + time; + timer.mRunning = true; + timer.run = callback; + + if (!Timer.sRunningTimers.contains(timer)) Timer.sRunningTimers.push(timer); + } + return timer; + } + + inline private static function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >> 0); + + // Backward Compatibility Variables + var handle(get, set):ALSource; inline function get_handle() return source; inline function set_handle(v) return source = v; + var timer(get, set):Timer; inline function get_timer() return completeTimer; inline function set_timer(v) return completeTimer = v; + var length(get, set):Null; inline function get_length() return endTime; inline function set_length(v) return endTime = v; + var toLoop(get, set):Int; inline function get_toLoop() return streamLoops; inline function set_toLoop(v) return streamLoops = v; + var bufferSizes(get, set):Array; inline function get_bufferSizes() return bufferLengths; inline function set_bufferSizes(v) return bufferLengths = v; + + var parent:AudioSource; + var disposed:Bool; + var streamed:Bool; + var playing:Bool; + var completed:Bool; + var lastTime:Float; + + var position:Vector4; + var angles:Vector2; + var anglesArray:Array; + var endTime:Null; + var loopTime:Float; + var loops:Int; + + var channels:Int; + var sampleRate:Int; + var wordSize:Int; // is bitsPerSample >> 3, ex: 8 bits is 1, 16 bits is 2, 32 bits is 4, etc. + var samples:Int; + var dataLength:Int; + var duration:Float; + + var streamTimer:Timer; + var completeTimer:Timer; + var source:ALSource; + var buffer:ALBuffer; + var standaloneBuffer:Bool; + var format:Int; // AL.FORMAT_... + var arrayType:TypedArrayType; + var loopPoints:Array; // In Samples + + var bufferLength:Int; // Size in bytes for current streamed audio buffers. + var requestBuffers:Int; + var queuedBuffers:Int; + var streamLoops:Int; + var streamEnded:Bool; + + var buffers:Array; + var unusedBuffers:Array; + + // ORDERING IS CURRENT TO NEXT, STARTS FROM THE LENGTH OF THE ARRAYS + var bufferDatas:Array; + var bufferTimes:Array; + var bufferLengths:Array; + var bufferTemps:Array; + var buffersToQueue:Int = 0; + + public function new(parent:AudioSource) { + this.parent = parent; + + if (loopPointsSupported == null) loopPointsSupported = AL.isExtensionPresent("AL_SOFT_loop_points"); + if (stereoAnglesExtensionSupported == null) stereoAnglesExtensionSupported = AL.isExtensionPresent("AL_EXT_STEREO_ANGLES"); + if (latencyExtensionSupported == null) latencyExtensionSupported = AL.isExtensionPresent("AL_SOFT_source_latency"); + } public function dispose() { - disposed = true; stop(); + disposed = true; position = null; angles = null; anglesArray = null; - if (handle != null) { - AL.sourcei(handle, AL.BUFFER, AL.NONE); - AL.deleteSource(handle); + if (source != null) { + AL.sourcei(source, AL.BUFFER, AL.NONE); + AL.deleteSource(source); + source = null; } - handle = null; - if (buffers != null) AL.deleteBuffers(buffers); - buffers = null; - bufferDatas = null; - bufferTimes = null; - bufferSizes = null; + if (standaloneBuffer && buffer != null) { + AL.bufferData(buffer, 0, null, 0, 0); + AL.deleteBuffer(buffer); + buffer = null; + } + loopPoints = null; + + if (buffers != null) { + for (buffer in buffers) AL.bufferData(buffer, 0, null, 0, 0); + AL.deleteBuffers(buffers); + buffers = null; + } + + if (bufferDatas != null) { + for (data in bufferDatas) if (bufferDataPool.length < MAX_POOL_BUFFERS) bufferDataPool.push(data); + bufferDatas = null; + } + + bufferTemps = null; + + completeTimer = null; + streamTimer = null; + unusedBuffers = null; + bufferTimes = null; + bufferLengths = null; } public function init() { - if (handle != null) return; - if (disposed = (handle = AL.createSource()) == null) return; + if (source != null || (disposed = parent == null || (source = AL.createSource()) == null)) return; + AL.sourcef(source, AL.MAX_GAIN, 32); + AL.distanceModel(AL.NONE); if (position == null) position = new Vector4(); if (angles == null) angles = new Vector2(Math.PI / 6, -Math.PI / 6); // https://github.com/kcat/openal-soft/issues/1032 - anglesArray = [0, 0]; + if (loopPoints == null) loopPoints = [0, 0]; + if (stereoAnglesExtensionSupported && anglesArray == null) anglesArray = [0, 0]; - if (moreFormatsSupported == null) moreFormatsSupported = AL.isExtensionPresent("AL_EXT_MCFORMATS"); - if (stereoAnglesExtensionSupported == null) stereoAnglesExtensionSupported = AL.isExtensionPresent("AL_EXT_STEREO_ANGLES"); - if (latencyExtensionSupported == null) latencyExtensionSupported = AL.isExtensionPresent("AL_SOFT_source_latency"); - if (loopPointsSupported == null) loopPointsSupported = AL.isExtensionPresent("AL_SOFT_loop_points"); - - AL.sourcef(handle, AL.MAX_GAIN, 10); - AL.distanceModel(AL.NONE); - - var buffer = parent.buffer; - var channels = buffer.channels, bitsPerSample = buffer.bitsPerSample; - - // Default is just AL.FORMAT_MONO8 if it doesn't match any to avoid yo ears getting blasted - var isCreativeXFi = AL.getString(AL.RENDERER) == "X-Fi"; - if (channels > 2 && (isCreativeXFi || moreFormatsSupported)) { // https://github.com/openalext/openalext/blob/master/AL_EXT_MCFORMATS.txt - if (channels == 3) format = bitsPerSample == 32 ? 0x1209 : (bitsPerSample == 16 ? 0x1208 : 0x1207); - else if (channels == 4) format = bitsPerSample == 32 ? 0x1206 : (bitsPerSample == 16 ? 0x1205 : 0x1204); - else if (channels == 6) format = bitsPerSample == 32 ? 0x120C : (bitsPerSample == 16 ? 0x120B : 0x120A); - else if (channels == 7) format = bitsPerSample == 32 ? 0x120F : (bitsPerSample == 16 ? 0x120E : 0x120D); - else if (channels == 8) format = bitsPerSample == 32 ? 0x1212 : (bitsPerSample == 16 ? 0x1211 : 0x1210); - } - // The 32 bit for STEREO and MONO are undocumented? https://github.com/kcat/openal-soft/issues/934, though it only works in X-Fi OpenAL?? - else if (bitsPerSample == 32 && isCreativeXFi) format = channels == 2 ? 0x1203 : 0x1202; - else if (channels == 2) format = bitsPerSample == 16 ? AL.FORMAT_STEREO16 : AL.FORMAT_STEREO8; - else format = bitsPerSample == 16 ? AL.FORMAT_MONO16 : AL.FORMAT_MONO8; - - if (buffer.data != null) { - streamed = false; - samples = (dataLength = (buffer.data.length >> 0)) / buffer.channels / (buffer.bitsPerSample >> 3); - - if ((buffer.__srcBuffer == null || (AL.getBufferi(buffer.__srcBuffer, AL.SIZE) >> 0) != dataLength) && (buffer.__srcBuffer = AL.createBuffer()) != null) - AL.bufferData(buffer.__srcBuffer, format, buffer.data, buffer.data.length, buffer.sampleRate); - - AL.sourcei(handle, AL.BUFFER, buffer.__srcBuffer); - } - #if lime_vorbis - else if (buffer.__srcVorbisFile != null) { - streamed = true; - dataLength = (samples = getFloat(buffer.__srcVorbisFile.pcmTotal())) * buffer.channels * (buffer.bitsPerSample >> 3); - - var constructor = buffer.bitsPerSample == 32 ? Int32 : buffer.bitsPerSample == 16 ? Int16 : Int8; - bufferSize = STREAM_BUFFER_SAMPLES * buffer.channels * (constructor == Int8 ? buffer.bitsPerSample >> 3 : 1); - buffers = AL.genBuffers(STREAM_MAX_BUFFERS); - bufferDatas = [for (i in 0...STREAM_MAX_BUFFERS) new ArrayBufferView(bufferSize, constructor)]; - bufferTimes = [for (i in 0...STREAM_MAX_BUFFERS) 0]; - bufferSizes = [for (i in 0...STREAM_MAX_BUFFERS) 0]; - unusedBuffers = []; - bufferSize *= constructor == Int8 ? 1 : buffer.bitsPerSample >> 3; - } - #end - - if (dataLength == 0) { - trace('NativeAudioSource Bug! dataLength is 0'); - dispose(); - } + resetBuffer(); } - public function play() { - if (playing || disposed) return; - - var t = completed ? 0 : getCurrentTime(); - playing = true; - setCurrentTime(t); - } - - public function pause() { - if (!disposed) AL.sourcePause(handle); - - playing = false; - stopStreamTimer(); - stopTimer(); - } - - public function stop() { - if (!disposed) { - AL.sourceStop(handle); - toLoop = 0; - } - - playing = false; - stopStreamTimer(); - stopTimer(); - } - - private function complete() { - if (!completed) parent.onComplete.dispatch(); + public function resetBuffer() { + if (parent.buffer == null) return; stop(); - completed = true; - } - // Streaming, atleast for vorbis for now. - // just incase if we support more than 1 in the future?? which i doubt - private function streamTell():Float { + AL.sourcei(source, AL.BUFFER, AL.NONE); + + final audioBuffer = parent.buffer; + channels = audioBuffer.channels; + sampleRate = audioBuffer.sampleRate; + wordSize = audioBuffer.bitsPerSample >> 3; + format = getALFormat(audioBuffer.bitsPerSample, channels); + arrayType = wordSize == 4 ? TypedArrayType.Uint32 : (wordSize == 2 ? TypedArrayType.Uint16 : TypedArrayType.Int8); + standaloneBuffer = false; + loopTime = 0; + endTime = null; + + if (buffer != null) { + if (standaloneBuffer) { + AL.bufferData(buffer, 0, null, 0, 0); + AL.deleteBuffer(buffer); + } + buffer = null; + } + + if (audioBuffer.data != null) { + streamed = false; + samples = Std.int((dataLength = audioBuffer.data.byteLength) / wordSize / channels); + } #if lime_vorbis - return parent.buffer.__srcVorbisFile.timeTell(); - #else - return 0; + else if (audioBuffer.__srcVorbisFile != null) { + streamed = true; + dataLength = Std.int(getFloat(samples = Int64.toInt(audioBuffer.__srcVorbisFile.pcmTotal())) * channels * wordSize); + } #end + else return; + + duration = getFloat(samples) / sampleRate * 1000; + + loopPoints[0] = 0; + loopPoints[1] = samples - 1; + + if (streamed) { + final length = STREAM_BUFFER_SAMPLES * channels; + bufferLength = length * wordSize; + + if (buffers == null) buffers = AL.genBuffers(STREAM_MAX_BUFFERS); + if (bufferDatas == null) { + unusedBuffers = []; + bufferDatas = []; + bufferTimes = []; + bufferLengths = []; + bufferTemps = []; + } + else + unusedBuffers.resize(0); + + for (i in 0...STREAM_MAX_BUFFERS) { + bufferTimes[i] = 0.0; + bufferLengths[i] = 0; + + var data = bufferDataPool.pop(); + if (data == null) data = new ArrayBufferView(length, arrayType); + else { + data.type = arrayType; + data.bytesPerElement = wordSize; + data.length = length; + if (data.byteLength != bufferLength) { + #if cpp + data.buffer.getData().resize(bufferLength); + data.buffer.fill(data.byteLength, bufferLength - data.byteLength, 0); + @:privateAccess data.buffer.length = bufferLength; + #else + data.buffer = new ArrayBuffer(bufferLength); + #end + } + data.byteLength = bufferLength; + } + bufferDatas[i] = data; + } + } + else { + if (buffers != null) { + for (buffer in buffers) AL.bufferData(buffer, 0, null, 0, 0); + AL.deleteBuffers(buffers); + buffers = null; + + for (data in bufferDatas) if (bufferDataPool.length < MAX_POOL_BUFFERS) bufferDataPool.push(data); + bufferDatas.resize(0); + } + + if (audioBuffer.__srcBuffer != null) { + if (AL.getBufferi(audioBuffer.__srcBuffer, AL.SIZE) != dataLength) { + AL.bufferData(audioBuffer.__srcBuffer, 0, null, 0, 0); + AL.deleteBuffer(audioBuffer.__srcBuffer); + if ((buffer = audioBuffer.__srcBuffer = AL.createBuffer()) != null) + AL.bufferData(buffer, format, audioBuffer.data, dataLength, sampleRate); + } + else + buffer = audioBuffer.__srcBuffer; + } + else if ((buffer = audioBuffer.__srcBuffer = AL.createBuffer()) != null) + AL.bufferData(buffer, format, audioBuffer.data, dataLength, sampleRate); + + AL.sourcei(source, AL.BUFFER, buffer); + } + + updateLoopPoints(); } - private function streamSeek(samples:Int64) { - #if lime_vorbis - // There's apparently a bug in libvorbis <= 1.3.4, hhttps://github.com/xiph/vorbis/blob/master/CHANGES#L39 - // But it seems to also happen in this lime libvorbis version which is 1.3.7...? -ralty - if (samples < 4) parent.buffer.__srcVorbisFile.rawSeek(0); else parent.buffer.__srcVorbisFile.pcmSeek(samples); - #end + function updateLoopPoints() { + if (loops <= 0) return AL.sourcei(source, AL.LOOPING, AL.FALSE); + + var time = getCurrentTime() + parent.offset, length = getRealLength(); + final fixed = time >= length; + if (fixed) time = loopTime; + + if (!streamed) { + var internalLoop = AL.TRUE; + if (length < duration - 1 && loopTime > 1) { + if (!loopPointsSupported) internalLoop = AL.FALSE; + else { + AL.sourceStop(source); + AL.sourcei(source, AL.BUFFER, AL.NONE); + if (!standaloneBuffer) { + if (standaloneBuffer = (buffer = AL.createBuffer()) != null) + AL.bufferData(buffer, format, parent.buffer.data, dataLength, sampleRate); + else { + buffer = parent.buffer.__srcBuffer; + internalLoop = AL.FALSE; + } + } + if (internalLoop == AL.TRUE) AL.bufferiv(buffer, 0x2015/*AL.LOOP_POINTS_SOFT*/, loopPoints); + AL.sourcei(source, AL.BUFFER, buffer); + } + } + + AL.sourcei(source, AL.LOOPING, internalLoop); + if (playing) setCurrentTime(time - parent.offset); + else updateCompleteTimer(); + } + else if (playing && (fixed || streamLoops > 0)) { + AL.sourcei(source, AL.LOOPING, AL.FALSE); + AL.sourceStop(source); + snapBuffersToTime(time, streamLoops > 0); + AL.sourcePlay(source); + } } - private function readToBufferData(data:ArrayBufferView):Int { - #if lime_vorbis - var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN, wordSize = parent.buffer.bitsPerSample >> 3; - var size = dataLength - (streamTell() * parent.buffer.sampleRate * parent.buffer.channels * wordSize); - var n:Int = size < bufferSize ? Math.floor(size) : bufferSize; - n -= n % (parent.buffer.channels * wordSize); + // https://github.com/xiph/vorbis/blob/master/CHANGES#L39 bug in libvorbis <= 1.3.4 + inline function streamSeek(samples:Int64) + if (samples <= 1) parent.buffer.__srcVorbisFile.rawSeek(0); else parent.buffer.__srcVorbisFile.pcmSeek(samples); - var total = 0, result = 0, wasEOF = false; - while (total < bufferSize) { - result = n > 0 ? parent.buffer.__srcVorbisFile.read(data.buffer, total, n, isBigEndian, wordSize, true) : 0; + inline function streamTell():Int64 + return parent.buffer.__srcVorbisFile.pcmTell(); + + inline function streamRead(buffer:ArrayBuffer, position:Int, length:Int, wordSize:Int):Int + return parent.buffer.__srcVorbisFile.read(buffer, position, length, isBigEndian, wordSize, true); + + function readToBufferData(data:ArrayBufferView, currentPCM:Int64):Int { + var length = (Int64.ofInt(loopPoints[1]) - currentPCM) * channels * wordSize; + var n = length < bufferLength ? length.low : bufferLength, total = 0, result = 0, wasEOF = false; + while (total < bufferLength) { + result = n > 0 ? streamRead(data.buffer, total, n, wordSize) : 0; if (result == Vorbis.HOLE) continue; else if (result <= Vorbis.EREAD) break; else if (result == 0) { - if (wasEOF || (streamEnded = loops <= toLoop)) break; - else { - wasEOF = true; - toLoop++; + if (streamEnded = wasEOF == (wasEOF = true) || loops <= streamLoops) break; - var samples = getSamples(loopTime != null ? loopTime + parent.offset : parent.offset); - streamSeek(samples); - if ((size = dataLength - (getFloat(samples) * parent.buffer.channels * wordSize)) < (n = bufferSize - total)) n = Math.floor(size); - n -= n % (parent.buffer.channels * wordSize); - } + streamSeek(Int64.ofInt(loopPoints[0])); + streamLoops++; + if ((length = Int64.ofInt(loopPoints[1] - loopPoints[0]) * channels * wordSize) < (n = bufferLength - total)) n = length.low; } else { total += result; @@ -238,261 +379,301 @@ class NativeAudioSource { } } - if (total < bufferSize) data.buffer.fill(total, n, 0); if (result < 0) { - trace('NativeAudioSource Streaming Bug! reading result is $result, streamEnded: $streamEnded, total: $total, n: $n'); + trace('NativeAudioSource readToBufferData Bug! reading result is $result, streamEnded: $streamEnded, total: $total, n: $n'); return result; } return total; - #else - return 0; - #end } - private function fillBuffer(buffer:ALBuffer):Int { + function fillBuffer(buffer:ALBuffer):Int { var i = STREAM_MAX_BUFFERS - requestBuffers; - var data = bufferDatas[i], time = streamTell(); - var decoded = readToBufferData(data); + var data = bufferDatas[i], currentPCM = streamTell(); + var decoded = readToBufferData(data, currentPCM); if (decoded > 0) { - AL.bufferData(buffer, format, data, decoded, parent.buffer.sampleRate); + AL.bufferData(buffer, format, data, decoded, sampleRate); - var n = STREAM_MAX_BUFFERS - 1; + var n = STREAM_MAX_BUFFERS - 1, j = i; while (i < n) { - bufferDatas[i] = bufferDatas[i + 1]; - bufferTimes[i] = bufferTimes[i + 1]; - bufferSizes[i] = bufferSizes[++i]; + bufferDatas[i] = bufferDatas[++j]; + bufferTimes[i] = bufferTimes[j]; + bufferLengths[i] = bufferLengths[j]; + i = j; } queuedBuffers = requestBuffers; bufferDatas[n] = data; - bufferTimes[n] = time; - bufferSizes[n] = decoded; + bufferTimes[n] = getFloat(currentPCM) / sampleRate; + bufferLengths[n] = decoded; } return decoded; } - private function streamRun() { - if (parent == null || parent.buffer == null || handle == null) return dispose(); - if (!playing || disposed) return stopStreamTimer(); + inline function queueBuffer(buffer:ALBuffer) bufferTemps[buffersToQueue++] = buffer; + inline function flushBuffers() { + AL.sourceQueueBuffers(source, buffersToQueue, bufferTemps); + buffersToQueue = 0; + } - try { - var processed = AL.getSourcei(handle, AL.BUFFERS_PROCESSED), n = STREAM_BUFFER_FREQUENCY, buffer; - while (processed-- > 0) { - buffer = AL.sourceUnqueueBuffer(handle); - if (!streamEnded && --n > 0 && fillBuffer(buffer) > 0) AL.sourceQueueBuffer(handle, buffer); + function skipBuffers(n:Int, fill:Int = 0) { + for (buffer in AL.sourceUnqueueBuffers(source, n)) { + if (!streamEnded && --fill > 0 && fillBuffer(buffer) > 0) queueBuffer(buffer); + else { + queuedBuffers = --requestBuffers; + unusedBuffers.push(buffer); + } + } + } + + function fillBuffers(n:Int) { + var buffer:ALBuffer; + while (n-- > 0 && queuedBuffers < STREAM_MAX_BUFFERS && !streamEnded) { + if ((buffer = unusedBuffers.pop()) != null) { + requestBuffers++; + if (fillBuffer(buffer) > 0) queueBuffer(buffer); else { - queuedBuffers = --requestBuffers; + requestBuffers--; unusedBuffers.push(buffer); } } + else if (fillBuffer(buffer = buffers[requestBuffers++]) > 0) queueBuffer(buffer); + else requestBuffers--; + } + } - if (!streamEnded) { - if (unusedBuffers.length != 0) { - requestBuffers++; - if (fillBuffer(buffer = unusedBuffers.pop()) > 0) { - queuedBuffers = requestBuffers; - AL.sourceQueueBuffer(handle, buffer); - } - else { - requestBuffers--; - unusedBuffers.push(buffer); - } - } - else if (queuedBuffers < STREAM_MAX_BUFFERS) { - if (fillBuffer(buffer = buffers[requestBuffers++]) > 0) AL.sourceQueueBuffer(handle, buffer); - else requestBuffers--; - } + function streamRun() { + if (source == null || parent.buffer == null || parent.buffer.__srcVorbisFile == null || !streamed || !playing) + return streamTimer.stop(); + + final queued = AL.getSourcei(source, AL.BUFFERS_QUEUED); + skipBuffers(AL.getSourcei(source, AL.BUFFERS_PROCESSED), STREAM_PROCESS_BUFFERS + (queued < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - queued : 0)); + fillBuffers(STREAM_PROCESS_BUFFERS - buffersToQueue); + flushBuffers(); + + if (AL.getSourcei(source, AL.SOURCE_STATE) == AL.STOPPED) { + AL.sourcePlay(source); + updateCompleteTimer(); + } + } + + function snapBuffersToTime(time:Float, force:Bool) { + if (source == null || parent.buffer == null || parent.buffer.__srcVorbisFile == null) return; + + final sec = time / 1000; + if (!force) { + var bufferTime:Float; + for (i in (STREAM_MAX_BUFFERS - queuedBuffers)...STREAM_MAX_BUFFERS) + if (sec >= (bufferTime = bufferTimes[i]) && sec < bufferTime + (bufferLengths[i] / wordSize / channels / sampleRate)) + { + skipBuffers(i - (STREAM_MAX_BUFFERS - queuedBuffers), STREAM_MIN_BUFFERS - STREAM_MAX_BUFFERS + i); + AL.sourcei(source, AL.SAMPLE_OFFSET, Math.floor((sec - bufferTime) * sampleRate)); + return flushBuffers(); } } - catch(e) trace('NativeAudioSource Bug! in streamRun $e'); - if (AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) { - AL.sourcePlay(handle); - resetTimer((getLength() - getCurrentTime()) / getPitch()); + AL.sourceUnqueueBuffers(source, AL.getSourcei(source, AL.BUFFERS_QUEUED)); + + streamEnded = false; + unusedBuffers.resize(0); + streamSeek(Int64.fromFloat(sec * sampleRate)); + + buffersToQueue = streamLoops = 0; + for (i in 0...(requestBuffers = queuedBuffers = STREAM_MIN_BUFFERS)) { + if (!streamEnded && fillBuffer(buffers[i]) > 0) queueBuffer(buffers[i]); + else queuedBuffers = --requestBuffers; } + flushBuffers(); } - // Timers - inline function stopStreamTimer() { - if (streamTimer != null) streamTimer.stop(); - streamTimer = null; - } - - private function resetStreamTimer() { - stopStreamTimer(); - if (streamed) (streamTimer = new Timer(STREAM_TIMER_FREQUENCY)).run = streamRun; - } - - inline function stopTimer() { - if (timer != null) timer.stop(); - timer = null; - } - - private function resetTimer(timeRemaining:Float) { - stopTimer(); - (timer = new Timer(timeRemaining)).run = timer_onRun; - } - - private function timer_onRun() { - var timeRemaining = (getLength() - getCurrentTime()) / getPitch(); - if (timeRemaining > 100 && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING && (!streamed || !streamEnded)) { - resetTimer(timeRemaining); + function timer_onRun() { + final pitch = getPitch(); + var timeRemaining = (getLength() - getCurrentTime()) / pitch; + if (timeRemaining > 50 && AL.getSourcei(source, AL.SOURCE_STATE) == AL.PLAYING && (!streamed || !streamEnded && streamLoops <= 0)) { + completeTimer = resetTimer(completeTimer, timeRemaining, timer_onRun); return; } - if (loops == 0) { - complete(); - return; + completeTimer.stop(); + + if (loops == 0) return complete(); + + if (streamLoops > 0) { + loops -= streamLoops; + streamLoops = 0; + completeTimer = resetTimer(completeTimer, (getLength() + parent.offset - loopTime) / pitch, timer_onRun); + } + else if (!loopPointsSupported || AL.getSourcei(source, AL.LOOPING) == AL.FALSE) { + loops--; + playing = true; + setCurrentTime(loopTime - parent.offset); } - var start = loopTime != null ? loopTime : 0; - if (toLoop <= 0) { - setLoops(--loops); - if (start > 0 || AL.getSourcei(handle, AL.SOURCE_STATE) != AL.PLAYING) setCurrentTime(start); - else resetTimer((getLength() - start) / getPitch()); - } - else { - if ((loops -= toLoop) < 0) loops = 0; - toLoop = 0; - resetTimer((getLength() - start) / getPitch()); + if (loops <= 0) { + loops = 0; + AL.sourcei(source, AL.LOOPING, AL.FALSE); } parent.onLoop.dispatch(); } - // Get & Set Methods - public function getCurrentTime():Float { - if (disposed) return 0; - else if (completed) return getLength(); + function updateCompleteTimer() { + if (playing) { + var timeRemaining = (getLength() - getCurrentTime()) / getPitch(); + if (timeRemaining > 50) completeTimer = resetTimer(completeTimer, timeRemaining, timer_onRun); + else { + if (completeTimer != null) completeTimer.stop(); + if (loops > 0) play(); + else complete(); + } + } + else if (completeTimer != null) + completeTimer.stop(); + } - var time = AL.getSourcef(handle, AL.SAMPLE_OFFSET) / parent.buffer.sampleRate; + function resetStreamTimer() { + if (!streamEnded && (streamTimer == null || !streamTimer.mRunning)) + streamTimer = resetTimer(streamTimer, STREAM_TIMER_CHECK_MS, streamRun); + } + + public function play() { + if (playing || disposed) return; + final time = completed ? 0 : getCurrentTime(); + playing = true; + setCurrentTime(time); + } + + public function pause() { + if (!disposed) AL.sourcePause(source); + lastTime = getCurrentTime(); + playing = false; + if (completeTimer != null) completeTimer.stop(); + if (streamTimer != null) streamTimer.stop(); + } + + public function stop() { + if (!disposed) AL.sourceStop(source); + lastTime = 0; + streamLoops = 0; + playing = false; + if (completeTimer != null) completeTimer.stop(); + if (streamTimer != null) streamTimer.stop(); + } + + public function complete() { + if (!completed) parent.onComplete.dispatch(); + stop(); + completed = true; + } + + public function getCurrentTime():Float { + if (disposed) return 0.0; + else if (completed) return getLength(); + else if (!playing) return lastTime - parent.offset; + + var time = AL.getSourcef(source, AL.SEC_OFFSET); if (streamed) { - if (playing && streamEnded && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) { + if (playing && streamEnded && AL.getSourcei(source, AL.SOURCE_STATE) == AL.STOPPED) { complete(); return getLength(); } - else + else if (bufferTimes != null) time += bufferTimes[STREAM_MAX_BUFFERS - queuedBuffers]; } time *= 1000; - var length = if (length == null) getRealLength(); else length; - if (loops > 0 && time > length) { - var start = loopTime != null ? Math.max(0, loopTime) : 0; - return ((time - start) % (length - start)) + start - parent.offset; - } - - return time - parent.offset; + var length = getRealLength(); + return if (loops <= 0 || time <= length) time - parent.offset; + else ((time - loopTime) % (length - loopTime)) + loopTime - parent.offset; } public function setCurrentTime(value:Float):Float { - if (disposed) return value; + if (disposed) return 0.0; - if (streamed) AL.sourceStop(handle); - else AL.sourcei(handle, AL.SAMPLE_OFFSET, getSamples(value + parent.offset)); + final length = getRealLength(); + value = Math.isFinite(value) ? Math.max(Math.min(value + parent.offset, length), parent.offset) : parent.offset; + if (streamed) AL.sourceStop(source); + else AL.sourcef(source, AL.SEC_OFFSET, value / 1000); + + final timeRemaining = (length - value) / getPitch(); if (playing) { - var timeRemaining = (getLength() - value) / getPitch(); if (timeRemaining < 8 && value > 8) complete(); else { - completed = streamEnded = false; + completed = false; if (streamed) { - AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED)); - - unusedBuffers.resize(0); - streamSeek(getSamples(value + parent.offset)); - - requestBuffers = queuedBuffers = STREAM_BUFFER_FREQUENCY; - for (i in 0...queuedBuffers) { - if (!streamEnded && fillBuffer(buffers[i]) > 0) AL.sourceQueueBuffer(handle, buffers[i]); - else queuedBuffers = --requestBuffers; - } - - if (!streamEnded) resetStreamTimer(); + snapBuffersToTime(value, false); + resetStreamTimer(); } - - resetTimer(timeRemaining); - AL.sourcePlay(handle); + if (AL.getSourcei(source, AL.SOURCE_STATE) != AL.PLAYING) AL.sourcePlay(source); } } - else - completed = false; - - if (!playing && streamed) bufferTimes[STREAM_MAX_BUFFERS - (requestBuffers = queuedBuffers = 1)] = value / 1000; - - return value; - } - - inline private function getRealLength():Float return samples / parent.buffer.sampleRate * 1000; - public function getLength():Null - return if (disposed) 0; else if (length == null) getRealLength() - parent.offset; else length - parent.offset; - - public function setLength(value:Null):Null { - if (value == length || disposed) return length = value; - - var buffer = parent.buffer, wordSize = buffer.bitsPerSample >> 3; - if ((length = value) == null) dataLength = streamed ? samples * buffer.channels * wordSize : getFloat(Int64.make(0, buffer.data.length)); - else dataLength = Math.ffloor(Math.max(0, Math.min(value, getRealLength())) / 1000 * buffer.sampleRate) * buffer.channels * wordSize; - - if (playing) { - if (streamed && (streamEnded || toLoop > 0)) setCurrentTime(getCurrentTime()); - else { - var timeRemaining = ((getLength() - parent.offset) - getCurrentTime()) / getPitch(); - if (timeRemaining > 0) resetTimer(timeRemaining); - } + else { + completed = timeRemaining < 8; + lastTime = value; } + + updateCompleteTimer(); return value; } public function getPitch():Float { return if (disposed) 1; - else AL.getSourcef(handle, AL.PITCH); + else AL.getSourcef(source, 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); - } - AL.sourcef(handle, AL.PITCH, value); + if (disposed || (value = Math.max(value, 0)) == AL.getSourcef(source, AL.PITCH)) return value; + AL.sourcef(source, AL.PITCH, value); + updateCompleteTimer(); return value; } public function getGain():Float { - if (disposed) return 1; - return AL.getSourcef(handle, AL.GAIN); + return if (disposed) 1; + else AL.getSourcef(source, AL.GAIN); } public function setGain(value:Float):Float { - if (!disposed) AL.sourcef(handle, AL.GAIN, value); + value = Math.max(value, 0); + if (!disposed) AL.sourcef(source, AL.GAIN, value); return value; } - inline public function getLoops():Int return loops; + public function getLoops():Int return loops; - inline public function setLoops(value:Int):Int { - if (!streamed && !disposed) AL.sourcei(handle, AL.LOOPING, (loopTime <= 0 && value > 0) ? AL.TRUE : AL.FALSE); - else if (loops == 0 && value > 0) setCurrentTime(getCurrentTime()); - if (value < 0) return loops = 0; - return loops = value; + public function setLoops(value:Int):Int { + if (loops == (loops = value < 0 ? 0 : value)) return loops; + updateLoopPoints(); + return loops; } - inline public function getLoopTime():Float return loopTime; + public function getLoopTime():Float return loopTime - parent.offset; - inline public function setLoopTime(value:Float):Float { - if (!streamed && !disposed) AL.sourcei(handle, AL.LOOPING, (value <= 0 && loops > 0) ? AL.TRUE : AL.FALSE); - return loopTime = value; + public function setLoopTime(value:Float):Float { + if (loopTime == (loopTime = Math.max(Math.min(value + parent.offset, duration), 0))) return loopTime - parent.offset; + if ((loopPoints[0] = Std.int(value / 1000 * sampleRate)) >= samples) loopPoints[0] = samples - 1; + updateLoopPoints(); + return loopTime - parent.offset; + } + + public function getRealLength():Float return if (endTime == null) duration; else endTime; + public function getLength():Float return if (disposed) 0; else (inline getRealLength()) - parent.offset; + + public function setLength(value:Null):Null { + if (endTime == (endTime = (value == null ? value : Math.max(Math.min(value + parent.offset, duration), 0)))) return endTime - parent.offset; + if ((loopPoints[1] = Std.int(getRealLength() / 1000 * sampleRate)) >= samples) loopPoints[1] = samples - 1; + updateLoopPoints(); + return endTime - parent.offset; } public function getLatency():Float { - /*#if (lime >= "8.3.0") - if (latencyExtensionSupported) { - final offsets = AL.getSourcedvSOFT(handle, AL.SEC_OFFSET_LATENCY_SOFT, 2); - if (offsets != null) return offsets[1] * 1000; - } - #end*/ + //#if (lime >= "8.4.0") + //if (latencyExtensionSupported) { + // final offsets = AL.getSourcedvSOFT(source, AL.SEC_OFFSET_LATENCY_SOFT, 2); + // if (offsets != null) return offsets[1] * 1000; + //} + //#end return 0; } @@ -505,11 +686,12 @@ class NativeAudioSource { if (angles == null) angles = new Vector2(left, right); else angles.setTo(left, right); - if (!disposed) { + if (!disposed && stereoAnglesExtensionSupported) { anglesArray[0] = angles.x; anglesArray[1] = angles.y; - AL.sourcei(handle, 0x1214/*AL.SOURCE_SPATIALIZE_SOFT*/, AL.FALSE); - AL.sourcefv(handle, 0x1030/*AL.STEREO_ANGLES*/, anglesArray); + AL.sourcei(source, 0x1214/*AL.SOURCE_SPATIALIZE_SOFT*/, AL.FALSE); + AL.sourcefv(source, 0x1030/*AL.STEREO_ANGLES*/, anglesArray); + AL.source3f(source, AL.POSITION, 0, 0, 0); } return angles; } @@ -527,9 +709,9 @@ class NativeAudioSource { // OpenAL Soft Positions doesn't seem to do anything but panning? if (!disposed) { - AL.sourcei(handle, 0x1214/*AL.SOURCE_SPATIALIZE_SOFT*/, Math.abs(position.x) > 1e-04 ? AL.TRUE : AL.FALSE); - AL.sourcei(handle, AL.MAX_DISTANCE, 1); - AL.source3f(handle, AL.POSITION, position.x, position.y, position.z); + if (stereoAnglesExtensionSupported) AL.sourcei(source, 0x1214/*AL.SOURCE_SPATIALIZE_SOFT*/, Math.abs(position.x) > 1e-04 ? AL.TRUE : AL.FALSE); + AL.sourcei(source, AL.MAX_DISTANCE, 1); + AL.source3f(source, AL.POSITION, position.x, position.y, position.z); } return position; } @@ -539,14 +721,11 @@ class NativeAudioSource { public function setPan(value:Float):Float { getPosition().setTo(value, 0, -Math.sqrt(1 - value * value)); if (!disposed) { - if (parent.buffer.channels > 1 && stereoAnglesExtensionSupported) + if (parent.buffer.channels > 1) setAngles(Math.PI * (Math.min(-value * 2 + 1, 1)) / 6, -Math.PI * Math.min(value * 2 + 1, 1) / 6); else setPosition(position); } return value; } - - inline private function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >> 0); - inline private function getSamples(ms:Float):Int64 return Int64.fromFloat(Math.max(0, Math.min(ms / 1000 * parent.buffer.sampleRate, samples))); } \ No newline at end of file diff --git a/source/lime/media/AudioBuffer.hx b/source/lime/media/AudioBuffer.hx index fd44bc3e..30c5448b 100644 --- a/source/lime/media/AudioBuffer.hx +++ b/source/lime/media/AudioBuffer.hx @@ -8,7 +8,10 @@ import lime.app.Future; import lime.app.Promise; import lime.media.openal.AL; import lime.media.openal.ALBuffer; +#if lime_vorbis +import lime.media.vorbis.Vorbis; import lime.media.vorbis.VorbisFile; +#end import lime.net.HTTPRequest; import lime.utils.Log; import lime.utils.UInt8Array; @@ -101,7 +104,10 @@ class AudioBuffer __srcHowl = null; #end #if lime_cffi - if (__srcBuffer != null) AL.deleteBuffer(__srcBuffer); + if (__srcBuffer != null) { + AL.bufferData(__srcBuffer, 0, null, 0, 0); + AL.deleteBuffer(__srcBuffer); + } __srcBuffer = null; #end #if lime_vorbis @@ -178,11 +184,9 @@ class AudioBuffer return audioBuffer; #elseif (lime_cffi && !macro) - #if lime_vorbis // CNE - if (funkin.options.Options.streamedMusic) { - var vorbisFile = VorbisFile.fromBytes(bytes); - if (vorbisFile != null) return fromVorbisFile(vorbisFile); - } + #if lime_vorbis + var vorbisFile = VorbisFile.fromBytes(bytes); + if (vorbisFile != null) return fromVorbisFile(vorbisFile); #end #if !cs var audioBuffer = new AudioBuffer(); @@ -312,29 +316,29 @@ class AudioBuffer if (vorbisFile == null) return null; var info = vorbisFile.info(); - if (info == null) return null; var audioBuffer = new AudioBuffer(); audioBuffer.channels = info.channels; audioBuffer.sampleRate = info.rate; audioBuffer.bitsPerSample = 16; - if (!vorbisFile.seekable() || - vorbisFile.pcmTotal() < #if lime_cffi @:privateAccess lime._internal.backend.native.NativeAudioSource.STREAM_BUFFER_SAMPLES #else 0x4000 #end) - { - // convert it to static if its too short or unseekable. + final pcmTotal = vorbisFile.pcmTotal(-1); + if (!vorbisFile.seekable() || pcmTotal < (audioBuffer.sampleRate << 2)) { vorbisFile.rawSeek(0); - var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN; - var bytes:Bytes = Bytes.alloc(Std.int(haxe.Int64.toInt(vorbisFile.pcmTotal()) * info.channels * 2)); + final isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN; + final bytes = Bytes.alloc(Std.int((pcmTotal.high * 4294967296. + (pcmTotal.low >> 0)) * info.channels * (audioBuffer.bitsPerSample >> 3))); var total = 0, result = 0; do { - total += (result = vorbisFile.read(bytes, total, 0x1000, isBigEndian, 2, true)); - } while (result > 0); + result = vorbisFile.read(bytes, total, 0x1000, isBigEndian, 2, true); + total += result; + } while (result > 0 || result == Vorbis.HOLE); audioBuffer.data = new UInt8Array(bytes); + vorbisFile.clear(); } - else audioBuffer.__srcVorbisFile = vorbisFile; + else + audioBuffer.__srcVorbisFile = vorbisFile; return audioBuffer; } diff --git a/source/lime/media/AudioSource.hx b/source/lime/media/AudioSource.hx index fd4c0078..992bfdb5 100644 --- a/source/lime/media/AudioSource.hx +++ b/source/lime/media/AudioSource.hx @@ -131,7 +131,7 @@ class AudioSource @:noCompletion inline private function init():Void { __backend.init(); - activeSources.push(this); + if (!activeSources.contains(this)) activeSources.push(this); } /** diff --git a/source/openfl/media/SoundChannel.hx b/source/openfl/media/SoundChannel.hx index f91320e8..5b32314d 100644 --- a/source/openfl/media/SoundChannel.hx +++ b/source/openfl/media/SoundChannel.hx @@ -137,7 +137,6 @@ import lime.media.openal.AL; #if lime __source.onComplete.remove(source_onComplete); - __source.onLoop.remove(source_onLoop); __source.dispose(); __source = null; #end @@ -165,14 +164,14 @@ import lime.media.openal.AL; #if lime_cffi var backend = __source.__backend, i = 0; if (backend.streamed) { - size = backend.bufferSizes[i = backend.bufferSizes.length - backend.queuedBuffers]; + size = backend.bufferLengths[i = backend.bufferLengths.length - backend.queuedBuffers]; buf = backend.bufferDatas[i].buffer; pos -= Math.floor(backend.bufferTimes[i] * buffer.sampleRate * buffer.channels * wordSize); while (pos > size) { - if (++i >= backend.bufferSizes.length) return false; + if (++i >= backend.bufferLengths.length) return false; pos -= size; buf = backend.bufferDatas[i].buffer; - size = backend.bufferSizes[i]; + size = backend.bufferLengths[i]; } } else @@ -189,10 +188,10 @@ import lime.media.openal.AL; if (c % 2 == 0) ((b > leftMax) ? (leftMax = b) : (if ((b = -b) > leftMin) (leftMin = b))); else ((b > rightMax) ? (rightMax = b) : (if ((b = -b) > rightMin) (rightMin = b))); if ((pos += wordSize) >= size) #if lime_cffi { - if (!backend.streamed || ++i >= backend.bufferSizes.length) break; + if (!backend.streamed || ++i >= backend.bufferLengths.length) break; pos = 0; buf = backend.bufferDatas[i].buffer; - size = backend.bufferSizes[i]; + size = backend.bufferLengths[i]; } #else break; #end @@ -222,7 +221,6 @@ import lime.media.openal.AL; } __source.onComplete.add(source_onComplete); - __source.onLoop.add(source_onLoop); __isValid = true; __source.play(); @@ -394,11 +392,6 @@ import lime.media.openal.AL; dispatchEvent(new Event(Event.SOUND_COMPLETE)); } - @:noCompletion private function source_onLoop():Void - { - //dispatchEvent(new Event(Event.SOUND_LOOP)); - } - @:noCompletion private function get___audioSource():AudioSource return __source; @:noCompletion private function set___audioSource(source:AudioSource):AudioSource return __source = source; }