// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Sample traits and 24-bit integer sample newtypes (subset).
///
/// Upstream `cpal` re-exports these from `dasp_sample` via `cpal/src/samples_formats.rs`.
/// For MoonBit, we implement a small, deterministic subset that is sufficient for:
/// - expressing sample format at the type level (`SizedSample`)
/// - filling silence (`Sample::equilibrium`)
/// - basic normalized conversions (`to_f32` / `from_f32`)
///|
pub struct I24 {
raw : Int
} derive(Debug, Eq)
///|
pub struct U24 {
raw : Int
} derive(Debug, Eq)
///|
let i24_min : Int = -8_388_608
///|
let i24_max : Int = 8_388_607
///|
let u24_min : Int = 0
///|
let u24_max : Int = 16_777_215
///|
let u24_mid : Int = 8_388_608
///|
pub fn I24::new(raw : Int) -> I24 {
I24(raw)
}
///|
pub fn I24::I24(raw : Int) -> I24 {
{ raw: Int::clamp(raw, min=i24_min, max=i24_max) }
}
///|
pub fn I24::to_int(self : I24) -> Int {
self.raw
}
///|
pub fn U24::new(raw : Int) -> U24 {
U24(raw)
}
///|
pub fn U24::U24(raw : Int) -> U24 {
{ raw: Int::clamp(raw, min=u24_min, max=u24_max) }
}
///|
pub fn U24::to_int(self : U24) -> Int {
self.raw
}
///|
pub trait Sample {
/// A "silent" sample value.
fn equilibrium() -> Self
/// Convert to the normalized `[-1.0, 1.0]` range.
fn to_f32(Self) -> Float
/// Convert from the normalized `[-1.0, 1.0]` range (clamped and quantized).
fn from_f32(Float) -> Self
}
///|
pub trait SizedSample: Sample {
/// The corresponding `SampleFormat` for this sample type.
fn format(Self) -> SampleFormat
}
///|
pub trait FromSample: Sample {
/// Convert from a normalized `[-1.0, 1.0]` float sample.
///
/// Note: Upstream `cpal` uses `dasp_sample::FromSample`, which is a generic conversion
/// trait between arbitrary sample types. MoonBit does not currently support polymorphic
/// trait methods, so we provide:
/// - `FromSample::from_sample(Float)` for constructing a sample type from normalized float
/// - `sample_cast` for generic conversion between `Sample` types via `Float`
fn from_sample(Float) -> Self
}
///|
fn clamp_unit(x : Float) -> Float {
if x < -1.0 {
-1.0
} else if x > 1.0 {
1.0
} else {
x
}
}
///|
fn round_to_int(x : Float) -> Int {
x.round().to_int()
}
///|
pub fn[A : Sample, B : Sample] sample_cast(x : A) -> B {
Sample::from_f32(x.to_f32())
}
///|
pub impl Sample for Float with fn equilibrium() {
0.0
}
///|
pub impl Sample for Float with fn to_f32(self : Float) -> Float {
self
}
///|
pub impl Sample for Float with fn from_f32(x : Float) -> Float {
clamp_unit(x)
}
///|
pub impl SizedSample for Float with fn format(_self : Float) -> SampleFormat {
F32
}
///|
pub impl FromSample for Float with fn from_sample(x : Float) -> Float {
Sample::from_f32(x)
}
///|
pub impl Sample for Double with fn equilibrium() {
0.0
}
///|
pub impl Sample for Double with fn to_f32(self : Double) -> Float {
Float::from_double(self)
}
///|
pub impl Sample for Double with fn from_f32(x : Float) -> Double {
clamp_unit(x).to_double()
}
///|
pub impl SizedSample for Double with fn format(_self : Double) -> SampleFormat {
F64
}
///|
pub impl FromSample for Double with fn from_sample(x : Float) -> Double {
Sample::from_f32(x)
}
///|
pub impl Sample for Int16 with fn equilibrium() {
Int16::from_int(0)
}
///|
pub impl Sample for Int16 with fn to_f32(self : Int16) -> Float {
Float::from_int(self.to_int()) / Float::from_int(32_768)
}
///|
pub impl Sample for Int16 with fn from_f32(x : Float) -> Int16 {
let x = clamp_unit(x)
let scaled = round_to_int(x * Float::from_int(32_768))
let clamped = Int::clamp(scaled, min=-32_768, max=32_767)
Int16::from_int(clamped)
}
///|
pub impl SizedSample for Int16 with fn format(_self : Int16) -> SampleFormat {
I16
}
///|
pub impl FromSample for Int16 with fn from_sample(x : Float) -> Int16 {
Sample::from_f32(x)
}
///|
pub impl Sample for UInt16 with fn equilibrium() {
(32_768).to_uint16()
}
///|
pub impl Sample for UInt16 with fn to_f32(self : UInt16) -> Float {
let v = self.to_int() - 32_768
Float::from_int(v) / Float::from_int(32_768)
}
///|
pub impl Sample for UInt16 with fn from_f32(x : Float) -> UInt16 {
let x = clamp_unit(x)
let scaled = round_to_int(x * Float::from_int(32_768)) + 32_768
let clamped = Int::clamp(scaled, min=0, max=65_535)
clamped.to_uint16()
}
///|
pub impl SizedSample for UInt16 with fn format(_self : UInt16) -> SampleFormat {
U16
}
///|
pub impl FromSample for UInt16 with fn from_sample(x : Float) -> UInt16 {
Sample::from_f32(x)
}
///|
pub impl Sample for Byte with fn equilibrium() {
(128).to_byte()
}
///|
pub impl Sample for Byte with fn to_f32(self : Byte) -> Float {
let v = self.to_int() - 128
Float::from_int(v) / Float::from_int(128)
}
///|
pub impl Sample for Byte with fn from_f32(x : Float) -> Byte {
let x = clamp_unit(x)
let scaled = round_to_int(x * Float::from_int(128)) + 128
let clamped = Int::clamp(scaled, min=0, max=255)
clamped.to_byte()
}
///|
pub impl SizedSample for Byte with fn format(_self : Byte) -> SampleFormat {
U8
}
///|
pub impl FromSample for Byte with fn from_sample(x : Float) -> Byte {
Sample::from_f32(x)
}
///|
pub impl Sample for I24 with fn equilibrium() {
I24(0)
}
///|
pub impl Sample for I24 with fn to_f32(self : I24) -> Float {
Float::from_int(self.raw) / Float::from_int(8_388_608)
}
///|
pub impl Sample for I24 with fn from_f32(x : Float) -> I24 {
let x = clamp_unit(x)
let scaled = round_to_int(x * Float::from_int(8_388_608))
I24(scaled)
}
///|
pub impl SizedSample for I24 with fn format(_self : I24) -> SampleFormat {
I24
}
///|
pub impl FromSample for I24 with fn from_sample(x : Float) -> I24 {
Sample::from_f32(x)
}
///|
pub impl Sample for U24 with fn equilibrium() {
U24(u24_mid)
}
///|
pub impl Sample for U24 with fn to_f32(self : U24) -> Float {
Float::from_int(self.raw - u24_mid) / Float::from_int(u24_mid)
}
///|
pub impl Sample for U24 with fn from_f32(x : Float) -> U24 {
let x = clamp_unit(x)
let scaled = round_to_int(x * Float::from_int(u24_mid)) + u24_mid
U24(scaled)
}
///|
pub impl SizedSample for U24 with fn format(_self : U24) -> SampleFormat {
U24
}
///|
pub impl FromSample for U24 with fn from_sample(x : Float) -> U24 {
Sample::from_f32(x)
}