Skip to content

[Audit][High] Mixer.mix() assumes mono S16 audio without validation #731

Description

@MichaelFisher1997

🔍 Module Scanned

modules/engine-audio/src/backends/sdl_audio.zig (automated audit scan)

📝 Summary

The Mixer.mix() function assumes all SoundData buffers contain mono S16 audio data, but SoundData stores a format: AudioFormat field and channels: u8 field that are never checked. Stereo sounds are mixed as mono (reading only left channel, writing to both outputs), and float32/unsigned8 sounds are incorrectly interpreted as S16.

📍 Location

File: modules/engine-audio/src/backends/sdl_audio.zig
Function/Scope: Mixer.mix() lines 249-296

🔴 Severity: High

High: Incorrect audio rendering (wrong stereo balance, volume, or garbled audio)

💥 Impact

If a sound with format .float32 or .unsigned8 is loaded:
The mixer treats the raw bytes as S16, interpreting float bits as sample values.
Result is extremely loud, distorted, or silent audio.

For stereo sounds with channels: 2:
The mixer reads only the left channel samples.
Each mono sample is written to BOTH left and right output channels.
Stereo imaging is destroyed.

🔎 Evidence

SoundData stores format and channel info but mixer ignores them:

pub const SoundData = struct {
buffer: []u8,
frequency: u32,
channels: u8, // Stored but never checked
format: AudioFormat, // Stored but never checked
length_samples: u32,
};

Mixer.mix() blindly assumes S16 mono:
// TODO: Handle other formats
const lo = u8_buf[valid_pos_idx * 2];
const hi = u8_buf[valid_pos_idx * 2 + 1];
const sample: i16 = std.mem.readInt(i16, &[2]u8{ lo, hi }, .little);

// Writes same mono sample to BOTH stereo channels
mix_buf[i * 2] += ...sample... * voice.effective_volume_l;
mix_buf[i * 2 + 1] += ...sample... * voice.effective_volume_r;

🛠️ Proposed Fix

  1. Add format validation in Mixer.play() to reject non-mono-S16 sounds
  2. OR implement proper format conversion in mix() for float32/unsigned8
  3. For stereo sounds, read interleaved L/R pairs separately

✅ Acceptance Criteria

SoundManager.createTestSound() continues to work (mono S16)
Stereo sounds play with correct left/right channel separation
Float32 audio is properly converted to S16 for mixing
zig build test passes

📚 References

Related issue: #683 (Mixer pointer lifetime)
Related issue: #718 (Cursor reset bounds)

Metadata

Metadata

Assignees

No one assigned

    Labels

    automated-auditIssues found by automated opencode audit scansbugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions