PR003: implement audio status-first returns for play operations

This commit is contained in:
bQUARKz 2026-03-09 06:59:28 +00:00
parent fe6931d420
commit 998252aa25
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
6 changed files with 117 additions and 36 deletions

View File

@ -1,4 +1,4 @@
use prometeu_hal::AudioBridge;
use prometeu_hal::{AudioBridge, AudioOpStatus};
use std::sync::Arc;
/// Maximum number of simultaneous audio voices supported by the hardware.
@ -113,7 +113,7 @@ impl AudioBridge for Audio {
pitch: f64,
priority: u8,
loop_mode: prometeu_hal::LoopMode,
) {
) -> AudioOpStatus {
let lm = match loop_mode {
prometeu_hal::LoopMode::Off => LoopMode::Off,
prometeu_hal::LoopMode::On => LoopMode::On,
@ -129,7 +129,7 @@ impl AudioBridge for Audio {
pitch: f64,
priority: u8,
loop_mode: prometeu_hal::LoopMode,
) {
) -> AudioOpStatus {
let lm = match loop_mode {
prometeu_hal::LoopMode::Off => LoopMode::Off,
prometeu_hal::LoopMode::On => LoopMode::On,
@ -177,9 +177,9 @@ impl Audio {
pitch: f64,
priority: u8,
loop_mode: LoopMode,
) {
) -> AudioOpStatus {
if voice_id >= MAX_CHANNELS {
return;
return AudioOpStatus::VoiceInvalid;
}
// Resolve the sample from the hardware pools
@ -193,14 +193,10 @@ impl Audio {
// "[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.",
// bank_id, sample_id, voice_id
// );
self.play_sample(s, voice_id, volume, pan, pitch, priority, loop_mode);
self.play_sample(s, voice_id, volume, pan, pitch, priority, loop_mode)
} else {
AudioOpStatus::SampleNotFound
}
// else {
// eprintln!(
// "[Audio] Failed to resolve sample from bank {} sample {}.",
// bank_id, sample_id
// );
// }
}
#[allow(clippy::too_many_arguments)]
@ -213,9 +209,9 @@ impl Audio {
pitch: f64,
priority: u8,
loop_mode: LoopMode,
) {
) -> AudioOpStatus {
if voice_id >= MAX_CHANNELS {
return;
return AudioOpStatus::VoiceInvalid;
}
// Update local state
@ -240,6 +236,7 @@ impl Audio {
priority,
loop_mode,
});
AudioOpStatus::Ok
}
pub fn stop(&mut self, voice_id: usize) {

View File

@ -7,6 +7,17 @@ pub enum LoopMode {
On,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum AudioOpStatus {
Ok = 0,
VoiceInvalid = 1,
SampleNotFound = 2,
ArgRangeInvalid = 3,
AssetNotFound = 4,
NoEffect = 5,
}
pub trait AudioBridge {
#[allow(clippy::too_many_arguments)]
fn play(
@ -19,7 +30,7 @@ pub trait AudioBridge {
pitch: f64,
priority: u8,
loop_mode: LoopMode,
);
) -> AudioOpStatus;
#[allow(clippy::too_many_arguments)]
fn play_sample(
&mut self,
@ -30,7 +41,7 @@ pub trait AudioBridge {
pitch: f64,
priority: u8,
loop_mode: LoopMode,
);
) -> AudioOpStatus;
fn stop(&mut self, voice_id: usize);
fn set_volume(&mut self, voice_id: usize, volume: u8);
fn set_pan(&mut self, voice_id: usize, pan: u8);

View File

@ -28,7 +28,7 @@ pub mod vm_fault;
pub mod window;
pub use asset_bridge::AssetBridge;
pub use audio_bridge::{AudioBridge, LoopMode};
pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode};
pub use gfx_bridge::{BlendMode, GfxBridge};
pub use hardware_bridge::HardwareBridge;
pub use host_context::{HostContext, HostContextProvider};

View File

@ -8,7 +8,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
"play_sample",
1,
5,
0,
1,
caps::AUDIO,
Determinism::Deterministic,
false,
@ -20,7 +20,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
"play",
1,
7,
0,
1,
caps::AUDIO,
Determinism::Deterministic,
false,

View File

@ -7,7 +7,9 @@ use prometeu_hal::sprite::Sprite;
use prometeu_hal::syscalls::Syscall;
use prometeu_hal::tile::Tile;
use prometeu_hal::vm_fault::VmFault;
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int};
use prometeu_hal::{
AudioOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int,
};
impl VirtualMachineRuntime {
const GFX_STATUS_OK: i64 = 0;
@ -201,22 +203,36 @@ impl NativeInterface for VirtualMachineRuntime {
Ok(())
}
Syscall::AudioPlaySample => {
let sample_id = expect_int(args, 0)? as u32;
let voice_id = expect_int(args, 1)? as usize;
let volume = expect_int(args, 2)? as u8;
let pan = expect_int(args, 3)? as u8;
let sample_id_raw = expect_int(args, 0)?;
let voice_id_raw = expect_int(args, 1)?;
let volume_raw = expect_int(args, 2)?;
let pan_raw = expect_int(args, 3)?;
let pitch = expect_number(args, 4, "pitch")?;
hw.audio_mut().play(
if sample_id_raw < 0
|| sample_id_raw > u16::MAX as i64
|| voice_id_raw < 0
|| voice_id_raw >= 16
|| !(0..=255).contains(&volume_raw)
|| !(0..=255).contains(&pan_raw)
|| !pitch.is_finite()
|| pitch <= 0.0
{
ret.push_int(AudioOpStatus::ArgRangeInvalid as i64);
return Ok(());
}
let status = hw.audio_mut().play(
0,
sample_id as u16,
voice_id,
volume,
pan,
sample_id_raw as u16,
voice_id_raw as usize,
volume_raw as u8,
pan_raw as u8,
pitch,
0,
prometeu_hal::LoopMode::Off,
);
ret.push_int(status as i64);
Ok(())
}
Syscall::AudioPlay => {
@ -227,19 +243,44 @@ impl NativeInterface for VirtualMachineRuntime {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())),
};
let sample_id = expect_int(args, 1)? as u16;
let voice_id = expect_int(args, 2)? as usize;
let volume = expect_int(args, 3)? as u8;
let pan = expect_int(args, 4)? as u8;
let sample_id_raw = expect_int(args, 1)?;
let voice_id_raw = expect_int(args, 2)?;
let volume_raw = expect_int(args, 3)?;
let pan_raw = expect_int(args, 4)?;
let pitch = expect_number(args, 5, "pitch")?;
let loop_mode = match expect_int(args, 6)? {
0 => prometeu_hal::LoopMode::Off,
_ => prometeu_hal::LoopMode::On,
};
let bank_id =
hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS).unwrap_or(0);
hw.audio_mut().play(bank_id, sample_id, voice_id, volume, pan, pitch, 0, loop_mode);
if sample_id_raw < 0
|| sample_id_raw > u16::MAX as i64
|| voice_id_raw < 0
|| voice_id_raw >= 16
|| !(0..=255).contains(&volume_raw)
|| !(0..=255).contains(&pan_raw)
|| !pitch.is_finite()
|| pitch <= 0.0
{
ret.push_int(AudioOpStatus::ArgRangeInvalid as i64);
return Ok(());
}
let Some(bank_id) = hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS) else {
ret.push_int(AudioOpStatus::AssetNotFound as i64);
return Ok(());
};
let status = hw.audio_mut().play(
bank_id,
sample_id_raw as u16,
voice_id_raw as usize,
volume_raw as u8,
pan_raw as u8,
pitch,
0,
loop_mode,
);
ret.push_int(status as i64);
Ok(())
}
Syscall::FsOpen => {

View File

@ -174,3 +174,35 @@ voices_active: 9
mix_cycles: 410
audio_commands: 6
```
## 11 Syscall Return and Fault Policy
`audio` follows status-first policy for operations with operational failure modes.
Fault boundary:
- `Trap`: structural ABI misuse (type/arity/capability/shape mismatch);
- `status`: operational failure;
- `Panic`: internal invariant break only.
### 11.1 MVP return shape
In the current MVP:
- `audio.play` returns `status:int`;
- `audio.play_sample` returns `status:int`.
### 11.2 Minimum status table for `play`/`play_sample`
- `0` = `OK`
- `1` = `VOICE_INVALID`
- `2` = `SAMPLE_NOT_FOUND`
- `3` = `ARG_RANGE_INVALID`
- `4` = `ASSET_NOT_FOUND`
- `5` = `NO_EFFECT`
Operational rules:
- no fallback to default bank when an asset cannot be resolved;
- no silent no-op for invalid `voice_id`;
- invalid numeric ranges (e.g. `volume`, `pan`, `pitch`) must return explicit status.