Update Files
This commit is contained in:
126
lib/aura/Sources/aura/utils/Assert.hx
Normal file
126
lib/aura/Sources/aura/utils/Assert.hx
Normal file
@ -0,0 +1,126 @@
|
||||
package aura.utils;
|
||||
|
||||
import haxe.Exception;
|
||||
import haxe.PosInfos;
|
||||
import haxe.exceptions.PosException;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
using haxe.macro.ExprTools;
|
||||
|
||||
class Assert {
|
||||
/**
|
||||
Checks whether the given expression evaluates to true. If this is not
|
||||
the case, a `AuraAssertionException` with additional information is
|
||||
thrown.
|
||||
|
||||
The assert level describes the severity of the assertion. If the
|
||||
severity is lower than the level stored in the `AURA_ASSERT_LEVEL` flag,
|
||||
the assertion is omitted from the code so that it doesn't decrease the
|
||||
runtime performance.
|
||||
|
||||
@param level The severity of this assertion.
|
||||
@param condition The conditional expression to test.
|
||||
@param message Optional message to display when the assertion fails.
|
||||
**/
|
||||
public static macro function assert(level: ExprOf<AssertLevel>, condition: ExprOf<Bool>, ?message: ExprOf<String>): Expr {
|
||||
final levelVal: AssertLevel = AssertLevel.fromExpr(level);
|
||||
final assertThreshold = AssertLevel.fromString(Context.definedValue("AURA_ASSERT_LEVEL"));
|
||||
|
||||
if (levelVal < assertThreshold) {
|
||||
return macro {};
|
||||
}
|
||||
|
||||
return macro {
|
||||
if (!$condition) {
|
||||
#if AURA_ASSERT_QUIT kha.System.stop(); #end
|
||||
|
||||
@:pos(condition.pos)
|
||||
final exception = new aura.utils.Assert.AuraAssertionException($v{condition.toString()}, ${message});
|
||||
|
||||
#if AURA_ASSERT_START_DEBUGGER
|
||||
@:privateAccess aura.utils.Assert.logError(exception.details());
|
||||
@:privateAccess aura.utils.Assert.logError("An assertion error was triggered, starting debugger...");
|
||||
|
||||
aura.utils.Debug.startDebugger();
|
||||
#else
|
||||
@:pos(condition.pos)
|
||||
@:privateAccess aura.utils.Assert.throwAssertionError(exception);
|
||||
#end
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Helper function to prevent Haxe "bug" that actually throws an error
|
||||
even when using `macro throw` (inlining this method also does not work).
|
||||
**/
|
||||
static function throwAssertionError(exp: AuraAssertionException, ?pos: PosInfos) {
|
||||
throw exp;
|
||||
}
|
||||
|
||||
static function logError(str: String, ?infos: PosInfos) {
|
||||
#if sys
|
||||
Sys.stderr().writeString(str + "\n");
|
||||
#elseif kha_krom
|
||||
Krom.log(str + "\n");
|
||||
#elseif kha_js
|
||||
js.html.Console.error(str);
|
||||
#else
|
||||
haxe.Log.trace(str, infos);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Exception that is thrown when an assertion fails.
|
||||
|
||||
@see `Assert`
|
||||
**/
|
||||
class AuraAssertionException extends PosException {
|
||||
|
||||
/**
|
||||
@param exprString The string representation of the failed assert condition.
|
||||
@param message Custom error message, use `null` to omit printing the message.
|
||||
**/
|
||||
public function new(exprString: String, message: Null<String>, ?previous: Exception, ?pos: Null<PosInfos>) {
|
||||
final optMsg = message != null ? '\n\tMessage: $message' : "";
|
||||
|
||||
super('\n[Aura] Failed assertion:$optMsg\n\tExpression: ($exprString)', previous, pos);
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract AssertLevel(Int) from Int to Int {
|
||||
var Debug: AssertLevel;
|
||||
var Warning: AssertLevel;
|
||||
var Error: AssertLevel;
|
||||
var Critical: AssertLevel;
|
||||
|
||||
// Don't use this level in assert() calls!
|
||||
var NoAssertions: AssertLevel;
|
||||
|
||||
public static function fromExpr(e: ExprOf<AssertLevel>): AssertLevel {
|
||||
switch (e.expr) {
|
||||
case EConst(CIdent(v)): return fromString(v);
|
||||
default: throw new Exception('Unsupported expression: $e');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Converts a string into an `AssertLevel`, the string must be spelled
|
||||
exactly as the assert level. `null` defaults to `AssertLevel.Critical`.
|
||||
**/
|
||||
public static function fromString(s: String): AssertLevel {
|
||||
return switch (s) {
|
||||
case "Debug": Debug;
|
||||
case "Warning": Warning;
|
||||
case "Error": Error;
|
||||
case "Critical" | null: Critical;
|
||||
case "NoAssertions": NoAssertions;
|
||||
default: throw 'Could not convert "$s" to AssertLevel';
|
||||
}
|
||||
}
|
||||
|
||||
@:op(A < B) static function lt(a:AssertLevel, b:AssertLevel):Bool;
|
||||
@:op(A > B) static function gt(a:AssertLevel, b:AssertLevel):Bool;
|
||||
}
|
103
lib/aura/Sources/aura/utils/BufferUtils.hx
Normal file
103
lib/aura/Sources/aura/utils/BufferUtils.hx
Normal file
@ -0,0 +1,103 @@
|
||||
package aura.utils;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.arrays.Float32Array;
|
||||
|
||||
inline function fillBuffer(buffer: Float32Array, value: FastFloat, length: Int = -1) {
|
||||
for (i in 0...(length == -1 ? buffer.length : length)) {
|
||||
buffer[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
inline function clearBuffer(buffer: Float32Array) {
|
||||
#if hl
|
||||
hl_fillByteArray(buffer, 0);
|
||||
#else
|
||||
fillBuffer(buffer, 0);
|
||||
#end
|
||||
}
|
||||
|
||||
inline function initZeroesVecI(vector: Vector<Int>) {
|
||||
#if (haxe_ver >= "4.300")
|
||||
vector.fill(0);
|
||||
#else
|
||||
for (i in 0...vector.length) {
|
||||
vector[i] = 0;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
inline function initZeroesF64(vector: Vector<Float>) {
|
||||
#if (haxe_ver >= "4.300")
|
||||
vector.fill(0);
|
||||
#else
|
||||
for (i in 0...vector.length) {
|
||||
vector[i] = 0;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
inline function initZeroesF32(vector: Vector<FastFloat>) {
|
||||
#if (haxe_ver >= "4.300")
|
||||
vector.fill(0);
|
||||
#else
|
||||
for (i in 0...vector.length) {
|
||||
vector[i] = 0;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an empty integer vector with the given length. It is guaranteed to
|
||||
be always filled with 0, independent of the target.
|
||||
**/
|
||||
inline function createEmptyVecI(length: Int): Vector<Int> {
|
||||
#if target.static
|
||||
return new Vector<Int>(length);
|
||||
#else
|
||||
// On dynamic targets, vectors hold `null` after creation instead of 0
|
||||
final vec = new Vector<Int>(length);
|
||||
inline initZeroesVecI(vec);
|
||||
return vec;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an empty float vector with the given length. It is guaranteed to be
|
||||
always filled with 0, independent of the target.
|
||||
**/
|
||||
inline function createEmptyVecF64(length: Int): Vector<Float> {
|
||||
#if target.static
|
||||
return new Vector<Float>(length);
|
||||
#else
|
||||
final vec = new Vector<Float>(length);
|
||||
inline initZeroesF64(vec);
|
||||
return vec;
|
||||
#end
|
||||
}
|
||||
|
||||
inline function createEmptyVecF32(length: Int): Vector<FastFloat> {
|
||||
#if target.static
|
||||
return new Vector<FastFloat>(length);
|
||||
#else
|
||||
final vec = new Vector<FastFloat>(length);
|
||||
inline initZeroesF32(vec);
|
||||
return vec;
|
||||
#end
|
||||
}
|
||||
|
||||
inline function createEmptyF32Array(length: Int): Float32Array {
|
||||
final out = new Float32Array(length);
|
||||
#if !js
|
||||
clearBuffer(out);
|
||||
#end
|
||||
return out;
|
||||
}
|
||||
|
||||
#if hl
|
||||
inline function hl_fillByteArray(a: kha.arrays.ByteArray, byteValue: Int) {
|
||||
(a.buffer: hl.Bytes).fill(0, a.byteLength, byteValue);
|
||||
}
|
||||
#end
|
48
lib/aura/Sources/aura/utils/CircularBuffer.hx
Normal file
48
lib/aura/Sources/aura/utils/CircularBuffer.hx
Normal file
@ -0,0 +1,48 @@
|
||||
package aura.utils;
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.arrays.Float32Array;
|
||||
|
||||
import aura.utils.BufferUtils;
|
||||
|
||||
class CircularBuffer {
|
||||
final data: Float32Array;
|
||||
var readHead: Int;
|
||||
var writeHead: Int;
|
||||
public var length(get, null): Int;
|
||||
public var delay = 0;
|
||||
|
||||
public inline function new(size: Int) {
|
||||
assert(Warning, size > 0);
|
||||
|
||||
this.data = createEmptyF32Array(size);
|
||||
this.length = size;
|
||||
this.writeHead = 0;
|
||||
this.readHead = 1;
|
||||
}
|
||||
|
||||
public inline function setDelay(delaySamples: Int) {
|
||||
delay = delaySamples;
|
||||
readHead = writeHead - delaySamples;
|
||||
if (readHead < 0) {
|
||||
readHead += length;
|
||||
}
|
||||
}
|
||||
|
||||
public inline function get_length(): Int {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
public inline function get(): FastFloat {
|
||||
return data[readHead];
|
||||
}
|
||||
|
||||
public inline function set(value: FastFloat) {
|
||||
data[writeHead] = value;
|
||||
}
|
||||
|
||||
public inline function increment() {
|
||||
if (++readHead >= length) readHead = 0;
|
||||
if (++writeHead >= length) writeHead = 0;
|
||||
}
|
||||
}
|
130
lib/aura/Sources/aura/utils/Debug.hx
Normal file
130
lib/aura/Sources/aura/utils/Debug.hx
Normal file
@ -0,0 +1,130 @@
|
||||
package aura.utils;
|
||||
|
||||
import kha.Image;
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.graphics2.Graphics;
|
||||
|
||||
import aura.utils.MathUtils;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Debug {
|
||||
static var id = 0;
|
||||
|
||||
public static inline function startDebugger() {
|
||||
#if js
|
||||
js.Syntax.code("debugger");
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Generates GraphViz/dot code to draw the channel tree for debugging. On
|
||||
html5 this code is copied to the clipboard, on other targets it is
|
||||
copied to the console but might be cut off (so better use html5 for
|
||||
that).
|
||||
**/
|
||||
public static function debugTreeViz() {
|
||||
#if AURA_DEBUG
|
||||
final content = new StringBuf();
|
||||
|
||||
content.add("digraph Aura_Tree_Snapshot {\n");
|
||||
|
||||
content.add('\tranksep=equally;\n');
|
||||
content.add('\trankdir=BT;\n');
|
||||
content.add('\tnode [fontname = "helvetica"];\n');
|
||||
|
||||
addTreeToViz(content, Aura.masterChannel);
|
||||
|
||||
content.add("}");
|
||||
|
||||
copyToClipboard(content.toString());
|
||||
#else
|
||||
trace("Please build with 'AURA_DEBUG' flag!");
|
||||
#end
|
||||
}
|
||||
|
||||
#if AURA_DEBUG
|
||||
static function addTreeToViz(buf: StringBuf, channelHandle: Handle) {
|
||||
buf.add('\t${id++} [\n');
|
||||
buf.add('\t\tshape=plaintext,\n');
|
||||
buf.add('\t\tlabel=<<table border="1" cellborder="0" style="rounded">\n');
|
||||
|
||||
buf.add('\t\t\t<tr><td colspan="2"><b>${Type.getClassName(Type.getClass(channelHandle))}</b></td></tr>\n');
|
||||
buf.add('\t\t\t<tr><td colspan="2">${Type.getClassName(Type.getClass(@:privateAccess channelHandle.channel))}</td></tr>\n');
|
||||
buf.add('\t\t\t<hr/>\n');
|
||||
buf.add('\t\t\t<tr><td><i>Tree level</i></td><td>${@:privateAccess channelHandle.channel.treeLevel}</td></tr>\n');
|
||||
buf.add('\t\t\t<hr/>\n');
|
||||
|
||||
for (key => val in channelHandle.getDebugAttrs()) {
|
||||
buf.add('\t\t\t<tr><td><i>$key</i></td><td>$val</td></tr>');
|
||||
}
|
||||
buf.add('\t\t</table>>\n');
|
||||
buf.add('\t];\n');
|
||||
|
||||
final thisID = id - 1;
|
||||
if (Std.isOfType(channelHandle, MixChannelHandle)) {
|
||||
var mixHandle: MixChannelHandle = cast channelHandle;
|
||||
for (inputHandle in mixHandle.inputHandles) {
|
||||
final inputID = id;
|
||||
|
||||
addTreeToViz(buf, inputHandle);
|
||||
buf.add('\t${inputID} -> ${thisID};\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
static function copyToClipboard(text: String) {
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
js.Browser.navigator.clipboard.writeText(text)
|
||||
.then(
|
||||
(_) -> { trace("Debug tree code has been copied to clipboard."); },
|
||||
(err) -> {
|
||||
trace('Debug tree code could not be copied to clipboard, writing to console instead. Reason: $err');
|
||||
trace(text);
|
||||
}
|
||||
);
|
||||
#else
|
||||
trace(text);
|
||||
#end
|
||||
}
|
||||
|
||||
public static function drawWaveform(buffer: Float32Array, g: Graphics, x: Float, y: Float, w: Float, h: Float) {
|
||||
g.begin(false);
|
||||
|
||||
g.opacity = 1.0;
|
||||
g.color = kha.Color.fromFloats(0.176, 0.203, 0.223);
|
||||
g.fillRect(x, y, w, h);
|
||||
|
||||
final borderSize = 2;
|
||||
g.color = kha.Color.fromFloats(0.099, 0.099, 0.099);
|
||||
g.drawRect(x + borderSize * 0.5, y + borderSize * 0.5, w - borderSize, h - borderSize, borderSize);
|
||||
|
||||
g.color = kha.Color.fromFloats(0.898, 0.411, 0.164);
|
||||
|
||||
final deinterleavedLength = Std.int(buffer.length / 2);
|
||||
final numLines = buffer.length - 1;
|
||||
final stepSize = w / numLines;
|
||||
final innerHeight = h - 2 * borderSize;
|
||||
for (c in 0...2) {
|
||||
if ( c == 1 ) g.color = kha.Color.fromFloats(0.023, 0.443, 0.796);
|
||||
for (i in 0...deinterleavedLength - 1) {
|
||||
final idx = i + c * deinterleavedLength;
|
||||
final y1 = y + borderSize + (1 - clampF(buffer[idx] * 0.5 + 0.5, 0, 1)) * innerHeight;
|
||||
final y2 = y + borderSize + (1 - clampF(buffer[idx + 1] * 0.5 + 0.5, 0, 1)) * innerHeight;
|
||||
g.drawLine(x + idx * stepSize, y1, x + (idx + 1) * stepSize, y2);
|
||||
}
|
||||
}
|
||||
|
||||
g.color = kha.Color.fromFloats(0.023, 0.443, 0.796);
|
||||
g.opacity = 0.5;
|
||||
// g.drawLine(x + w / 2, y, x + w / 2, y + h, 2);
|
||||
|
||||
g.end();
|
||||
}
|
||||
|
||||
public static function createRenderTarget(w: Int, h: Int): Image {
|
||||
return Image.createRenderTarget(Std.int(w), Std.int(h), null, NoDepthAndStencil, 1);
|
||||
}
|
||||
// #end
|
||||
}
|
22
lib/aura/Sources/aura/utils/Decibel.hx
Normal file
22
lib/aura/Sources/aura/utils/Decibel.hx
Normal file
@ -0,0 +1,22 @@
|
||||
package aura.utils;
|
||||
|
||||
/**
|
||||
The decibel (dB) is a relative unit of measurement equal to one tenth of a bel (B).
|
||||
It expresses the ratio of two values of a power or root-power quantity on a logarithmic scale.
|
||||
|
||||
The number of decibels is ten times the logarithm to base 10 of the ratio of two power quantities.
|
||||
|
||||
A change in power by a factor of 10 corresponds to a 10 dB change in level.
|
||||
At the half power point an audio circuit or an antenna exhibits an attenuation of approximately 3 dB.
|
||||
A change in amplitude by a factor of 10 results in a change in power by a factor of 100, which corresponds to a 20 dB change in level.
|
||||
A change in amplitude ratio by a factor of 2 (equivalently factor of 4 in power change) approximately corresponds to a 6 dB change in level.
|
||||
**/
|
||||
class Decibel {
|
||||
@:pure public static inline function toDecibel(volume: Float): Float {
|
||||
return 20 * MathUtils.log10(volume);
|
||||
}
|
||||
|
||||
@:pure public static inline function toLinear(db: Float): Float {
|
||||
return Math.pow(10, db / 20);
|
||||
}
|
||||
}
|
27
lib/aura/Sources/aura/utils/FrequencyUtils.hx
Normal file
27
lib/aura/Sources/aura/utils/FrequencyUtils.hx
Normal file
@ -0,0 +1,27 @@
|
||||
package aura.utils;
|
||||
|
||||
import aura.utils.Assert.*;
|
||||
|
||||
@:pure inline function frequencyToFactor(freq: Hertz, maxFreq: Hertz): Float {
|
||||
assert(Debug, freq <= maxFreq);
|
||||
|
||||
return freq / maxFreq;
|
||||
}
|
||||
|
||||
@:pure inline function factorToFrequency(factor: Float, maxFreq: Hertz): Hertz {
|
||||
assert(Debug, 0.0 <= factor && factor <= 1.0);
|
||||
|
||||
return Std.int(factor * maxFreq);
|
||||
}
|
||||
|
||||
@:pure inline function sampleRateToMaxFreq(sampleRate: Hertz): Hertz {
|
||||
return Std.int(sampleRate / 2.0);
|
||||
}
|
||||
|
||||
@:pure inline function msToSamples(sampleRate: Hertz, milliseconds: Millisecond): Int {
|
||||
return Math.ceil((milliseconds * 0.001) * sampleRate);
|
||||
}
|
||||
|
||||
@:pure inline function samplesToMs(sampleRate: Hertz, samples: Int): Millisecond {
|
||||
return (samples / sampleRate) * 1000;
|
||||
}
|
53
lib/aura/Sources/aura/utils/Interpolator.hx
Normal file
53
lib/aura/Sources/aura/utils/Interpolator.hx
Normal file
@ -0,0 +1,53 @@
|
||||
package aura.utils;
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.simd.Float32x4;
|
||||
|
||||
import aura.types.AudioBuffer.AudioBufferChannelView;
|
||||
|
||||
class LinearInterpolator {
|
||||
public var lastValue: FastFloat;
|
||||
public var targetValue: FastFloat;
|
||||
public var currentValue: FastFloat;
|
||||
|
||||
public inline function new(targetValue: FastFloat) {
|
||||
this.targetValue = this.currentValue = this.lastValue = targetValue;
|
||||
}
|
||||
|
||||
public inline function updateLast() {
|
||||
this.lastValue = this.currentValue = this.targetValue;
|
||||
}
|
||||
|
||||
public inline function getLerpStepSize(numSteps: Int): FastFloat {
|
||||
return (this.targetValue - this.lastValue) / numSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
Return a 32x4 SIMD register where each value contains the step size times
|
||||
its index for efficient usage in `LinearInterpolator.applySIMD32x4()`.
|
||||
**/
|
||||
public inline function getLerpStepSizes32x4(numSteps: Int): Float32x4 {
|
||||
final stepSize = getLerpStepSize(numSteps);
|
||||
return Float32x4.mul(Float32x4.loadAllFast(stepSize), Float32x4.loadFast(1.0, 2.0, 3.0, 4.0));
|
||||
}
|
||||
|
||||
/**
|
||||
Applies four consecutive interpolation steps to `samples` (multiplicative)
|
||||
using Kha's 32x4 SIMD API, starting at index `i`. `stepSizes32x4` must
|
||||
be a SIMD register filled with `LinearInterpolator.getLerpStepSizes32x4()`.
|
||||
|
||||
There is no bound checking in place! It is assumed that 4 samples can
|
||||
be accessed starting at `i`.
|
||||
**/
|
||||
public inline function applySIMD32x4(samples: AudioBufferChannelView, i: Int, stepSizes32x4: Float32x4) {
|
||||
var rampValues = Float32x4.add(Float32x4.loadAllFast(currentValue), stepSizes32x4);
|
||||
currentValue = Float32x4.getFast(rampValues, 3);
|
||||
|
||||
var signalValues = Float32x4.loadFast(samples[i], samples[i + 1], samples[i + 2], samples[i + 3]);
|
||||
var res = Float32x4.mul(signalValues, rampValues);
|
||||
samples[i + 0] = Float32x4.getFast(res, 0);
|
||||
samples[i + 1] = Float32x4.getFast(res, 1);
|
||||
samples[i + 2] = Float32x4.getFast(res, 2);
|
||||
samples[i + 3] = Float32x4.getFast(res, 3);
|
||||
}
|
||||
}
|
13
lib/aura/Sources/aura/utils/MapExtension.hx
Normal file
13
lib/aura/Sources/aura/utils/MapExtension.hx
Normal file
@ -0,0 +1,13 @@
|
||||
package aura.utils;
|
||||
|
||||
/**
|
||||
Merges the contents of `from` into `to` and returns the latter (`to` is
|
||||
modified).
|
||||
**/
|
||||
@:generic
|
||||
inline function mergeIntoThis<K, V>(to: Map<K, V>, from: Map<K, V>): Map<K, V> {
|
||||
for (key => val in from) {
|
||||
to[key] = val;
|
||||
}
|
||||
return to;
|
||||
}
|
131
lib/aura/Sources/aura/utils/MathUtils.hx
Normal file
131
lib/aura/Sources/aura/utils/MathUtils.hx
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
Various math helper functions.
|
||||
**/
|
||||
package aura.utils;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
import aura.math.Vec3;
|
||||
|
||||
/** 1.0 / ln(10) in double precision **/
|
||||
inline var LN10_INV_DOUBLE: Float = 0.43429448190325181666793241674895398318767547607421875;
|
||||
/** 1.0 / ln(10) in single precision **/
|
||||
inline var LN10_INV_SINGLE: kha.FastFloat = 0.4342944920063018798828125;
|
||||
|
||||
/** 1.0 / e (Euler's number) **/
|
||||
inline var E_INV: kha.FastFloat = 0.367879441171442321595523770161460867;
|
||||
|
||||
@:pure inline function maxI(a: Int, b: Int): Int {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
@:pure inline function minI(a: Int, b: Int): Int {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
@:pure inline function maxF(a: Float, b: Float): Float {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
@:pure inline function minF(a: Float, b: Float): Float {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
@:pure inline function lerp(valA: Float, valB: Float, fac: Float): Float {
|
||||
return valA * (1 - fac) + valB * fac;
|
||||
}
|
||||
|
||||
@:pure inline function lerpF32(valA: FastFloat, valB: FastFloat, fac: FastFloat): FastFloat {
|
||||
return valA * (1 - fac) + valB * fac;
|
||||
}
|
||||
|
||||
@:pure inline function clampI(val: Int, min: Int = 0, max: Int = 1): Int {
|
||||
return maxI(min, minI(max, val));
|
||||
}
|
||||
|
||||
@:pure inline function clampF(val: Float, min: Float = 0.0, max: Float = 1.0): Float {
|
||||
return maxF(min, minF(max, val));
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the base-10 logarithm of a number.
|
||||
**/
|
||||
@:pure inline function log10(v: Float): Float {
|
||||
return Math.log(v) * LN10_INV_DOUBLE;
|
||||
}
|
||||
|
||||
/**
|
||||
Calculate the counterclockwise angle of the rotation of `vecOther` relative
|
||||
to `vecBase` around the rotation axis of `vecNormal`. All input vectors
|
||||
*must* be normalized!
|
||||
**/
|
||||
@:pure inline function getFullAngleDegrees(vecBase: Vec3, vecOther: Vec3, vecNormal: Vec3): Float {
|
||||
final dot = vecBase.dot(vecOther);
|
||||
final det = determinant3x3(vecBase, vecOther, vecNormal);
|
||||
var radians = Math.atan2(det, dot);
|
||||
|
||||
// Move [-PI, 0) to [PI, 2 * PI]
|
||||
if (radians < 0) {
|
||||
radians += 2 * Math.PI;
|
||||
}
|
||||
return radians * 180 / Math.PI;
|
||||
}
|
||||
|
||||
@:pure inline function determinant3x3(col1: Vec3, col2: Vec3, col3: Vec3): Float {
|
||||
return (
|
||||
col1.x * col2.y * col3.z
|
||||
+ col2.x * col3.y * col1.z
|
||||
+ col3.x * col1.y * col2.z
|
||||
- col1.z * col2.y * col3.x
|
||||
- col2.z * col3.y * col1.x
|
||||
- col3.z * col1.y * col2.x
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Projects the given point to a plane described by its normal vector. The
|
||||
origin of the plane is assumed to be at (0, 0, 0).
|
||||
**/
|
||||
@:pure inline function projectPointOntoPlane(point: Vec3, planeNormal: Vec3): Vec3 {
|
||||
return point.sub(planeNormal.mult(planeNormal.dot(point)));
|
||||
}
|
||||
|
||||
@:pure inline function isPowerOf2(val: Int): Bool {
|
||||
return (val & (val - 1)) == 0;
|
||||
}
|
||||
|
||||
@:pure inline function getNearestIndexF(value: Float, stepSize: Float): Int {
|
||||
final quotient: Int = Std.int(value / stepSize);
|
||||
final remainder: Float = value % stepSize;
|
||||
return (remainder > stepSize / 2) ? (quotient + 1) : (quotient);
|
||||
}
|
||||
|
||||
/**
|
||||
Calculates the logarithm of base 2 for the given unsigned(!) integer `n`,
|
||||
which is the position of the most significant bit set.
|
||||
**/
|
||||
@:pure inline function log2Unsigned(n: Int): Int {
|
||||
// TODO: optimize? See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
|
||||
var res = 0;
|
||||
|
||||
var tmp = n >>> 1; // Workaround for https://github.com/HaxeFoundation/haxe/issues/10783
|
||||
while (tmp != 0) {
|
||||
res++;
|
||||
tmp >>>= 1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Calculates 2^n for a given unsigned integer `n`. **/
|
||||
@:pure inline function exp2(n: Int): Int {
|
||||
return 1 << n;
|
||||
}
|
||||
|
||||
@:pure inline function div4(n: Int): Int {
|
||||
return n >>> 2;
|
||||
}
|
||||
|
||||
@:pure inline function mod4(n: Int): Int {
|
||||
return n & 3;
|
||||
}
|
37
lib/aura/Sources/aura/utils/Pointer.hx
Normal file
37
lib/aura/Sources/aura/utils/Pointer.hx
Normal file
@ -0,0 +1,37 @@
|
||||
package aura.utils;
|
||||
|
||||
@:generic
|
||||
class Pointer<T> {
|
||||
public var value: Null<T>;
|
||||
|
||||
public inline function new(value: Null<T> = null) {
|
||||
set(value);
|
||||
}
|
||||
|
||||
public inline function set(value: Null<T>) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public inline function get(): Null<T> {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
Return the pointer's value typed as not-nullable. Use at your own risk.
|
||||
**/
|
||||
public inline function getSure(): T {
|
||||
return @:nullSafety(Off) (this.value: T);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Workaround for covariance issues when using generics. Use `PointerType<T>`
|
||||
instead of `Pointer<T>` when using generic pointers as function parameters.
|
||||
**/
|
||||
@:generic
|
||||
typedef PointerType<T> = {
|
||||
public var value: Null<T>;
|
||||
|
||||
public function set(value: Null<T>): Void;
|
||||
public function get(): Null<T>;
|
||||
}
|
24
lib/aura/Sources/aura/utils/Profiler.hx
Normal file
24
lib/aura/Sources/aura/utils/Profiler.hx
Normal file
@ -0,0 +1,24 @@
|
||||
package aura.utils;
|
||||
|
||||
#if (cpp && AURA_WITH_OPTICK)
|
||||
@:cppInclude('optick.h')
|
||||
#end
|
||||
class Profiler {
|
||||
public static inline function frame(threadName: String) {
|
||||
#if (cpp && AURA_WITH_OPTICK)
|
||||
untyped __cpp__("OPTICK_FRAME({0})", threadName);
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function event() {
|
||||
#if (cpp && AURA_WITH_OPTICK)
|
||||
untyped __cpp__("OPTICK_EVENT()");
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function shutdown() {
|
||||
#if (cpp && AURA_WITH_OPTICK)
|
||||
untyped __cpp__("OPTICK_SHUTDOWN()");
|
||||
#end
|
||||
}
|
||||
}
|
79
lib/aura/Sources/aura/utils/Resampler.hx
Normal file
79
lib/aura/Sources/aura/utils/Resampler.hx
Normal file
@ -0,0 +1,79 @@
|
||||
package aura.utils;
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
|
||||
import aura.utils.MathUtils;
|
||||
|
||||
/**
|
||||
Various utilities for resampling (i.e. changing the sample rate) of signals.
|
||||
|
||||
Terminology used in this class for a resampling process:
|
||||
- **Source data** describes the data prior to resampling.
|
||||
- **Target data** describes the resampled data.
|
||||
**/
|
||||
class Resampler {
|
||||
|
||||
/**
|
||||
Return the amount of samples required for storing the result of
|
||||
resampling data with the given `sourceDataLength` to the
|
||||
`targetSampleRate`.
|
||||
**/
|
||||
public static inline function getResampleLength(sourceDataLength: Int, sourceSampleRate: Hertz, targetSampleRate: Hertz): Int {
|
||||
return Math.ceil(sourceDataLength * (targetSampleRate / sourceSampleRate));
|
||||
}
|
||||
|
||||
/**
|
||||
Transform a position (in samples) relative to the source's sample rate
|
||||
into a position (in samples) relative to the target's sample rate and
|
||||
return the transformed position.
|
||||
**/
|
||||
public static inline function sourceSamplePosToTargetPos(sourceSamplePos: Float, sourceSampleRate: Hertz, targetSampleRate: Hertz): Float {
|
||||
return sourceSamplePos * (targetSampleRate / sourceSampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
Transform a position (in samples) relative to the target's sample rate
|
||||
into a position (in samples) relative to the source's sample rate and
|
||||
return the transformed position.
|
||||
**/
|
||||
public static inline function targetSamplePosToSourcePos(targetSamplePos: Float, sourceSampleRate: Hertz, targetSampleRate: Hertz): Float {
|
||||
return targetSamplePos * (sourceSampleRate / targetSampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
Resample the given `sourceData` from `sourceSampleRate` to
|
||||
`targetSampleRate` and write the resampled data into `targetData`.
|
||||
|
||||
It is expected that
|
||||
`targetData.length == Resampler.getResampleLength(sourceData.length, sourceSampleRate, targetSampleRate)`,
|
||||
otherwise this method may fail (there are no safety checks in place)!
|
||||
**/
|
||||
public static inline function resampleFloat32Array(sourceData: Float32Array, sourceSampleRate: Hertz, targetData: Float32Array, targetSampleRate: Hertz) {
|
||||
for (i in 0...targetData.length) {
|
||||
targetData[i] = sampleAtTargetPositionLerp(sourceData, i, sourceSampleRate, targetSampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sample the given `sourceData` at `targetSamplePos` (position in samples
|
||||
relative to the target data) using linear interpolation for values
|
||||
between source samples.
|
||||
|
||||
@param sourceSampleRate The sample rate of the source data
|
||||
@param targetSampleRate The sample rate of the target data
|
||||
**/
|
||||
public static function sampleAtTargetPositionLerp(sourceData: Float32Array, targetSamplePos: Float, sourceSampleRate: Hertz, targetSampleRate: Hertz): Float {
|
||||
assert(Critical, targetSamplePos >= 0.0);
|
||||
|
||||
final sourceSamplePos = targetSamplePosToSourcePos(targetSamplePos, sourceSampleRate, targetSampleRate);
|
||||
|
||||
final maxPos = sourceData.length - 1;
|
||||
final pos1 = Math.floor(sourceSamplePos);
|
||||
final pos2 = pos1 + 1;
|
||||
|
||||
final value1 = (pos1 > maxPos) ? sourceData[maxPos] : sourceData[pos1];
|
||||
final value2 = (pos2 > maxPos) ? sourceData[maxPos] : sourceData[pos2];
|
||||
|
||||
return lerp(value1, value2, sourceSamplePos - Math.floor(sourceSamplePos));
|
||||
}
|
||||
}
|
32
lib/aura/Sources/aura/utils/ReverseIterator.hx
Normal file
32
lib/aura/Sources/aura/utils/ReverseIterator.hx
Normal file
@ -0,0 +1,32 @@
|
||||
package aura.utils;
|
||||
|
||||
/**
|
||||
Use this as a static extension:
|
||||
|
||||
```haxe
|
||||
using ReverseIterator;
|
||||
|
||||
for (i in (0...10).reversed()) {
|
||||
// Do something...
|
||||
}
|
||||
```
|
||||
**/
|
||||
inline function reversed(iter: IntIterator, step: Int = 1) {
|
||||
return @:privateAccess new ReverseIterator(iter.min, iter.max, step);
|
||||
}
|
||||
|
||||
private class ReverseIterator {
|
||||
var currentIndex: Int;
|
||||
var end: Int;
|
||||
|
||||
var step: Int;
|
||||
|
||||
public inline function new(start: Int, end: Int, step: Int) {
|
||||
this.currentIndex = start;
|
||||
this.end = end;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public inline function hasNext() return currentIndex > end;
|
||||
public inline function next() return (currentIndex -= step) + step;
|
||||
}
|
36
lib/aura/Sources/aura/utils/StepIterator.hx
Normal file
36
lib/aura/Sources/aura/utils/StepIterator.hx
Normal file
@ -0,0 +1,36 @@
|
||||
// =============================================================================
|
||||
// Adapted from
|
||||
// https://code.haxe.org/category/data-structures/step-iterator.html
|
||||
// =============================================================================
|
||||
|
||||
package aura.utils;
|
||||
|
||||
/**
|
||||
Use this as a static extension:
|
||||
|
||||
```haxe
|
||||
using aura.utils.StepIterator;
|
||||
|
||||
for (i in (0...10).step(2)) {
|
||||
// Do something...
|
||||
}
|
||||
```
|
||||
**/
|
||||
inline function step(iter: IntIterator, step: Int) {
|
||||
return @:privateAccess new StepIterator(iter.min, iter.max, step);
|
||||
}
|
||||
|
||||
private class StepIterator {
|
||||
var currentIndex: Int;
|
||||
final end: Int;
|
||||
final step: Int;
|
||||
|
||||
public inline function new(start: Int, end: Int, step: Int) {
|
||||
this.currentIndex = start;
|
||||
this.end = end;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public inline function hasNext() return currentIndex < end;
|
||||
public inline function next() return (currentIndex += step) - step;
|
||||
}
|
23
lib/aura/Sources/aura/utils/TestSignals.hx
Normal file
23
lib/aura/Sources/aura/utils/TestSignals.hx
Normal file
@ -0,0 +1,23 @@
|
||||
package aura.utils;
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
|
||||
class TestSignals {
|
||||
/**
|
||||
Fill the given `array` with a signal that represents a DC or 0Hz signal.
|
||||
**/
|
||||
public static inline function fillDC(array: Float32Array) {
|
||||
for (i in 0...array.length) {
|
||||
array[i] = (i == 0) ? 0.0 : 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fill the given `array` with a single unit impulse.
|
||||
**/
|
||||
public static inline function fillUnitImpulse(array: Float32Array) {
|
||||
for (i in 0...array.length) {
|
||||
array[i] = (i == 0) ? 1.0 : 0.0;
|
||||
}
|
||||
}
|
||||
}
|
136
lib/aura/Sources/aura/utils/macro/ExtensibleEnumBuilder.hx
Normal file
136
lib/aura/Sources/aura/utils/macro/ExtensibleEnumBuilder.hx
Normal file
@ -0,0 +1,136 @@
|
||||
package aura.utils.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type.ClassType;
|
||||
|
||||
/**
|
||||
This macro implements integer enum types that can extend from others, at the
|
||||
cost of some limitations.
|
||||
|
||||
## Usage
|
||||
```haxe
|
||||
@:autoBuild(aura.utils.macro.ExtensibleEnumBuilder.build())
|
||||
@:build(aura.utils.macro.ExtensibleEnumBuilder.build())
|
||||
class BaseEnum {
|
||||
var ABaseEnumValue;
|
||||
}
|
||||
|
||||
class ExtendingEnum extends BaseEnum {
|
||||
var AnExtendingEnumValue;
|
||||
}
|
||||
```
|
||||
|
||||
This macro transforms the variables in above example into the static inline
|
||||
variables `BaseEnum.ABaseEnumValue = 0` and `ExtendingEnum.AnExtendingEnumValue = 1`.
|
||||
|
||||
The compiler dump after the macro looks as follows:
|
||||
|
||||
```haxe
|
||||
// BaseEnum.dump
|
||||
@:used @:autoBuild(aura.utils.macro.ExtensibleEnumBuilder.build()) @:build(aura.utils.macro.ExtensibleEnumBuilder.build())
|
||||
class BaseEnum {
|
||||
|
||||
@:value(0)
|
||||
public static inline var ABaseEnumValue:Int = 0;
|
||||
|
||||
@:value(ABaseEnumValue + 1)
|
||||
static inline var _SubtypeOffset:Int = 1;
|
||||
}
|
||||
|
||||
// ExtendingEnum.dump
|
||||
@:used @:build(aura.utils.macro.ExtensibleEnumBuilder.build()) @:autoBuild(aura.utils.macro.ExtensibleEnumBuilder.build())
|
||||
class ExtendingEnum extends BaseEnum {
|
||||
|
||||
@:value(@:privateAccess Main.BaseEnum._SubtypeOffset)
|
||||
public static inline var AnExtendingEnumValue:Int = 1;
|
||||
|
||||
@:value(AnExtendingEnumValue + 1)
|
||||
static inline var _SubtypeOffset:Int = 2;
|
||||
}
|
||||
```
|
||||
|
||||
## Limitations
|
||||
- Only integer types are supported.
|
||||
- The enums are stored in classes instead of `enum abstract` types.
|
||||
- Actual values are typed as int, no auto-completion and less intelligent switch/case statements.
|
||||
- No actual OOP-like inheritance (which wouldn't work with enums since enum inheritance would need to be contravariant).
|
||||
More importantly, only the values of the variables are extended, but subclassing enums _don't inherit the variables_
|
||||
of their superclass enums.
|
||||
- Little complexity and compile time added by using a macro.
|
||||
**/
|
||||
class ExtensibleEnumBuilder {
|
||||
@:persistent static final SUBTYPE_VARNAME = "_SubtypeOffset";
|
||||
|
||||
public static macro function build(): Array<Field> {
|
||||
final fields = Context.getBuildFields();
|
||||
final newFields = new Array<Field>();
|
||||
|
||||
final cls = Context.getLocalClass().get();
|
||||
final superClass = cls.superClass;
|
||||
final isExtending = superClass != null;
|
||||
|
||||
var lastField: Null<Field> = null;
|
||||
for (field in fields) {
|
||||
switch (field.kind) {
|
||||
case FVar(complexType, expr):
|
||||
|
||||
var newExpr: Expr;
|
||||
if (lastField == null) {
|
||||
if (isExtending) {
|
||||
final path = classTypeToStringPath(superClass.t.get());
|
||||
newExpr = macro @:pos(Context.currentPos()) @:privateAccess ${strPathToExpr(path)}.$SUBTYPE_VARNAME;
|
||||
}
|
||||
else {
|
||||
newExpr = macro 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
newExpr = macro $i{lastField.name} + 1;
|
||||
}
|
||||
|
||||
newFields.push({
|
||||
name: field.name,
|
||||
access: [APublic, AStatic, AInline],
|
||||
kind: FVar(complexType, newExpr),
|
||||
meta: field.meta,
|
||||
doc: field.doc,
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
lastField = field;
|
||||
|
||||
default:
|
||||
newFields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
newFields.push({
|
||||
name: SUBTYPE_VARNAME,
|
||||
access: [APrivate, AStatic, AInline],
|
||||
kind: FVar(macro: Int, lastField != null ? macro $i{lastField.name} + 1 : macro 0),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
return newFields;
|
||||
}
|
||||
|
||||
static function classTypeToStringPath(classType: ClassType): String {
|
||||
var moduleName = classType.module.split(".").pop();
|
||||
|
||||
final name = moduleName + "." + classType.name;
|
||||
return classType.pack.length == 0 ? name : classType.pack.join(".") + "." + name;
|
||||
}
|
||||
|
||||
static inline function strPathToExpr(path: String): Expr {
|
||||
// final pathArray = path.split(".");
|
||||
// final first = EConst(CIdent(pathArray.shift()));
|
||||
// var expr = { expr: first, pos: Context.currentPos() };
|
||||
|
||||
// for (item in pathArray) {
|
||||
// expr = { expr: EField(expr, item), pos: Context.currentPos() };
|
||||
// }
|
||||
// return expr;
|
||||
return macro $p{path.split(".")}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user