resampler that satisfies the streamcloser interface
This commit is contained in:
parent
7e737b864d
commit
8ebac4b864
|
@ -18,17 +18,19 @@ type Player struct {
|
|||
oldStreamer beep.StreamCloser
|
||||
oldCtrl *beep.Ctrl
|
||||
mixer *beep.Mixer
|
||||
sr beep.SampleRate
|
||||
}
|
||||
|
||||
func NewPlayer() *Player {
|
||||
return &Player{
|
||||
mixer: &beep.Mixer{},
|
||||
sr: beep.SampleRate(48000),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) Init() error {
|
||||
sr := beep.SampleRate(48000)
|
||||
if err := speaker.Init(sr, sr.N(time.Second/10)); err != nil {
|
||||
|
||||
if err := speaker.Init(p.sr, p.sr.N(time.Second/10)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -43,12 +45,14 @@ func (p *Player) Select(station Station) error {
|
|||
return err
|
||||
}
|
||||
|
||||
streamer, _, err := mp3.Decode(res.Body)
|
||||
streamer, format, err := mp3.Decode(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.PlayStream(streamer)
|
||||
resampled := Resample(4, format.SampleRate, p.sr, streamer)
|
||||
|
||||
p.PlayStream(resampled)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
package player
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
)
|
||||
|
||||
// Resample takes a Streamer which is assumed to stream at the old sample rate and returns a
|
||||
// Streamer, which streams the data from the original Streamer resampled to the new sample rate.
|
||||
//
|
||||
// This is, for example, useful when mixing multiple Streamer with different sample rates, either
|
||||
// through a beep.Mixer, or through a speaker. Speaker has a constant sample rate. Thus, playing
|
||||
// Streamer which stream at a different sample rate will lead to a changed speed and pitch of the
|
||||
// playback.
|
||||
//
|
||||
// sr := beep.SampleRate(48000)
|
||||
// speaker.Init(sr, sr.N(time.Second/2))
|
||||
// speaker.Play(beep.Resample(3, format.SampleRate, sr, s))
|
||||
//
|
||||
// In the example, the original sample rate of the source if format.SampleRate. We want to play it
|
||||
// at the speaker's native sample rate and thus we need to resample.
|
||||
//
|
||||
// The quality argument specifies the quality of the resampling process. Higher quality implies
|
||||
// worse performance. Values below 1 or above 64 are invalid and Resample will panic. Here's a table
|
||||
// for deciding which quality to pick.
|
||||
//
|
||||
// quality | use case
|
||||
// --------|---------
|
||||
// 1 | very high performance, on-the-fly resampling, low quality
|
||||
// 3-4 | good performance, on-the-fly resampling, good quality
|
||||
// 6 | higher CPU usage, usually not suitable for on-the-fly resampling, very good quality
|
||||
// >6 | even higher CPU usage, for offline resampling, very good quality
|
||||
//
|
||||
// Sane quality values are usually below 16. Higher values will consume too much CPU, giving
|
||||
// negligible quality improvements.
|
||||
//
|
||||
// Resample propagates errors from s.
|
||||
func Resample(quality int, old, new beep.SampleRate, s beep.StreamCloser) *Resampler {
|
||||
return ResampleRatio(quality, float64(old)/float64(new), s)
|
||||
}
|
||||
|
||||
// ResampleRatio is same as Resample, except it takes the ratio of the old and the new sample rate,
|
||||
// specifically, the old sample rate divided by the new sample rate. Aside from correcting the
|
||||
// sample rate, this can be used to change the speed of the audio. For example, resampling at the
|
||||
// ratio of 2 and playing at the original sample rate will cause doubled speed in playback.
|
||||
func ResampleRatio(quality int, ratio float64, s beep.StreamCloser) *Resampler {
|
||||
if quality < 1 || 64 < quality {
|
||||
panic(fmt.Errorf("resample: invalid quality: %d", quality))
|
||||
}
|
||||
return &Resampler{
|
||||
s: s,
|
||||
ratio: ratio,
|
||||
first: true,
|
||||
buf1: make([][2]float64, 512),
|
||||
buf2: make([][2]float64, 512),
|
||||
pts: make([]point, quality*2),
|
||||
off: 0,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Resampler is a Streamer created by Resample and ResampleRatio functions. It allows dynamic
|
||||
// changing of the resampling ratio, which can be useful for dynamically changing the speed of
|
||||
// streaming.
|
||||
type Resampler struct {
|
||||
s beep.StreamCloser // the orignal streamer
|
||||
ratio float64 // old sample rate / new sample rate
|
||||
first bool // true when Stream was not called before
|
||||
buf1, buf2 [][2]float64 // buf1 contains previous buf2, new data goes into buf2, buf1 is because interpolation might require old samples
|
||||
pts []point // pts is for points used for interpolation
|
||||
off int // off is the position of the start of buf2 in the original data
|
||||
pos int // pos is the current position in the resampled data
|
||||
}
|
||||
|
||||
// Stream streams the original audio resampled according to the current ratio.
|
||||
func (r *Resampler) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
// if it's the first time, we need to fill buf2 with initial data, buf1 remains zeroed
|
||||
if r.first {
|
||||
sn, _ := r.s.Stream(r.buf2)
|
||||
r.buf2 = r.buf2[:sn]
|
||||
r.first = false
|
||||
}
|
||||
// we start resampling, sample by sample
|
||||
for len(samples) > 0 {
|
||||
again:
|
||||
for c := range samples[0] {
|
||||
// calculate the current position in the original data
|
||||
j := float64(r.pos) * r.ratio
|
||||
|
||||
// find quality*2 closest samples to j and translate them to points for interpolation
|
||||
for pi := range r.pts {
|
||||
// calculate the index of one of the closest samples
|
||||
k := int(j) + pi - len(r.pts)/2 + 1
|
||||
|
||||
var y float64
|
||||
switch {
|
||||
// the sample is in buf1
|
||||
case k < r.off:
|
||||
y = r.buf1[len(r.buf1)+k-r.off][c]
|
||||
// the sample is in buf2
|
||||
case k < r.off+len(r.buf2):
|
||||
y = r.buf2[k-r.off][c]
|
||||
// the sample is beyond buf2, so we need to load new data
|
||||
case k >= r.off+len(r.buf2):
|
||||
// we load into buf1
|
||||
sn, _ := r.s.Stream(r.buf1)
|
||||
// this condition happens when the original Streamer got
|
||||
// drained and j is after the end of the
|
||||
// original data
|
||||
if int(j) >= r.off+len(r.buf2)+sn {
|
||||
return n, n > 0
|
||||
}
|
||||
// this condition happens when the original Streamer got
|
||||
// drained and this one of the closest samples is after the
|
||||
// end of the original data
|
||||
if k >= r.off+len(r.buf2)+sn {
|
||||
y = 0
|
||||
break
|
||||
}
|
||||
// otherwise everything is fine, we swap buffers and start
|
||||
// calculating the sample again
|
||||
r.off += len(r.buf2)
|
||||
r.buf1 = r.buf1[:sn]
|
||||
r.buf1, r.buf2 = r.buf2, r.buf1
|
||||
goto again
|
||||
}
|
||||
|
||||
r.pts[pi] = point{float64(k), y}
|
||||
}
|
||||
|
||||
// calculate the resampled sample using polynomial interpolation from the
|
||||
// quality*2 closest samples
|
||||
samples[0][c] = lagrange(r.pts, j)
|
||||
}
|
||||
samples = samples[1:]
|
||||
n++
|
||||
r.pos++
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// Err propagates the original Streamer's errors.
|
||||
func (r *Resampler) Err() error {
|
||||
return r.s.Err()
|
||||
}
|
||||
|
||||
func (r *Resampler) Close() error {
|
||||
return r.s.Close()
|
||||
}
|
||||
|
||||
// Ratio returns the current resampling ratio.
|
||||
func (r *Resampler) Ratio() float64 {
|
||||
return r.ratio
|
||||
}
|
||||
|
||||
// SetRatio sets the resampling ratio. This does not cause any glitches in the stream.
|
||||
func (r *Resampler) SetRatio(ratio float64) {
|
||||
r.pos = int(float64(r.pos) * r.ratio / ratio)
|
||||
r.ratio = ratio
|
||||
}
|
||||
|
||||
// lagrange calculates the value at x of a polynomial of order len(pts)+1 which goes through all
|
||||
// points in pts
|
||||
func lagrange(pts []point, x float64) (y float64) {
|
||||
y = 0.0
|
||||
for j := range pts {
|
||||
l := 1.0
|
||||
for m := range pts {
|
||||
if j == m {
|
||||
continue
|
||||
}
|
||||
l *= (x - pts[m].X) / (pts[j].X - pts[m].X)
|
||||
}
|
||||
y += pts[j].Y * l
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
type point struct {
|
||||
X, Y float64
|
||||
}
|
Loading…
Reference in New Issue