forked from Onek8/LNXSDK
Update Aura
This commit is contained in:
@ -30,15 +30,15 @@ class LoaderImpl {
|
||||
}
|
||||
|
||||
public static function loadSoundFromDescription(desc: Dynamic, done: kha.Sound->Void, failed: AssetError->Void) {
|
||||
var sound = Krom.loadSound(desc.files[0]);
|
||||
if (sound == null) {
|
||||
var sound = new kha.krom.Sound(desc.files[0]);
|
||||
if (sound.uncompressedData == null) {
|
||||
failed({
|
||||
url: desc.files.join(","),
|
||||
error: "Could not load sound(s)",
|
||||
});
|
||||
}
|
||||
else {
|
||||
done(new kha.krom.Sound(Bytes.ofData(sound)));
|
||||
done(sound);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,24 +2,28 @@ package kha.krom;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Sound extends kha.Sound {
|
||||
public function new(bytes: Bytes) {
|
||||
public function new(filename: String) {
|
||||
super();
|
||||
|
||||
var count = Std.int(bytes.length / 4);
|
||||
uncompressedData = new kha.arrays.Float32Array(count);
|
||||
for (i in 0...count) {
|
||||
uncompressedData[i] = bytes.getFloat(i * 4);
|
||||
}
|
||||
var sound = Krom.loadSound(filename);
|
||||
if (sound != null) {
|
||||
var bytes = Bytes.ofData(sound.buffer);
|
||||
var count = Std.int(bytes.length / 4);
|
||||
uncompressedData = new kha.arrays.Float32Array(count);
|
||||
for (i in 0...count) {
|
||||
uncompressedData[i] = bytes.getFloat(i * 4);
|
||||
}
|
||||
|
||||
compressedData = null;
|
||||
this.sampleRate = sound.sampleRate;
|
||||
this.channels = sound.channels;
|
||||
this.length = sound.length;
|
||||
}
|
||||
}
|
||||
|
||||
override public function uncompress(done: Void->Void): Void {
|
||||
done();
|
||||
}
|
||||
|
||||
override public function unload(): Void {
|
||||
super.unload();
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ class Sound implements Resource {
|
||||
var soundBytes = output.getBytes();
|
||||
var count = Std.int(soundBytes.length / 4);
|
||||
if (header.channel == 1) {
|
||||
length = count / kha.audio2.Audio.samplesPerSecond; // header.sampleRate;
|
||||
length = count / header.sampleRate;
|
||||
uncompressedData = new kha.arrays.Float32Array(count * 2);
|
||||
for (i in 0...count) {
|
||||
uncompressedData[i * 2 + 0] = soundBytes.getFloat(i * 4);
|
||||
@ -76,7 +76,7 @@ class Sound implements Resource {
|
||||
}
|
||||
}
|
||||
else {
|
||||
length = count / 2 / kha.audio2.Audio.samplesPerSecond; // header.sampleRate;
|
||||
length = count / 2 / header.sampleRate;
|
||||
uncompressedData = new kha.arrays.Float32Array(count);
|
||||
for (i in 0...count) {
|
||||
uncompressedData[i] = soundBytes.getFloat(i * 4);
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
// Keep this file so that the headers are included in the compilation
|
||||
#include "hl/aura/math/FFT.h"
|
||||
#include "hl/aura/math/fft.h"
|
||||
#include "hl/aura/types/complex_array.h"
|
||||
|
||||
@ -2,27 +2,27 @@
|
||||
|
||||
#include <hl.h>
|
||||
|
||||
//#include <aura/types/_ComplexArray/HL_ComplexArrayImpl.h>
|
||||
#include <aura/types/_ComplexArray/HL_ComplexArrayImpl.h>
|
||||
|
||||
#include "hl/aura/aurahl.h"
|
||||
#include "common_c/math/fft.h"
|
||||
#include "common_c/types/complex_t.h"
|
||||
|
||||
//HL_PRIM void AURA_HL_FUNC(ditfft2)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, int t, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int f, int n, int step, bool inverse) {
|
||||
// const aura_complex_t *times = (aura_complex_t*) time_array->self;
|
||||
// aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
|
||||
HL_PRIM void AURA_HL_FUNC(ditfft2)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, int t, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int f, int n, int step, bool inverse) {
|
||||
const aura_complex_t *times = (aura_complex_t*) time_array->self;
|
||||
aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
|
||||
|
||||
/// aura_ditfft2(times, t, freqs, f, n, step, inverse);
|
||||
//}
|
||||
aura_ditfft2(times, t, freqs, f, n, step, inverse);
|
||||
}
|
||||
|
||||
//HL_PRIM void AURA_HL_FUNC(ditfft2_iterative)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int n, bool inverse, aura__types___ComplexArray__HL_ComplexArrayImpl exp_rotation_step_table) {
|
||||
// const aura_complex_t *times = (aura_complex_t*) time_array->self;
|
||||
// aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
|
||||
HL_PRIM void AURA_HL_FUNC(ditfft2_iterative)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int n, bool inverse, aura__types___ComplexArray__HL_ComplexArrayImpl exp_rotation_step_table) {
|
||||
const aura_complex_t *times = (aura_complex_t*) time_array->self;
|
||||
aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
|
||||
|
||||
// const aura_complex_t *exp_lut = (aura_complex_t*) exp_rotation_step_table->self;
|
||||
const aura_complex_t *exp_lut = (aura_complex_t*) exp_rotation_step_table->self;
|
||||
|
||||
// aura_ditfft2_iterative(times, freqs, n, inverse, exp_lut);
|
||||
//}
|
||||
aura_ditfft2_iterative(times, freqs, n, inverse, exp_lut);
|
||||
}
|
||||
|
||||
//DEFINE_PRIM(_VOID, ditfft2, _BYTES _I32 _BYTES _I32 _I32 _I32 _BOOL)
|
||||
//DEFINE_PRIM(_VOID, ditfft2_iterative, _BYTES _BYTES _I32 _BOOL _BYTES)
|
||||
DEFINE_PRIM(_VOID, ditfft2, _BYTES _I32 _BYTES _I32 _I32 _I32 _BOOL)
|
||||
DEFINE_PRIM(_VOID, ditfft2_iterative, _BYTES _BYTES _I32 _BOOL _BYTES)
|
||||
|
||||
@ -53,6 +53,10 @@ class Aura {
|
||||
|
||||
static final hrtfs = new Map<String, HRTF>();
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
public static var audioContext: js.html.audio.AudioContext;
|
||||
#end
|
||||
|
||||
public static function init(?options: AuraOptions) {
|
||||
sampleRate = kha.audio2.Audio.samplesPerSecond;
|
||||
assert(Critical, sampleRate != 0, "sampleRate must not be 0!");
|
||||
@ -63,12 +67,16 @@ class Aura {
|
||||
listener = new Listener();
|
||||
|
||||
BufferCache.init();
|
||||
|
||||
// Sample buffer to prevent allocation
|
||||
final initialBufferSize = 4096;
|
||||
if (!BufferCache.getBuffer(TFloat32Array, p_samplesBuffer, 1, initialBufferSize)) {
|
||||
trace('CRITICAL: Failed to allocate initial sample buffer during Aura.init()!');
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
if (kha.SystemImpl.mobile) {
|
||||
audioContext = kha.js.MobileWebAudio._context;
|
||||
}
|
||||
else {
|
||||
audioContext = new js.html.audio.AudioContext();
|
||||
}
|
||||
#end
|
||||
|
||||
// Create a few preconfigured mix channels
|
||||
masterChannel = createMixChannel("master");
|
||||
createMixChannel("music").setMixChannel(masterChannel);
|
||||
@ -134,16 +142,31 @@ class Aura {
|
||||
}
|
||||
#end
|
||||
|
||||
count++;
|
||||
function onChannelCountInitialized() {
|
||||
count++;
|
||||
if (onProgress != null) {
|
||||
onProgress(count, length, soundName);
|
||||
}
|
||||
|
||||
if (onProgress != null) {
|
||||
onProgress(count, length, soundName);
|
||||
if (count == length) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
if (count == length) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
if (kha.SystemImpl.mobile) {
|
||||
// Mobile web audio channels are always decoded and
|
||||
// the channel count is set by Kha afterwards
|
||||
onChannelCountInitialized();
|
||||
}
|
||||
else {
|
||||
// HACK: Kha does not set sound.channel for compressed
|
||||
// sounds on non-mobile html5 targets, so do it manually
|
||||
aura.channels.Html5StreamChannel.initializeChannelCount(sound, onChannelCountInitialized);
|
||||
}
|
||||
#else
|
||||
onChannelCountInitialized();
|
||||
#end
|
||||
}, (error: kha.AssetError) -> { onLoadingError(error, failed, soundName); });
|
||||
}
|
||||
|
||||
@ -320,7 +343,7 @@ class Aura {
|
||||
}
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
final newChannel = kha.SystemImpl.mobile ? new Html5MobileStreamChannel(sound, loop) : new Html5StreamChannel(sound, loop);
|
||||
final newChannel = kha.SystemImpl.mobile ? new Html5MobileStreamChannel(sound, loop, cast(mixChannelHandle.channel, MixChannel)) : new Html5StreamChannel(sound, loop, cast(mixChannelHandle.channel, MixChannel));
|
||||
#else
|
||||
final khaChannel: Null<kha.audio1.AudioChannel> = kha.audio2.Audio1.stream(sound, loop);
|
||||
if (khaChannel == null) {
|
||||
|
||||
@ -8,6 +8,9 @@ import aura.threading.Message;
|
||||
import aura.types.AudioBuffer;
|
||||
import aura.utils.Interpolator.LinearInterpolator;
|
||||
import aura.utils.MathUtils;
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
import js.html.audio.GainNode;
|
||||
#end
|
||||
|
||||
/**
|
||||
Main-thread handle to an audio channel in the audio thread.
|
||||
@ -90,6 +93,7 @@ class BaseChannelHandle {
|
||||
}
|
||||
|
||||
if (mixChannelHandle == null) {
|
||||
channel.cleanUp();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -105,6 +109,12 @@ class BaseChannelHandle {
|
||||
final success = @:privateAccess mixChannelHandle.addInputChannel(this);
|
||||
if (success) {
|
||||
parentHandle = mixChannelHandle;
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
if (channel is MixChannel) {
|
||||
channel.gain.disconnect();
|
||||
channel.gain.connect(@:privateAccess parentHandle.getMixChannel().gain);
|
||||
}
|
||||
#end
|
||||
} else {
|
||||
parentHandle = null;
|
||||
}
|
||||
@ -164,6 +174,10 @@ abstract class BaseChannel {
|
||||
var paused: Bool = false;
|
||||
var finished: Bool = true;
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
public var gain: GainNode;
|
||||
#end
|
||||
|
||||
abstract function nextSamples(requestedSamples: AudioBuffer, sampleRate: Hertz): Void;
|
||||
|
||||
abstract function play(retrigger: Bool): Void;
|
||||
@ -218,6 +232,8 @@ abstract class BaseChannel {
|
||||
}
|
||||
}
|
||||
|
||||
function cleanUp() {}
|
||||
|
||||
function parseMessage(message: Message) {
|
||||
switch (message.id) {
|
||||
case ChannelMessageID.Play: play(cast message.data);
|
||||
|
||||
@ -10,14 +10,21 @@ import js.html.audio.ChannelMergerNode;
|
||||
import js.html.audio.GainNode;
|
||||
import js.html.audio.MediaElementAudioSourceNode;
|
||||
import js.html.URL;
|
||||
import js.lib.ArrayBuffer;
|
||||
|
||||
import kha.SystemImpl;
|
||||
import kha.js.MobileWebAudio;
|
||||
import kha.js.MobileWebAudioChannel;
|
||||
|
||||
import aura.Aura;
|
||||
import aura.format.audio.OggVorbisReader;
|
||||
import aura.threading.Message;
|
||||
import aura.types.AudioBuffer;
|
||||
|
||||
using StringTools;
|
||||
|
||||
using aura.format.BytesExtension;
|
||||
|
||||
/**
|
||||
Channel dedicated for streaming playback on html5.
|
||||
|
||||
@ -36,16 +43,16 @@ import aura.types.AudioBuffer;
|
||||
class Html5StreamChannel extends BaseChannel {
|
||||
static final virtualChannels: Array<Html5StreamChannel> = [];
|
||||
|
||||
final audioContext: AudioContext;
|
||||
final audioElement: AudioElement;
|
||||
final source: MediaElementAudioSourceNode;
|
||||
var audioContext: AudioContext;
|
||||
var audioElement: AudioElement;
|
||||
var source: MediaElementAudioSourceNode;
|
||||
|
||||
final gain: GainNode;
|
||||
final leftGain: GainNode;
|
||||
final rightGain: GainNode;
|
||||
final attenuationGain: GainNode;
|
||||
final splitter: ChannelSplitterNode;
|
||||
final merger: ChannelMergerNode;
|
||||
var masterGain: GainNode;
|
||||
var leftGain: GainNode;
|
||||
var rightGain: GainNode;
|
||||
var attenuationGain: GainNode;
|
||||
var splitter: ChannelSplitterNode;
|
||||
var merger: ChannelMergerNode;
|
||||
|
||||
var virtualPosition: Float;
|
||||
var lastUpdateTime: Float;
|
||||
@ -53,13 +60,13 @@ class Html5StreamChannel extends BaseChannel {
|
||||
var dopplerRatio: Float = 1.0;
|
||||
var pitch: Float = 1.0;
|
||||
|
||||
public function new(sound: kha.Sound, loop: Bool) {
|
||||
audioContext = new AudioContext();
|
||||
public function new(sound: kha.Sound, loop: Bool, parentChannel: MixChannel) {
|
||||
audioContext = Aura.audioContext;
|
||||
audioElement = Browser.document.createAudioElement();
|
||||
source = audioContext.createMediaElementSource(audioElement);
|
||||
|
||||
final mimeType = #if kha_debug_html5 "audio/ogg" #else "audio/mp4" #end;
|
||||
final soundData: js.lib.ArrayBuffer = sound.compressedData.getData();
|
||||
final mimeType = sound.compressedData.isByteMagic(0, "OggS") ? "audio/ogg" : "audio/mp4";
|
||||
final soundData: ArrayBuffer = sound.compressedData.getData();
|
||||
final blob = new js.html.Blob([soundData], {type: mimeType});
|
||||
|
||||
// TODO: if removing channels, use revokeObjectUrl() ?
|
||||
@ -67,36 +74,37 @@ class Html5StreamChannel extends BaseChannel {
|
||||
audioElement.src = URL.createObjectURL(blob);
|
||||
audioElement.loop = loop;
|
||||
untyped audioElement.preservesPitch = false;
|
||||
audioElement.addEventListener("ended", () -> {
|
||||
stop();
|
||||
});
|
||||
|
||||
splitter = audioContext.createChannelSplitter(2);
|
||||
leftGain = audioContext.createGain();
|
||||
rightGain = audioContext.createGain();
|
||||
attenuationGain = audioContext.createGain();
|
||||
merger = audioContext.createChannelMerger(2);
|
||||
gain = audioContext.createGain();
|
||||
masterGain = audioContext.createGain();
|
||||
|
||||
source.connect(splitter);
|
||||
|
||||
// The sound data needs to be decoded because `sounds.channels` returns `0`.
|
||||
audioContext.decodeAudioData(soundData, function (buffer) {
|
||||
// TODO: add more cases for Quad and 5.1 ? - https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#audio_channels
|
||||
switch (buffer.numberOfChannels) {
|
||||
case 1:
|
||||
splitter.connect(leftGain, 0);
|
||||
splitter.connect(rightGain, 0);
|
||||
case 2:
|
||||
splitter.connect(leftGain, 0);
|
||||
splitter.connect(rightGain, 1);
|
||||
default:
|
||||
}
|
||||
});
|
||||
// TODO: add more cases for Quad and 5.1 ? - https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#audio_channels
|
||||
switch (sound.channels) {
|
||||
case 1:
|
||||
splitter.connect(leftGain, 0);
|
||||
splitter.connect(rightGain, 0);
|
||||
case 2:
|
||||
splitter.connect(leftGain, 0);
|
||||
splitter.connect(rightGain, 1);
|
||||
default:
|
||||
throw 'Unsupported channel count: ${sound.channels}';
|
||||
}
|
||||
|
||||
leftGain.connect(merger, 0, 0);
|
||||
rightGain.connect(merger, 0, 1);
|
||||
merger.connect(attenuationGain);
|
||||
attenuationGain.connect(gain);
|
||||
attenuationGain.connect(masterGain);
|
||||
|
||||
gain.connect(audioContext.destination);
|
||||
masterGain.connect(parentChannel.gain);
|
||||
|
||||
if (isVirtual()) {
|
||||
virtualChannels.push(this);
|
||||
@ -177,20 +185,45 @@ class Html5StreamChannel extends BaseChannel {
|
||||
finished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Clean up Web Audio nodes. Called automatically when `BaseChannelHandle.setMixChannel(null)` is used.
|
||||
**/
|
||||
override function cleanUp() {
|
||||
source.disconnect();
|
||||
splitter.disconnect();
|
||||
leftGain.disconnect();
|
||||
rightGain.disconnect();
|
||||
merger.disconnect();
|
||||
attenuationGain.disconnect();
|
||||
masterGain.disconnect();
|
||||
audioElement.pause();
|
||||
audioElement.src = "";
|
||||
URL.revokeObjectURL(audioElement.src);
|
||||
|
||||
source = null;
|
||||
splitter = null;
|
||||
leftGain = null;
|
||||
rightGain = null;
|
||||
merger = null;
|
||||
attenuationGain = null;
|
||||
masterGain = null;
|
||||
audioElement = null;
|
||||
}
|
||||
|
||||
function nextSamples(requestedSamples: AudioBuffer, sampleRate: Hertz) {}
|
||||
|
||||
override function parseMessage(message: Message) {
|
||||
switch (message.id) {
|
||||
// Because we're using a HTML implementation here, we cannot use the
|
||||
// LinearInterpolator parameters
|
||||
case ChannelMessageID.PVolume: attenuationGain.gain.value = cast message.data;
|
||||
case ChannelMessageID.PVolume: masterGain.gain.value = cast message.data;
|
||||
case ChannelMessageID.PPitch:
|
||||
pitch = cast message.data;
|
||||
updatePlaybackRate();
|
||||
case ChannelMessageID.PDopplerRatio:
|
||||
dopplerRatio = cast message.data;
|
||||
updatePlaybackRate();
|
||||
case ChannelMessageID.PDstAttenuation: gain.gain.value = cast message.data;
|
||||
case ChannelMessageID.PDstAttenuation: attenuationGain.gain.value = cast message.data;
|
||||
case ChannelMessageID.PVolumeLeft: leftGain.gain.value = cast message.data;
|
||||
case ChannelMessageID.PVolumeRight: rightGain.gain.value = cast message.data;
|
||||
|
||||
@ -217,24 +250,23 @@ class Html5StreamChannel extends BaseChannel {
|
||||
https://github.com/Kode/Kha/commit/12494b1112b64e4286b6a2fafc0f08462c1e7971
|
||||
**/
|
||||
class Html5MobileStreamChannel extends BaseChannel {
|
||||
final audioContext: AudioContext;
|
||||
final khaChannel: kha.js.MobileWebAudioChannel;
|
||||
var audioContext: AudioContext;
|
||||
var khaChannel: kha.js.MobileWebAudioChannel;
|
||||
var parentChannel: MixChannel;
|
||||
|
||||
final leftGain: GainNode;
|
||||
final rightGain: GainNode;
|
||||
final attenuationGain: GainNode;
|
||||
final splitter: ChannelSplitterNode;
|
||||
final merger: ChannelMergerNode;
|
||||
var leftGain: GainNode;
|
||||
var rightGain: GainNode;
|
||||
var attenuationGain: GainNode;
|
||||
var splitter: ChannelSplitterNode;
|
||||
var merger: ChannelMergerNode;
|
||||
|
||||
var dopplerRatio: Float = 1.0;
|
||||
var pitch: Float = 1.0;
|
||||
|
||||
public function new(sound: kha.Sound, loop: Bool) {
|
||||
audioContext = MobileWebAudio._context;
|
||||
public function new(sound: kha.Sound, loop: Bool, pc: MixChannel) {
|
||||
audioContext = Aura.audioContext;
|
||||
khaChannel = new kha.js.MobileWebAudioChannel(cast sound, loop);
|
||||
|
||||
@:privateAccess khaChannel.gain.disconnect(audioContext.destination);
|
||||
@:privateAccess khaChannel.source.disconnect(@:privateAccess khaChannel.gain);
|
||||
parentChannel = pc;
|
||||
|
||||
splitter = audioContext.createChannelSplitter(2);
|
||||
leftGain = audioContext.createGain();
|
||||
@ -242,8 +274,6 @@ class Html5MobileStreamChannel extends BaseChannel {
|
||||
merger = audioContext.createChannelMerger(2);
|
||||
attenuationGain = audioContext.createGain();
|
||||
|
||||
@:privateAccess khaChannel.source.connect(splitter);
|
||||
|
||||
// TODO: add more cases for Quad and 5.1 ? - https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#audio_channels
|
||||
switch (sound.channels) {
|
||||
case 1:
|
||||
@ -253,6 +283,7 @@ class Html5MobileStreamChannel extends BaseChannel {
|
||||
splitter.connect(leftGain, 0);
|
||||
splitter.connect(rightGain, 1);
|
||||
default:
|
||||
throw 'Unsupported channel count: ${sound.channels}';
|
||||
}
|
||||
|
||||
leftGain.connect(merger, 0, 0);
|
||||
@ -260,14 +291,19 @@ class Html5MobileStreamChannel extends BaseChannel {
|
||||
merger.connect(attenuationGain);
|
||||
attenuationGain.connect(@:privateAccess khaChannel.gain);
|
||||
|
||||
@:privateAccess khaChannel.gain.connect(audioContext.destination);
|
||||
reconnectKhaChannelNodes();
|
||||
}
|
||||
|
||||
public function play(retrigger: Bool) {
|
||||
if (retrigger) {
|
||||
khaChannel.position = 0;
|
||||
}
|
||||
|
||||
@:privateAccess khaChannel.source.onended = null;
|
||||
khaChannel.play();
|
||||
// `MobileWebAudioChannel` recreates a 'source' when `khaChannel.play()` is called
|
||||
// Reconnect 'source' and 'gain' to the proper nodes
|
||||
reconnectKhaChannelNodes();
|
||||
|
||||
paused = false;
|
||||
finished = false;
|
||||
@ -283,6 +319,30 @@ class Html5MobileStreamChannel extends BaseChannel {
|
||||
finished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Clean up Web Audio nodes. Called automatically when `BaseChannelHandle.setMixChannel(null)` is used.
|
||||
**/
|
||||
override function cleanUp() {
|
||||
@:privateAccess khaChannel.source.onended = null;
|
||||
@:privateAccess khaChannel.source.disconnect();
|
||||
splitter.disconnect();
|
||||
leftGain.disconnect();
|
||||
rightGain.disconnect();
|
||||
merger.disconnect();
|
||||
attenuationGain.disconnect();
|
||||
@:privateAccess khaChannel.gain.disconnect();
|
||||
khaChannel.stop();
|
||||
|
||||
@:privateAccess khaChannel.gain = null;
|
||||
@:privateAccess khaChannel.source = null;
|
||||
splitter = null;
|
||||
leftGain = null;
|
||||
rightGain = null;
|
||||
merger = null;
|
||||
attenuationGain = null;
|
||||
khaChannel = null;
|
||||
}
|
||||
|
||||
function nextSamples(requestedSamples: AudioBuffer, sampleRate: Hertz) {}
|
||||
|
||||
override function parseMessage(message: Message) {
|
||||
@ -311,6 +371,49 @@ class Html5MobileStreamChannel extends BaseChannel {
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
function reconnectKhaChannelNodes() {
|
||||
@:privateAccess khaChannel.gain.disconnect();
|
||||
@:privateAccess khaChannel.source.disconnect();
|
||||
@:privateAccess khaChannel.source.connect(splitter);
|
||||
@:privateAccess khaChannel.source.onended = stop;
|
||||
@:privateAccess khaChannel.gain.connect(parentChannel.gain);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeChannelCount(sound: kha.Sound, done: Void->Void) {
|
||||
/*
|
||||
Peek into the file to detect the file format, Kha sadly does not expose
|
||||
this information.
|
||||
|
||||
Using `Reflect.field(kha.Assets.sounds, soundName + "Description").files`
|
||||
(i.e. the data in files.json generated by Khamake) would introduce a
|
||||
dependency on Kha internals: A `kha.Sound` can be backed by multiple
|
||||
exported files and Kha's `LoaderImpl` decides on the order in which
|
||||
those files are tried to be loaded.
|
||||
*/
|
||||
final isOgg = sound.compressedData.isByteMagic(0, "OggS");
|
||||
if (isOgg) {
|
||||
final oggReader = new OggVorbisReader(sound.compressedData);
|
||||
sound.channels = oggReader.getNumChannels();
|
||||
done();
|
||||
}
|
||||
else {
|
||||
/*
|
||||
In case of other formats, try to let the JS runtime decode the
|
||||
entire sound data.
|
||||
|
||||
HACK: decodeAudioData() detaches the array buffer but requires a
|
||||
non-detached buffer, so a clone is made to ensure that the array
|
||||
buffer of sound.compressedData is never detached and can still be
|
||||
used by other code.
|
||||
*/
|
||||
final soundDataClone: ArrayBuffer = sound.compressedData.getData().slice(0);
|
||||
kha.audio2.Audio._context.decodeAudioData(soundDataClone, function (buffer) {
|
||||
sound.channels = buffer.numberOfChannels;
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
@ -6,6 +6,11 @@ import haxe.ds.Vector;
|
||||
import sys.thread.Mutex;
|
||||
#end
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
import aura.Aura;
|
||||
import js.html.audio.AudioContext;
|
||||
#end
|
||||
|
||||
import aura.channels.BaseChannel.BaseChannelHandle;
|
||||
import aura.threading.BufferCache;
|
||||
import aura.threading.Message;
|
||||
@ -89,7 +94,17 @@ class MixChannel extends BaseChannel {
|
||||
**/
|
||||
var inputChannelsCopy: Vector<BaseChannel>;
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
var audioContext: AudioContext;
|
||||
#end
|
||||
|
||||
public function new() {
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
audioContext = Aura.audioContext;
|
||||
gain = audioContext.createGain();
|
||||
gain.connect(audioContext.destination);
|
||||
#end
|
||||
|
||||
inputChannels = new Vector<BaseChannel>(channelSize);
|
||||
|
||||
// Make sure super.isPlayable() is true until we find better semantics
|
||||
@ -296,4 +311,18 @@ class MixChannel extends BaseChannel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
//TODO: add the rest of the messages for effects or create a separate `Html5MixChannel` class?
|
||||
override function parseMessage(message: Message) {
|
||||
switch (message.id) {
|
||||
// Because we're using a HTML implementation here, we cannot use the
|
||||
// LinearInterpolator parameters
|
||||
case ChannelMessageID.PVolume: gain.gain.value = cast message.data;
|
||||
|
||||
default:
|
||||
super.parseMessage(message);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@ -44,8 +44,12 @@ class StreamChannel extends BaseChannel {
|
||||
}
|
||||
final khaBuffer = p_khaBuffer.get();
|
||||
|
||||
khaChannel.nextSamples(khaBuffer, requestedSamples.channelLength, sampleRate);
|
||||
khaChannel.nextSamples(khaBuffer, requestedSamples.numChannels * requestedSamples.channelLength, sampleRate);
|
||||
requestedSamples.deinterleaveFromFloat32Array(khaBuffer, requestedSamples.numChannels);
|
||||
|
||||
if (khaChannel.finished) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
override function parseMessage(message: Message) {
|
||||
|
||||
@ -43,7 +43,7 @@ abstract class Panner extends DSP {
|
||||
public function new(handle: BaseChannelHandle) {
|
||||
this.inUse = true; // Don't allow using panners with addInsert()
|
||||
this.handle = handle;
|
||||
this.handle.channel.panner = this;
|
||||
handle.channel.panner = this;
|
||||
}
|
||||
|
||||
public inline function setHandle(handle: BaseChannelHandle) {
|
||||
@ -52,7 +52,7 @@ abstract class Panner extends DSP {
|
||||
}
|
||||
reset3D();
|
||||
this.handle = handle;
|
||||
this.handle.channel.panner = this;
|
||||
handle.channel.panner = this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -61,10 +61,13 @@ class StereoPanner extends Panner {
|
||||
|
||||
public inline function setBalance(balance: Balance) {
|
||||
this._balance = balance;
|
||||
|
||||
final volumeLeft = Math.sqrt(~balance);
|
||||
final volumeRight = Math.sqrt(balance);
|
||||
|
||||
sendMessage({ id: StereoPannerMessageID.PVolumeLeft, data: volumeLeft });
|
||||
sendMessage({ id: StereoPannerMessageID.PVolumeRight, data: volumeRight });
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
handle.channel.sendMessage({ id: ChannelMessageID.PVolumeLeft, data: volumeLeft });
|
||||
handle.channel.sendMessage({ id: ChannelMessageID.PVolumeRight, data: volumeRight });
|
||||
|
||||
17
lib/aura/Sources/aura/format/BytesExtension.hx
Normal file
17
lib/aura/Sources/aura/format/BytesExtension.hx
Normal file
@ -0,0 +1,17 @@
|
||||
package aura.format;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
Variant of `aura.format.InputExtension.isByteMagic()` for `haxe.io.Bytes`.
|
||||
**/
|
||||
inline function isByteMagic(bytes: Bytes, position: Int, magicASCII: String): Bool {
|
||||
var match = true;
|
||||
for (i in 0...magicASCII.length) {
|
||||
match = match && bytes.get(position + i) == magicASCII.fastCodeAt(i);
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
@ -3,6 +3,8 @@ package aura.format;
|
||||
import haxe.Int64;
|
||||
import haxe.io.Input;
|
||||
|
||||
using StringTools;
|
||||
|
||||
inline function readInt64(inp: Input): Int64 {
|
||||
final first = inp.readInt32();
|
||||
final second = inp.readInt32();
|
||||
@ -19,3 +21,24 @@ inline function readUInt32(inp: Input): Int64 {
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
Platform- and encoding-independent way of matching the input with an ASCII
|
||||
magic string. This function does not consider the input endianess, it is
|
||||
assumed that the order of characters in `magicASCII` matches the byte order
|
||||
in the input stream.
|
||||
|
||||
- `inp.readString(len, haxe.io.Encoding.UTF8)` does not work if the input
|
||||
streams contains data that can be interpreted as multi-byte characters.
|
||||
|
||||
- `inp.readString(len, haxe.io.Encoding.RawNative)` does not yield
|
||||
platform-indepent results.
|
||||
**/
|
||||
inline function isByteMagic(inp: Input, magicASCII: String): Bool {
|
||||
var match = true;
|
||||
for (i in 0...magicASCII.length) {
|
||||
match = match && inp.readByte() == magicASCII.fastCodeAt(i);
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
86
lib/aura/Sources/aura/format/audio/OggVorbisReader.hx
Normal file
86
lib/aura/Sources/aura/format/audio/OggVorbisReader.hx
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
Ogg layout:
|
||||
https://en.wikipedia.org/wiki/Ogg#Page_structure
|
||||
|
||||
Vorbis layout:
|
||||
https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2
|
||||
https://wiki.xiph.org/OggVorbis
|
||||
**/
|
||||
|
||||
package aura.format.audio;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesInput;
|
||||
|
||||
using aura.format.InputExtension;
|
||||
|
||||
class OggVorbisReader {
|
||||
|
||||
static inline final OGG_PAGE_HEADER_TYPE_BEG_OF_STREAM = 2;
|
||||
static inline final VORBIS_PACKET_TYPE_IDENTIFICATION = 1;
|
||||
|
||||
final inp: BytesInput;
|
||||
|
||||
final firstSegmentPosition = 0;
|
||||
final segmentTable: Bytes;
|
||||
|
||||
public inline function new(bytes: Bytes) {
|
||||
this.inp = new BytesInput(bytes);
|
||||
inp.bigEndian = false;
|
||||
|
||||
if (!inp.isByteMagic("OggS")) {
|
||||
throw "Cannot read .ogg file, file does not start with 'OggS' magic";
|
||||
}
|
||||
|
||||
inp.position += 1; // Skip version
|
||||
|
||||
final oggHeaderType = inp.readByte();
|
||||
if (oggHeaderType != OGG_PAGE_HEADER_TYPE_BEG_OF_STREAM) {
|
||||
throw "Cannot read .ogg file, first header type was expected to be 'Beginning Of Stream'";
|
||||
}
|
||||
|
||||
inp.position += 8; // Skip granule position
|
||||
inp.position += 4; // Skip bitstream serial number
|
||||
|
||||
final pageSequenceNumber = inp.readUInt32();
|
||||
if (pageSequenceNumber != 0) {
|
||||
throw "Cannot read .ogg file, first page sequence number was expected to be 0";
|
||||
}
|
||||
|
||||
inp.position += 4; // Skip checksum (for now)
|
||||
|
||||
final numPageSegments = inp.readByte();
|
||||
if (numPageSegments == 0) {
|
||||
throw "Cannot read .ogg file, first page has no segments";
|
||||
}
|
||||
|
||||
segmentTable = Bytes.alloc(numPageSegments);
|
||||
inp.readFullBytes(segmentTable, 0, numPageSegments);
|
||||
|
||||
firstSegmentPosition = inp.position;
|
||||
|
||||
final packetType = inp.readByte();
|
||||
if (packetType != VORBIS_PACKET_TYPE_IDENTIFICATION) {
|
||||
throw "Cannot read .ogg file, Vorbis identification header expected";
|
||||
}
|
||||
|
||||
if (!inp.isByteMagic("vorbis")) {
|
||||
throw "Cannot read .ogg file, only Ogg Vorbis files are supported";
|
||||
}
|
||||
|
||||
// See https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2, version must be 0
|
||||
final version = inp.readUInt32();
|
||||
if (version != 0) {
|
||||
throw "Cannot read .ogg file, Vorbis version field expected to be 0";
|
||||
}
|
||||
}
|
||||
|
||||
public function getNumChannels(): Int {
|
||||
inp.position = firstSegmentPosition + 1 + 6 + 4; // Skip packet type + vorbis identifier + version
|
||||
|
||||
final numChannels = inp.readByte();
|
||||
assert(Critical, numChannels > 0);
|
||||
|
||||
return numChannels;
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ abstract Vec3(FastVector3) from FastVector3 to FastVector3 {
|
||||
return new FastVector4(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
#if (AURA_WITH_IRON || leenkx)
|
||||
#if (AURA_WITH_IRON || armory)
|
||||
@:from
|
||||
public static inline function fromIronVec3(v: iron.math.Vec3): Vec3{
|
||||
return new FastVector3(v.x, v.y, v.z);
|
||||
|
||||
@ -25,7 +25,7 @@ class ChannelMessageID extends MessageID {
|
||||
final PPitch;
|
||||
final PDopplerRatio;
|
||||
final PDstAttenuation;
|
||||
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
final PVolumeLeft;
|
||||
final PVolumeRight;
|
||||
|
||||
@ -7,12 +7,13 @@ const targetsHL = ["windows-hl", "linux-hl", "macos-hl", "osx-hl", "android-hl",
|
||||
const targetsCPP = ["windows", "linux", "macos", "osx"];
|
||||
const targetsHTML5 = ["html5", "debug-html5"];
|
||||
|
||||
function addBackends(project) {
|
||||
project.localLibraryPath = "Backends";
|
||||
async function addBackends(project) {
|
||||
//project.localLibraryPath = "Backends";
|
||||
|
||||
const isHL = targetsHL.indexOf(Project.platform) >= 0;
|
||||
|
||||
if (isHL) {
|
||||
await project.addProject("Backends/hl");
|
||||
project.addDefine("AURA_BACKEND_HL");
|
||||
console.log("[Aura] Using HL/C backend");
|
||||
}
|
||||
@ -31,7 +32,7 @@ async function main() {
|
||||
project.addSources('Sources');
|
||||
|
||||
if (process.argv.indexOf("--aura-no-backend") == -1) {
|
||||
addBackends(project);
|
||||
await addBackends(project);
|
||||
}
|
||||
else {
|
||||
project.addDefine("AURA_NO_BACKEND");
|
||||
|
||||
Reference in New Issue
Block a user