// moonaudio — pure MoonBit audio decoding and DSP toolkit.
// Copyright 2026 Sakuraqwq1
// Licensed under the Apache License, Version 2.0.
///|
/// Bounds check used by every byte level reader.
///
/// Audio containers are read from untrusted files, so every multi-byte read is
/// guarded: a malformed length must surface as a `Truncated` error instead of a
/// trap.
pub fn require_bytes(
data : Bytes,
offset : Int32,
length : Int32,
what : String,
) -> Unit raise AudioError {
if offset < 0 || length < 0 || offset + length > data.length() {
raise Truncated(
"\{what}: need \{length} byte(s) at offset \{offset}, file has \{data.length()}",
)
}
}
///|
/// Read one unsigned byte.
pub fn read_u8(data : Bytes, offset : Int32) -> Int32 raise AudioError {
require_bytes(data, offset, 1, "u8")
data.at(offset).to_int()
}
///|
/// Read a little endian unsigned 16 bit integer.
pub fn read_u16le(data : Bytes, offset : Int32) -> Int32 raise AudioError {
require_bytes(data, offset, 2, "u16le")
data.at(offset).to_int() | (data.at(offset + 1).to_int() << 8)
}
///|
/// Read a big endian unsigned 16 bit integer.
pub fn read_u16be(data : Bytes, offset : Int32) -> Int32 raise AudioError {
require_bytes(data, offset, 2, "u16be")
(data.at(offset).to_int() << 8) | data.at(offset + 1).to_int()
}
///|
/// Read a little endian unsigned 24 bit integer.
pub fn read_u24le(data : Bytes, offset : Int32) -> Int32 raise AudioError {
require_bytes(data, offset, 3, "u24le")
data.at(offset).to_int() |
(data.at(offset + 1).to_int() << 8) |
(data.at(offset + 2).to_int() << 16)
}
///|
/// Read a big endian unsigned 24 bit integer.
pub fn read_u24be(data : Bytes, offset : Int32) -> Int32 raise AudioError {
require_bytes(data, offset, 3, "u24be")
(data.at(offset).to_int() << 16) |
(data.at(offset + 1).to_int() << 8) |
data.at(offset + 2).to_int()
}
///|
/// Read a little endian unsigned 32 bit integer, widened to `Int64` so the
/// value stays positive.
pub fn read_u32le(data : Bytes, offset : Int32) -> Int64 raise AudioError {
require_bytes(data, offset, 4, "u32le")
data.at(offset).to_int64() |
(data.at(offset + 1).to_int64() << 8) |
(data.at(offset + 2).to_int64() << 16) |
(data.at(offset + 3).to_int64() << 24)
}
///|
/// Read a big endian unsigned 32 bit integer, widened to `Int64`.
pub fn read_u32be(data : Bytes, offset : Int32) -> Int64 raise AudioError {
require_bytes(data, offset, 4, "u32be")
(data.at(offset).to_int64() << 24) |
(data.at(offset + 1).to_int64() << 16) |
(data.at(offset + 2).to_int64() << 8) |
data.at(offset + 3).to_int64()
}
///|
/// Read a little endian signed 16 bit integer, sign extended to `Int32`.
pub fn read_i16le(data : Bytes, offset : Int32) -> Int32 raise AudioError {
sign_extend(read_u16le(data, offset), 16)
}
///|
/// Read a big endian signed 16 bit integer, sign extended to `Int32`.
pub fn read_i16be(data : Bytes, offset : Int32) -> Int32 raise AudioError {
sign_extend(read_u16be(data, offset), 16)
}
///|
/// Read a little endian signed 24 bit integer, sign extended to `Int32`.
pub fn read_i24le(data : Bytes, offset : Int32) -> Int32 raise AudioError {
sign_extend(read_u24le(data, offset), 24)
}
///|
/// Read a big endian signed 24 bit integer, sign extended to `Int32`.
pub fn read_i24be(data : Bytes, offset : Int32) -> Int32 raise AudioError {
sign_extend(read_u24be(data, offset), 24)
}
///|
/// Read a little endian signed 32 bit integer.
pub fn read_i32le(data : Bytes, offset : Int32) -> Int32 raise AudioError {
require_bytes(data, offset, 4, "i32le")
data.at(offset).to_int() |
(data.at(offset + 1).to_int() << 8) |
(data.at(offset + 2).to_int() << 16) |
(data.at(offset + 3).to_int() << 24)
}
///|
/// Read a big endian signed 32 bit integer.
pub fn read_i32be(data : Bytes, offset : Int32) -> Int32 raise AudioError {
require_bytes(data, offset, 4, "i32be")
(data.at(offset).to_int() << 24) |
(data.at(offset + 1).to_int() << 16) |
(data.at(offset + 2).to_int() << 8) |
data.at(offset + 3).to_int()
}
///|
/// Read a little endian IEEE 754 single precision sample.
pub fn read_f32le(data : Bytes, offset : Int32) -> Double raise AudioError {
f32_bits_to_double(read_u32le(data, offset))
}
///|
/// Read a big endian IEEE 754 single precision sample.
pub fn read_f32be(data : Bytes, offset : Int32) -> Double raise AudioError {
f32_bits_to_double(read_u32be(data, offset))
}
///|
/// Read a little endian IEEE 754 double precision sample.
pub fn read_f64le(data : Bytes, offset : Int32) -> Double raise AudioError {
require_bytes(data, offset, 8, "f64le")
let mut bits = 0L
for index = 7; index >= 0; index = index - 1 {
bits = (bits << 8) | data.at(offset + index).to_int64()
}
bits.reinterpret_as_double()
}
///|
/// Read a big endian IEEE 754 double precision sample.
pub fn read_f64be(data : Bytes, offset : Int32) -> Double raise AudioError {
require_bytes(data, offset, 8, "f64be")
let mut bits = 0L
for index = 0; index < 8; index = index + 1 {
bits = (bits << 8) | data.at(offset + index).to_int64()
}
bits.reinterpret_as_double()
}
///|
/// Read `length` bytes as an ASCII string.
pub fn read_ascii(
data : Bytes,
offset : Int32,
length : Int32,
) -> String raise AudioError {
require_bytes(data, offset, length, "ascii")
@utf8.decode_lossy(data.exact_view(start=offset, end=offset + length))
}
///|
/// `true` when `data[offset .. offset + length]` equals `expected`.
pub fn ascii_is(
data : Bytes,
offset : Int32,
length : Int32,
expected : String,
) -> Bool {
if offset < 0 || length < 0 || offset + length > data.length() {
return false
}
if length != expected.length() {
return false
}
for index = 0; index < length; index = index + 1 {
if data.at(offset + index).to_int() != expected.at(index).to_int() {
return false
}
}
true
}
///|
/// Interpret `length` bytes starting at `offset` as ASCII, or `None` when the
/// range leaves the buffer.
pub fn ascii_at(data : Bytes, offset : Int32, length : Int32) -> String? {
if offset < 0 || length < 0 || offset + length > data.length() {
return None
}
Some(@utf8.decode_lossy(data.exact_view(start=offset, end=offset + length)))
}
///|
/// Little endian 16 bit pair of bytes, handy for chunk headers.
pub fn u16le_bytes(value : Int32) -> Bytes {
let buffer = @buffer.Buffer::Buffer(size_hint=2)
buffer.write_byte((value & 0xFF).to_byte())
buffer.write_byte(((value >> 8) & 0xFF).to_byte())
buffer.to_bytes()
}