encodeBytes static method
Uint8List
encodeBytes(
- Float32List samples, {
- required int sampleRate,
- required int channels,
- int audioFormat = 1,
- int bitsPerSample = 16,
Encode normalized -1,1 float samples (interleaved) into a WAV byte buffer.
samples are interleaved: ch0, ch1, ..., chN, ch0, ch1, ...
channels must divide samples.length.
By default writes 16-bit PCM. You can choose:
- PCM: format=1, bitsPerSample in {8,16,24,32}
- Float: format=3, bitsPerSample must be 32
Implementation
static Uint8List encodeBytes(
Float32List samples, {
required int sampleRate,
required int channels,
int audioFormat = 1, // 1 PCM, 3 IEEE float
int bitsPerSample = 16,
}) {
if (channels <= 0) throw ArgumentError.value(channels, 'channels');
if (sampleRate <= 0) throw ArgumentError.value(sampleRate, 'sampleRate');
if (samples.length % channels != 0) {
throw ArgumentError(
'samples.length (${samples.length}) must be a multiple of channels ($channels)',
);
}
if (audioFormat == 1) {
if (bitsPerSample != 8 &&
bitsPerSample != 16 &&
bitsPerSample != 24 &&
bitsPerSample != 32) {
throw ArgumentError('PCM bitsPerSample must be 8/16/24/32');
}
} else if (audioFormat == 3) {
if (bitsPerSample != 32) {
throw ArgumentError('Float WAV requires bitsPerSample=32');
}
} else {
throw ArgumentError('audioFormat must be 1 (PCM) or 3 (float)');
}
final frames = samples.length ~/ channels;
final blockAlign = (channels * bitsPerSample) ~/ 8;
final byteRate = sampleRate * blockAlign;
// data chunk size in bytes
final dataSize = frames * blockAlign;
// Minimal fmt chunk is 16 bytes for PCM/float (no extensible)
const fmtChunkSize = 16;
// RIFF size = 4 ("WAVE") + (8 + fmt) + (8 + data)
final riffSize = 4 + (8 + fmtChunkSize) + (8 + dataSize);
// Total file bytes = 8 ("RIFF"+size) + riffSize
final totalSize = 8 + riffSize;
final out = BytesBuilder(copy: false);
void writeAscii4(String s) {
if (s.length != 4) throw ArgumentError('FourCC must be 4 chars');
out.add(Uint8List.fromList(s.codeUnits));
}
void writeU16LE(int v) {
final b = ByteData(2)..setUint16(0, v, Endian.little);
out.add(b.buffer.asUint8List());
}
void writeU32LE(int v) {
final b = ByteData(4)..setUint32(0, v, Endian.little);
out.add(b.buffer.asUint8List());
}
// ---- Header ----
writeAscii4('RIFF');
writeU32LE(riffSize);
writeAscii4('WAVE');
// ---- fmt chunk ----
writeAscii4('fmt ');
writeU32LE(fmtChunkSize);
writeU16LE(audioFormat);
writeU16LE(channels);
writeU32LE(sampleRate);
writeU32LE(byteRate);
writeU16LE(blockAlign);
writeU16LE(bitsPerSample);
// ---- data chunk ----
writeAscii4('data');
writeU32LE(dataSize);
// ---- Samples ----
// Clamp to [-1,1] before conversion
double clamp(double x) => x < -1.0 ? -1.0 : (x > 1.0 ? 1.0 : x);
if (audioFormat == 3) {
// 32-bit float
final b = ByteData(dataSize);
int o = 0;
for (int i = 0; i < samples.length; i++) {
final v = clamp(samples[i]).toDouble();
b.setFloat32(o, v, Endian.little);
o += 4;
}
out.add(b.buffer.asUint8List());
} else {
// PCM integer
switch (bitsPerSample) {
case 8:
// unsigned 8-bit: 128 is zero
final b = Uint8List(dataSize);
for (int i = 0; i < samples.length; i++) {
final v = clamp(samples[i]).toDouble();
final u = (v * 128.0 + 128.0).round();
b[i] = u.clamp(0, 255);
}
out.add(b);
break;
case 16:
final b = ByteData(dataSize);
int o = 0;
for (int i = 0; i < samples.length; i++) {
final v = clamp(samples[i]).toDouble();
// scale to [-32768, 32767]
int s = (v * 32768.0).round();
s = s.clamp(-32768, 32767);
b.setInt16(o, s, Endian.little);
o += 2;
}
out.add(b.buffer.asUint8List());
break;
case 24:
// 24-bit signed little-endian
final b = Uint8List(dataSize);
int o = 0;
for (int i = 0; i < samples.length; i++) {
final v = clamp(samples[i]).toDouble();
int s = (v * 8388608.0).round(); // 2^23
s = s.clamp(-8388608, 8388607);
// Two's complement in 24 bits
final u = s & 0xFFFFFF;
b[o++] = (u & 0xFF);
b[o++] = ((u >> 8) & 0xFF);
b[o++] = ((u >> 16) & 0xFF);
}
out.add(b);
break;
case 32:
final b = ByteData(dataSize);
int o = 0;
for (int i = 0; i < samples.length; i++) {
final v = clamp(samples[i]).toDouble();
int s = (v * 2147483648.0).round(); // 2^31
s = s.clamp(-2147483648, 2147483647);
b.setInt32(o, s, Endian.little);
o += 4;
}
out.add(b.buffer.asUint8List());
break;
}
}
// Chunks are already even-sized here (fmt=16, data depends on align),
// but RIFF requires word alignment *per chunk*. Since we wrote exact sizes,
// and blockAlign guarantees dataSize is multiple of (bits/8*channels),
// it can still be odd in PCM 8-bit mono (blockAlign=1). In that case, pad.
if (dataSize.isOdd) {
out.add(Uint8List(1)); // pad byte, NOT counted in chunk size
}
final bytes = out.toBytes();
// Sanity check: total size might be +1 if padded
if (bytes.length != totalSize && bytes.length != totalSize + 1) {
// Don’t throw; just keep it as debug help.
// ignore: avoid_print
print(
'WavWriter: size mismatch expected $totalSize(+pad) got ${bytes.length}',
);
}
return bytes;
}