-
Notifications
You must be signed in to change notification settings - Fork 151
Composing and controlling
In this part, we'll learn how to compose new, more complex streamers out of simpler ones and how to control their playback.
We'll start roughly where we left off in the previous part (excluding the resampling). If you don't have the code, here it is:
package main
import (
"log"
"os"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
)
func main() {
f, err := os.Open("Rockafeller Skank.mp3")
if err != nil {
log.Fatal(err)
}
streamer, format, err := mp3.Decode(f)
if err != nil {
log.Fatal(err)
}
defer streamer.Close()
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan bool)
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
<-done
}
Before we get into the hard-core composing and controlling, I'll teach you how you can observe a streamer, if it is a StreamSeeker
. Namely, how you can check it's current playing position.
A StreamSeeker
(which our streamer
is) has three interesting methods: Len() int
, Position() int
, and Seek(p int) error
. Note that all of the methods accept and return int
s, not time.Duration
. That's because a streamer itself doesn't know its sample rate, so all it can do is work with positions in the number of samples. We know the sample rate, though. We can use the format.SampleRate.D
method to convert those int
s to time.Duration
. With this knowledge, we can easily track the current position of our streamer:
done := make(chan bool)
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
for {
select {
case <-done:
return
case <-time.After(time.Second):
speaker.Lock()
fmt.Println(format.SampleRate.D(streamer.Position()).Round(time.Second))
speaker.Unlock()
}
}
We've replaced the simple <-done
line with a more complex loop. It finishes when the playback finishes, but other than that, it prints the current streamer position every second.
Let's analyze how we do that. First, we lock the speaker with speaker.Lock()
. Why is that? The speaker is pulling data from the streamer
in the background, concurrently with the rest of the program. Locking the speaker temporarily prevents it from accessing all streamers. That way, we can safely access active streamers without running into race conditions.
After locking the speaker, we simply convert the speaker.Position()
to a time.Duration
, round it to seconds and print it out. Then we unlock the speaker so that the playback can continue.
Let's run it!
$ go run tracking.go
1s
2s
3s
4s
5s
6s
...
Works perfectly!
Now onto some composing and controlling!
Making new streamers from old ones, that's a common pattern in Beep. We've already seen it with beep.Resample
and beep.Seq
. The new streamer functions by using the old streamer under the hood, somehow manipulating its samples.
Another such streamer is beep.Loop
. It's a very simple one. Keep all the code as is, just add the loop := ...
line and edit speaker.Play
to play the loop
:
loop := beep.Loop(3, streamer)
done := make(chan bool)
speaker.Play(beep.Seq(loop, beep.Callback(func() {
done <- true
})))
The beep.Loop
function takes two arguments: the loop count, and a beep.StreamSeeker
. It can't take just a regular beep.Streamer
because it needs to rewind it for looping. Thankfully, the streamer
is a beep.StreamSeeker
.
Now, run the program and wait until the audio finishes:
$ go run loop.go
1s
2s
3s
...
3m33s
1s
2s
...
When the song finishes, it starts over. It will play 3x in total. If we set the loop count to negative, e.g. -1
, it will loop indefinitely.
Maybe your song is too long and it takes too much to finish. No problemo! We can speed it up with beep.ResampleRatio
(the first argument, 4
, is once again the quality index):
loop := beep.Loop(3, streamer)
fast := beep.ResampleRatio(4, 5, loop)
done := make(chan bool)
speaker.Play(beep.Seq(fast, beep.Callback(func() {
done <- true
})))
This speeds up the playback 5x, so the output will look like this:
$ go run loop.go
5s
10s
15s
20s
...
Enjoy.
Notice one thing. We've wrapped the original streamer
in beep.Loop
, then we wrapped that in beep.ResampleRatio
, but we're still getting and printing the current position directly from the original streamer
! That's right. It nicely demonstrates how beep.Loop
and beep.ResampleRatio
use the original streamer, each time taking only as much data as they need.