mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 10:35:44 +00:00
119 lines
2.8 KiB
Go
119 lines
2.8 KiB
Go
package media
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"unsafe"
|
|
)
|
|
|
|
// buffer is a simple non-concurrent safe buffer for audio samples.
|
|
type buffer struct {
|
|
stretch bool
|
|
frameHz []int
|
|
|
|
raw samples
|
|
buckets []Bucket
|
|
cur *Bucket
|
|
}
|
|
|
|
type Bucket struct {
|
|
mem samples
|
|
ms float32
|
|
lv int
|
|
dst int
|
|
}
|
|
|
|
func newBuffer(frames []float32, hz int) (*buffer, error) {
|
|
if hz < 2000 {
|
|
return nil, errors.New("hz should be > than 2000")
|
|
}
|
|
|
|
buf := buffer{}
|
|
|
|
// preallocate continuous array
|
|
s := 0
|
|
for _, f := range frames {
|
|
s += frame(hz, f)
|
|
}
|
|
buf.raw = make(samples, s)
|
|
|
|
next := 0
|
|
for _, f := range frames {
|
|
s := frame(hz, f)
|
|
buf.buckets = append(buf.buckets, Bucket{
|
|
mem: buf.raw[next : next+s],
|
|
ms: f,
|
|
})
|
|
next += s
|
|
}
|
|
buf.cur = &buf.buckets[len(buf.buckets)-1]
|
|
return &buf, nil
|
|
}
|
|
|
|
func (b *buffer) choose(l int) {
|
|
for _, bb := range b.buckets {
|
|
if l >= len(bb.mem) {
|
|
b.cur = &bb
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *buffer) resample(hz int) {
|
|
b.stretch = true
|
|
for i := range b.buckets {
|
|
b.buckets[i].dst = frame(hz, float32(b.buckets[i].ms))
|
|
}
|
|
}
|
|
|
|
// write fills the buffer until it's full and then passes the gathered data into a callback.
|
|
//
|
|
// There are two cases to consider:
|
|
// 1. Underflow, when the length of the written data is less than the buffer's available space.
|
|
// 2. Overflow, when the length exceeds the current available buffer space.
|
|
//
|
|
// We overwrite any previous values in the buffer and move the internal write pointer
|
|
// by the length of the written data.
|
|
// In the first case, we won't call the callback, but it will be called every time
|
|
// when the internal buffer overflows until all samples are read.
|
|
func (b *buffer) write(s samples, onFull func(samples, float32)) (r int) {
|
|
for r < len(s) {
|
|
buf := b.cur
|
|
w := copy(buf.mem[buf.lv:], s[r:])
|
|
r += w
|
|
buf.lv += w
|
|
if buf.lv == len(buf.mem) {
|
|
if b.stretch {
|
|
onFull(buf.mem.stretch(buf.dst), buf.ms)
|
|
} else {
|
|
onFull(buf.mem, buf.ms)
|
|
}
|
|
b.choose(len(s) - r)
|
|
b.cur.lv = 0
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// frame calculates an audio stereo frame size, i.e. 48k*frame/1000*2
|
|
// with round(x / 2) * 2 for the closest even number
|
|
func frame(hz int, frame float32) int {
|
|
return int(math.Round(float64(hz)*float64(frame)/1000/2) * 2 * 2)
|
|
}
|
|
|
|
// stretch does a simple stretching of audio samples.
|
|
// something like: [1,2,3,4,5,6] -> [1,2,x,x,3,4,x,x,5,6,x,x] -> [1,2,1,2,3,4,3,4,5,6,5,6]
|
|
func (s samples) stretch(size int) []int16 {
|
|
out := buf[:size]
|
|
n := len(s)
|
|
ratio := float32(size) / float32(n)
|
|
sPtr := unsafe.Pointer(&s[0])
|
|
for i, l, r := 0, 0, 0; i < n; i += 2 {
|
|
l, r = r, int(float32((i+2)>>1)*ratio)<<1 // index in src * ratio -> approximated index in dst *2 due to int16
|
|
for j := l; j < r; j += 2 {
|
|
*(*int32)(unsafe.Pointer(&out[j])) = *(*int32)(sPtr) // out[j] = s[i]; out[j+1] = s[i+1]
|
|
}
|
|
sPtr = unsafe.Add(sPtr, uintptr(4))
|
|
}
|
|
return out
|
|
}
|