Support for 50 minute songs (html5 untested))

Experimental support for 3+ hour long songs comes later
This commit is contained in:
NeeEoo
2023-01-13 04:22:00 +01:00
parent 137ffd399c
commit c4a50908d4
6 changed files with 544 additions and 66 deletions
+1 -1
View File
@@ -1 +1 @@
2036
2042
@@ -0,0 +1,253 @@
package lime._internal.backend.html5;
import lime.math.Vector4;
import lime.media.AudioSource;
@:access(lime.media.AudioBuffer)
class HTML5AudioSource
{
private var completed:Bool;
private var gain:Float;
private var id:Int;
private var length:Float;
private var loops:Int;
private var parent:AudioSource;
private var playing:Bool;
private var position:Vector4;
public function new(parent:AudioSource)
{
this.parent = parent;
id = -1;
gain = 1;
position = new Vector4();
}
public function dispose():Void {}
public function init():Void {}
public function play():Void
{
#if lime_howlerjs
if (playing || parent.buffer == null || parent.buffer.__srcHowl == null)
{
return;
}
playing = true;
var time = getCurrentTime();
completed = false;
var cacheVolume = untyped parent.buffer.__srcHowl._volume;
untyped parent.buffer.__srcHowl._volume = parent.gain;
id = parent.buffer.__srcHowl.play();
untyped parent.buffer.__srcHowl._volume = cacheVolume;
// setGain (parent.gain);
setPosition(parent.position);
parent.buffer.__srcHowl.on("end", howl_onEnd, id);
// Calling setCurrentTime causes html5 audio to replay from this position on next frame
#if force_html5_audio
if (time == 0) setCurrentTime(time);
#else
setCurrentTime(time);
#end
#end
}
public function pause():Void
{
#if lime_howlerjs
playing = false;
if (parent.buffer != null && parent.buffer.__srcHowl != null)
{
parent.buffer.__srcHowl.pause(id);
}
#end
}
public function stop():Void
{
#if lime_howlerjs
playing = false;
if (parent.buffer != null && parent.buffer.__srcHowl != null)
{
parent.buffer.__srcHowl.stop(id);
parent.buffer.__srcHowl.off("end", howl_onEnd, id);
}
#end
}
// Event Handlers
private function howl_onEnd()
{
#if lime_howlerjs
playing = false;
if (loops > 0)
{
loops--;
stop();
// currentTime = 0;
play();
return;
}
else if (parent.buffer != null && parent.buffer.__srcHowl != null)
{
parent.buffer.__srcHowl.stop(id);
parent.buffer.__srcHowl.off("end", howl_onEnd, id);
}
completed = true;
parent.onComplete.dispatch();
#end
}
// Get & Set Methods
public function getCurrentTime():Float
{
if (id == -1)
{
return 0;
}
#if lime_howlerjs
if (completed)
{
return getLength();
}
else if (parent.buffer != null && parent.buffer.__srcHowl != null)
{
var time = (parent.buffer.__srcHowl.seek(id) * 1000) - parent.offset;
if (time < 0) return 0;
return time;
}
#end
return 0;
}
public function setCurrentTime(value:Float):Float
{
#if lime_howlerjs
if (parent.buffer != null && parent.buffer.__srcHowl != null)
{
// if (playing) buffer.__srcHowl.play (id);
var pos = (value + parent.offset) / 1000;
if (pos < 0) pos = 0;
parent.buffer.__srcHowl.seek(pos, id);
}
#end
return value;
}
public function getGain():Float
{
return gain;
}
public function setGain(value:Float):Float
{
#if lime_howlerjs
// set howler volume only if we have an active id.
// Passing -1 might create issues in future play()'s.
if (parent.buffer != null && parent.buffer.__srcHowl != null && id != -1)
{
parent.buffer.__srcHowl.volume(value, id);
}
#end
return gain = value;
}
public function getLength():Float
{
if (length != 0)
{
return length;
}
#if lime_howlerjs
if (parent.buffer != null && parent.buffer.__srcHowl != null)
{
return parent.buffer.__srcHowl.duration() * 1000;
}
#end
return 0;
}
public function setLength(value:Float):Float
{
return length = value;
}
public function getLoops():Int
{
return loops;
}
public function setLoops(value:Int):Int
{
return loops = value;
}
public function getPitch():Float
{
#if lime_howlerjs
return parent.buffer.__srcHowl.rate();
#else
return 1;
#end
}
public function setPitch(value:Float):Float
{
#if lime_howlerjs
parent.buffer.__srcHowl.rate(value);
#end
return getPitch();
}
public function getPosition():Vector4
{
#if lime_howlerjs
// This should work, but it returns null (But checking the inside of the howl, the _pos is actually null... so ¯\_(ツ)_/¯)
/*
var arr = parent.buffer.__srcHowl.pos())
position.x = arr[0];
position.y = arr[1];
position.z = arr[2];
*/
#end
return position;
}
public function setPosition(value:Vector4):Vector4
{
position.x = value.x;
position.y = value.y;
position.z = value.z;
position.w = value.w;
#if lime_howlerjs
if (parent.buffer != null && parent.buffer.__srcHowl != null && parent.buffer.__srcHowl.pos != null) parent.buffer.__srcHowl.pos(position.x, position.y, position.z, id);
// There are more settings to the position of the sound on the "pannerAttr()" function of howler. Maybe somebody who understands sound should look into it?
#end
return position;
}
}
@@ -34,16 +34,19 @@ class NativeAudioSource
private var dataLength:Int;
private var format:Int;
private var handle:ALSource;
private var length:Null<Int>;
private var length:Null<Float>;
private var loops:Int;
private var parent:AudioSource;
private var playing:Bool;
private var position:Vector4;
private var samples:Int;
private var samples:Float;
private var stream:Bool;
private var streamTimer:Timer;
private var timer:Timer;
private var pcmTotal:Int64;
private var sampleRate:Int;
var disposed:Bool = false;
public function new(parent:AudioSource)
@@ -107,7 +110,11 @@ class NativeAudioSource
stream = true;
var vorbisFile = parent.buffer.__srcVorbisFile;
dataLength = Std.int(Int64.toInt(vorbisFile.pcmTotal()) * parent.buffer.channels * (parent.buffer.bitsPerSample / 8));
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);
buffers = new Array();
bufferTimeBlocks = new Array();
@@ -119,6 +126,8 @@ class NativeAudioSource
}
handle = AL.createSource();
samples = Int64.toInt(pcmTotal);
}
else
{
@@ -140,43 +149,15 @@ class NativeAudioSource
{
AL.sourcei(handle, AL.BUFFER, parent.buffer.__srcBuffer);
}
}
samples = Std.int((dataLength * 8) / (parent.buffer.channels * parent.buffer.bitsPerSample));
samples = Int64.toInt((Int64.make(0, dataLength) * Int64.ofInt(8)) / Int64.ofInt(parent.buffer.channels * parent.buffer.bitsPerSample));
}
initBuffers.push(this);
}
public function play():Void
{
/*var pitch:Float = AL.getSourcef (handle, AL.PITCH);
trace(pitch);
AL.sourcef (handle, AL.PITCH, pitch*0.9);
pitch = AL.getSourcef (handle, AL.PITCH);
trace(pitch); */
/*var pos = getPosition();
trace(AL.DISTANCE_MODEL);
AL.distanceModel(AL.INVERSE_DISTANCE);
trace(AL.DISTANCE_MODEL);
AL.sourcef(handle, AL.ROLLOFF_FACTOR, 5);
setPosition(new Vector4(10, 10, -100));
pos = getPosition();
trace(pos); */
/*var filter = AL.createFilter();
trace(AL.getErrorString());
AL.filteri(filter, AL.FILTER_TYPE, AL.FILTER_LOWPASS);
trace(AL.getErrorString());
AL.filterf(filter, AL.LOWPASS_GAIN, 0.5);
trace(AL.getErrorString());
AL.filterf(filter, AL.LOWPASS_GAINHF, 0.5);
trace(AL.getErrorString());
AL.sourcei(handle, AL.DIRECT_FILTER, filter);
trace(AL.getErrorString()); */
if (disposed) {
trace("TRIED TO PLAY DISPOSED?? WTFFF");
trace(haxe.CallStack.toString(haxe.CallStack.callStack()));
@@ -373,7 +354,7 @@ class NativeAudioSource
}
// Get & Set Methods
public function getCurrentTime():Int
public function getCurrentTime():Float
{
if (completed)
{
@@ -389,11 +370,11 @@ class NativeAudioSource
}
else
{
var offset = AL.getSourcei(handle, AL.BYTE_OFFSET);
var offset:Int = AL.getSourcei(handle, AL.BYTE_OFFSET);
var ratio = (offset / dataLength);
var totalSeconds = samples / parent.buffer.sampleRate;
var time = Std.int(totalSeconds * ratio * 1000) - parent.offset;
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;
@@ -404,7 +385,7 @@ class NativeAudioSource
return 0;
}
public function setCurrentTime(value:Int):Int
public function setCurrentTime(value:Float):Float
{
// `setCurrentTime()` has side effects and is never safe to skip.
/* if (value == getCurrentTime())
@@ -434,7 +415,7 @@ class NativeAudioSource
var totalSeconds = samples / parent.buffer.sampleRate;
if (secondOffset < 0) secondOffset = 0;
if (secondOffset > totalSeconds) secondOffset = totalSeconds;
else if (secondOffset > totalSeconds) secondOffset = totalSeconds;
var ratio = (secondOffset / totalSeconds);
var totalOffset = Std.int(dataLength * ratio);
@@ -451,7 +432,7 @@ class NativeAudioSource
timer.stop();
}
var timeRemaining = Std.int((getLength() - value) / getPitch());
var timeRemaining = (getLength() - value) / getPitch();
if (timeRemaining > 0)
{
@@ -491,17 +472,17 @@ class NativeAudioSource
return value;
}
public function getLength():Int
public function getLength():Float
{
if (length != null)
{
return length;
}
return Std.int(samples / parent.buffer.sampleRate * 1000) - parent.offset;
return (samples / parent.buffer.sampleRate * 1000) - parent.offset;
}
public function setLength(value:Int):Int
public function setLength(value:Float):Float
{
if (playing && length != value)
{
@@ -510,7 +491,7 @@ class NativeAudioSource
timer.stop();
}
var timeRemaining = Std.int((value - getCurrentTime()) / getPitch());
var timeRemaining = (value - getCurrentTime()) / getPitch();
if (timeRemaining > 0)
{
@@ -553,7 +534,7 @@ class NativeAudioSource
timer.stop();
}
var timeRemaining = Std.int((getLength() - getCurrentTime()) / value);
var timeRemaining = (getLength() - getCurrentTime()) / value;
if (timeRemaining > 0)
{
+6 -6
View File
@@ -13,9 +13,9 @@ class AudioSource
{
public var onComplete = new Event<Void->Void>();
public var buffer:AudioBuffer;
public var currentTime(get, set):Int;
public var currentTime(get, set):Float;
public var gain(get, set):Float;
public var length(get, set):Int;
public var length(get, set):Float;
public var loops(get, set):Int;
public var pitch(get, set):Float;
public var offset:Int;
@@ -69,12 +69,12 @@ class AudioSource
}
// Get & Set Methods
@:noCompletion private function get_currentTime():Int
@:noCompletion private function get_currentTime():Float
{
return __backend.getCurrentTime();
}
@:noCompletion private function set_currentTime(value:Int):Int
@:noCompletion private function set_currentTime(value:Float):Float
{
return __backend.setCurrentTime(value);
}
@@ -89,12 +89,12 @@ class AudioSource
return __backend.setGain(value);
}
@:noCompletion private function get_length():Int
@:noCompletion private function get_length():Float
{
return __backend.getLength();
}
@:noCompletion private function set_length(value:Int):Int
@:noCompletion private function set_length(value:Float):Float
{
return __backend.setLength(value);
}
+21 -15
View File
@@ -599,16 +599,16 @@ class Sound extends EventDispatcher
#end
}
var changeID:Int = 0;
public function regen() {
var audioBuffer = new AudioBuffer();
var changeID:Int = 0;
public function regen() {
var audioBuffer = new AudioBuffer();
audioBuffer.bitsPerSample = __buffer.bitsPerSample;
audioBuffer.channels = __buffer.channels;
audioBuffer.data = __buffer.data;
audioBuffer.sampleRate = __buffer.sampleRate;
__buffer = audioBuffer;
}
}
/**
Generates a new SoundChannel object to play back the sound. This method
@@ -636,10 +636,10 @@ class Sound extends EventDispatcher
return null;
}
if (changeID < funkin.system.Main.changeID) {
changeID = funkin.system.Main.changeID;
regen();
}
if (changeID < funkin.system.Main.changeID) {
changeID = funkin.system.Main.changeID;
regen();
}
if (sndTransform == null)
{
@@ -653,7 +653,7 @@ class Sound extends EventDispatcher
var pan = SoundMixer.__soundTransform.pan + sndTransform.pan;
if (pan > 1) pan = 1;
if (pan < -1) pan = -1;
else if (pan < -1) pan = -1;
var volume = SoundMixer.__soundTransform.volume * sndTransform.volume;
@@ -680,23 +680,29 @@ class Sound extends EventDispatcher
return new ID3Info();
}
@:noCompletion private function get_length():Int
@:noCompletion private function get_length():Float
{
#if lime
if (__buffer != null)
{
#if (js && html5 && howlerjs)
return Std.int(__buffer.src.duration() * 1000);
return __buffer.src.duration() * 1000;
#else
if (__buffer.data != null)
{
var samples = (__buffer.data.length * 8) / (__buffer.channels * __buffer.bitsPerSample);
return Std.int(samples / __buffer.sampleRate * 1000);
// var samples = (__buffer.data.length * 8) / (__buffer.channels * __buffer.bitsPerSample);
// return Std.int(samples / __buffer.sampleRate * 1000);
var samples = (Int64.make(0, __buffer.data.length) * Int64.ofInt(8)) / Int64.ofInt(__buffer.channels * __buffer.bitsPerSample);
var value = samples / Int64.ofInt(__buffer.sampleRate) * Int64.ofInt(1000);
return Int64.toInt(value);
}
else if (__buffer.__srcVorbisFile != null)
{
var samples = Int64.toInt(__buffer.__srcVorbisFile.pcmTotal());
return Std.int(samples / __buffer.sampleRate * 1000);
//var samples = Int64.toInt(__buffer.__srcVorbisFile.pcmTotal());
//return Std.int(samples / __buffer.sampleRate * 1000);
var samples = __buffer.__srcVorbisFile.pcmTotal();
var value = Int64.fromFloat(__buffer.__srcVorbisFile.timeTotal()) * 1000;
return Int64.toInt(value);
}
else
{
+238
View File
@@ -0,0 +1,238 @@
package openfl.media;
#if !flash
import openfl.events.Event;
import openfl.events.EventDispatcher;
#if lime
import lime.media.AudioSource;
#end
/**
The SoundChannel class controls a sound in an application. Every sound is
assigned to a sound channel, and the application can have multiple sound
channels that are mixed together. The SoundChannel class contains a
`stop()` method, properties for monitoring the amplitude
(volume) of the channel, and a property for assigning a SoundTransform
object to the channel.
@event soundComplete Dispatched when a sound has finished playing.
**/
#if !openfl_debug
@:fileXml('tags="haxe,release"')
@:noDebug
#end
@:access(openfl.media.SoundMixer)
@:final @:keep class SoundChannel extends EventDispatcher
{
/**
The current amplitude(volume) of the left channel, from 0(silent) to 1
(full amplitude).
**/
public var leftPeak(default, null):Float;
/**
When the sound is playing, the `position` property indicates in
milliseconds the current point that is being played in the sound file.
When the sound is stopped or paused, the `position` property
indicates the last point that was played in the sound file.
A common use case is to save the value of the `position`
property when the sound is stopped. You can resume the sound later by
restarting it from that saved position.
If the sound is looped, `position` is reset to 0 at the
beginning of each loop.
**/
public var position(get, set):Float;
/**
The 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
assignment, and right speaker assignment.
**/
public var soundTransform(get, set):SoundTransform;
public var pitch(get, set):Float;
@:noCompletion private var __isValid:Bool;
@:noCompletion private var __soundTransform:SoundTransform;
#if lime
@:noCompletion private var __source:AudioSource;
#end
#if openfljs
@:noCompletion private static function __init__()
{
untyped Object.defineProperties(SoundChannel.prototype, {
"position": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_position (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_position (v); }")
},
"soundTransform": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_soundTransform (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_soundTransform (v); }")
},
});
}
#end
@:noCompletion private function new(source:#if lime AudioSource #else Dynamic #end = null, soundTransform:SoundTransform = null):Void
{
super(this);
leftPeak = 1;
rightPeak = 1;
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
SoundMixer.__registerSoundChannel(this);
}
/**
Stops the sound playing in the channel.
**/
public function stop():Void
{
SoundMixer.__unregisterSoundChannel(this);
if (!__isValid) return;
#if lime
__source.stop();
#end
__dispose();
}
@:noCompletion private function __dispose():Void
{
if (!__isValid) return;
#if lime
__source.onComplete.remove(source_onComplete);
__source.dispose();
__source = null;
#end
__isValid = false;
}
@:noCompletion private function __updateTransform():Void
{
this.soundTransform = soundTransform;
}
// Get & Set Methods
@:noCompletion private function get_position():Float
{
if (!__isValid) return 0;
#if lime
return __source.currentTime + __source.offset;
#else
return 0;
#end
}
@:noCompletion private function set_position(value:Float):Float
{
if (!__isValid) return 0;
#if lime
__source.currentTime = value - __source.offset;
#end
return value;
}
@:noCompletion private function get_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();
}
@:noCompletion private function set_soundTransform(value:SoundTransform):SoundTransform
{
if (value != null)
{
__soundTransform.pan = value.pan;
__soundTransform.volume = value.volume;
var pan = SoundMixer.__soundTransform.pan + __soundTransform.pan;
if (pan < -1) pan = -1;
else if (pan > 1) pan = 1;
var volume = SoundMixer.__soundTransform.volume * __soundTransform.volume;
if (__isValid)
{
#if lime
__source.gain = volume;
var position = __source.position;
position.x = pan;
position.z = -1 * Math.sqrt(1 - Math.pow(pan, 2));
__source.position = position;
return value;
#end
}
}
return value;
}
// Event Handlers
@:noCompletion private function source_onComplete():Void
{
SoundMixer.__unregisterSoundChannel(this);
__dispose();
dispatchEvent(new Event(Event.SOUND_COMPLETE));
}
}
#else
typedef SoundChannel = flash.media.SoundChannel;
#end