looper/playback.h
Zachary Hall 27113f6443
Some checks failed
Build / build-gentoo (push) Failing after 22s
Build / download-system-deps (push) Successful in 3m16s
Build / get-source-code (push) Successful in 12m8s
Build / build-appimage (push) Successful in 3m58s
Build / build-android (push) Failing after 2m59s
Build / build-windows (push) Failing after 7m22s
WTF - Asserting the return value of a function causes it to not run?!
2024-12-08 09:55:27 -08:00

357 lines
13 KiB
C++

#pragma once
#ifdef __ANDROID__
#include <oboe/Oboe.h>
#endif
#include <thread>
#include <SDL.h>
#include <SDL_audio.h>
#include <string>
#include <atomic>
#include <mutex>
#include <SoundTouch.h>
#include <span>
#include <optional>
#include <vector>
#include <queue>
#include <deque>
#include <map>
#include "file_backend.hpp"
#include "playback_backend.hpp"
#include "config.hpp"
#include "playback_process.hpp"
#include <google/protobuf/any.h>
#include <ipc/internal.pb.h>
using namespace soundtouch;
using std::span;
using std::optional;
using std::vector;
using std::queue;
using std::deque;
enum {
/// @brief No signals have occurred.
PlaybackSignalNone = 0,
/// @brief The file was changed. Recheck the properties of the file because they are likely different.
PlaybackSignalFileChanged = 1 << 0,
/// @brief The speed was changed.
PlaybackSignalSpeedChanged = 1 << 1,
/// @brief The speed was changed.
PlaybackSignalTempoChanged = 1 << 2,
/// @brief The speed was changed.
PlaybackSignalPitchChanged = 1 << 3,
/// @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,
/// @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,
/// @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,
/// @brief An error occurred and playback has likely (but not necessarily) stopped. Call @ref Playback::GetError for details.
PlaybackSignalErrorOccurred = 1 << 7,
/// @brief Playback was seeked by the @ref Playback::Seek function
PlaybackSignalSeeked = 1 << 8,
/// @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
};
/// @brief Playback handler base class.
class Playback {
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);
public:
inline virtual bool is_proxy() {
return false;
}
inline Playback() {};
inline virtual ~Playback() {}
/// @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.
inline virtual std::optional<std::string> get_current_file() {
return {};
}
/// @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.
inline virtual std::optional<std::string> get_current_title() {
return {};
}
inline virtual int get_current_stream() {
return 0;
}
inline virtual std::vector<PlaybackStream> get_streams() {
std::vector<PlaybackStream> output;
return output;
}
inline virtual void play_stream(int idx) {}
/// @brief Gets the position of the playing file.
inline virtual double GetPosition() {
return 0.0;
}
/// @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.
inline virtual double GetLength() {
return 0.0;
}
/// @brief Sets the playback position into the current file.
inline virtual void Seek(double position) {}
/// @brief Loads a new file.
inline virtual void Load(std::string filePath) {}
/// @brief Plays a new file.
inline virtual void Start(std::string filePath, int streamIdx = 0) {}
/// @brief Checks whether or not the file is paused.
/// @returns true if playback is paused, false otherwise
inline virtual bool IsPaused() {
return true;
}
/// @brief Toggles the pause state of the playback.
inline virtual void Pause() {}
/// @brief Stops all playback.
inline virtual void Stop() {}
/// @brief Checks whether or not playback is stopped.
/// @returns true if playback is stopped, false otherwise.
inline virtual bool IsStopped() {
return true;
}
/// @brief Sets the tempo multiplier of the playback.
inline virtual void SetTempo(float tempo) {}
/// @brief Sets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
inline virtual void SetSpeed(float speed) {}
/// @brief Sets the pitch multiplier of the playback
inline virtual void SetPitch(float pitch) {}
/// @brief Sets the volume of the playback, as a percentage
inline virtual void SetVolume(float volume) {}
/// @brief Gets the tempo multiplier of the playback.
inline virtual float GetTempo() {
return 1.0;
}
/// @brief Gets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
inline virtual float GetSpeed() {
return 1.0;
}
/// @brief Gets the pitch multiplier of the playback.
inline virtual float GetPitch() {
return 1.0;
}
/// @brief Gets the volume of the playback, as a percentage
inline virtual float GetVolume() {
return 1.0;
}
/// @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;
}
/// @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;
}
/// @brief Helper function to set the paused status via the @ref Playback::IsPaused and @ref Playback::Pause APIs.
/// @param paused The new pause state.
inline virtual void SetPaused(bool paused) {
if (IsPaused() != paused) {
Pause();
}
}
virtual void InitLoopFunction() {
}
virtual void LoopFunction() {
}
virtual void DeinitLoopFunction() {
}
virtual void set_property(std::string path, google::protobuf::Any value) {
}
virtual std::optional<google::protobuf::Any> get_property(std::string path) {
return {};
}
virtual std::optional<google::protobuf::Any> reset_property(std::string path) {
return {};
}
bool loop_started = false;
virtual void start_loop() {
InitLoopFunction();
loop_started = true;
}
virtual void stop_loop() {
DeinitLoopFunction();
loop_started = false;
}
virtual void LoopHook() {
if (loop_started) {
LoopFunction();
}
}
virtual std::vector<Property> get_property_list() {
return {};
}
inline virtual void Render(std::string path) { }
static Playback *Create(bool *daemon_found, bool daemon = false);
};
class DBusAPISender;
class PlaybackInstance : public Playback
#ifdef __ANDROID__
, public oboe::AudioStreamDataCallback
#endif
{
private:
#ifdef __ANDROID__
std::shared_ptr<oboe::AudioStream> ostream;
public:
oboe::DataCallbackResult onAudioReady(
oboe::AudioStream *audioStream,
void *audioData,
int32_t numFrames) override;
private:
#endif
PlaybackProcess *process;
std::string filePath;
#ifndef USE_SDL
std::mutex audio_playback_mutex;
#endif
std::atomic_bool audio_playback_paused;
void LockAudioDevice();
void UnlockAudioDevice();
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;
std::atomic_bool load_requested;
std::atomic_bool load_finished;
std::atomic_bool stream_changed;
std::atomic_bool stopped;
std::atomic_bool just_stopped;
std::atomic_bool just_started;
std::mutex flag_mutex;
std::mutex error_mutex;
std::thread thread;
double position;
double length;
bool paused;
Uint8* buf;
size_t bufsize;
SDL_AudioDeviceID device;
SoundTouch *st;
SDL_AudioSpec spec;
SDL_AudioSpec backend_spec;
SDL_AudioStream *sdl_stream = nullptr;
void SDLCallbackInner(Uint8 *stream, int len);
static void SDLCallback(void *userdata, Uint8 *stream, int len);
void Load(const char *file, int idx);
void Unload();
std::vector<PlaybackStream> streams;
std::mutex stream_list_mutex;
double real_volume = 1.0;
void ThreadFunc();
void UpdateST();
double GetMaxSeconds();
queue<std::string> errors;
std::mutex current_file_mutex;
int current_stream;
std::optional<std::string> current_file;
std::optional<std::string> current_title;
float prev_pitch, prev_speed, prev_tempo;
FILE_TYPE *file;
bool initial_render = false;
std::mutex output_file_mutex;
std::optional<std::string> output_file;
inline void wait(std::function<bool()> fn) {
while (!fn()) {
#ifdef NO_THREADS
LoopHook();
#endif
std::this_thread::sleep_for(20ms);
}
}
public:
PlaybackInstance();
~PlaybackInstance() override;
std::optional<std::string> get_current_file() override;
std::optional<std::string> get_current_title() override;
int get_current_stream() override;
inline bool is_proxy() override {
return false;
}
std::vector<PlaybackStream> get_streams() override;
void play_stream(int idx) override;
void Load(std::string filePath) override;
double GetPosition() override;
double GetLength() override;
void Seek(double position) override;
void Start(std::string filePath, int streamIdx = 0) override;
bool IsPaused() override;
void Pause() override;
void Stop() override;
void Update();
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;
void InitLoopFunction() override;
void DeinitLoopFunction() override;
void LoopFunction() override;
void set_property(std::string path, google::protobuf::Any) override;
std::optional<google::protobuf::Any> get_property(std::string path) override;
std::optional<google::protobuf::Any> reset_property(std::string path) override;
std::vector<Property> get_property_list() override;
void Render(std::string opath) override;
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;
};