Update Files

This commit is contained in:
2025-01-22 16:18:30 +01:00
parent ed4603cf95
commit a36294b518
16718 changed files with 2960346 additions and 0 deletions

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

View 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

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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(".")}
}
}