ditty/audio.go

326 lines
5.5 KiB
Go

package main
import (
"fmt"
"io"
"log"
"math"
"os"
"path"
"strings"
"sync"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/flac"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"github.com/faiface/beep/vorbis"
"github.com/faiface/beep/wav"
)
const defaultBufferSize = 500 * time.Millisecond
var (
playingFileName string
playingFileInfo string
playingFileID int64
playingStreamer beep.StreamSeekCloser
playingFormat beep.Format
playingSampleRate beep.SampleRate
nextStreamer beep.StreamSeekCloser
nextFormat beep.Format
nextFileName string
volume *effects.Volume
ctrl *beep.Ctrl
audioLock = new(sync.Mutex)
)
type audioFile struct {
File *os.File
Streamer beep.StreamSeekCloser
Format beep.Format
Metadata *metadata
}
func openFile(filePath string, metadata *metadata) (*audioFile, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
if metadata == nil {
metadata = readMetadata(f)
_, err = f.Seek(0, io.SeekStart)
if err != nil {
log.Fatal(err)
}
}
var (
streamer beep.StreamSeekCloser
format beep.Format
)
switch strings.ToLower(path.Ext(filePath)) {
case ".wav":
streamer, format, err = wav.Decode(f)
case ".mp3":
streamer, format, err = mp3.Decode(f)
case ".ogg", ".weba", ".webm":
streamer, format, err = vorbis.Decode(f)
case ".flac":
streamer, format, err = flac.Decode(f)
default:
err = fmt.Errorf("unsupported file format")
}
if err != nil {
return nil, err
}
a := audioFile{File: f, Streamer: streamer, Format: format, Metadata: metadata}
return &a, nil
}
func play(audioFile *audioFile) {
audioLock.Lock()
defer audioLock.Unlock()
speaker.Lock()
if playingStreamer != nil {
playingStreamer.Close()
}
speaker.Unlock()
thisFileID := time.Now().UnixNano()
playingFileID = thisFileID
playingStreamer = audioFile.Streamer
playingFormat = audioFile.Format
playingFileName = audioFile.File.Name()
playingFileInfo = ""
if audioFile.Metadata.Title != "" {
playingFileInfo = audioFile.Metadata.Title
if audioFile.Metadata.Artist != "" {
playingFileInfo = audioFile.Metadata.Artist + " - " + playingFileInfo
}
}
if audioFile.Format.SampleRate != playingSampleRate {
err := speaker.Init(audioFile.Format.SampleRate, audioFile.Format.SampleRate.N(bufferSize))
if err != nil {
log.Fatalf("failed to initialize audio device: %s", err)
}
playingSampleRate = audioFile.Format.SampleRate
} else {
speaker.Clear()
}
streamer := beep.Seq(audioFile.Streamer, beep.Callback(func() {
if playingFileID != thisFileID {
return
}
go nextTrack()
}))
speaker.Lock()
if volume != nil {
volume.Streamer = streamer
ctrl.Paused = false
} else {
volume = &effects.Volume{
Streamer: streamer,
Base: volumeBase,
Volume: 0.0,
Silent: false,
}
ctrl = &beep.Ctrl{
Streamer: volume,
Paused: false,
}
}
speaker.Unlock()
if streamFdInt >= 0 {
if streamFd == nil {
streamFd = os.NewFile(uintptr(streamFdInt), "out")
go func() {
for {
_ = wav.Encode(streamFd, ctrl, playingFormat)
time.Sleep(250 * time.Millisecond)
}
}()
}
} else {
speaker.Play(ctrl)
}
go app.QueueUpdateDraw(func() {
updateMain()
updateQueue()
updateStatus()
})
}
func pause() {
audioLock.Lock()
defer audioLock.Unlock()
if ctrl == nil {
return
}
speaker.Lock()
ctrl.Paused = !ctrl.Paused
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}
func nextTrack() {
if mainBufferCursor-1 < len(mainBufferFiles)-1 {
mainBufferCursor++
entry := selectedEntry()
audioFile, err := openFile(path.Join(mainBufferDirectory, entry.File.Name()), entry.Metadata)
if err != nil {
return
}
play(audioFile)
go app.QueueUpdateDraw(updateMain)
}
}
func skipPrevious() {
if mainBufferCursor > 1 {
if offsetEntry(-1).File.IsDir() {
return
}
listPrevious()
go listSelect()
}
}
func skipNext() {
if mainBufferCursor < len(mainBufferFiles) {
if offsetEntry(1).File.IsDir() {
return
}
listNext()
go listSelect()
}
}
func roundUnit(x, unit float64) float64 {
return math.Round(x/unit) * unit
}
func supportedFormat(filePath string) bool {
switch strings.ToLower(path.Ext(filePath)) {
case ".wav":
return true
case ".mp3":
return true
case ".ogg", ".weba", ".webm":
return true
case ".flac":
return true
default:
return false
}
}
func fileFormat(fileName string) string {
switch strings.ToLower(path.Ext(fileName)) {
case ".wav":
return "WAV"
case ".mp3":
return "MP3"
case ".ogg", ".weba", ".webm":
return "OGG"
case ".flac":
return "FLAC"
default:
return "?"
}
}
func adjustVolume(adjustment float64) {
audioLock.Lock()
defer audioLock.Unlock()
if volume == nil {
return
}
speaker.Lock()
volume.Volume += adjustment
volumeUpdated()
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}
func setVolume(vol float64) {
audioLock.Lock()
defer audioLock.Unlock()
if volume == nil {
return
}
speaker.Lock()
volume.Volume = vol
volumeUpdated()
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}
func decreaseVolume() {
adjustVolume(-0.5)
}
func increaseVolume() {
adjustVolume(0.5)
}
func volumeUpdated() {
if volume.Volume <= -7.5 {
volume.Volume = -7.5
volume.Silent = true
} else {
volume.Silent = false
if volume.Volume > 0 {
volume.Volume = 0
}
}
}
func toggleMute() {
audioLock.Lock()
defer audioLock.Unlock()
if volume == nil {
return
}
speaker.Lock()
volume.Silent = !volume.Silent
speaker.Unlock()
go app.QueueUpdateDraw(updateStatus)
}