1
0
forked from Onek8/LNXSDK

merge upstream

This commit is contained in:
2026-06-24 07:06:56 +00:00
19 changed files with 409 additions and 98 deletions

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -20,6 +20,7 @@ class LeenkxSendMessageNode extends LogicNode {
}
override function run(from:Int) {
#if js
var connection = inputs[1].get();
if (connection == null) return;
var api: String = inputs[2].get();
@ -348,6 +349,7 @@ class LeenkxSendMessageNode extends LogicNode {
return;
}
}
#end
}
}

View File

@ -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"

View File

@ -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)

View File

@ -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) {

View File

@ -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);

View File

@ -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

View File

@ -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
}

View File

@ -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) {

View File

@ -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;
}
/**

View File

@ -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 });

View 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;
}

View File

@ -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;
}

View 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;
}
}

View File

@ -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);

View File

@ -25,7 +25,7 @@ class ChannelMessageID extends MessageID {
final PPitch;
final PDopplerRatio;
final PDstAttenuation;
#if (kha_html5 || kha_debug_html5)
final PVolumeLeft;
final PVolumeRight;

View File

@ -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");