PR003: implement audio status-first returns for play operations
This commit is contained in:
parent
fe6931d420
commit
998252aa25
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user