2023-04-24 13:45:06 -07:00
# pragma once
2024-03-23 18:41:26 -07:00
# include <SDL_mixer.h>
2024-04-10 18:00:19 -07:00
extern " C " {
# include <vgmstream.h>
}
2023-04-24 13:45:06 -07:00
# include <thread>
2023-07-15 14:52:49 -07:00
# include <SDL.h>
# include <SDL_audio.h>
2023-04-24 13:45:06 -07:00
# include <string>
# include <atomic>
# include <mutex>
2023-07-15 14:52:49 -07:00
# include <SoundTouch.h>
# include <span>
2023-09-03 11:54:07 -07:00
# include <optional>
2023-12-22 14:13:48 -08:00
# include <vector>
# include <queue>
2024-04-09 10:15:05 -07:00
# include <deque>
# include <map>
2023-07-15 14:52:49 -07:00
using namespace soundtouch ;
using std : : span ;
2023-09-03 11:54:07 -07:00
using std : : optional ;
2023-12-22 14:13:48 -08:00
using std : : vector ;
using std : : queue ;
2024-04-09 10:15:05 -07:00
using std : : deque ;
2024-03-23 18:41:26 -07:00
enum {
2024-04-09 10:15:05 -07:00
/// @brief No signals have occurred.
2024-03-23 18:41:26 -07:00
PlaybackSignalNone = 0 ,
2024-04-09 10:15:05 -07:00
/// @brief The file was changed. Recheck the properties of the file because they are likely different.
2024-03-23 18:41:26 -07:00
PlaybackSignalFileChanged = 1 < < 0 ,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
2024-03-23 18:41:26 -07:00
PlaybackSignalSpeedChanged = 1 < < 1 ,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
2024-03-23 18:41:26 -07:00
PlaybackSignalTempoChanged = 1 < < 2 ,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
2024-03-23 18:41:26 -07:00
PlaybackSignalPitchChanged = 1 < < 3 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was paused. If @ref PlaybackSignalResumed has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
2024-03-23 18:41:26 -07:00
PlaybackSignalPaused = 1 < < 4 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was resumed. If @ref PlaybackSignalPaused has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
2024-03-23 18:41:26 -07:00
PlaybackSignalResumed = 1 < < 5 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was stopped entirely. If @ref PlaybackSignalStarted has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
2024-03-23 18:41:26 -07:00
PlaybackSignalStopped = 1 < < 6 ,
2024-04-09 10:15:05 -07:00
/// @brief An error occurred and playback has likely (but not necessarily) stopped. Call @ref Playback::GetError for details.
2024-03-23 18:41:26 -07:00
PlaybackSignalErrorOccurred = 1 < < 7 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was seeked by the @ref Playback::Seek function
2024-03-23 18:41:26 -07:00
PlaybackSignalSeeked = 1 < < 8 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback has started. If @ref PlaybackSignalStopped has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
2024-03-23 18:41:26 -07:00
PlaybackSignalStarted = 1 < < 9
} ;
2024-04-09 10:15:05 -07:00
/// @brief Playback handler base class.
2023-04-24 13:45:06 -07:00
class Playback {
2024-04-09 10:15:05 -07:00
protected :
friend class DBusAPI ;
/// @brief The handle-specific signal state.
std : : map < void * , uint16_t > signals_occurred ;
/// @brief The global error state for initializing handle-specific state.
std : : vector < std : : string > errors ;
/// @brief The handle-specific error state.
std : : map < void * , std : : deque < std : : string > > errors_occurred ;
/// @brief The mutex for signals
std : : mutex signal_mutex ;
/// @brief The mutex for errors
std : : mutex error_mutex ;
/// @brief Signals other code with the specified signal(s). Use with PlaybackSignal* enum variants.
void set_signal ( uint16_t signal ) ;
/// @brief Signals an error, and pushes details to other code.
void set_error ( std : : string desc ) ;
2024-03-26 18:39:02 -07:00
public :
2024-04-09 10:15:05 -07:00
inline virtual bool is_proxy ( ) {
return false ;
}
2024-03-26 18:39:02 -07:00
inline Playback ( ) { } ;
inline virtual ~ Playback ( ) { }
2024-04-09 10:15:05 -07:00
/// @brief Gets the current file, if any.
/// @returns No value if there is no file playing, or the path to the file if there is a file playing.
2024-03-26 18:39:02 -07:00
inline virtual std : : optional < std : : string > get_current_file ( ) {
return { } ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the current file's title, if any, for display to the user.
/// @returns No value if there is no file playing. If there is a file playing, a string is returned. The contents of the string depend on whether or not the file has a title tag. If so, the string contains the contents of that tag. Otherwise, the string contains the stem of the file's path.
2024-03-26 18:39:02 -07:00
inline virtual std : : optional < std : : string > get_current_title ( ) {
return { } ;
}
2024-04-13 12:56:39 -07:00
inline virtual int get_current_stream ( ) {
return 0 ;
}
inline virtual std : : vector < std : : string > get_streams ( ) {
std : : vector < std : : string > output ;
return output ;
}
inline virtual void play_stream ( int idx ) { }
2024-04-09 10:15:05 -07:00
/// @brief Gets the position of the playing file.
2024-03-26 18:39:02 -07:00
inline virtual double GetPosition ( ) {
return 0.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the length of the file. If the length is less than or equal to zero, the file can be considered to have an unknown length.
2024-03-26 18:39:02 -07:00
inline virtual double GetLength ( ) {
return 0.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Sets the playback position into the current file.
2024-03-26 18:39:02 -07:00
inline virtual void Seek ( double position ) { }
2024-04-13 12:56:39 -07:00
/// @brief Loads a new file.
inline virtual void Load ( std : : string filePath ) { }
2024-04-09 10:15:05 -07:00
/// @brief Plays a new file.
2024-04-13 12:56:39 -07:00
inline virtual void Start ( std : : string filePath , int streamIdx = 0 ) { }
2024-04-09 10:15:05 -07:00
/// @brief Checks whether or not the file is paused.
/// @returns true if playback is paused, false otherwise
2024-03-26 18:39:02 -07:00
inline virtual bool IsPaused ( ) {
return true ;
}
2024-04-09 10:15:05 -07:00
/// @brief Toggles the pause state of the playback.
2024-03-26 18:39:02 -07:00
inline virtual void Pause ( ) { }
2024-04-09 10:15:05 -07:00
/// @brief Stops all playback.
2024-03-26 18:39:02 -07:00
inline virtual void Stop ( ) { }
2024-04-09 10:15:05 -07:00
/// @brief Checks whether or not playback is stopped.
/// @returns true if playback is stopped, false otherwise.
2024-03-26 18:39:02 -07:00
inline virtual bool IsStopped ( ) {
return true ;
}
2024-04-09 10:15:05 -07:00
/// @brief Sets the tempo multiplier of the playback.
2024-03-26 18:39:02 -07:00
inline virtual void SetTempo ( float tempo ) { }
2024-04-09 10:15:05 -07:00
/// @brief Sets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
2024-03-26 18:39:02 -07:00
inline virtual void SetSpeed ( float speed ) { }
2024-04-09 10:15:05 -07:00
/// @brief Sets the pitch multiplier of the playback
2024-03-26 18:39:02 -07:00
inline virtual void SetPitch ( float pitch ) { }
2024-04-09 10:15:05 -07:00
/// @brief Sets the volume of the playback, as a percentage
2024-03-26 18:39:02 -07:00
inline virtual void SetVolume ( float volume ) { }
2024-04-09 10:15:05 -07:00
/// @brief Gets the tempo multiplier of the playback.
2024-03-26 18:39:02 -07:00
inline virtual float GetTempo ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
2024-03-26 18:39:02 -07:00
inline virtual float GetSpeed ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the pitch multiplier of the playback.
2024-03-26 18:39:02 -07:00
inline virtual float GetPitch ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the volume of the playback, as a percentage
2024-03-26 18:39:02 -07:00
inline virtual float GetVolume ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Returns and clears signals, using a registered handle if specified.
/// @param signal The signal(s) to check for and clear.
/// @param handle The handle, which was specified to @ref Playback::register_handle , or nullptr.
uint16_t handle_signals ( uint16_t signal , void * handle = nullptr ) ;
/// @brief Registers a handle for usage in handle_signals and GetError.
void register_handle ( void * handle ) ;
/// @brief Unregisters a handle that is no longer needed.
void unregister_handle ( void * handle ) ;
/// @brief Gets the last error from the playback engine, or no value if there was none.
/// @param handle The handle as registered with @ref Playback::register_handle
inline virtual optional < std : : string > GetError ( void * handle ) {
std : : optional < std : : string > output = { } ;
if ( ErrorExists ( handle ) ) {
error_mutex . lock ( ) ;
if ( errors_occurred . contains ( handle ) ) {
output = errors_occurred [ handle ] . back ( ) ;
errors_occurred [ handle ] . pop_back ( ) ;
}
error_mutex . unlock ( ) ;
}
return output ;
2024-03-26 18:39:02 -07:00
}
2024-04-09 10:15:05 -07:00
/// @brief Checks if an unread error has occurred.
/// @param handle The handle as registered with @ref Playback::register_handle
inline virtual bool ErrorExists ( void * handle ) {
bool output = false ;
error_mutex . lock ( ) ;
if ( errors_occurred . contains ( handle ) ) {
output = ! errors_occurred [ handle ] . empty ( ) ;
}
error_mutex . unlock ( ) ;
return output ;
2024-03-26 18:39:02 -07:00
}
2024-04-09 10:15:05 -07:00
/// @brief Helper function to set the paused status via the @ref Playback::IsPaused and @ref Playback::Pause APIs.
/// @param paused The new pause state.
2024-03-26 18:39:02 -07:00
inline virtual void SetPaused ( bool paused ) {
if ( IsPaused ( ) ! = paused ) {
Pause ( ) ;
}
}
2024-04-09 10:15:05 -07:00
static Playback * Create ( bool * daemon_found , bool daemon = false ) ;
2024-03-26 18:39:02 -07:00
} ;
class DBusAPISender ;
class PlaybackInstance : public Playback {
2023-04-24 13:45:06 -07:00
private :
std : : string filePath ;
std : : atomic_bool running ;
std : : atomic_bool file_changed ;
std : : atomic_bool seeking ;
std : : atomic_bool update ;
2023-09-03 11:54:07 -07:00
std : : atomic_bool restart ;
std : : atomic_bool playback_ready ;
2024-03-23 18:41:26 -07:00
std : : atomic_bool speed_changed ;
std : : atomic_bool tempo_changed ;
std : : atomic_bool pitch_changed ;
std : : atomic_bool pause_changed ;
2024-04-13 12:56:39 -07:00
std : : atomic_bool load_requested ;
std : : atomic_bool load_finished ;
std : : atomic_bool stream_changed ;
2023-04-24 13:45:06 -07:00
std : : mutex flag_mutex ;
2023-12-22 14:13:48 -08:00
std : : mutex error_mutex ;
2023-04-24 13:45:06 -07:00
std : : thread thread ;
double position ;
double length ;
bool paused ;
2023-07-15 14:52:49 -07:00
Uint8 * buf ;
2023-07-10 12:45:24 -07:00
size_t bufsize ;
2023-07-15 14:52:49 -07:00
Mix_CommonMixer_t general_mixer ;
SDL_AudioDeviceID device ;
SoundTouch * st ;
SDL_AudioSpec spec ;
2024-04-10 18:00:19 -07:00
SDL_AudioStream * sdl_stream ;
2023-10-16 10:44:25 -07:00
/// @brief A fake SDL_AudioSpec used to trick SDL Mixer X into allocating a bigger buffer.
SDL_AudioSpec fakespec ;
2024-04-10 18:00:19 -07:00
SDL_AudioSpec vgmstream_spec ;
2023-07-15 14:52:49 -07:00
void SDLCallbackInner ( Uint8 * stream , int len ) ;
static void SDLCallback ( void * userdata , Uint8 * stream , int len ) ;
2024-04-13 12:56:39 -07:00
Mix_Music * LoadMix ( const char * file ) ;
VGMSTREAM * LoadVgm ( const char * file , int idx = 0 ) ;
void UnloadMix ( Mix_Music * music ) ;
void UnloadVgm ( VGMSTREAM * stream ) ;
2024-04-10 18:00:19 -07:00
VGMSTREAM * stream ;
2024-04-13 12:56:39 -07:00
std : : vector < std : : string > streams ;
std : : mutex stream_list_mutex ;
2024-04-10 18:00:19 -07:00
double real_volume = 1.0 ;
2023-04-24 13:45:06 -07:00
void ThreadFunc ( ) ;
2023-09-03 11:54:07 -07:00
void UpdateST ( ) ;
double GetMaxSeconds ( ) ;
2023-12-22 14:13:48 -08:00
queue < std : : string > errors ;
2024-03-23 18:41:26 -07:00
std : : mutex current_file_mutex ;
2024-04-13 12:56:39 -07:00
int current_stream ;
2024-03-23 18:41:26 -07:00
std : : optional < std : : string > current_file ;
2024-03-26 18:39:02 -07:00
std : : optional < std : : string > current_title ;
2024-03-23 18:41:26 -07:00
float prev_pitch , prev_speed , prev_tempo ;
2024-03-26 18:39:02 -07:00
2023-04-24 13:45:06 -07:00
public :
2024-03-26 18:39:02 -07:00
PlaybackInstance ( ) ;
~ PlaybackInstance ( ) override ;
std : : optional < std : : string > get_current_file ( ) override ;
std : : optional < std : : string > get_current_title ( ) override ;
2024-04-13 12:56:39 -07:00
int get_current_stream ( ) override ;
2024-04-09 10:15:05 -07:00
inline bool is_proxy ( ) override {
return false ;
}
2024-04-13 12:56:39 -07:00
std : : vector < std : : string > get_streams ( ) override ;
void play_stream ( int idx ) override ;
void Load ( std : : string filePath ) override ;
2024-03-26 18:39:02 -07:00
double GetPosition ( ) override ;
double GetLength ( ) override ;
void Seek ( double position ) override ;
2024-04-13 12:56:39 -07:00
void Start ( std : : string filePath , int streamIdx = 0 ) override ;
2024-03-26 18:39:02 -07:00
bool IsPaused ( ) override ;
void Pause ( ) override ;
void Stop ( ) override ;
2023-04-24 13:45:06 -07:00
void Update ( ) ;
2024-03-26 18:39:02 -07:00
bool IsStopped ( ) override ;
void SetTempo ( float tempo ) override ;
void SetPitch ( float pitch ) override ;
void SetSpeed ( float speed ) override ;
void SetVolume ( float volume ) override ;
float GetTempo ( ) override ;
float GetPitch ( ) override ;
float GetSpeed ( ) override ;
float GetVolume ( ) override ;
2023-04-24 13:45:06 -07:00
float volume ;
float speed ;
2023-07-15 14:52:49 -07:00
float tempo ;
float pitch ;
2023-12-22 14:13:48 -08:00
float MaxSeconds = 100.0 ;
float MaxSpeed = 4.0 ;
float MaxPitch = 4.0 ;
float MaxTempo = 4.0 ;
float MinSpeed = 0.25 ;
float MinPitch = 0.25 ;
float MinTempo = 0.25 ;
2023-04-24 13:45:06 -07:00
} ;