246 lines
5.6 KiB
Go
246 lines
5.6 KiB
Go
//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"
|
|
}
|