Skip to content

MLX WAV serializer silently hard-clips out-of-range generated audio #78

Description

@brxs

Describe the bug

The optimized MLX CLI silently hard-clips decoded audio before writing its 16-bit WAV output:

audio = np.clip(audio, -1.0, 1.0)

save_wav() documents that its input is already within [-1, 1], but this range is not validated or guaranteed at the serialization boundary.

LoRA merging changes model weights and can alter the amplitude of the decoded waveform. If any adapter produces samples outside [-1, 1], the current serializer permanently flattens those samples without warning. This can result in severe audible clipping even at the default LoRA strength of 1.0.

The same risk applies to any other generation path capable of producing out-of-range decoded samples.

To reproduce

The serializer behavior can be reproduced independently of a particular model, prompt, or LoRA:

import numpy as np

audio = np.array([
    [0.0, 0.5, 1.0, 1.25, 1.75],
    [0.0, -0.5, -1.0, -1.25, -1.75],
], dtype=np.float32)

save_wav("out.wav", audio)

Inspecting the resulting PCM16 samples shows that every input magnitude above 1.0 is written at full scale. Their relative amplitudes are lost.

The generation path can be diagnosed by inspecting the waveform immediately before np.clip:

peak = float(np.max(np.abs(audio)))
outside_range = int(np.count_nonzero(np.abs(audio) > 1.0))
print(f"peak={peak}, samples outside PCM range={outside_range}")

LoRA-enabled generations have been observed producing significantly out-of-range decoded samples at strength 1.0, causing a substantial fraction of the resulting WAV to be pinned at full scale.

Actual behavior

All decoded samples outside [-1, 1] are silently clamped before conversion to PCM16. This permanently changes the waveform and can introduce audible distortion.

Expected behavior

The MLX CLI should not silently apply destructive hard clipping when decoded audio violates the expected serializer range.

Possible behaviors include:

  • detect out-of-range audio and emit a clear warning or error;
  • support 32-bit float WAV output;
  • expose an explicit peak-protection option; or
  • apply no-boost global attenuation when the decoded peak exceeds the selected PCM ceiling.

Unconditional normalization would be undesirable because it would also boost quiet generations.

It would also be useful to clarify whether [-1, 1] is intended as a strict decoder invariant. If it is, that invariant should be validated before writing the output rather than silently enforced through clipping.

Environment

  • Stable Audio 3 commit: 0385302ea26522f00c80392c4b708df5ebf1adf5
  • Platform: macOS arm64
  • Python: 3.11.14
  • MLX: 0.32.0
  • NumPy: 2.4.6
  • Output path: optimized MLX CLI
  • Output format: 44.1 kHz stereo PCM16 WAV

Additional context

Relevant implementation:

https://github.com/Stability-AI/stable-audio-3/blob/main/optimized/mlx/scripts/sa3_mlx.py

This report concerns the serialization policy rather than the behavior of one specific LoRA. Any generation producing an absolute decoded peak above 1.0 will be destructively clipped by the current implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions