diff --git a/.drone.yml b/.drone.yml index 894af72..9551cff 100644 --- a/.drone.yml +++ b/.drone.yml @@ -47,17 +47,6 @@ steps: event: - tag -- name: gitea release - image: plugins/gitea-release - settings: - api_key: - from_secret: gitea_api_key - base_url: https://git.nemunai.re/ - files: deploy/reveil-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - when: - event: - - tag - - name: docker image: plugins/docker settings: diff --git a/api/alarm.go b/api/alarm.go index 071073d..7b1e9d0 100644 --- a/api/alarm.go +++ b/api/alarm.go @@ -33,17 +33,6 @@ func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) { c.JSON(http.StatusOK, true) }) - router.POST("/alarm/next", func(c *gin.Context) { - if player.CommonPlayer == nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No player currently playing"}) - return - } else { - player.CommonPlayer.NextTrack() - } - - c.JSON(http.StatusOK, true) - }) - router.DELETE("/alarm", func(c *gin.Context) { if player.CommonPlayer != nil { err := player.CommonPlayer.Stop() diff --git a/player/player.go b/player/player.go index a86bfcc..2162fd1 100644 --- a/player/player.go +++ b/player/player.go @@ -6,12 +6,19 @@ import ( "math" "math/rand" "os" - "os/exec" "os/signal" + "path" "strings" "syscall" "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/wav" + "git.nemunai.re/nemunaire/reveil/config" "git.nemunai.re/nemunaire/reveil/model" ) @@ -19,11 +26,11 @@ import ( var CommonPlayer *Player type Player struct { - Playlist []string - MaxRunTime time.Duration - Stopper chan bool - currentCmd *exec.Cmd - currentCmdCh chan bool + Playlist []string + MaxRunTime time.Duration + Stopper chan bool + + sampleRate beep.SampleRate claironTime time.Duration claironFile string @@ -31,7 +38,7 @@ type Player struct { ntick int64 hasClaironed bool launched time.Time - volume uint16 + volume *effects.Volume dontUpdateVolume bool reverseOrder bool playedItem int @@ -59,11 +66,11 @@ func NewPlayer(cfg *config.Config) (*Player, error) { } p := Player{ - Stopper: make(chan bool, 1), - currentCmdCh: make(chan bool, 1), - MaxRunTime: settings.MaxRunTime * time.Minute, - claironTime: settings.GongInterval * time.Minute, - claironFile: reveil.CurrentGongPath(cfg), + Stopper: make(chan bool, 1), + MaxRunTime: settings.MaxRunTime * time.Minute, + sampleRate: beep.SampleRate(cfg.SampleRate), + claironTime: settings.GongInterval * time.Minute, + claironFile: reveil.CurrentGongPath(cfg), } // Load our track list @@ -93,27 +100,95 @@ func NewPlayer(cfg *config.Config) (*Player, error) { return &p, nil } -func (p *Player) playFile(filepath string) (err error) { - p.currentCmd = exec.Command("paplay", filepath) - if err = p.currentCmd.Start(); err != nil { - log.Println("Running paplay err: ", err.Error()) - p.currentCmdCh <- true +func loadFile(filepath string) (name string, s beep.StreamSeekCloser, format beep.Format, err error) { + var fd *os.File + + name = path.Base(filepath) + + fd, err = os.Open(filepath) + if err != nil { return } - log.Println("Running paplay ", filepath) + switch strings.ToLower(path.Ext(filepath)) { + case ".flac": + s, format, err = flac.Decode(fd) + case ".mp3": + s, format, err = mp3.Decode(fd) + default: + s, format, err = wav.Decode(fd) + } - err = p.currentCmd.Wait() - p.currentCmdCh <- true + if err != nil { + fd.Close() + return + } return } func (p *Player) WakeUp() { + log.Println("RUN WAKEUP FUNC") + log.Println("Playlist in use:", strings.Join(p.Playlist, " ; ")) + // Create infinite stream + stream := beep.Iterate(func() beep.Streamer { + if !p.hasClaironed && time.Since(p.launched) >= p.claironTime { + log.Println("clairon time!") + p.claironTime += p.claironTime / 2 + _, sample, format, err := loadFile(p.claironFile) + if err == nil { + p.volume.Volume = 0.1 + p.dontUpdateVolume = true + if format.SampleRate != p.sampleRate { + return beep.Resample(3, format.SampleRate, p.sampleRate, sample) + } else { + return sample + } + } else { + log.Println("Error loading clairon:", err) + } + } + + p.dontUpdateVolume = false + p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 + + if p.reverseOrder { + p.playedItem -= 1 + } else { + p.playedItem += 1 + } + + if p.playedItem >= len(p.Playlist) { + p.playedItem = 0 + } else if p.playedItem < 0 { + p.playedItem = len(p.Playlist) - 1 + } + + // Load our current item + _, sample, format, err := loadFile(p.Playlist[p.playedItem]) + if err != nil { + log.Println("Error loading audio file %s: %s", p.Playlist[p.playedItem], err.Error()) + return nil + } + + // Resample if needed + log.Println("playing list item:", p.playedItem, "/", len(p.Playlist), ":", p.Playlist[p.playedItem]) + if format.SampleRate != p.sampleRate { + return beep.Resample(3, format.SampleRate, p.sampleRate, sample) + } else { + return sample + } + }) + // Prepare sound player - p.volume = 3500 + log.Println("Initializing sound player...") + speaker.Init(p.sampleRate, p.sampleRate.N(time.Second/10)) + defer speaker.Close() + + p.volume = &effects.Volume{stream, 10, -2, false} + speaker.Play(p.volume) ticker := time.NewTicker(time.Second) defer ticker.Stop() @@ -125,53 +200,20 @@ func (p *Player) WakeUp() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGHUP) - p.currentCmdCh <- true loop: for { select { - case <-p.currentCmdCh: - if !p.hasClaironed && time.Since(p.launched) >= p.claironTime { - log.Println("clairon time!") - p.claironTime += p.claironTime / 2 - p.SetVolume(65535) - p.dontUpdateVolume = true - go p.playFile(p.claironFile) - } else { - p.dontUpdateVolume = false - p.volume = 3500 + uint16(math.Log(1+float64(p.ntick)/8)*9500) - p.SetVolume(p.volume) - - if p.reverseOrder { - p.playedItem -= 1 - } else { - p.playedItem += 1 - } - - if p.playedItem >= len(p.Playlist) { - p.playedItem = 0 - } else if p.playedItem < 0 { - p.playedItem = len(p.Playlist) - 1 - } - - log.Println("Next track: ", p.Playlist[p.playedItem]) - go p.playFile(p.Playlist[p.playedItem]) - } - - case <-ticker.C: - p.ntick += 1 - if !p.dontUpdateVolume { - p.volume = 3500 + uint16(math.Log(1+float64(p.ntick)/8)*9500) - p.SetVolume(p.volume) - } - case <-p.Stopper: log.Println("Stopper activated") break loop - case <-maxRun: log.Println("Max run time exhausted") break loop - + case <-ticker.C: + p.ntick += 1 + if !p.dontUpdateVolume { + p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 + } case <-interrupt: break loop } @@ -181,47 +223,31 @@ loop: // Calm down music loopcalm: - for i := 0; i < 128 && p.volume >= 15000; i += 1 { - timer := time.NewTimer(40 * time.Millisecond) - - p.volume -= 256 - p.SetVolume(p.volume) + for i := 0; i < 2000; i += 1 { + p.volume.Volume -= 0.001 + timer := time.NewTimer(4 * time.Millisecond) select { case <-p.Stopper: log.Println("Hard stop received...") timer.Stop() - p.volume = 0 + p.volume.Volume = 0 break loopcalm case <-timer.C: break } } - if p.currentCmd != nil && p.currentCmd.Process != nil { - p.currentCmd.Process.Kill() - } - - p.SetVolume(65535) - if p == CommonPlayer { log.Println("Destoying common player") CommonPlayer = nil - } -} -func (p *Player) NextTrack() { - if p.currentCmd != nil && p.currentCmd.Process != nil { - p.currentCmd.Process.Kill() + // TODO: find a better way to deallocate the card + os.Exit(42) } } -func (p *Player) SetVolume(volume uint16) error { - cmd := exec.Command("amixer", "-D", "pulse", "set", "Master", fmt.Sprintf("%d", volume)) - return cmd.Run() -} - func (p *Player) Stop() error { log.Println("Trying to stop the player") p.Stopper <- true