///|
/// Bounded push/pull encoder. Input is interleaved signed scalar samples.
/// Internal PCM never exceeds block_size * channels. Drain next after push;
/// an accepted count of zero means the pending output/block must be drained.
struct Encoder {
config : EncoderConfig
pcm : Array[Int32]
digest : Md5
plan_scratch : EncoderPlanScratch
seek_points : Array[SeekPoint]
mut seek_cursor : Int32
mut frame_offset : Int64
mut queued : Bytes?
mut ended : Bool
mut failed : FlacError?
mut summary : EncoderSummary?
mut samples : Int64
mut frames : Int64
mut min_frame : Int32
mut max_frame : Int32
mut peak_pcm : Int32
}
///|
fn validate_encoder_config(config : EncoderConfig) -> Unit raise FlacError {
let f = config.format
if f.channels < 1 ||
f.channels > 8 ||
f.bits_per_sample < 4 ||
f.bits_per_sample > 32 {
raise Invalid(0L, "encoder requires 1-8 channels and 4-32-bit PCM")
}
if f.sample_rate < 0 ||
f.sample_rate > 1048575 ||
(f.sample_rate == 0 && !config.non_audio) {
raise Invalid(0L, "invalid encoder rate; zero requires explicit non_audio")
}
if config.block_size < 16 || config.block_size > 65535 {
raise Invalid(0L, "encoder block size must be 16-65535")
}
if config.effort < 0 || config.effort > 8 {
raise Invalid(0L, "encoder effort must be 0-8")
}
if config.max_metadata_bytes < 42 {
raise Limit("encoder metadata limit is below STREAMINFO size")
}
}
///|
pub fn Encoder::new(config : EncoderConfig) -> Encoder raise FlacError {
validate_encoder_config(config)
let config = { ..config, metadata: config.metadata[:].to_owned(), }
let header = encoder_header(config, 0L, Bytes::make(16, 0), 0, 0)
let seek_points = if config.metadata.any(block => block.kind == 3) {
let (metadata, _) = parse_metadata(
header,
max_metadata_bytes=config.max_metadata_bytes,
)
metadata.seek_points
} else {
[]
}
{
config,
pcm: [],
digest: Md5::new(),
plan_scratch: EncoderPlanScratch::new(),
seek_points,
seek_cursor: 0,
frame_offset: 0L,
queued: Some(header),
ended: false,
failed: None,
summary: None,
samples: 0L,
frames: 0L,
min_frame: 0,
max_frame: 0,
peak_pcm: 0,
}
}
///|
/// Accepted scalar count; each accepted sample is copied and range checked.
/// Partial interchannel groups may span pushes, but must be complete at finish.
pub fn Encoder::push(
self : Encoder,
samples : Array[Int32],
) -> Int32 raise FlacError {
self.push_view(samples[:])
}
///|
/// Borrows only for this call; accepted scalars are checked before any copy.
/// Whole-buffer encoding uses this path to avoid a temporary owned slice.
fn Encoder::push_view(
self : Encoder,
samples : ArrayView[Int32],
) -> Int32 raise FlacError {
if self.failed is Some(error) {
raise error
}
if self.ended {
raise Invalid(self.samples, "PCM supplied after encoder finish")
}
if self.queued is Some(_) {
return 0
}
let capacity = self.config.block_size * self.config.format.channels
let room = capacity - self.pcm.length()
let count = if samples.length() < room { samples.length() } else { room }
let bound = 1L << (self.config.format.bits_per_sample - 1)
// Validate the accepted prefix before changing state, so a bad chunk cannot
// leave a partially accepted frame that might later be mistaken for success.
for i in 0..= bound {
let error = Invalid(self.samples, "PCM sample outside declared bit depth")
self.failed = Some(error)
raise error
}
}
self.pcm.append(samples[:count])
if self.pcm.length() > self.peak_pcm {
self.peak_pcm = self.pcm.length()
}
count
}
///|
/// Number of scalar PCM values retained, excluding caller-owned output.
pub fn Encoder::buffered_samples(self : Encoder) -> Int32 {
self.pcm.length()
}
///|
/// Peak retained scalar values; bounded by configured block_size * channels.
pub fn Encoder::peak_buffered_samples(self : Encoder) -> Int32 {
self.peak_pcm
}
///|
/// Declares PCM EOF. Empty streams and incomplete interchannel groups fail.
pub fn Encoder::finish(self : Encoder) -> Unit raise FlacError {
if self.failed is Some(error) {
raise error
}
if self.ended {
return
}
let error = if self.pcm.length() % self.config.format.channels != 0 {
Some(Invalid(self.samples, "incomplete interleaved PCM sample group"))
} else if self.samples == 0L && self.pcm.is_empty() {
Some(Invalid(0L, "cannot encode an empty FLAC stream"))
} else {
None
}
if error is Some(error) {
self.failed = Some(error)
raise error
}
self.ended = true
}
///|
/// Adaptive variable blocking observes a complete configured window. Strong
/// changes in roughness split it in half; the decision never depends on pushes.
fn Encoder::block_length(self : Encoder) -> Int32 {
let available = self.pcm.length() / self.config.format.channels
let maximum = self.config.block_size
if available < maximum {
return available
}
if !self.config.variable_block || maximum < 32 {
return maximum
}
let channels = self.config.format.channels
let half = maximum / 2
let mut early = 0L
let mut late = 0L
for i in 1.. (late + 1L) * 4L || late > (early + 1L) * 4L {
half
} else {
maximum
}
}
///|
/// Pull owned compressed bytes. Initial bytes contain unknown streaming
/// STREAMINFO fields; EncoderDone returns the same-length finalized header.
pub fn Encoder::next(self : Encoder) -> EncodeEvent raise FlacError {
if self.failed is Some(error) {
raise error
}
self.read_encoded() catch {
error => {
self.failed = Some(error)
raise error
}
}
}
///|
fn Encoder::read_encoded(self : Encoder) -> EncodeEvent raise FlacError {
if self.queued is Some(bytes) {
self.queued = None
return EncodedBytes(bytes)
}
if self.summary is Some(summary) {
return EncoderDone(summary)
}
let format = self.config.format
let full = self.pcm.length() >= self.config.block_size * format.channels
if !full && !self.ended {
return NeedPcm
}
if self.pcm.is_empty() {
if self.seek_cursor < self.seek_points.length() &&
self.seek_points[self.seek_cursor].sample != 0xffffffffffffffffUL {
raise Invalid(
self.samples,
"encoder SEEKTABLE point is outside encoded audio",
)
}
let md5 = self.digest.digest()
let final_header = encoder_header(
self.config,
self.samples,
md5,
self.min_frame,
self.max_frame,
)
let summary : EncoderSummary = {
total_samples: self.samples,
frames: self.frames,
min_frame_size: self.min_frame,
max_frame_size: self.max_frame,
md5,
final_header,
}
self.summary = Some(summary)
return EncoderDone(summary)
}
let count = self.block_length()
if self.samples > 68719476735L - count.to_int64() {
raise Limit("FLAC sample count exceeds 36 bits")
}
if !self.config.variable_block && self.frames >= 2147483648L {
raise Limit("FLAC frame number exceeds 31 bits")
}
let planes = Array::makei(format.channels, c => {
Array::makei(count, i => self.pcm[i * format.channels + c])
})
let number = if self.config.variable_block {
self.samples
} else {
self.frames
}
// new validates the format/effort/window, push_view atomically validates
// every stored scalar, and these owned planes have the same positive count.
let bytes = encode_frame_validated(
format,
planes,
number,
self.config.variable_block,
self.config.effort,
plan_scratch=self.plan_scratch,
)
if bytes.length() > 16777215 {
raise Limit("encoded frame exceeds STREAMINFO size field")
}
if self.seek_cursor < self.seek_points.length() {
let point = self.seek_points[self.seek_cursor]
if point.sample != 0xffffffffffffffffUL &&
point.sample < (self.samples + count.to_int64()).to_uint64() {
if point.sample != self.samples.to_uint64() ||
point.offset != self.frame_offset.to_uint64() ||
point.samples != count {
raise Invalid(
self.samples,
"encoder SEEKTABLE point does not match encoded frame",
)
}
self.seek_cursor += 1
}
}
md5_update_frame(self.digest, {
sample_number: self.samples,
number,
variable_block: self.config.variable_block,
sample_rate: format.sample_rate,
bits_per_sample: format.bits_per_sample,
channels: planes,
block_size: count,
byte_offset: 0L,
byte_length: bytes.length(),
})
self.samples += count.to_int64()
self.frames += 1L
self.frame_offset += bytes.length().to_int64()
if self.min_frame == 0 || bytes.length() < self.min_frame {
self.min_frame = bytes.length()
}
if bytes.length() > self.max_frame {
self.max_frame = bytes.length()
}
let consumed = count * format.channels
if consumed == self.pcm.length() {
// Fixed windows and the final partial window consume all retained PCM.
self.pcm.clear()
} else {
// Adaptive variable blocking may emit half a full window; preserve the
// remaining validated samples for the next push/next cycle.
let retained = self.pcm[consumed:].to_owned()
self.pcm.clear()
for sample in retained {
self.pcm.push(sample)
}
}
EncodedBytes(bytes)
}
///|
/// Whole-buffer convenience API. Interleaved PCM is not modified. The returned
/// native FLAC has final STREAMINFO counts, frame bounds and source PCM MD5.
pub fn encode_pcm(
samples : Array[Int32],
config : EncoderConfig,
) -> Bytes raise FlacError {
let encoder = Encoder::new(config)
let chunks : Array[Bytes] = []
let mut cursor = 0
let mut length = 0L
while true {
match encoder.next() {
EncodedBytes(bytes) => {
length += bytes.length().to_int64()
if length > 2147483647L {
raise Limit("whole encoded buffer exceeds maximum Bytes length")
}
chunks.push(bytes)
}
NeedPcm =>
if cursor == samples.length() {
encoder.finish()
} else {
let room = config.block_size * config.format.channels
let remaining = samples.length() - cursor
let count = if remaining < room { remaining } else { room }
cursor += encoder.push_view(samples[cursor:cursor + count])
}
EncoderDone(summary) => {
let out = @buffer.Buffer()
out.write_bytes(summary.final_header)
for i in 1..