//go:build !disable_format_tta && !disable_codec_tta package tta import ( "bytes" "errors" "git.gammaspectra.live/S.O.N.G/Kirika/audio" "git.gammaspectra.live/S.O.N.G/Kirika/audio/format" "git.gammaspectra.live/S.O.N.G/Kirika/vector" libtta "git.gammaspectra.live/S.O.N.G/go-tta" "io" "math" "runtime" "unsafe" ) const BlockSize = 1024 * 128 type Format struct { } func (f Format) Name() string { return "tta" } func (f Format) DecoderDescription() string { return "S.O.N.G/go-tta" } func (f Format) EncoderDescription() string { return f.DecoderDescription() } func NewFormat() Format { return Format{} } type fakeReadWriteSeeker struct { r io.ReadSeekCloser } func (i fakeReadWriteSeeker) Read(p []byte) (n int, err error) { return i.r.Read(p) } func (i fakeReadWriteSeeker) Seek(offset int64, whence int) (int64, error) { return i.r.Seek(offset, whence) } func (i fakeReadWriteSeeker) Close() error { return i.r.Close() } func (i fakeReadWriteSeeker) Write(_ []byte) (n int, err error) { return 0, io.ErrShortWrite } type fakeReadWriteSeeker2 struct { w io.WriteCloser } func (i fakeReadWriteSeeker2) Read(_ []byte) (n int, err error) { return 0, io.ErrNoProgress } func (i fakeReadWriteSeeker2) Seek(_ int64, _ int) (int64, error) { return 0, io.ErrNoProgress } func (i fakeReadWriteSeeker2) Close() error { return i.w.Close() } func (i fakeReadWriteSeeker2) Write(p []byte) (n int, err error) { return i.w.Write(p) } func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { decoder := libtta.MakeDecoder(fakeReadWriteSeeker{r}) if decoder == nil { return nil, errors.New("invalid decoder") } info := &libtta.Info{} if err := decoder.GetInfo(info, 0); err != nil { return nil, err } source := audio.NewSource[int32](int(info.Bps), int(info.Sps), int(info.Nch)) go func() { defer source.Close() smpSize := int(info.Nch) * ((int(info.Bps) + 7) / 8) buffer := make([]byte, BlockSize*smpSize) var writeLen int for { if writeLen = int(decoder.ProcessStream(buffer, nil)) * smpSize; writeLen == 0 { break } source.IngestInt32(vector.BytesToInt32(buffer[:writeLen], int(info.Bps)), int(info.Bps)) } }() return source, nil } func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) { return format.NewAnalyzerChannel(f.Open(r)) } func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error { var bitsPerSample = 16 if options != nil { var val interface{} var ok bool var intVal int if val, ok = options["bitdepth"]; ok { if intVal, ok = val.(int); ok { bitsPerSample = intVal } } } var encoder *libtta.Encoder if writeSeeker, ok := writer.(io.ReadWriteSeeker); ok { encoder = libtta.MakeEncoder(writeSeeker) } else { encoder = libtta.MakeEncoder(fakeReadWriteSeeker2{w: writer}) } if encoder == nil { return errors.New("could not create encoder") } defer encoder.Close() err := encoder.SetInfo(&libtta.Info{ Format: 1, Nch: uint32(source.GetChannels()), Bps: uint32(bitsPerSample), Sps: uint32(source.GetSampleRate()), Samples: math.MaxUint32, }) if err != nil { return err } switch bitsPerSample { case 32: for block := range source.ToInt32(32).GetBlocks() { encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(block))), len(block)*4), nil) runtime.KeepAlive(block) } case 24: for block := range source.ToInt32(24).GetBlocks() { samples := vector.Int32ToBytes(block, 24) encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(samples))), len(samples)), nil) runtime.KeepAlive(samples) } case 16: for block := range source.ToInt16().GetBlocks() { encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(block))), len(block)*2), nil) runtime.KeepAlive(block) } case 8: for block := range source.ToInt32(8).GetBlocks() { samples := vector.Int32ToBytes(block, 8) encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(samples))), len(samples)), nil) runtime.KeepAlive(samples) } default: return errors.New("not supported bits per sample") } encoder.Finalize() return nil } func (f Format) Identify(peek [format.IdentifyPeekBytes]byte, extension string) bool { return bytes.Compare(peek[:4], []byte{'T', 'T', 'A', '1'}) == 0 || extension == "tta" }