diff --git a/source/flixel/sound/FlxSound.hx b/source/flixel/sound/FlxSound.hx index ae596dab..cb3afbe2 100644 --- a/source/flixel/sound/FlxSound.hx +++ b/source/flixel/sound/FlxSound.hx @@ -248,7 +248,6 @@ class FlxSound extends FlxBasic { var _amplitudeLeft:Float; var _amplitudeRight:Float; var _amplitudeUpdate:Bool; - var _amplitudeTime:Float; var _alreadyPaused:Null; public function new() { @@ -468,7 +467,7 @@ class FlxSound extends FlxBasic { function init(?looped:Null, ?autoDestroy:Null, ?onComplete:NullVoid>):FlxSound { if (looped != null) this.looped = looped; if (autoDestroy != null) this.autoDestroy = autoDestroy; - if (onComplete != null) this.onComplete = onComplete; // DEPRECATED + if (onComplete != null) this.onComplete = onComplete; if (_sound != null) makeChannel(); else _length = 0; @@ -483,6 +482,7 @@ class FlxSound extends FlxBasic { * @since raltyMod */ function makeChannel() @:privateAccess { + if (_sound.__buffer == null) return; if (_source != null) _source.dispose(); if (_channel == null) (_source = (_channel = new SoundChannel(null)).__source = new AudioSource(_sound.__buffer)).gain = 0; @@ -684,20 +684,19 @@ class FlxSound extends FlxBasic { #end _channel.soundTransform = _transform; - _channel.__lastPeakTime = 0; + _channel.__lastPeakSamples = 0; _channel.__leftPeak = 0; _channel.__rightPeak = 0; _channel.addEventListener(Event.SOUND_COMPLETE, stopped); #if lime_openal _source.__backend.playing = true; #end _source.offset = 0; - _source.currentTime = startTime - _offset; + _source.currentTime = startTime + _offset; #if !lime_openal _source.play(); #end looped = looped; loopTime = loopTime; endTime = endTime; - _amplitudeTime = -1; active = true; } @@ -773,13 +772,11 @@ class FlxSound extends FlxBasic { @:privateAccess return _sound != null ? _sound.__buffer : null; function update_amplitude():Void @:privateAccess { - if (_channel == null || _time == _amplitudeTime || !_amplitudeUpdate) return; - _channel.__updatePeaks(); + if (_channel == null || !_channel.__updatePeaks() || !_amplitudeUpdate) return; _amplitudeUpdate = false; _amplitudeLeft = _channel.__leftPeak; _amplitudeRight = _channel.__rightPeak; - _amplitudeTime = _time; } inline function get_amplitudeLeft():Float { @@ -794,7 +791,7 @@ class FlxSound extends FlxBasic { inline function get_amplitude():Float { update_amplitude(); - return if (stereo) (_amplitudeLeft + _amplitudeRight) * 0.5; else _amplitudeLeft; + return Math.max(_amplitudeLeft, _amplitudeRight);//if (stereo) (_amplitudeLeft + _amplitudeRight) * 0.5; else _amplitudeLeft; } inline function get_channels():Int @@ -845,9 +842,9 @@ class FlxSound extends FlxBasic { return _time; } function get_time():Float { - if (_channel == null || AudioManager.context == null) return _time; + if (_channel == null || /*AudioManager.context == null*/funkin.backend.system.Main.audioDisconnected /*CNE IMPLEMENTATION*/) return _time; - final pos = _channel.position + _offset; + final pos = _channel.position - _offset; if (!playing || _realPitch <= 0) { _lastTime = null; return _time = pos; @@ -876,7 +873,7 @@ class FlxSound extends FlxBasic { } else if (playing) { _source.offset = 0; - _source.currentTime = time - _offset; + _source.currentTime = time + _offset; } } @@ -887,7 +884,7 @@ class FlxSound extends FlxBasic { inline function get_offset():Float return _offset; inline function set_offset(offset:Float):Float { if (_offset == (_offset = offset)) return offset; - //time = time - _offset; + //time = time + _offset; return offset; } diff --git a/source/funkin/backend/system/modules/AudioSwitchFix.hx b/source/funkin/backend/system/modules/AudioSwitchFix.hx index 382fcc0c..25d904db 100644 --- a/source/funkin/backend/system/modules/AudioSwitchFix.hx +++ b/source/funkin/backend/system/modules/AudioSwitchFix.hx @@ -16,8 +16,13 @@ import haxe.Timer; class AudioSwitchFix { public static function onAudioDisconnected() @:privateAccess { var soundList:Array = [FlxG.sound.music]; - for (sound in FlxG.sound.list) if (sound.playing) soundList.push(sound); - for (source in AudioSource.activeSources) source.dispose(); + for (sound in FlxG.sound.list) if (sound.playing) { + sound.pause(); + soundList.push(sound); + } + + var i = AudioSource.activeSources.length; + while (i-- > 0) AudioSource.activeSources[i].dispose(); AudioManager.shutdown(); @@ -36,7 +41,7 @@ class AudioSwitchFix { // #end for (sound in soundList) { - sound._paused = true; + sound.makeChannel(); sound.resume(); } diff --git a/source/funkin/backend/utils/AudioAnalyzer.hx b/source/funkin/backend/utils/AudioAnalyzer.hx index 49c5a991..1d944551 100644 --- a/source/funkin/backend/utils/AudioAnalyzer.hx +++ b/source/funkin/backend/utils/AudioAnalyzer.hx @@ -2,44 +2,121 @@ package funkin.backend.utils; import flixel.sound.FlxSound; import lime.media.AudioBuffer; +import lime.utils.ArrayBufferView; -// Thank you yosh :DDD -lunar -// (https://github.com/CodenameCrew/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx) +#if lime_vorbis +import lime.media.vorbis.Vorbis; +import lime.media.vorbis.VorbisFile; +#end + +// ORIGINAL CODES FROM YOSH & LUNAR https://github.com/CodenameCrew/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx +// REWRITTEN BY RALTYRO class AudioAnalyzer { public var buffer:AudioBuffer; - var __peakByte:Float = 0; - var __timeMulti:Float = 0; + var __toBits:Float; + var __wordSize:Int; + var __bitUnsignedSize:Int; + var __bitSize:Int; + + #if lime_vorbis + var __vorbis:VorbisFile; + #end public function new(sound:FlxSound) { - @:privateAccess buffer = sound._sound.__buffer; + buffer = sound.buffer; - __peakByte = Math.pow(2, buffer.bitsPerSample-1)-1; + __bitUnsignedSize = 1 << buffer.bitsPerSample; + __bitSize = 1 << (buffer.bitsPerSample - 1); + __toBits = buffer.sampleRate / 1000 * buffer.channels * (__wordSize = buffer.bitsPerSample >> 3); - __timeMulti = 1 / (1 / buffer.sampleRate); - __timeMulti *= buffer.bitsPerSample; - __timeMulti -= __timeMulti % buffer.bitsPerSample; + #if lime_vorbis + __prepareVorbis(); + #end } public function analyze(startPos:Float, endPos:Float):Float { - var bytesStartPos:Int = Math.floor(startPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample; - var bytesEndPos:Int = Math.floor(endPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample; + if (buffer.data != null) return __analyze(startPos, endPos); + #if lime_vorbis + else if (__prepareVorbis()) return __analyzeVorbis(startPos, endPos); + #end + return 0; + } - bytesStartPos -= bytesStartPos % buffer.bitsPerSample; - bytesEndPos -= bytesEndPos % buffer.bitsPerSample; + inline function __analyze(startPos:Float, endPos:Float):Float { + var pos = Math.floor(startPos * __toBits), end = Math.floor(endPos * __toBits), buf = #if js buffer.data #else buffer.data.buffer #end; + var max = 0, b = 0; + if (__wordSize == 1) { // 8-bit audio data is unsigned (0 is 128, 128 is 256, -128 is 0) + while (pos < end) { + if (max < (b = #if js buf[pos] #else buf.get(pos) #end - __bitSize)) max = b; + pos++; + } + } + else { + pos -= pos % __wordSize; + end -= end % __wordSize; - var maxByte:Int = 0; - for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / buffer.bitsPerSample)) { - #if js - var byte:Int = buffer.data[bytesStartPos + (i * buffer.bitsPerSample)] - | (buffer.data[bytesStartPos + (i * buffer.bitsPerSample) + 1] << 8); - #else - var byte:Int = buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample)) - | (buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample) + 1) << 8); - #end - if (byte > 256 * 128) byte -= 256 * 256; - if (maxByte < byte) maxByte = byte; + var w = 0; + while (pos < end) { + while (w < buffer.bitsPerSample) { + b |= #if js buf[pos] #else buf.get(pos) #end << w; + w += 8; + pos++; + } + if (b > __bitSize) b -= __bitUnsignedSize; + if (max < b) max = b; + w = b = 0; + } } - return maxByte/__peakByte; + return max / __bitSize; } + + #if lime_vorbis // As far i know, only native supports vorbis + var __buffer:ArrayBufferView; + function __prepareVorbis():Bool @:privateAccess { + if (buffer.__srcVorbisFile == null) return __vorbis != null; + if (__vorbis != null) return true; + if ((__vorbis = buffer.__srcVorbisFile.clone()) != null) { // IM HOPING IT HAVE A GC CLOSURE. + __buffer = new ArrayBufferView(0x1000, buffer.bitsPerSample == 32 ? Int32 : buffer.bitsPerSample == 16 ? Int16 : Int8); + return true; + } + return false; + } + + var prevPos:Int = 0; + inline function __analyzeVorbis(startPos:Float, endPos:Float):Float { + var n = Math.floor((endPos - startPos) * __toBits); + if (n < __wordSize) return 0; + + n -= n % __wordSize; + + if (prevPos != Math.floor(startPos)) { + __vorbis.timeSeek(startPos / 1000); + prevPos = Math.floor(endPos); + } + + var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN, bufferSize = __buffer.length, buf = __buffer.buffer; + var max = 0, b = 0, pos = 0, w = 0, result; + while (n > 0) { + n -= (result = __vorbis.read(buf, 0, n < bufferSize ? n : bufferSize, isBigEndian, __wordSize, true)); + + if (result == Vorbis.HOLE) continue; + else if (result <= 0) break; + + while (pos < result) { + while (w < buffer.bitsPerSample) { + b |= #if js buf[pos] #else buf.get(pos) #end << w; + w += 8; + pos++; + } + if (b > __bitSize) b -= __bitUnsignedSize; + if (max < b) max = b; + w = b = 0; + } + pos = 0; + } + + return max / __bitSize; + } + #end } \ No newline at end of file diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index bc6a58d3..2c420393 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -823,7 +823,6 @@ class CoolUtil if (frontEnd.music != null) @:privateAccess frontEnd.destroySound(frontEnd.music); frontEnd.list.remove(music); frontEnd.defaultMusicGroup.add(frontEnd.music = music); - music.persist = true; } /** diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 3c6d7c68..fea17595 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -1570,10 +1570,10 @@ class Charter extends UIState { vocals.pause(); for (strumLine in strumLines.members) strumLine.vocals.pause(); } else { - FlxG.sound.music.play(Conductor.songPosition + Conductor.songOffset); - vocals.play(FlxG.sound.music.getActualTime()); + FlxG.sound.music.play(true, Conductor.songPosition + Conductor.songOffset); + vocals.play(true, FlxG.sound.music.getActualTime()); for (strumLine in strumLines.members) { - strumLine.vocals.play(FlxG.sound.music.getActualTime()); + strumLine.vocals.play(true, FlxG.sound.music.getActualTime()); } } } diff --git a/source/lime/_internal/backend/native/NativeAudioSource.hx b/source/lime/_internal/backend/native/NativeAudioSource.hx index 4b187188..33368f95 100644 --- a/source/lime/_internal/backend/native/NativeAudioSource.hx +++ b/source/lime/_internal/backend/native/NativeAudioSource.hx @@ -16,7 +16,6 @@ import lime.media.vorbis.VorbisFile; import lime.math.Vector4; import lime.media.AudioBuffer; import lime.media.AudioSource; -import lime.system.System; import lime.utils.ArrayBufferView; #if !lime_debug @@ -26,7 +25,7 @@ import lime.utils.ArrayBufferView; @:access(lime.media.AudioBuffer) @:access(lime.utils.ArrayBufferView) class NativeAudioSource { - private static var STREAM_BUFFER_SIZE:Int = 0x4000; + private static var STREAM_BUFFER_SIZE:Int = 0x4000; // what if it was odd channels (excluding mono). private static var STREAM_MAX_BUFFERS:Int = 32; private static var STREAM_TIMER_FREQUENCY:Int = 100; private static var STREAM_BUFFER_FREQUENCY:Int = 6; @@ -47,6 +46,7 @@ class NativeAudioSource { private var streamTimer:Timer; private var bufferDatas:Array; private var bufferTimes:Array; + private var bufferSizes:Array; private var requestBuffers:Int; private var queuedBuffers:Int; private var toLoop:Int; @@ -74,30 +74,48 @@ class NativeAudioSource { buffers = null; bufferDatas = null; bufferTimes = null; + bufferSizes = null; unusedBuffers = null; } public function init() { if (handle != null) return; - var buffer = parent.buffer; - if (buffer.channels == 1) format = buffer.bitsPerSample == 8 ? AL.FORMAT_MONO8 : AL.FORMAT_MONO16; - else format = buffer.bitsPerSample == 8 ? AL.FORMAT_STEREO8 : AL.FORMAT_STEREO16; - if (disposed = (handle = AL.createSource()) == null) return; AL.sourcef(handle, AL.MAX_GAIN, 10); - if (streamed = buffer.__srcVorbisFile != null) { - dataLength = (samples = getFloat(buffer.__srcVorbisFile.pcmTotal())) * buffer.channels * (buffer.bitsPerSample == 8 ? 1 : 2); + var buffer = parent.buffer; + var channels = buffer.channels, bitsPerSample = buffer.bitsPerSample; - var constructor = buffer.bitsPerSample == 8 ? Int8 : Int16; + // 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 || AL.isExtensionPresent("AL_EXT_MCFORMATS"))) { // 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 lime_vorbis + 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; buffers = AL.genBuffers(STREAM_MAX_BUFFERS); bufferDatas = [for (i in 0...STREAM_MAX_BUFFERS) new ArrayBufferView(STREAM_BUFFER_SIZE, constructor)]; bufferTimes = [for (i in 0...STREAM_MAX_BUFFERS) 0]; + bufferSizes = [for (i in 0...STREAM_MAX_BUFFERS) 0]; unusedBuffers = []; } - else { - samples = (dataLength = getFloat(Int64.make(0, buffer.data.length))) / buffer.channels / (buffer.bitsPerSample == 8 ? 1 : 2); + else #end { + streamed = false; + samples = (dataLength = getFloat(Int64.make(0, buffer.data.length))) / buffer.channels / (buffer.bitsPerSample >> 3); if (buffer.__srcBuffer == null && (buffer.__srcBuffer = AL.createBuffer()) != null) AL.bufferData(buffer.__srcBuffer, format, buffer.data, buffer.data.length, buffer.sampleRate); @@ -129,12 +147,8 @@ class NativeAudioSource { public function stop() { if (!disposed) { - if (AL.getSourcei(handle, AL.SOURCE_STATE) != AL.STOPPED) AL.sourceStop(handle); - if (streamed) { - AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED)); - unusedBuffers.resize(0); - } - requestBuffers = queuedBuffers = toLoop = 0; + AL.sourceStop(handle); + toLoop = 0; } playing = false; @@ -152,31 +166,29 @@ class NativeAudioSource { // just incase if we support more than 1 in the future?? which i doubt private function streamTell():Float { #if lime_vorbis - /*if (parent.buffer.__srcVorbisFile != null) */return parent.buffer.__srcVorbisFile.timeTell(); - #end + return parent.buffer.__srcVorbisFile.timeTell(); + #else return 0; + #end } private function streamSeek(samples:Int64) { #if lime_vorbis - //if (parent.buffer.__srcVorbisFile != null) { - // 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); - // break; - //} + // 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 } private function readToBufferData(data:ArrayBufferView):Int { - var total = 0, result = 0, wasEOF = false; - var bigEndian = System.endianness == lime.system.Endian.BIG_ENDIAN, wordSize = parent.buffer.bitsPerSample == 8 ? 1 : 2; + #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 < STREAM_BUFFER_SIZE ? Math.floor(size) : STREAM_BUFFER_SIZE; - var vorbisFile = parent.buffer.__srcVorbisFile; + var total = 0, result = 0, wasEOF = false; while (total < STREAM_BUFFER_SIZE) { - result = n > 0 ? vorbisFile.read(data.buffer, total, n, bigEndian, wordSize, true) : 0; + result = n > 0 ? parent.buffer.__srcVorbisFile.read(data.buffer, total, n, isBigEndian, wordSize, true) : 0; if (result == Vorbis.HOLE) continue; else if (result <= Vorbis.EREAD) break; @@ -205,36 +217,37 @@ class NativeAudioSource { return result; } return total; - } - - private function cycleBuffer() { - var i = STREAM_MAX_BUFFERS - (queuedBuffers = requestBuffers), n = STREAM_MAX_BUFFERS - 1; - var data = bufferDatas[i], time = bufferTimes[i]; - while (i < n) { - bufferDatas[i] = bufferDatas[i + 1]; - bufferTimes[i] = bufferTimes[++i]; - } - bufferDatas[n] = data; - bufferTimes[n] = time; + #else + return 0; + #end } private function fillBuffer(buffer:ALBuffer):Int { - var i = STREAM_MAX_BUFFERS - requestBuffers, time = streamTell(); - var decoded = readToBufferData(bufferDatas[i]); + var i = STREAM_MAX_BUFFERS - requestBuffers; + var data = bufferDatas[i], time = streamTell(); + var decoded = readToBufferData(data); if (decoded > 0) { - AL.bufferData(buffer, format, bufferDatas[i], decoded, parent.buffer.sampleRate); - bufferTimes[i] = time; - cycleBuffer(); + AL.bufferData(buffer, format, data, decoded, parent.buffer.sampleRate); + + var n = STREAM_MAX_BUFFERS - 1; + while (i < n) { + bufferDatas[i] = bufferDatas[i + 1]; + bufferTimes[i] = bufferTimes[i + 1]; + bufferSizes[i] = bufferSizes[++i]; + } + queuedBuffers = requestBuffers; + bufferDatas[n] = data; + bufferTimes[n] = time; + bufferSizes[n] = decoded; } return decoded; } private function streamRun() { - #if lime_vorbis if (parent == null || parent.buffer == null || handle == null) return dispose(); - if (!playing || disposed) stopStreamTimer(); + if (!playing || disposed) return stopStreamTimer(); try { var processed = AL.getSourcei(handle, AL.BUFFERS_PROCESSED), n = STREAM_BUFFER_FREQUENCY, buffer; @@ -264,25 +277,28 @@ class NativeAudioSource { else requestBuffers--; } } - - if (AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) { - AL.sourcePlay(handle); - resetTimer((getLength() - getCurrentTime()) / getPitch()); - } } catch(e) trace('NativeAudioSource Bug! in streamRun $e'); - #end + + if (AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) AL.sourcePlay(handle); + if (streamEnded && timer == null) resetTimer((getLength() - getCurrentTime()) / getPitch()); } // Timers - inline function stopStreamTimer() if (streamTimer != null) streamTimer.stop(); + 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(); + inline function stopTimer() { + if (timer != null) timer.stop(); + timer = null; + } private function resetTimer(timeRemaining:Float) { stopTimer(); @@ -290,6 +306,8 @@ class NativeAudioSource { } private function timer_onRun() { + stopTimer(); + var timeRemaining = (getLength() - getCurrentTime()) / getPitch(); if (timeRemaining > 30 && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING) { resetTimer(timeRemaining); @@ -317,66 +335,69 @@ class NativeAudioSource { // Get & Set Methods public function getCurrentTime():Float { if (completed) return getLength(); - else if (!disposed) { - var time = ((streamed ? bufferTimes[STREAM_MAX_BUFFERS - queuedBuffers] : 0) + AL.getSourcef(handle, AL.SEC_OFFSET)) * 1000 - parent.offset; - if (loops > 0 && time > getLength()) { - var start = (loopTime != null ? Math.max(0, loopTime) : 0) + parent.offset; - return ((time - start) % (getLength() - start)) + start; - } - else if (time > 0) return time; + else if (disposed) return 0; + + var time = AL.getSourcef(handle, AL.SAMPLE_OFFSET) / parent.buffer.sampleRate; + if (streamed) 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 0; + + return time - parent.offset; } public function setCurrentTime(value:Float):Float { if (disposed) return value; - if (streamed) { - AL.sourceStop(handle); - AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED)); - - unusedBuffers.resize(0); - streamSeek(getSamples(value + parent.offset)); - } - else - AL.sourcei(handle, AL.SAMPLE_OFFSET, getSamples(value + parent.offset)); + if (streamed) AL.sourceStop(handle); + else AL.sourcei(handle, AL.SAMPLE_OFFSET, getSamples(value + parent.offset)); if (playing) { - var timeRemaining = (getLength() - value) / AL.getSourcef(handle, AL.PITCH); - if (timeRemaining > 0) { + var timeRemaining = (getLength() - value) / getPitch(); + if (completed = timeRemaining <= 0) complete(); + else { if (streamed) { + AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED)); + + unusedBuffers.resize(0); + streamSeek(getSamples(value + parent.offset)); + for (i in 0...(requestBuffers = queuedBuffers = STREAM_BUFFER_FREQUENCY)) if (fillBuffer(buffers[i]) > 0) AL.sourceQueueBuffer(handle, buffers[i]); resetStreamTimer(); } - resetTimer(timeRemaining); + else + resetTimer(timeRemaining); + AL.sourcePlay(handle); } - else { - stopStreamTimer(); - complete(); - } } + 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 (length == null) getRealLength() - parent.offset; - else length - parent.offset; - } + 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 == 8 ? 1 : 2; + 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) setCurrentTime(getCurrentTime()); + if (streamed) { + if (streamEnded || toLoop > 0) setCurrentTime(getCurrentTime()); + } else { var timeRemaining = ((getLength() - parent.offset) - getCurrentTime()) / getPitch(); if (timeRemaining > 0) resetTimer(timeRemaining); @@ -392,12 +413,9 @@ class NativeAudioSource { public function setPitch(value:Float):Float { if (disposed || value == AL.getSourcef(handle, AL.PITCH)) return value; - if (playing) { - if (value <= 0) stopTimer(); - else { - var timeRemaining = (getLength() - getCurrentTime()) / value; - if (timeRemaining > 0) resetTimer(timeRemaining); - } + if (playing && (streamEnded || !streamed)) { + var timeRemaining = (getLength() - getCurrentTime()) / value; + if (timeRemaining > 0) resetTimer(timeRemaining); } AL.sourcef(handle, AL.PITCH, value); return value; @@ -454,6 +472,6 @@ class NativeAudioSource { return position; } - inline private function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >>> 0); + 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 87215835..40ebfbec 100644 --- a/source/lime/media/AudioBuffer.hx +++ b/source/lime/media/AudioBuffer.hx @@ -105,10 +105,7 @@ class AudioBuffer __srcBuffer = null; #end #if lime_vorbis - if (__srcVorbisFile != null) { - __srcVorbisFile.clear(); - @:privateAccess __srcVorbisFile.handle = null; - } + if (__srcVorbisFile != null) __srcVorbisFile.clear(); __srcVorbisFile = null; #end } @@ -131,7 +128,7 @@ class AudioBuffer } var audioBuffer = new AudioBuffer(); - audioBuffer.src = new Howl({src: [base64String], html5: true, preload: false}); + audioBuffer.src = new Howl({src: [base64String], preload: false}); return audioBuffer; #elseif (lime_cffi && !macro) #if !cs @@ -177,7 +174,7 @@ class AudioBuffer #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}); + audioBuffer.src = new Howl({src: ["data:" + __getCodec(bytes) + ";base64," + Base64.encode(bytes)], preload: false}); return audioBuffer; #elseif (lime_cffi && !macro) diff --git a/source/lime/media/vorbis/VorbisFile.hx b/source/lime/media/vorbis/VorbisFile.hx new file mode 100644 index 00000000..bd29a7a7 --- /dev/null +++ b/source/lime/media/vorbis/VorbisFile.hx @@ -0,0 +1,356 @@ +package lime.media.vorbis; + +#if (!lime_doc_gen || lime_vorbis) +import haxe.Int64; +import haxe.io.Bytes; +import lime._internal.backend.native.NativeCFFI; + +#if hl +@:keep +#end +@:access(lime._internal.backend.native.NativeCFFI) +class VorbisFile +{ + public var bitstream(default, null):Int; + + @:noCompletion private var handle:Dynamic; + + @:noCompletion var _filePath:String; + @:noCompletion var _bytes:Bytes; + + @:noCompletion private function new(handle:Dynamic) + { + this.handle = handle; + } + + public function bitrate(bitstream:Int = -1):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_bitrate(handle, bitstream); + #else + return 0; + #end + } + + public function bitrateInstant():Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_bitrate_instant(handle); + #else + return 0; + #end + } + + public function clear():Void + { + #if (lime_cffi && lime_vorbis && !macro) + NativeCFFI.lime_vorbis_file_clear(handle); + #end + handle = null; + _filePath = null; + _bytes = null; + } + + public function clone():VorbisFile + { + if (_filePath != null) return fromFile(_filePath); + if (_bytes != null) return fromBytes(_bytes); + return null; + } + + public function comment(bitstream:Int = -1):VorbisComment + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_comment(handle, bitstream); + + if (data != null) + { + var comment = new VorbisComment(); + comment.userComments = data.userComments; + comment.vendor = data.vendor; + return comment; + } + #end + + return null; + } + + public function crosslap(other:VorbisFile):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_crosslap(handle, other.handle); + #else + return 0; + #end + } + + public static function fromBytes(bytes:Bytes):VorbisFile + { + #if (lime_cffi && lime_vorbis && !macro) + var handle = NativeCFFI.lime_vorbis_file_from_bytes(bytes); + + if (handle != null) + { + var vorbisFile = new VorbisFile(handle); + vorbisFile._bytes = bytes; + return vorbisFile; + } + #end + + return null; + } + + public static function fromFile(path:String):VorbisFile + { + #if (lime_cffi && lime_vorbis && !macro) + var handle = NativeCFFI.lime_vorbis_file_from_file(path); + + if (handle != null) + { + var vorbisFile = new VorbisFile(handle); + vorbisFile._filePath = path; + return vorbisFile; + } + #end + + return null; + } + + public function info(bitstream:Int = -1):VorbisInfo + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_info(handle, bitstream); + + if (data != null) + { + var info = new VorbisInfo(); + info.bitrateLower = data.bitrateLower; + info.bitrateNominal = data.bitrateNominal; + info.bitrateUpper = data.bitrateUpper; + info.channels = data.channels; + info.rate = data.rate; + info.version = data.version; + return info; + } + #end + + return null; + } + + public function pcmSeek(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmSeekLap(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek_lap(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmSeekPage(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek_page(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmSeekPageLap(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek_page_lap(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmTell():Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_pcm_tell(handle); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function pcmTotal(bitstream:Int = -1):Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_pcm_total(handle, bitstream); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function rawSeek(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_raw_seek(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function rawSeekLap(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_raw_seek_lap(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function rawTell():Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_raw_tell(handle); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function rawTotal(bitstream:Int = -1):Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_raw_total(handle, bitstream); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function read(buffer:Bytes, position:Int, length:Int = 4096, bigEndianPacking:Bool = false, wordSize:Int = 2, signed:Bool = true):Int + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_read(handle, buffer, position, length, bigEndianPacking, wordSize, signed); + if (data == null) return 0; + bitstream = data.bitstream; + return data.returnValue; + #else + return 0; + #end + } + + // public function readFilter (buffer:Bytes, length:Int = 4096, endianness:Endian = LITTLE_ENDIAN, wordSize:Int = 2, signed:Bool = true, bitstream:Int = 0, filter, filter_param + public function readFloat(pcmChannels:Bytes, samples:Int):Int + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_read_float(handle, pcmChannels, samples); + if (data == null) return 0; + bitstream = data.bitstream; + return data.returnValue; + #else + return 0; + #end + } + + public function seekable():Bool + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_seekable(handle); + #else + return false; + #end + } + + public function serialNumber(bitstream:Int = -1):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_serial_number(handle, bitstream); + #else + return 0; + #end + } + + public function streams():Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_streams(handle); + #else + return 0; + #end + } + + public function timeSeek(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek(handle, s); + #else + return 0; + #end + } + + public function timeSeekLap(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek_lap(handle, s); + #else + return 0; + #end + } + + public function timeSeekPage(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek_page(handle, s); + #else + return 0; + #end + } + + public function timeSeekPageLap(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek_page_lap(handle, s); + #else + return 0; + #end + } + + public function timeTell():Float + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_tell(handle); + #else + return 0; + #end + } + + public function timeTotal(bitstream:Int = -1):Float + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_total(handle, bitstream); + #else + return 0; + #end + } +} +#end diff --git a/source/openfl/media/SoundChannel.hx b/source/openfl/media/SoundChannel.hx index bccb6138..c5c042af 100644 --- a/source/openfl/media/SoundChannel.hx +++ b/source/openfl/media/SoundChannel.hx @@ -82,7 +82,7 @@ import lime.media.openal.AL; @:noCompletion private var __sound:Sound; @:noCompletion private var __isValid:Bool; @:noCompletion private var __soundTransform:SoundTransform; - @:noCompletion private var __lastPeakTime:Float; + @:noCompletion private var __lastPeakSamples:Float; @:noCompletion private var __leftPeak:Float; @:noCompletion private var __rightPeak:Float; #if lime @@ -151,86 +151,66 @@ import lime.media.openal.AL; this.soundTransform = soundTransform; } - // hi i made these - raltyro - // took me 8 months to figure this out - @:noCompletion inline private static var scanSamples = 480; - - @:noCompletion private function __checkUpdatePeaks(time:Float):Bool + @:noCompletion private function __updatePeaks():Bool { - if (Math.abs(__lastPeakTime - time) < Math.max(1, pitch * 8)) return false; - __lastPeakTime = time; - return true; - } + var backend = __source.__backend; + var samples = AL.getSourcef(backend.handle, AL.SAMPLE_OFFSET); + if (__lastPeakSamples == samples) return false; - /*@: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_MAX_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--; - } - } - - @:noCompletion private function __updatePeaks():Void - { + __lastPeakSamples = samples; __leftPeak = __rightPeak = 0; - - if (!__isValid) return; - - #if lime_vorbis if (__source.__backend.streamed) return __updateVorbisPeaks(); #end + #if (lime_cffi && !macro) + if (!__isValid) return false; var buffer = __source.buffer; - if (buffer == null || buffer.data == null || !__checkUpdatePeaks(position)) return; + var wordSize = buffer.bitsPerSample >> 3, bitUnsignedSize = 1 << buffer.bitsPerSample, bitSize = 1 << (buffer.bitsPerSample - 1); + var pos = Math.floor(samples * buffer.channels * wordSize); + var n = Math.floor(Math.min(buffer.sampleRate / 80, 600)), b = 0, w = 0, c = 0, i = 0, size = 0, buf; - 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++; + if (backend.streamed) { + buf = backend.bufferDatas[i = NativeAudioSource.STREAM_MAX_BUFFERS - backend.queuedBuffers] #if !js .buffer #end; + pos %= (size = NativeAudioSource.STREAM_BUFFER_SIZE * wordSize); + } + else { + buf = buffer.data #if !js .buffer #end; + size = #if js buf.byteLength #else buf.length #end; } - }*/ - @:noCompletion private function __updatePeaks():Void - { - __leftPeak = __rightPeak = 0; - if (!__isValid) return; + pos -= pos % wordSize; + + while (n > 0) { + if (wordSize == 1) b = #if js buf[pos] #else buf.get(pos) #end - bitSize; + else { + while (w < buffer.bitsPerSample) { + b |= #if js buf[pos] #else buf.get(pos) #end << w; + w += 8; + pos++; + } + if (b > bitSize) b -= bitUnsignedSize; + } + ((c % 2 == 0) ? (if (__leftPeak < b) __leftPeak = b) : (if (__rightPeak < b) __rightPeak = b)); + + if (pos >= size) { + if (!backend.streamed || ++i >= backend.bufferDatas.length) break; + buf = backend.bufferDatas[i] #if !js .buffer #end; + pos = 0; + } + + if (++c > buffer.channels) { + c = 0; + n--; + } + w = b = 0; + } + + if (buffer.channels == 1) __rightPeak = __leftPeak / bitSize; + else { + __leftPeak = __leftPeak / bitSize; + __rightPeak = __rightPeak / bitSize; + } + #end + + return true; } @:noCompletion private function __initAudioSource(source:#if lime AudioSource #else Dynamic #end):Void diff --git a/source/openfl/utils/Assets.hx b/source/openfl/utils/Assets.hx index 0909bdd4..eef85212 100644 --- a/source/openfl/utils/Assets.hx +++ b/source/openfl/utils/Assets.hx @@ -248,8 +248,7 @@ class Assets var path = getPath(id); // TODO: What if it is a WAV or non-Vorbis file? var vorbisFile = VorbisFile.fromFile(path); - var buffer = AudioBuffer.fromVorbisFile(vorbisFile); - return Sound.fromAudioBuffer(buffer); + if (vorbisFile != null) return Sound.fromAudioBuffer(AudioBuffer.fromVorbisFile(vorbisFile)); } #end return getSound(id, useCache);