package flac import ( "bytes" "fmt" "io" "math" "os/exec" "strconv" "unsafe" ) type Metadata struct { SampleRate int64 BPS int64 Channels int64 NumSamples int64 // Total length of file in samples (per channel). MD5Sum string } func GetMetadata(path string) (md Metadata, err error) { cmd := exec.Command( "metaflac", "--show-sample-rate", "--show-bps", "--show-channels", "--show-total-samples", "--show-md5sum", path) out, err := cmd.Output() if err != nil { return md, fmt.Errorf("Failed to open file: %w", err) } l := bytes.Split(out, []byte{'\n'}) if len(l) != 6 { return md, fmt.Errorf("Output not understood:\n%s", out) } l = l[:5] md.SampleRate, err = strconv.ParseInt(string(l[0]), 10, 64) if err != nil { return md, fmt.Errorf("Invalid sample rate: %w", err) } md.BPS, err = strconv.ParseInt(string(l[1]), 10, 64) if err != nil { return md, fmt.Errorf("Invalid bps: %w", err) } md.Channels, err = strconv.ParseInt(string(l[2]), 10, 64) if err != nil { return md, fmt.Errorf("Invalid channels: %w", err) } md.NumSamples, err = strconv.ParseInt(string(l[3]), 10, 64) if err != nil { return md, fmt.Errorf("Invalid length: %w", err) } md.MD5Sum = string(l[4]) return md, nil } // Load nSamples of data from the given file. func Load(path string) (L []float32, R []float32, err error) { md, err := GetMetadata(path) if err != nil { return nil, nil, err } if md.BPS != 16 || md.Channels != 2 { return nil, nil, fmt.Errorf("Only 16 bit stereo flac files are supported.") } cmd := exec.Command( "flac", "--decode", "--stdout", "--force-raw-format", "--endian=little", "--sign=signed", path) r, err := cmd.StdoutPipe() if err != nil { return nil, nil, fmt.Errorf("Failed to connect to flac process: %w", err) } defer r.Close() if err := cmd.Start(); err != nil { return nil, nil, fmt.Errorf("Failed to start flac process: %w", err) } // Allocate L and R, and fill. L = make([]float32, md.NumSamples) R = make([]float32, md.NumSamples) written := 0 buf := make([]byte, 16384) sampleVals := (*[1 << 44]int16)(unsafe.Pointer(&buf[0]))[:len(buf)/2] for { n, err := r.Read(buf) if n != 0 { for i := range sampleVals[:n/4] { L[written] = float32(sampleVals[2*i]) / math.MaxInt16 R[written] = float32(sampleVals[2*i+1]) / math.MaxInt16 written++ } } if err == io.EOF { break } if err != nil { return L, R, fmt.Errorf("Failed to read from flac process: %w", err) } } return } // Save to path, assuming L and R are normalized to [-1,1] func Save(path string, L, R []float32) error { cmd := exec.Command( "flac", "--best", "--force", "--no-padding", "--force-raw-format", "--endian=little", "--sign=signed", "--channels=2", "--bps=16", "--sample-rate=48000", "--input-size="+fmt.Sprintf("%d", 4*len(L)), // In bytes. "-", "-o", path) w, err := cmd.StdinPipe() if err != nil { return fmt.Errorf("Failed to connect to flac process: %w", err) } defer w.Close() if err := cmd.Start(); err != nil { return fmt.Errorf("Failed to start flac process: %w", err) } iBuf := make([]int16, 2*len(L)) for i := range L { iBuf[2*i] = int16(math.MaxInt16 * L[i]) iBuf[2*i+1] = int16(math.MaxInt16 * R[i]) } buf := (*[1 << 44]byte)(unsafe.Pointer(&iBuf[0]))[:4*len(L)] if _, err := w.Write(buf); err != nil { return fmt.Errorf("Failed to write flac data: %w", err) } return nil }