"""Audio formatting utilities."""
import base64
import io
import numpy as np
def float_to_pcm16(audio: np.ndarray) -> bytes:
"""Convert float32 audio in [-1, 1] to little-endian 16-bit PCM bytes."""
clipped = np.clip(audio, -1.0, 1.0)
pcm = (clipped * 32767).astype(np.int16)
return pcm.tobytes()
def pcm16_to_base64(pcm_bytes: bytes) -> str:
return base64.b64encode(pcm_bytes).decode("ascii")
def float_to_wav_bytes(audio: np.ndarray, sample_rate: int) -> bytes:
"""Wrap float audio into a WAV file in memory."""
pcm = float_to_pcm16(audio)
header = _build_wav_header(len(pcm), sample_rate, 1, 16)
return header + pcm
def _build_wav_header(data_size: int, sample_rate: int, channels: int, bits_per_sample: int) -> bytes:
byte_rate = sample_rate * channels * bits_per_sample // 8
block_align = channels * bits_per_sample // 8
header = b"RIFF"
header += (36 + data_size).to_bytes(4, "little")
header += b"WAVE"
header += b"fmt "
header += (16).to_bytes(4, "little") # subchunk size
header += (1).to_bytes(2, "little") # PCM
header += channels.to_bytes(2, "little")
header += sample_rate.to_bytes(4, "little")
header += byte_rate.to_bytes(4, "little")
header += block_align.to_bytes(2, "little")
header += bits_per_sample.to_bytes(2, "little")
header += b"data"
header += data_size.to_bytes(4, "little")
return header