//go:build !disable_format_tta && !disable_codec_tta // +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/cgo" libtta "git.gammaspectra.live/S.O.N.G/go-tta" "io" "math" "unsafe" ) const BlockSize = 1024 * 128 type Format struct { } func (f Format) Name() string { return "tta" } func (f Format) Description() string { return "S.O.N.G/go-tta" } 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(p []byte) (n int, err error) { return 0, io.ErrShortWrite } type fakeReadWriteSeeker2 struct { w io.WriteCloser } func (i fakeReadWriteSeeker2) Read(p []byte) (n int, err error) { return 0, io.ErrNoProgress } func (i fakeReadWriteSeeker2) Seek(offset int64, whence 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 audio.Source{}, errors.New("invalid decoder") } newChannel := make(chan []float32) info := &libtta.Info{} if err := decoder.GetInfo(info, 0); err != nil { return audio.Source{}, err } go func() { defer close(newChannel) 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 } bufferSlice := buffer[:writeLen] bitDepth := int(info.Bps) nsamples := len(bufferSlice) / (bitDepth / 8) var buf []float32 switch bitDepth { case 32: buf = cgo.Int32ToFloat32(unsafe.Slice((*int32)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth) case 24: buf = cgo.Int24ToFloat32(unsafe.Slice((*byte)(unsafe.Pointer(&bufferSlice[0])), nsamples*3), bitDepth) case 16: buf = cgo.Int16ToFloat32(unsafe.Slice((*int16)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth) case 8: buf = cgo.Int8ToFloat32(unsafe.Slice((*int8)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth) } newChannel <- buf } }() return audio.Source{ Channels: int(info.Nch), SampleRate: int(info.Sps), Blocks: newChannel, }, nil } func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) { decoder := libtta.MakeDecoder(fakeReadWriteSeeker{r}) if decoder == nil { return audio.Source{}, nil, errors.New("invalid decoder") } newChannel := make(chan []float32) analyzerChannel := make(format.AnalyzerChannel) info := &libtta.Info{} if err := decoder.GetInfo(info, 0); err != nil { return audio.Source{}, nil, err } go func() { defer close(newChannel) defer close(analyzerChannel) 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 } bitDepth := int(info.Bps) intSamples := cgo.BytesToInt32(buffer[:writeLen], bitDepth) newChannel <- cgo.Int32ToFloat32(intSamples, bitDepth) analyzerChannel <- &format.AnalyzerPacket{ Samples: intSamples, Channels: int(info.Nch), BitDepth: bitDepth, SampleRate: int(info.Sps), } } }() return audio.Source{ Channels: int(info.Nch), SampleRate: int(info.Sps), Blocks: newChannel, }, analyzerChannel, nil } 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.Channels), Bps: uint32(bitsPerSample), Sps: uint32(source.SampleRate), Samples: math.MaxUint32, }) if err != nil { return err } switch bitsPerSample { case 24: for block := range source.Blocks { samples := cgo.Float32ToInt24(block) encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)), nil) } case 16: for block := range source.Blocks { samples := cgo.Float32ToInt16(block) encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*2), nil) } case 8: for block := range source.Blocks { samples := cgo.Float32ToInt8(block) encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)), nil) } default: return errors.New("not supported bits per sample") } encoder.Finalize() return nil } func (f Format) Identify(peek []byte, extension string) bool { return bytes.Compare(peek[:4], []byte{'T', 'T', 'A', '1'}) == 0 || extension == "tta" }