looper/playback.h

353 lines
13 KiB
C
Raw Normal View History

2023-04-24 13:45:06 -07:00
#pragma once
#include <SDL_mixer.h>
2024-04-10 18:00:19 -07:00
extern "C" {
#include <vgmstream.h>
}
#ifdef __ANDROID__
#include <oboe/Oboe.h>
#endif
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>
2024-08-08 13:12:37 -07:00
#include "file_backend.hpp"
#include "playback_backend.hpp"
#include "config.hpp"
2024-08-08 13:12:37 -07:00
#include "playback_process.hpp"
#include <google/protobuf/any.h>
2024-10-19 10:19:08 -07:00
#include <ipc/internal.pb.h>
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-13 12:56:39 -07:00
inline virtual int get_current_stream() {
return 0;
}
2024-04-14 13:25:49 -07:00
inline virtual std::vector<PlaybackStream> get_streams() {
std::vector<PlaybackStream> output;
2024-04-13 12:56:39 -07:00
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-24 09:59:51 -07:00
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 {};
2024-04-24 09:59:51 -07:00
}
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();
}
}
2024-10-19 10:19:08 -07:00
virtual std::vector<Property> get_property_list() {
return {};
}
2024-08-08 13:12:37 -07:00
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
#ifdef __ANDROID__
, public oboe::AudioStreamDataCallback
#endif
{
2023-04-24 13:45:06 -07:00
private:
#ifdef __ANDROID__
std::shared_ptr<oboe::AudioStream> ostream;
public:
oboe::DataCallbackResult onAudioReady(
oboe::AudioStream *audioStream,
void *audioData,
int32_t numFrames) override;
private:
#endif
2024-08-08 13:12:37 -07:00
PlaybackProcess *process;
2023-04-24 13:45:06 -07:00
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;
2024-04-13 12:56:39 -07:00
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;
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;
SDL_AudioDeviceID device;
SoundTouch *st;
SDL_AudioSpec spec;
2024-08-08 13:12:37 -07:00
SDL_AudioSpec backend_spec;
SDL_AudioStream *sdl_stream = nullptr;
void SDLCallbackInner(Uint8 *stream, int len);
static void SDLCallback(void *userdata, Uint8 *stream, int len);
2024-08-08 13:12:37 -07:00
void Load(const char *file, int idx);
void Unload();
2024-04-10 18:00:19 -07:00
VGMSTREAM *stream;
2024-04-24 09:59:51 -07:00
Mix_Music *music;
2024-04-14 13:25:49 -07:00
std::vector<PlaybackStream> streams;
2024-04-13 12:56:39 -07:00
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();
void UpdateST();
double GetMaxSeconds();
queue<std::string> errors;
std::mutex current_file_mutex;
2024-04-13 12:56:39 -07:00
int current_stream;
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-08-08 13:12:37 -07:00
FILE_TYPE *file;
2024-09-28 10:31:06 -07:00
bool initial_render = false;
inline void wait(std::function<bool()> fn) {
while (!fn()) {
#ifdef NO_THREADS
LoopHook();
#endif
std::this_thread::sleep_for(20ms);
}
}
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-14 13:25:49 -07:00
std::vector<PlaybackStream> get_streams() override;
2024-04-13 12:56:39 -07:00
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;
2024-04-24 09:59:51 -07:00
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;
2024-10-19 10:19:08 -07:00
std::vector<Property> get_property_list() 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;
2024-08-08 13:12:37 -07:00
};