looper/playback.h

250 lines
9.8 KiB
C
Raw Normal View History

2023-04-24 13:45:06 -07:00
#pragma once
#include <SDL_mixer.h>
2023-04-24 13:45:06 -07:00
#include <thread>
#include <SDL.h>
#include <SDL_audio.h>
2023-04-24 13:45:06 -07:00
#include <string>
#include <atomic>
#include <mutex>
#include <SoundTouch.h>
#include <span>
#include <optional>
#include <vector>
#include <queue>
2024-04-09 10:15:05 -07:00
#include <deque>
#include <map>
using namespace soundtouch;
using std::span;
using std::optional;
using std::vector;
using std::queue;
2024-04-09 10:15:05 -07:00
using std::deque;
enum {
2024-04-09 10:15:05 -07:00
/// @brief No signals have occurred.
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.
PlaybackSignalFileChanged = 1 << 0,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
PlaybackSignalSpeedChanged = 1 << 1,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
PlaybackSignalTempoChanged = 1 << 2,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
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.
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.
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.
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.
PlaybackSignalErrorOccurred = 1 << 7,
2024-04-09 10:15:05 -07:00
/// @brief Playback was seeked by the @ref Playback::Seek function
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.
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-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-09 10:15:05 -07:00
/// @brief Plays a new file.
2024-03-26 18:39:02 -07:00
inline virtual void Start(std::string filePath) {}
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;
std::atomic_bool restart;
std::atomic_bool playback_ready;
std::atomic_bool speed_changed;
std::atomic_bool tempo_changed;
std::atomic_bool pitch_changed;
std::atomic_bool pause_changed;
2023-04-24 13:45:06 -07:00
std::mutex flag_mutex;
std::mutex error_mutex;
2023-04-24 13:45:06 -07:00
std::thread thread;
double position;
double length;
bool paused;
Uint8* buf;
size_t bufsize;
Mix_CommonMixer_t general_mixer;
SDL_AudioDeviceID device;
SoundTouch *st;
SDL_AudioSpec spec;
/// @brief A fake SDL_AudioSpec used to trick SDL Mixer X into allocating a bigger buffer.
SDL_AudioSpec fakespec;
void SDLCallbackInner(Uint8 *stream, int len);
static void SDLCallback(void *userdata, Uint8 *stream, int len);
Mix_Music *Load(const char* file);
void Unload(Mix_Music* music);
2023-04-24 13:45:06 -07:00
void ThreadFunc();
void UpdateST();
double GetMaxSeconds();
queue<std::string> errors;
std::mutex current_file_mutex;
std::optional<std::string> current_file;
2024-03-26 18:39:02 -07:00
std::optional<std::string> current_title;
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-09 10:15:05 -07:00
inline bool is_proxy() override {
return false;
}
2024-03-26 18:39:02 -07:00
double GetPosition() override;
double GetLength() override;
void Seek(double position) override;
void Start(std::string filePath) override;
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;
float tempo;
float pitch;
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
};