// 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.
///|
/// Trampolines used to bridge native ALSA stream worker threads (C) to MoonBit closures.
///|
fn sample_format_from_tag(tag : UInt) -> @core.SampleFormat {
match tag {
1 => F32
2 => I16
3 => U16
4 => U8
5 => I32
6 => U32
7 => I24
8 => U24
9 => F64
10 => I8
_ => F32
}
}
///|
fn call_output_data_callback(
data_callback : (@core.Data, @core.OutputCallbackInfo) -> Unit,
sample_format_tag : UInt,
bytes : FixedArray[Byte],
callback_secs : Int64,
callback_nanos : Int,
playback_secs : Int64,
playback_nanos : Int,
) -> Unit {
let fmt = sample_format_from_tag(sample_format_tag)
let data = @core.Data(fmt, bytes)
let callback = @core.StreamInstant(callback_secs, callback_nanos)
let playback = @core.StreamInstant(playback_secs, playback_nanos)
let ts = @core.OutputStreamTimestamp(callback, playback)
let info = @core.OutputCallbackInfo(ts)
data_callback(data, info)
}
///|
let call_output_data_callback_fr : FuncRef[
(
(@core.Data, @core.OutputCallbackInfo) -> Unit,
UInt,
FixedArray[Byte],
Int64,
Int,
Int64,
Int,
) -> Unit,
] = fn(
data_callback,
sample_format_tag,
bytes,
callback_secs,
callback_nanos,
playback_secs,
playback_nanos,
) {
call_output_data_callback(
data_callback, sample_format_tag, bytes, callback_secs, callback_nanos, playback_secs,
playback_nanos,
)
}
///|
fn call_input_data_callback(
data_callback : (@core.Data, @core.InputCallbackInfo) -> Unit,
sample_format_tag : UInt,
bytes : FixedArray[Byte],
callback_secs : Int64,
callback_nanos : Int,
capture_secs : Int64,
capture_nanos : Int,
) -> Unit {
let fmt = sample_format_from_tag(sample_format_tag)
let data = @core.Data(fmt, bytes)
let callback = @core.StreamInstant(callback_secs, callback_nanos)
let capture = @core.StreamInstant(capture_secs, capture_nanos)
let ts = @core.InputStreamTimestamp(callback, capture)
let info = @core.InputCallbackInfo(ts)
data_callback(data, info)
}
///|
let call_input_data_callback_fr : FuncRef[
(
(@core.Data, @core.InputCallbackInfo) -> Unit,
UInt,
FixedArray[Byte],
Int64,
Int,
Int64,
Int,
) -> Unit,
] = fn(
data_callback,
sample_format_tag,
bytes,
callback_secs,
callback_nanos,
capture_secs,
capture_nanos,
) {
call_input_data_callback(
data_callback, sample_format_tag, bytes, callback_secs, callback_nanos, capture_secs,
capture_nanos,
)
}
///|
fn call_stream_error_callback(
error_callback : (@core.StreamError) -> Unit,
op_tag : Int,
status : Int,
) -> Unit {
let op = match op_tag {
1 => "snd_pcm_writei"
2 => "snd_pcm_readi"
3 => "snd_pcm_open"
4 => "snd_pcm_hw_params"
5 => "pthread_create"
6 => "xrun"
_ => "AlsaStream"
}
error_callback(alsa_stream_error_from_errno(status, op))
}
///|
fn alsa_stream_error_from_errno(status : Int, op : String) -> @core.StreamError {
// Best-effort errno mapping (Linux): match common CPAL error variants.
//
// Note: `status` is typically a negative errno value (e.g. -ENODEV, -EPIPE).
if status == -2 || status == -1 || status == -13 || status == -19 {
// ENOENT / EPERM / EACCES / ENODEV
@core.stream_error_device_not_available()
} else if status == -5 || status == -77 || status == -86 {
// EIO / EBADFD / ESTRPIPE
@core.stream_error_stream_invalidated()
} else if status == -32 {
// EPIPE (XRUN)
@core.stream_error_buffer_underrun()
} else {
@core.stream_error_backend_specific("\{op} (errno \{status})")
}
}
///|
let call_stream_error_callback_fr : FuncRef[
((@core.StreamError) -> Unit, Int, Int) -> Unit,
] = fn(error_callback, op_tag, status) {
call_stream_error_callback(error_callback, op_tag, status)
}