mirror of
https://github.com/faiface/beep.git
synced 2026-05-25 00:54:30 -04:00
213 lines
4.5 KiB
Go
213 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/faiface/beep"
|
|
"github.com/faiface/beep/effects"
|
|
"github.com/faiface/beep/mp3"
|
|
"github.com/faiface/beep/speaker"
|
|
"github.com/gdamore/tcell"
|
|
)
|
|
|
|
func drawTextLine(screen tcell.Screen, x, y int, s string, style tcell.Style) {
|
|
for _, r := range s {
|
|
screen.SetContent(x, y, r, nil, style)
|
|
x++
|
|
}
|
|
}
|
|
|
|
type audioPanel struct {
|
|
sampleRate beep.SampleRate
|
|
streamer beep.StreamSeeker
|
|
ctrl *beep.Ctrl
|
|
resampler *beep.Resampler
|
|
volume *effects.Volume
|
|
}
|
|
|
|
func newAudioPanel(sampleRate beep.SampleRate, streamer beep.StreamSeeker) *audioPanel {
|
|
ctrl := &beep.Ctrl{Streamer: beep.Loop(-1, streamer)}
|
|
resampler := beep.ResampleRatio(4, 1, ctrl)
|
|
volume := &effects.Volume{Streamer: resampler, Base: 2}
|
|
return &audioPanel{sampleRate, streamer, ctrl, resampler, volume}
|
|
}
|
|
|
|
func (ap *audioPanel) play() {
|
|
speaker.Play(ap.volume)
|
|
}
|
|
|
|
func (ap *audioPanel) draw(screen tcell.Screen) {
|
|
mainStyle := tcell.StyleDefault.
|
|
Background(tcell.NewHexColor(0x473437)).
|
|
Foreground(tcell.NewHexColor(0xD7D8A2))
|
|
statusStyle := mainStyle.
|
|
Foreground(tcell.NewHexColor(0xDDC074)).
|
|
Bold(true)
|
|
|
|
screen.Fill(' ', mainStyle)
|
|
|
|
drawTextLine(screen, 0, 0, "Welcome to the Speedy Player!", mainStyle)
|
|
drawTextLine(screen, 0, 1, "Press [ESC] to quit.", mainStyle)
|
|
drawTextLine(screen, 0, 2, "Press [SPACE] to pause/resume.", mainStyle)
|
|
drawTextLine(screen, 0, 3, "Use keys in (?/?) to turn the buttons.", mainStyle)
|
|
|
|
speaker.Lock()
|
|
position := ap.sampleRate.D(ap.streamer.Position())
|
|
length := ap.sampleRate.D(ap.streamer.Len())
|
|
volume := ap.volume.Volume
|
|
speed := ap.resampler.Ratio()
|
|
speaker.Unlock()
|
|
|
|
positionStatus := fmt.Sprintf("%v / %v", position.Round(time.Second), length.Round(time.Second))
|
|
volumeStatus := fmt.Sprintf("%.1f", volume)
|
|
speedStatus := fmt.Sprintf("%.3fx", speed)
|
|
|
|
drawTextLine(screen, 0, 5, "Position (Q/W):", mainStyle)
|
|
drawTextLine(screen, 16, 5, positionStatus, statusStyle)
|
|
|
|
drawTextLine(screen, 0, 6, "Volume (A/S):", mainStyle)
|
|
drawTextLine(screen, 16, 6, volumeStatus, statusStyle)
|
|
|
|
drawTextLine(screen, 0, 7, "Speed (Z/X):", mainStyle)
|
|
drawTextLine(screen, 16, 7, speedStatus, statusStyle)
|
|
}
|
|
|
|
func (ap *audioPanel) handle(event tcell.Event) (changed, quit bool) {
|
|
switch event := event.(type) {
|
|
case *tcell.EventKey:
|
|
if event.Key() == tcell.KeyESC {
|
|
return false, true
|
|
}
|
|
|
|
if event.Key() != tcell.KeyRune {
|
|
return false, false
|
|
}
|
|
|
|
switch unicode.ToLower(event.Rune()) {
|
|
case ' ':
|
|
speaker.Lock()
|
|
ap.ctrl.Paused = !ap.ctrl.Paused
|
|
speaker.Unlock()
|
|
return false, false
|
|
|
|
case 'q', 'w':
|
|
speaker.Lock()
|
|
newPos := ap.streamer.Position()
|
|
if event.Rune() == 'q' {
|
|
newPos -= ap.sampleRate.N(time.Second)
|
|
}
|
|
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
|
|
}
|
|
if err := ap.streamer.Seek(newPos); err != nil {
|
|
report(err)
|
|
}
|
|
speaker.Unlock()
|
|
return true, false
|
|
|
|
case 'a':
|
|
speaker.Lock()
|
|
ap.volume.Volume -= 0.1
|
|
speaker.Unlock()
|
|
return true, false
|
|
|
|
case 's':
|
|
speaker.Lock()
|
|
ap.volume.Volume += 0.1
|
|
speaker.Unlock()
|
|
return true, false
|
|
|
|
case 'z':
|
|
speaker.Lock()
|
|
ap.resampler.SetRatio(ap.resampler.Ratio() * 15 / 16)
|
|
speaker.Unlock()
|
|
return true, false
|
|
|
|
case 'x':
|
|
speaker.Lock()
|
|
ap.resampler.SetRatio(ap.resampler.Ratio() * 16 / 15)
|
|
speaker.Unlock()
|
|
return true, false
|
|
}
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) != 2 {
|
|
fmt.Fprintf(os.Stderr, "Usage: %s song.mp3\n", os.Args[0])
|
|
os.Exit(1)
|
|
}
|
|
f, err := os.Open(os.Args[1])
|
|
if err != nil {
|
|
report(err)
|
|
}
|
|
streamer, format, err := mp3.Decode(f)
|
|
if err != nil {
|
|
report(err)
|
|
}
|
|
defer streamer.Close()
|
|
|
|
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30))
|
|
|
|
screen, err := tcell.NewScreen()
|
|
if err != nil {
|
|
report(err)
|
|
}
|
|
err = screen.Init()
|
|
if err != nil {
|
|
report(err)
|
|
}
|
|
defer screen.Fini()
|
|
|
|
ap := newAudioPanel(format.SampleRate, streamer)
|
|
|
|
screen.Clear()
|
|
ap.draw(screen)
|
|
screen.Show()
|
|
|
|
ap.play()
|
|
|
|
seconds := time.Tick(time.Second)
|
|
events := make(chan tcell.Event)
|
|
go func() {
|
|
for {
|
|
events <- screen.PollEvent()
|
|
}
|
|
}()
|
|
|
|
loop:
|
|
for {
|
|
select {
|
|
case event := <-events:
|
|
changed, quit := ap.handle(event)
|
|
if quit {
|
|
break loop
|
|
}
|
|
if changed {
|
|
screen.Clear()
|
|
ap.draw(screen)
|
|
screen.Show()
|
|
}
|
|
case <-seconds:
|
|
screen.Clear()
|
|
ap.draw(screen)
|
|
screen.Show()
|
|
}
|
|
}
|
|
}
|
|
|
|
func report(err error) {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|