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;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Maximum number of simultaneous audio voices supported by the hardware.
|
/// Maximum number of simultaneous audio voices supported by the hardware.
|
||||||
@ -113,7 +113,7 @@ impl AudioBridge for Audio {
|
|||||||
pitch: f64,
|
pitch: f64,
|
||||||
priority: u8,
|
priority: u8,
|
||||||
loop_mode: prometeu_hal::LoopMode,
|
loop_mode: prometeu_hal::LoopMode,
|
||||||
) {
|
) -> AudioOpStatus {
|
||||||
let lm = match loop_mode {
|
let lm = match loop_mode {
|
||||||
prometeu_hal::LoopMode::Off => LoopMode::Off,
|
prometeu_hal::LoopMode::Off => LoopMode::Off,
|
||||||
prometeu_hal::LoopMode::On => LoopMode::On,
|
prometeu_hal::LoopMode::On => LoopMode::On,
|
||||||
@ -129,7 +129,7 @@ impl AudioBridge for Audio {
|
|||||||
pitch: f64,
|
pitch: f64,
|
||||||
priority: u8,
|
priority: u8,
|
||||||
loop_mode: prometeu_hal::LoopMode,
|
loop_mode: prometeu_hal::LoopMode,
|
||||||
) {
|
) -> AudioOpStatus {
|
||||||
let lm = match loop_mode {
|
let lm = match loop_mode {
|
||||||
prometeu_hal::LoopMode::Off => LoopMode::Off,
|
prometeu_hal::LoopMode::Off => LoopMode::Off,
|
||||||
prometeu_hal::LoopMode::On => LoopMode::On,
|
prometeu_hal::LoopMode::On => LoopMode::On,
|
||||||
@ -177,9 +177,9 @@ impl Audio {
|
|||||||
pitch: f64,
|
pitch: f64,
|
||||||
priority: u8,
|
priority: u8,
|
||||||
loop_mode: LoopMode,
|
loop_mode: LoopMode,
|
||||||
) {
|
) -> AudioOpStatus {
|
||||||
if voice_id >= MAX_CHANNELS {
|
if voice_id >= MAX_CHANNELS {
|
||||||
return;
|
return AudioOpStatus::VoiceInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the sample from the hardware pools
|
// Resolve the sample from the hardware pools
|
||||||
@ -193,14 +193,10 @@ impl Audio {
|
|||||||
// "[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.",
|
// "[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.",
|
||||||
// bank_id, sample_id, voice_id
|
// 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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -213,9 +209,9 @@ impl Audio {
|
|||||||
pitch: f64,
|
pitch: f64,
|
||||||
priority: u8,
|
priority: u8,
|
||||||
loop_mode: LoopMode,
|
loop_mode: LoopMode,
|
||||||
) {
|
) -> AudioOpStatus {
|
||||||
if voice_id >= MAX_CHANNELS {
|
if voice_id >= MAX_CHANNELS {
|
||||||
return;
|
return AudioOpStatus::VoiceInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update local state
|
// Update local state
|
||||||
@ -240,6 +236,7 @@ impl Audio {
|
|||||||
priority,
|
priority,
|
||||||
loop_mode,
|
loop_mode,
|
||||||
});
|
});
|
||||||
|
AudioOpStatus::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self, voice_id: usize) {
|
pub fn stop(&mut self, voice_id: usize) {
|
||||||
|
|||||||
@ -7,6 +7,17 @@ pub enum LoopMode {
|
|||||||
On,
|
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 {
|
pub trait AudioBridge {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn play(
|
fn play(
|
||||||
@ -19,7 +30,7 @@ pub trait AudioBridge {
|
|||||||
pitch: f64,
|
pitch: f64,
|
||||||
priority: u8,
|
priority: u8,
|
||||||
loop_mode: LoopMode,
|
loop_mode: LoopMode,
|
||||||
);
|
) -> AudioOpStatus;
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn play_sample(
|
fn play_sample(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -30,7 +41,7 @@ pub trait AudioBridge {
|
|||||||
pitch: f64,
|
pitch: f64,
|
||||||
priority: u8,
|
priority: u8,
|
||||||
loop_mode: LoopMode,
|
loop_mode: LoopMode,
|
||||||
);
|
) -> AudioOpStatus;
|
||||||
fn stop(&mut self, voice_id: usize);
|
fn stop(&mut self, voice_id: usize);
|
||||||
fn set_volume(&mut self, voice_id: usize, volume: u8);
|
fn set_volume(&mut self, voice_id: usize, volume: u8);
|
||||||
fn set_pan(&mut self, voice_id: usize, pan: u8);
|
fn set_pan(&mut self, voice_id: usize, pan: u8);
|
||||||
|
|||||||
@ -28,7 +28,7 @@ pub mod vm_fault;
|
|||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
pub use asset_bridge::AssetBridge;
|
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 gfx_bridge::{BlendMode, GfxBridge};
|
||||||
pub use hardware_bridge::HardwareBridge;
|
pub use hardware_bridge::HardwareBridge;
|
||||||
pub use host_context::{HostContext, HostContextProvider};
|
pub use host_context::{HostContext, HostContextProvider};
|
||||||
|
|||||||
@ -8,7 +8,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
|
|||||||
"play_sample",
|
"play_sample",
|
||||||
1,
|
1,
|
||||||
5,
|
5,
|
||||||
0,
|
1,
|
||||||
caps::AUDIO,
|
caps::AUDIO,
|
||||||
Determinism::Deterministic,
|
Determinism::Deterministic,
|
||||||
false,
|
false,
|
||||||
@ -20,7 +20,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
|
|||||||
"play",
|
"play",
|
||||||
1,
|
1,
|
||||||
7,
|
7,
|
||||||
0,
|
1,
|
||||||
caps::AUDIO,
|
caps::AUDIO,
|
||||||
Determinism::Deterministic,
|
Determinism::Deterministic,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@ -7,7 +7,9 @@ use prometeu_hal::sprite::Sprite;
|
|||||||
use prometeu_hal::syscalls::Syscall;
|
use prometeu_hal::syscalls::Syscall;
|
||||||
use prometeu_hal::tile::Tile;
|
use prometeu_hal::tile::Tile;
|
||||||
use prometeu_hal::vm_fault::VmFault;
|
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 {
|
impl VirtualMachineRuntime {
|
||||||
const GFX_STATUS_OK: i64 = 0;
|
const GFX_STATUS_OK: i64 = 0;
|
||||||
@ -201,22 +203,36 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::AudioPlaySample => {
|
Syscall::AudioPlaySample => {
|
||||||
let sample_id = expect_int(args, 0)? as u32;
|
let sample_id_raw = expect_int(args, 0)?;
|
||||||
let voice_id = expect_int(args, 1)? as usize;
|
let voice_id_raw = expect_int(args, 1)?;
|
||||||
let volume = expect_int(args, 2)? as u8;
|
let volume_raw = expect_int(args, 2)?;
|
||||||
let pan = expect_int(args, 3)? as u8;
|
let pan_raw = expect_int(args, 3)?;
|
||||||
let pitch = expect_number(args, 4, "pitch")?;
|
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,
|
0,
|
||||||
sample_id as u16,
|
sample_id_raw as u16,
|
||||||
voice_id,
|
voice_id_raw as usize,
|
||||||
volume,
|
volume_raw as u8,
|
||||||
pan,
|
pan_raw as u8,
|
||||||
pitch,
|
pitch,
|
||||||
0,
|
0,
|
||||||
prometeu_hal::LoopMode::Off,
|
prometeu_hal::LoopMode::Off,
|
||||||
);
|
);
|
||||||
|
ret.push_int(status as i64);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::AudioPlay => {
|
Syscall::AudioPlay => {
|
||||||
@ -227,19 +243,44 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
Value::String(s) => s.clone(),
|
Value::String(s) => s.clone(),
|
||||||
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())),
|
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())),
|
||||||
};
|
};
|
||||||
let sample_id = expect_int(args, 1)? as u16;
|
let sample_id_raw = expect_int(args, 1)?;
|
||||||
let voice_id = expect_int(args, 2)? as usize;
|
let voice_id_raw = expect_int(args, 2)?;
|
||||||
let volume = expect_int(args, 3)? as u8;
|
let volume_raw = expect_int(args, 3)?;
|
||||||
let pan = expect_int(args, 4)? as u8;
|
let pan_raw = expect_int(args, 4)?;
|
||||||
let pitch = expect_number(args, 5, "pitch")?;
|
let pitch = expect_number(args, 5, "pitch")?;
|
||||||
let loop_mode = match expect_int(args, 6)? {
|
let loop_mode = match expect_int(args, 6)? {
|
||||||
0 => prometeu_hal::LoopMode::Off,
|
0 => prometeu_hal::LoopMode::Off,
|
||||||
_ => prometeu_hal::LoopMode::On,
|
_ => prometeu_hal::LoopMode::On,
|
||||||
};
|
};
|
||||||
|
|
||||||
let bank_id =
|
if sample_id_raw < 0
|
||||||
hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS).unwrap_or(0);
|
|| sample_id_raw > u16::MAX as i64
|
||||||
hw.audio_mut().play(bank_id, sample_id, voice_id, volume, pan, pitch, 0, loop_mode);
|
|| 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::FsOpen => {
|
Syscall::FsOpen => {
|
||||||
|
|||||||
@ -174,3 +174,35 @@ voices_active: 9
|
|||||||
mix_cycles: 410
|
mix_cycles: 410
|
||||||
audio_commands: 6
|
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