///|
pub(all) enum AudioLayout {
Interleaved
Deinterleaved
} derive(Debug, Eq)
///|
pub struct AudioBuffer {
samples : Array[Int16]
channels : Int
layout : AudioLayout
} derive(Debug, Eq)
///|
pub fn AudioBuffer::new(
samples~ : Array[Int16],
channels~ : Int,
layout? : AudioLayout = Interleaved,
) -> AudioBuffer raise MediaError {
if channels <= 0 {
raise InvalidMedia("audio channel count must be positive")
}
if samples.length() % channels != 0 {
raise InvalidMedia(
"audio sample count is not divisible by the channel count",
)
}
{ samples: samples.copy(), channels, layout, }
}
///|
pub fn AudioBuffer::channels(self : AudioBuffer) -> Int {
self.channels
}
///|
pub fn AudioBuffer::frames(self : AudioBuffer) -> Int {
self.samples.length() / self.channels
}
///|
pub fn AudioBuffer::sample_count(self : AudioBuffer) -> Int {
self.samples.length()
}
///|
pub fn AudioBuffer::layout(self : AudioBuffer) -> AudioLayout {
self.layout
}
///|
pub fn AudioBuffer::samples(self : AudioBuffer) -> Array[Int16] {
self.samples.copy()
}
///|
fn AudioBuffer::sample_index(
self : AudioBuffer,
channel : Int,
frame : Int,
) -> Int raise MediaError {
if channel < 0 || channel >= self.channels {
raise InvalidMedia("audio channel index is out of range")
}
if frame < 0 || frame >= self.frames() {
raise InvalidMedia("audio frame index is out of range")
}
match self.layout {
Interleaved => frame * self.channels + channel
Deinterleaved => channel * self.frames() + frame
}
}
///|
fn AudioBuffer::sample_unchecked(
self : AudioBuffer,
channel : Int,
frame : Int,
) -> Int16 {
let index = match self.layout {
Interleaved => frame * self.channels + channel
Deinterleaved => channel * self.frames() + frame
}
self.samples[index]
}
///|
pub fn AudioBuffer::sample(
self : AudioBuffer,
channel : Int,
frame : Int,
) -> Int16 raise MediaError {
ignore(self.sample_index(channel, frame))
self.sample_unchecked(channel, frame)
}
///|
pub fn AudioBuffer::with_layout(
self : AudioBuffer,
layout : AudioLayout,
) -> AudioBuffer {
if layout == self.layout {
return self
}
let converted : Array[Int16] = []
match layout {
Interleaved =>
for frame = 0; frame < self.frames(); frame = frame + 1 {
for channel = 0; channel < self.channels; channel = channel + 1 {
converted.push(self.sample_unchecked(channel, frame))
}
}
Deinterleaved =>
for channel = 0; channel < self.channels; channel = channel + 1 {
for frame = 0; frame < self.frames(); frame = frame + 1 {
converted.push(self.sample_unchecked(channel, frame))
}
}
}
{ samples: converted, channels: self.channels, layout, }
}
///|
pub fn AudioBuffer::slice_frames(
self : AudioBuffer,
start : Int,
end : Int,
) -> AudioBuffer raise MediaError {
if start < 0 || end < start || end > self.frames() {
raise InvalidMedia("audio frame range is out of bounds")
}
let sliced : Array[Int16] = []
match self.layout {
Interleaved =>
for frame = start; frame < end; frame = frame + 1 {
for channel = 0; channel < self.channels; channel = channel + 1 {
sliced.push(self.sample(channel, frame))
}
}
Deinterleaved =>
for channel = 0; channel < self.channels; channel = channel + 1 {
for frame = start; frame < end; frame = frame + 1 {
sliced.push(self.sample(channel, frame))
}
}
}
AudioBuffer::new(samples=sliced, channels=self.channels, layout=self.layout)
}
///|
pub fn AudioBuffer::from_pcm_s16(
data : Bytes,
channels~ : Int,
little_endian? : Bool = true,
layout? : AudioLayout = Interleaved,
) -> AudioBuffer raise MediaError {
if data.length() % 2 != 0 {
raise InvalidMedia("signed 16-bit PCM contains a partial sample")
}
let samples : Array[Int16] = []
for offset = 0; offset < data.length(); offset = offset + 2 {
let bits = if little_endian {
data[offset].to_uint16() | (data[offset + 1].to_uint16() << 8)
} else {
(data[offset].to_uint16() << 8) | data[offset + 1].to_uint16()
}
samples.push(Int16::reinterpret_from_uint16(bits))
}
AudioBuffer::new(samples~, channels~, layout~)
}
///|
pub fn AudioBuffer::to_pcm_s16(
self : AudioBuffer,
little_endian? : Bool = true,
) -> Bytes {
let output : Array[Byte] = []
for sample in self.samples {
let bits = sample.reinterpret_as_uint16()
if little_endian {
output.push(bits.to_byte())
output.push((bits >> 8).to_byte())
} else {
output.push((bits >> 8).to_byte())
output.push(bits.to_byte())
}
}
Bytes::from_array(output)
}