Skip to content

Commit 037b251

Browse files
committed
examples: add initial speedy-player implementation
1 parent 0c860ef commit 037b251

File tree

1 file changed

+209
-0
lines changed

1 file changed

+209
-0
lines changed

examples/speedy-player/main.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"time"
7+
8+
"github.com/faiface/beep"
9+
"github.com/faiface/beep/effects"
10+
"github.com/faiface/beep/mp3"
11+
"github.com/faiface/beep/speaker"
12+
"github.com/gdamore/tcell"
13+
)
14+
15+
func drawTextLine(screen tcell.Screen, x, y int, s string, style tcell.Style) {
16+
for _, r := range s {
17+
screen.SetContent(x, y, r, nil, style)
18+
x++
19+
}
20+
}
21+
22+
type audioPanel struct {
23+
sampleRate beep.SampleRate
24+
streamer beep.StreamSeeker
25+
ctrl *beep.Ctrl
26+
resampler *beep.Resampler
27+
volume *effects.Volume
28+
}
29+
30+
func newAudioPanel(sampleRate beep.SampleRate, streamer beep.StreamSeeker) *audioPanel {
31+
ctrl := &beep.Ctrl{Streamer: beep.Loop(-1, streamer)}
32+
resampler := beep.ResampleRatio(4, 1, ctrl)
33+
volume := &effects.Volume{Streamer: resampler, Base: 2}
34+
return &audioPanel{sampleRate, streamer, ctrl, resampler, volume}
35+
}
36+
37+
func (ap *audioPanel) play() {
38+
speaker.Play(ap.volume)
39+
}
40+
41+
func (ap *audioPanel) draw(screen tcell.Screen) {
42+
mainStyle := tcell.StyleDefault.
43+
Background(tcell.NewHexColor(0x473437)).
44+
Foreground(tcell.NewHexColor(0xD7D8A2))
45+
statusStyle := mainStyle.
46+
Foreground(tcell.NewHexColor(0xDDC074)).
47+
Bold(true)
48+
49+
screen.Fill(' ', mainStyle)
50+
51+
drawTextLine(screen, 0, 0, "Welcome to the Speedy Player!", mainStyle)
52+
drawTextLine(screen, 0, 1, "Press [ESC] to quit.", mainStyle)
53+
drawTextLine(screen, 0, 2, "Press [SPACE] to pause/resume.", mainStyle)
54+
drawTextLine(screen, 0, 3, "Use keys in (?/?) to turn the buttons.", mainStyle)
55+
56+
speaker.Lock()
57+
position := ap.sampleRate.D(ap.streamer.Position())
58+
length := ap.sampleRate.D(ap.streamer.Len())
59+
volume := ap.volume.Volume
60+
speed := ap.resampler.Ratio()
61+
speaker.Unlock()
62+
63+
positionStatus := fmt.Sprintf("%v / %v", position.Round(time.Second), length.Round(time.Second))
64+
volumeStatus := fmt.Sprintf("%.1f", volume)
65+
speedStatus := fmt.Sprintf("%.3fx", speed)
66+
67+
drawTextLine(screen, 0, 5, "Position (Q/W):", mainStyle)
68+
drawTextLine(screen, 16, 5, positionStatus, statusStyle)
69+
70+
drawTextLine(screen, 0, 6, "Volume (A/S):", mainStyle)
71+
drawTextLine(screen, 16, 6, volumeStatus, statusStyle)
72+
73+
drawTextLine(screen, 0, 7, "Speed (Z/X):", mainStyle)
74+
drawTextLine(screen, 16, 7, speedStatus, statusStyle)
75+
}
76+
77+
func (ap *audioPanel) handle(event tcell.Event) (changed, quit bool) {
78+
switch event := event.(type) {
79+
case *tcell.EventKey:
80+
if event.Key() == tcell.KeyESC {
81+
return false, true
82+
}
83+
if event.Key() != tcell.KeyRune {
84+
return false, false
85+
}
86+
switch event.Rune() {
87+
case ' ':
88+
speaker.Lock()
89+
ap.ctrl.Paused = !ap.ctrl.Paused
90+
speaker.Unlock()
91+
return false, false
92+
93+
case 'q', 'Q', 'w', 'W':
94+
speaker.Lock()
95+
newPos := ap.streamer.Position()
96+
if event.Rune() == 'q' || event.Rune() == 'Q' {
97+
newPos -= ap.sampleRate.N(time.Second)
98+
}
99+
if event.Rune() == 'w' || event.Rune() == 'W' {
100+
newPos += ap.sampleRate.N(time.Second)
101+
}
102+
if newPos < 0 {
103+
newPos = 0
104+
}
105+
if newPos >= ap.streamer.Len() {
106+
newPos = ap.streamer.Len() - 1
107+
}
108+
if err := ap.streamer.Seek(newPos); err != nil {
109+
report(err)
110+
}
111+
speaker.Unlock()
112+
return true, false
113+
114+
case 'a':
115+
speaker.Lock()
116+
ap.volume.Volume -= 0.1
117+
speaker.Unlock()
118+
return true, false
119+
120+
case 's':
121+
speaker.Lock()
122+
ap.volume.Volume += 0.1
123+
speaker.Unlock()
124+
return true, false
125+
126+
case 'z':
127+
speaker.Lock()
128+
ap.resampler.SetRatio(ap.resampler.Ratio() * 15 / 16)
129+
speaker.Unlock()
130+
return true, false
131+
132+
case 'x':
133+
speaker.Lock()
134+
ap.resampler.SetRatio(ap.resampler.Ratio() * 16 / 15)
135+
speaker.Unlock()
136+
return true, false
137+
}
138+
}
139+
return false, false
140+
}
141+
142+
func main() {
143+
if len(os.Args) != 2 {
144+
fmt.Fprintf(os.Stderr, "Usage: %s song.mp3\n", os.Args[0])
145+
os.Exit(1)
146+
}
147+
f, err := os.Open(os.Args[1])
148+
if err != nil {
149+
report(err)
150+
}
151+
streamer, format, err := mp3.Decode(f)
152+
if err != nil {
153+
report(err)
154+
}
155+
defer streamer.Close()
156+
157+
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30))
158+
159+
screen, err := tcell.NewScreen()
160+
if err != nil {
161+
report(err)
162+
}
163+
err = screen.Init()
164+
if err != nil {
165+
report(err)
166+
}
167+
defer screen.Fini()
168+
169+
ap := newAudioPanel(format.SampleRate, streamer)
170+
171+
screen.Clear()
172+
ap.draw(screen)
173+
screen.Show()
174+
175+
ap.play()
176+
177+
seconds := time.Tick(time.Second)
178+
events := make(chan tcell.Event)
179+
go func() {
180+
for {
181+
events <- screen.PollEvent()
182+
}
183+
}()
184+
185+
loop:
186+
for {
187+
select {
188+
case event := <-events:
189+
changed, quit := ap.handle(event)
190+
if quit {
191+
break loop
192+
}
193+
if changed {
194+
screen.Clear()
195+
ap.draw(screen)
196+
screen.Show()
197+
}
198+
case <-seconds:
199+
screen.Clear()
200+
ap.draw(screen)
201+
screen.Show()
202+
}
203+
}
204+
}
205+
206+
func report(err error) {
207+
fmt.Fprintln(os.Stderr, err)
208+
os.Exit(1)
209+
}

0 commit comments

Comments
 (0)