docs: document packages, exported API and non-obvious logic

This commit is contained in:
Mathis Maquenne
2026-08-06 13:04:10 +02:00
parent 5dfd832d0d
commit b1ad9b4904
41 changed files with 757 additions and 7 deletions
+51 -3
View File
@@ -1,3 +1,9 @@
// Package audio converts downloaded audio between formats.
//
// Deezer does not serve wav, so a wav download is really a flac download
// followed by FLACToWAV. The conversion is lossless in both directions: flac
// decodes to exactly the PCM samples it was encoded from, so nothing is lost
// by going through it.
package audio
import (
@@ -16,12 +22,37 @@ import (
)
const (
headerSize = 44
formatPCM = 1
// headerSize is the canonical PCM wav header: a 12 byte RIFF/WAVE header,
// a 24 byte fmt chunk, and an 8 byte data chunk header.
headerSize = 44
formatPCM = 1
// ctxCheckInterval is how often, in flac frames, cancellation is polled.
// A flac frame is a few thousand samples, so checking every frame would
// add a select to the innermost decode loop for no practical gain in
// responsiveness.
ctxCheckInterval = 64
maxDataSize = math.MaxUint32 - (headerSize - 8)
// maxDataSize is the largest audio payload that still fits. RIFF stores
// its sizes as uint32, and the RIFF size field covers the header after
// its own first 8 bytes as well as the data, so the audio itself has to
// stay that much below the limit. This works out to roughly 6 hours of
// CD quality stereo, which no single track will reach, but silently
// producing a file with a wrapped size field would be worse than an
// error.
maxDataSize = math.MaxUint32 - (headerSize - 8)
)
// FLACToWAV decodes the flac at srcPath and writes it as a PCM wav to
// dstPath.
//
// The size is checked twice, once from the flac header before doing any work
// and once against the bytes actually written, because NSamples is zero in
// flac streams that were encoded without a known length.
//
// Output goes to a temporary file in the destination directory and is renamed
// into place at the end, so a cancelled or failed conversion never leaves a
// half decoded file where a playable one is expected.
func FLACToWAV(ctx context.Context, srcPath, dstPath string) error {
stream, err := flac.Open(srcPath)
if err != nil {
@@ -54,6 +85,9 @@ func FLACToWAV(ctx context.Context, srcPath, dstPath string) error {
}
}()
// The header goes down with a zero data size and is patched afterwards:
// the real length is only known once every frame has been decoded, and
// buffering the whole stream in memory to find out first is not worth it.
w := bufio.NewWriter(file)
if err := writeHeader(w, info.SampleRate, info.NChannels, info.BitsPerSample, 0); err != nil {
return err
@@ -66,6 +100,8 @@ func FLACToWAV(ctx context.Context, srcPath, dstPath string) error {
if dataSize > maxDataSize {
return fmt.Errorf("audio data of %d bytes exceeds the wav format limit", dataSize)
}
// RIFF chunks must end on an even offset. Only reachable with 8 or 24 bit
// mono, where a sample is an odd number of bytes.
if dataSize%2 != 0 {
if err := w.WriteByte(0); err != nil {
return err
@@ -162,6 +198,12 @@ func writeSamples(ctx context.Context, w io.Writer, stream *flac.Stream, nChanne
return dataSize, nil
}
// putSample encodes one sample little endian into buf.
//
// 8 bit wav is the odd one out: it stores unsigned samples biased by 128,
// while every wider depth is signed two's complement. Writing an 8 bit sample
// signed produces audio that sounds like loud static, so the bias is not
// optional.
func putSample(buf []byte, sample int32, bytesPerSample int) {
if bytesPerSample == 1 {
buf[0] = byte(sample + 128)
@@ -174,6 +216,12 @@ func putSample(buf []byte, sample int32, bytesPerSample int) {
}
}
// patchSizes rewrites the two length fields once the real data size is known:
// the RIFF size at offset 4 and the data chunk size just before the samples
// begin.
//
// The pad byte counts towards the RIFF size but not towards the data chunk
// size, which is why only the first of the two includes it.
func patchSizes(file *os.File, dataSize int64) error {
buf := make([]byte, 4)