1 ///
2 /// Audio engine -- manage current audio player and sources.
3 ///	@file		engine.h - pianod
4 ///	@author		Perette Barella
5 ///	@date		2014-11-30
6 ///	@copyright	Copyright (c) 2014-2020 Devious Fish. All rights reserved.
7 ///
8 
9 #pragma once
10 
11 #include <config.h>
12 
13 #include <ctime>
14 
15 #include <football.h>
16 
17 #include "musictypes.h"
18 #include "retainer.h"
19 #include "connection.h"
20 #include "response.h"
21 #include "mediaunit.h"
22 #include "tuner.h"
23 
24 /// Audio engine commands such as start, stop, pause, change playlists or seeds.
25 typedef enum engine_commands_t {
26     TIMESTATUS = CMD_RANGE_ENGINE,
27     GETVOLUME,
28     SETVOLUME,
29     GETCROSSFADETIME,
30     SETCROSSFADETIME,
31     GETCROSSFADELEVEL,
32     SETCROSSFADELEVEL,
33     ADJUSTVOLUME,
34     GETHISTORYSIZE,
35     SETHISTORYSIZE,
36     WAITFORENDOFSONG,
37     WAITFORNEXTSONG,
38     QUERYSTATUS,
39     QUERYHISTORY,
40     QUERYQUEUE,
41     NEXTSONG,
42     STOPPLAYBACK,
43     PAUSEPLAYBACK,
44     RESUMEPLAYBACK,
45     TOGGLEPLAYBACK,
46     PLAYQUEUEMODE,
47     PLAYPLAYLIST,
48     PLAYMIX,
49     PLAYDIRECT,
50     SELECTQUEUEMODE,
51     SELECTMIX,
52     SELECTPLAYLIST,
53     SELECTDIRECT,
54     PLAYLISTRENAME,
55     PLAYLISTDELETE,
56     PLAYLISTCREATE,
57     PLAYLISTFROMFILTER,
58     GETSUGGESTIONS,
59     LISTSONGSBYFILTER,
60     LISTSONGSBYPLAYLIST,
61     REQUESTMUSIC,
62     REQUESTCLEAR,
63     REQUESTCANCEL,
64     RATE,
65     RATEPLAYLIST,
66     SEEDLIST,
67     SEEDALTER,
68     ALTERAUDIOCONFIG,
69     // EXPLAINSONGCHOICE, Not implemented/future
70     // CREATEBOOKMARK, Not implemented/future
71 #ifndef NDEBUG
72     FILTERECHO
73 #endif
74 } ENGINECOMMAND;
75 
76 /** Audio engine, responsible for one "room" worth of audio.
77     Each audio engine has its own queue, selected playlist(s),
78     and playback status.
79  */
80 class AudioEngine : public Football::Interpreter<PianodConnection, ENGINECOMMAND> {
81 private:
82     /// Prerequisites that may be necessary to execute a particular command.
83     typedef enum requirement_t {
84         REQUIRE_SOURCE = 0x01, ///< Marks other flags as source-related
85         REQUIRE_PLAYLIST = 0x02, ///< Marks other flags as playlist-related.
86         REQUIRE_PLAYER = 0x04, ///< Require an audio player exist
87         REQUIRE_REQUEST = 0x10, ///< Require request capability
88     } REQUIREMENT;
89 
90     enum class PlaybackState {
91         Paused,
92         Playing
93     };
94 
95     /// States player progresses through when segueing between tracks.
96     enum class TransitionProgress {
97         Playing,    ///< the audio player is not transitioning
98         Purged,     ///< the queue has een purged in preparation for end-of-song
99         Cueing,     ///< a second audio player has been initialized and is initializing/prefetching.
100         Crossfading,///< the audio players are both playing as audio is cross-faded
101         Done        ///< transition is done, but old song keeps right on going.
102     };
103 
104     /// Behaviors possible when the player is idle.
105     enum class QueueMode {
106         Stopped,        ///< Don't start playing anything.
107         Requests,       ///< Play only requested songs
108         RandomPlay,     ///< Random selections from the current source if no requests.
109     };
110 
111     /// Whether and how completely track information was announced at start of playback.
112     enum class Announced {
113         Never,          // Track was never announced
114         Partially,      // Track was announced but playpoint/duration incomplete.
115         Completely      // Track was announced with full and accurate data.
116     };
117 
118     /// Structure used to track player progress/status.
119     struct {
120         time_t playback_effective_start = 0; ///< When not stalled, current time minus playback point.
121         time_t onset = 0; ///< Clock time at which a playback stall was detected.
122         time_t onset_playpoint = 0; ///< Playback point at which a playback stall was detected.
123     } stall;
124 
125     static const int pause_timeout = 1800; ///< Pause timeout applied to all sources/songs
126     static const int aquire_tracks_retry = 600; ///< Retry period if unable to get random tracks.
127 
128     const float prefetch_time = 5; ///< Number of seconds before cuing at which queue is refreshed.
129 
130     int history_size = 10; ///< Maximum number of tracks retained in history.
131 
132     volatile bool quit_requested = false; ///< Flag for signal handlers to request shutdown.
133     bool quit_initiated = false; ///< Flag set once a shutdown request has been initiated.
134 
135     time_t track_acquisition_time = 0; ///< The next time at which random track aquisition may be attempted.
136 
137     PianodService *service = nullptr;
138 
139     Retainer <PianodSong *> current_song = nullptr;
140     Retainer <PianodSong *> cueing_song = nullptr;
141     SongList requests;
142     SongList random_queue;
143     SongList song_history;
144 
145     Retainer <PianodPlaylist *> current_playlist = nullptr;
146     Media::Player *player = nullptr;
147     Media::Player *cueing_player = nullptr;
148     TransitionProgress transition_state = TransitionProgress::Playing;
149 
150     Announced track_announced = Announced::Never;
151     PlaybackState playback_state = PlaybackState::Paused;
152     QueueMode queue_mode = QueueMode::Stopped;
153     time_t pause_expiration = 0;
154     bool aborting_playback = false;
155     bool empty_warning_given = false;
156 
157     AudioSettings audio;
158     Tuner::Tuner mix;
159 
160     // Standard parser things
161     virtual bool hasPermission (PianodConnection &conn, ENGINECOMMAND command) override;
162     virtual void handleCommand (PianodConnection &conn, ENGINECOMMAND command) override;
163     virtual const FB_PARSE_DEFINITION *statements (void) override;
164 
165     // Interpreter helpers
166     MusicThingie *getThingOrCurrent (const PianodConnection &conn,
167                                      Ownership::Action usage = Ownership::Action::USE) const;
168     MusicThingie *getThingOrCurrent (const PianodConnection &conn,
169                                      MusicThingie::Type want,
170                                      Ownership::Action usage = Ownership::Action::USE) const;
171     ThingieList getThingsOrCurrent (const PianodConnection &conn,
172                                     PartialResponse *diagnostics,
173                                     Ownership::Action usage = Ownership::Action::USE) const;
174     ThingieList getThingsOrCurrent (const PianodConnection &conn,
175                                     MusicThingie::Type want,
176                                     PartialResponse *diagnostics,
177                                     Ownership::Action usage = Ownership::Action::USE) const;
178     SongList getSongsOrCurrent (const PianodConnection &conn,
179                                 PartialResponse *diagnostics,
180                                 Ownership::Action usage = Ownership::Action::USE) const;
181     PianodPlaylist *getPlaylistOrCurrent (const PianodConnection &conn,
182                                            Ownership::Action usage) const;
183     PlaylistList getPlaylistsOrCurrent (const PianodConnection &conn,
184                                         PartialResponse *diagnostics,
185                                         Ownership::Action usage = Ownership::Action::USE) const;
186     PlaylistList getPlaylistsOrCurrent (const PianodConnection &conn,
187                                         PartialResponse *diagnostics,
188                                         Ownership::Action usage,
189                                         const ThingieList &default_playlist) const;
190     void require (PianodConnection &conn, unsigned long requirements) const;
191 
192 
193     // Internal routines
queueEmpty(void)194     inline bool queueEmpty (void) const {
195         return requests.empty() && random_queue.empty();
196     }
197     bool startPlayer (void);
198     void promotePlayer (void);
199     void cleanupPlayer (void);
200     float monitorPlayer (void);
201 
202     // Status and other senders
203     void playbackState (PlaybackState state, PianodConnection *conn = nullptr);
204     void queueMode (QueueMode mode, PianodConnection *conn = nullptr);
205     void sendSongLists (PianodConnection &conn, ENGINECOMMAND command);
206     bool sendPlaybackStatus (Football::Thingie &there, bool only_if_accurate = false);
207     void sendQueueMode (Football::Thingie &there);
208     void sendSelectedPlaylist (Football::Thingie &there);
209 
210     // Alternate senders
211     inline bool sendPlaybackStatus (bool only_if_accurate = false) {
212         return sendPlaybackStatus (*service, only_if_accurate);
213     };
sendQueueMode(void)214     inline void sendQueueMode (void) { sendQueueMode (*service); };
sendSelectedPlaylist(void)215     inline void sendSelectedPlaylist (void) { sendSelectedPlaylist (*service); };
216 
217     // Helper methods
218     void PurgeUnselectedSongs (void);
219     bool considerCreatingPlayer (void);
220     bool acquireRandomTracks (void);
221 
222     // Callback methods and handlers
223     void sourceReady (const Media::Source *);
224     bool sourceRemovalCheck (const Media::Source *source);
225     void sourceRemoved (const Media::Source *source);
226     void sourceOffline (const Media::Source *);
227     void sourceStatus (RESPONSE_CODE status, const char *detail);
228 
229     void playlistsChanged ();
230     void mixChanged (bool automatic, const char *why);
231 
232 public:
233     AudioEngine (PianodService *svc, const AudioSettings &audio_options);
234     ~AudioEngine (void);
235     void shutdown (bool immediate);
236     float periodic(void); /// Should be called intermittently by the main run-loop
237     void sendStatus (PianodConnection &there);
238     void updateStatus (PianodConnection &there);
239     void registerWithInterpreter (PianodService *service);
240     void usersChangedNotification();
241     UserList getAutotuneUsers ();
audioSettings()242     inline const AudioSettings &audioSettings () { return audio; };
243 };
244