package player import ( "fmt" "log" "math" "math/rand" "os" "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" ) var CommonPlayer *Player type Player struct { Playlist []string MaxRunTime time.Duration Stopper chan bool sampleRate beep.SampleRate claironTime time.Duration claironFile string ntick int64 hasClaironed bool launched time.Time volume *effects.Volume dontUpdateVolume bool reverseOrder bool playedItem int } func WakeUp(cfg *config.Config) (err error) { if CommonPlayer != nil { return fmt.Errorf("Unable to start the player: a player is already running") } CommonPlayer, err = NewPlayer(cfg) if err != nil { return err } go CommonPlayer.WakeUp() return nil } func NewPlayer(cfg *config.Config) (*Player, error) { // Load our settings settings, err := reveil.ReadSettings(cfg.SettingsFile) if err != nil { return nil, fmt.Errorf("Unable to read settings: %w", err) } p := Player{ 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 tracks, err := reveil.LoadTracks(cfg) if err != nil { return nil, fmt.Errorf("Unable to load tracks: %w", err) } var playlist []string // Creating playlist log.Println("Loading playlist...") for _, track := range tracks { if !track.Enabled { continue } p.Playlist = append(p.Playlist, track.Path) } log.Println("Shuffling playlist...") // Shuffle the playlist rand.Shuffle(len(playlist), func(i, j int) { playlist[i], playlist[j] = playlist[j], playlist[i] }) return &p, nil } 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 } 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) } 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 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() p.launched = time.Now() // Prepare graceful shutdown maxRun := time.After(p.MaxRunTime) interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGHUP) loop: for { select { 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 } } log.Println("Stopping the player...") // Calm down music loopcalm: 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.Volume = 0 break loopcalm case <-timer.C: break } } if p == CommonPlayer { log.Println("Destoying common player") CommonPlayer = nil // TODO: find a better way to deallocate the card os.Exit(42) } } func (p *Player) Stop() error { log.Println("Trying to stop the player") p.Stopper <- true return nil }