diff --git a/buffer.go b/buffer.go index a8536b0..f1aba9f 100644 --- a/buffer.go +++ b/buffer.go @@ -4,6 +4,8 @@ import ( "fmt" "math" "time" + + "github.com/gopxl/beep/internal/util" ) // SampleRate is the number of samples per second. @@ -63,11 +65,11 @@ func (f Format) DecodeUnsigned(p []byte) (sample [2]float64, n int) { func (f Format) encode(signed bool, p []byte, sample [2]float64) (n int) { switch { case f.NumChannels == 1: - x := norm((sample[0] + sample[1]) / 2) + x := util.Clamp((sample[0]+sample[1])/2, -1, 1) p = p[encodeFloat(signed, f.Precision, p, x):] case f.NumChannels >= 2: for c := range sample { - x := norm(sample[c]) + x := util.Clamp(sample[c], -1, 1) p = p[encodeFloat(signed, f.Precision, p, x):] } for c := len(sample); c < f.NumChannels; c++ { @@ -150,16 +152,6 @@ func unsignedToFloat(precision int, xUint64 uint64) float64 { return float64(xUint64)/(math.Exp2(float64(precision)*8))*2 - 1 } -func norm(x float64) float64 { - if x < -1 { - return -1 - } - if x > +1 { - return +1 - } - return x -} - // Buffer is a storage for audio data. You can think of it as a bytes.Buffer for audio samples. type Buffer struct { f Format diff --git a/compositors.go b/compositors.go index d874d9f..8dabdb8 100644 --- a/compositors.go +++ b/compositors.go @@ -19,10 +19,7 @@ func (t *take) Stream(samples [][2]float64) (n int, ok bool) { if t.remains <= 0 { return 0, false } - toStream := t.remains - if len(samples) < toStream { - toStream = len(samples) - } + toStream := min(t.remains, len(samples)) n, ok = t.s.Stream(samples[:toStream]) t.remains -= n return n, ok @@ -102,15 +99,10 @@ func Mix(s ...Streamer) Streamer { var tmp [512][2]float64 for len(samples) > 0 { - toStream := len(tmp) - if toStream > len(samples) { - toStream = len(samples) - } + toStream := min(len(tmp), len(samples)) // clear the samples - for i := range samples[:toStream] { - samples[i] = [2]float64{} - } + clear(samples[:toStream]) snMax := 0 // max number of streamed samples in this iteration for _, st := range s { diff --git a/compositors_test.go b/compositors_test.go index 53b9dd5..4c27803 100644 --- a/compositors_test.go +++ b/compositors_test.go @@ -76,9 +76,7 @@ func TestMix(t *testing.T) { maxLen := 0 for _, d := range data { - if len(d) > maxLen { - maxLen = len(d) - } + maxLen = max(maxLen, len(d)) } want := make([][2]float64, maxLen) diff --git a/ctrl.go b/ctrl.go index 6dc7e30..8ca1601 100644 --- a/ctrl.go +++ b/ctrl.go @@ -35,9 +35,7 @@ func (c *Ctrl) Stream(samples [][2]float64) (n int, ok bool) { return 0, false } if c.Paused { - for i := range samples { - samples[i] = [2]float64{} - } + clear(samples) return len(samples), true } return c.Streamer.Stream(samples) diff --git a/effects/transition.go b/effects/transition.go index a0af349..1aede34 100644 --- a/effects/transition.go +++ b/effects/transition.go @@ -57,11 +57,7 @@ func (t *TransitionStreamer) Stream(samples [][2]float64) (n int, ok bool) { for i := 0; i < n; i++ { pos := t.pos + i progress := float64(pos) / float64(t.len) - if progress < 0 { - progress = 0 - } else if progress > 1 { - progress = 1 - } + progress = min(progress, 1.0) value := t.transitionFunc(progress) gain := t.startGain + (t.endGain-t.startGain)*value diff --git a/examples/speedy-player/main.go b/examples/speedy-player/main.go index bf199d6..4058d59 100644 --- a/examples/speedy-player/main.go +++ b/examples/speedy-player/main.go @@ -103,12 +103,10 @@ func (ap *audioPanel) handle(event tcell.Event) (changed, quit bool) { if event.Rune() == 'w' { newPos += ap.sampleRate.N(time.Second) } - if newPos < 0 { - newPos = 0 - } - if newPos >= ap.streamer.Len() { - newPos = ap.streamer.Len() - 1 - } + // Clamp the position to be within the stream + newPos = max(newPos, 0) + newPos = min(newPos, ap.streamer.Len()-1) + if err := ap.streamer.Seek(newPos); err != nil { report(err) } @@ -130,9 +128,7 @@ func (ap *audioPanel) handle(event tcell.Event) (changed, quit bool) { case 'z': speaker.Lock() newRatio := ap.resampler.Ratio() * 15 / 16 - if newRatio < 0.001 { - newRatio = 0.001 - } + newRatio = max(newRatio, 0.001) // Limit to a reasonable ratio ap.resampler.SetRatio(newRatio) speaker.Unlock() return true, false @@ -140,9 +136,7 @@ func (ap *audioPanel) handle(event tcell.Event) (changed, quit bool) { case 'x': speaker.Lock() newRatio := ap.resampler.Ratio() * 16 / 15 - if newRatio > 100 { - newRatio = 100 - } + newRatio = min(newRatio, 100) // Limit to a reasonable ratio ap.resampler.SetRatio(newRatio) speaker.Unlock() return true, false diff --git a/generators/silence.go b/generators/silence.go index 9c093d3..d36cea3 100644 --- a/generators/silence.go +++ b/generators/silence.go @@ -7,9 +7,7 @@ import "github.com/gopxl/beep" func Silence(num int) beep.Streamer { if num < 0 { return beep.StreamerFunc(func(samples [][2]float64) (m int, ok bool) { - for i := range samples { - samples[i] = [2]float64{} - } + clear(samples) return len(samples), true }) } @@ -21,9 +19,7 @@ func Silence(num int) beep.Streamer { if num < len(samples) { samples = samples[:num] } - for i := range samples { - samples[i] = [2]float64{} - } + clear(samples) num -= len(samples) return len(samples), true diff --git a/internal/testtools/asserts.go b/internal/testtools/asserts.go index 3f48dca..d5bfe43 100644 --- a/internal/testtools/asserts.go +++ b/internal/testtools/asserts.go @@ -22,10 +22,7 @@ func AssertStreamerHasCorrectReturnBehaviour(t *testing.T, s beep.Streamer, expe buf := make([][2]float64, 512) samplesLeft := expectedSamples - leaveUnreadInFirstCase for samplesLeft > 0 { - toRead := len(buf) - if toRead > samplesLeft { - toRead = samplesLeft - } + toRead := min(samplesLeft, len(buf)) n, ok := s.Stream(buf[:toRead]) if !ok { t.Fatalf("streamer returned !ok before it was expected to be drained") diff --git a/internal/util/math.go b/internal/util/math.go new file mode 100644 index 0000000..e335edb --- /dev/null +++ b/internal/util/math.go @@ -0,0 +1,7 @@ +package util + +import "cmp" + +func Clamp[T cmp.Ordered](x, minV, maxV T) T { + return max(min(x, maxV), minV) +} diff --git a/internal/util/math_test.go b/internal/util/math_test.go new file mode 100644 index 0000000..aa669f3 --- /dev/null +++ b/internal/util/math_test.go @@ -0,0 +1,13 @@ +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClamp(t *testing.T) { + assert.Equal(t, 0, Clamp(-5, 0, 1)) + assert.Equal(t, 1, Clamp(5, 0, 1)) + assert.Equal(t, 0.5, Clamp(0.5, 0, 1)) +} diff --git a/midi/decode.go b/midi/decode.go index 057e5c7..8847b1d 100644 --- a/midi/decode.go +++ b/midi/decode.go @@ -100,10 +100,7 @@ func (d *decoder) Stream(samples [][2]float64) (n int, ok bool) { } for len(samples) > 0 { - cn := len(d.bufLeft) - if cn > len(samples) { - cn = len(samples) - } + cn := min(len(d.bufLeft), len(samples)) d.seq.Render(d.bufLeft[:cn], d.bufRight[:cn]) for i := 0; i < cn; i++ { diff --git a/mixer.go b/mixer.go index 9f0dd19..341c205 100644 --- a/mixer.go +++ b/mixer.go @@ -27,15 +27,10 @@ func (m *Mixer) Stream(samples [][2]float64) (n int, ok bool) { var tmp [512][2]float64 for len(samples) > 0 { - toStream := len(tmp) - if toStream > len(samples) { - toStream = len(samples) - } + toStream := min(len(tmp), len(samples)) // clear the samples - for i := range samples[:toStream] { - samples[i] = [2]float64{} - } + clear(samples[:toStream]) for si := 0; si < len(m.streamers); si++ { // mix the stream diff --git a/resample.go b/resample.go index c2eeee5..75fd709 100644 --- a/resample.go +++ b/resample.go @@ -115,12 +115,8 @@ func (r *Resampler) Stream(samples [][2]float64) (n int, ok bool) { } // Adjust the window to be within the available buffers. - if windowStart < 0 { - windowStart = 0 - } - if windowEnd > r.end { - windowEnd = r.end - } + windowStart = max(windowStart, 0) + windowEnd = min(windowEnd, r.end) // For each channel... for c := range samples[0] { diff --git a/speaker/speaker.go b/speaker/speaker.go index 8103b7f..0503a70 100644 --- a/speaker/speaker.go +++ b/speaker/speaker.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/gopxl/beep" + "github.com/gopxl/beep/internal/util" ) const channelCount = 2 @@ -186,12 +187,7 @@ func (s *sampleReader) Read(buf []byte) (n int, err error) { for i := range s.buf[:ns] { for c := range s.buf[i] { val := s.buf[i][c] - if val < -1 { - val = -1 - } - if val > +1 { - val = +1 - } + val = util.Clamp(val, -1, 1) valInt16 := int16(val * (1<<15 - 1)) low := byte(valInt16) high := byte(valInt16 >> 8) diff --git a/streamers.go b/streamers.go index 34b8e6b..888fdf4 100644 --- a/streamers.go +++ b/streamers.go @@ -12,9 +12,7 @@ func Silence(num int) Streamer { if 0 < num && num < len(samples) { samples = samples[:num] } - for i := range samples { - samples[i] = [2]float64{} - } + clear(samples) if num > 0 { num -= len(samples) } diff --git a/wav/decode.go b/wav/decode.go index c52fa8f..9d6f2f8 100644 --- a/wav/decode.go +++ b/wav/decode.go @@ -7,8 +7,9 @@ import ( "io" "time" - "github.com/gopxl/beep" "github.com/pkg/errors" + + "github.com/gopxl/beep" ) // Decode takes a Reader containing audio data in WAVE format and returns a StreamSeekCloser, @@ -207,10 +208,9 @@ func (d *decoder) Stream(samples [][2]float64) (n int, ok bool) { return 0, false } bytesPerFrame := int(d.h.BytesPerFrame) - numBytes := int32(len(samples) * bytesPerFrame) - if numBytes > d.h.DataSize-d.pos { - numBytes = d.h.DataSize - d.pos - } + wantBytes := len(samples) * bytesPerFrame + availableBytes := int(d.h.DataSize - d.pos) + numBytes := min(wantBytes, availableBytes) p := make([]byte, numBytes) n, err := d.r.Read(p) if err != nil && err != io.EOF {