diff --git a/config.hpp b/config.hpp new file mode 100644 index 0000000..90692c9 --- /dev/null +++ b/config.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#if defined(__EMSCRIPTEN__)||defined(__ANDROID__) +#define NO_THREADS +#endif + +#ifdef SOUNDTOUCH_INTEGER_SAMPLES +#define SDL_SAMPLE_FMT AUDIO_S16SYS +#else +#define SDL_SAMPLE_FMT AUDIO_F32SYS +#endif +#ifdef __ANDROID__ +#define USE_OBOE +#ifdef SOUNDTOUCH_INTEGER_SAMPLES +#define SAMPLE_FMT oboe::AudioFormat::I16 +#else +#define SAMPLE_FMT oboe::AudioFormat::Float +#endif +#else +#define SAMPLE_FMT SDL_SAMPLE_FMT +#define USE_SDL +#endif \ No newline at end of file diff --git a/playback.cpp b/playback.cpp index b6c35b7..20aa17c 100644 --- a/playback.cpp +++ b/playback.cpp @@ -135,6 +135,7 @@ void PlaybackInstance::SDLCallback(void *userdata, Uint8 *stream, int len) { } void PlaybackInstance::Load(const char *file, int idx) { SDL_LockAudioDevice(device); + load_finished.store(false); playback_ready.store(false); if (process != nullptr) delete process; try { @@ -188,7 +189,6 @@ void PlaybackInstance::Load(const char *file, int idx) { } delete backend_spec_proxy; playback_ready.store(true); - load_finished.store(true); this->process->set_position(0.0); } else { set_error("Failed to create playback backend."); @@ -196,8 +196,13 @@ void PlaybackInstance::Load(const char *file, int idx) { delete process; process = nullptr; } - initial_render = true; SDL_UnlockAudioDevice(device); + load_finished.store(true); + flag_mutex.lock(); + stopped.store(false); + paused = false; + just_started.store(true); + flag_mutex.unlock(); } void PlaybackInstance::Unload() { if (process == nullptr) return; @@ -206,7 +211,8 @@ void PlaybackInstance::Unload() { process = nullptr; SDL_FreeAudioStream(sdl_stream); sdl_stream = nullptr; - free(buf); + if (buf) free(buf); + buf = nullptr; SDL_UnlockAudioDevice(device); } void PlaybackInstance::UpdateST() { @@ -249,19 +255,14 @@ void PlaybackInstance::InitLoopFunction() { } SDL_AudioSpec obtained; SDL_AudioSpec desired; - desired.format = - #ifdef SOUNDTOUCH_INTEGER_SAMPLES - AUDIO_S16SYS; - #else - AUDIO_F32SYS; - #endif + desired.format = SDL_SAMPLE_FMT; desired.freq = 48000; desired.samples = 200; desired.channels = 2; desired.callback = PlaybackInstance::SDLCallback; desired.userdata = this; st = new SoundTouch(); -#ifndef __ANDROID__ +#ifdef USE_SDL if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) { ERROR.writefln("Error opening audio device: '%s'", SDL_GetError()); set_error("Failed to open audio device!"); @@ -269,18 +270,13 @@ void PlaybackInstance::InitLoopFunction() { loop_started = false; return; } -#else + SDL_PauseAudioDevice(device, 0); +#elif defined(USE_OBOE) oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Output); builder.setSharingMode(oboe::SharingMode::Shared); builder.setPerformanceMode(oboe::PerformanceMode::None); - builder.setFormat( -#ifdef SOUNDTOUCH_INTEGER_SAMPLES - oboe::AudioFormat::I16 -#else - oboe::AudioFormat::Float -#endif - ); + builder.setFormat(SAMPLE_FMT); builder.setDataCallback(this); auto res = builder.openStream(ostream); if (res != oboe::Result::OK) { @@ -318,25 +314,46 @@ Total samples: %u" , obtained.samples ); ostream->requestStart(); +#else +#error Invalid configuration detected. #endif spec = obtained; st->setSampleRate(spec.freq); st->setChannels(spec.channels); UpdateST(); - SDL_PauseAudioDevice(device, 0); - Load(filePath.c_str(), 0); reload = false; - if (process && process->process_running()) { - playback_ready.store(true); - } else { - playback_ready.store(false); - } - load_finished.store(true); + playback_ready.store(false); + flag_mutex.lock(); + this->position = 0.0; + seeking.store(false); + paused = true; + flag_mutex.unlock(); + playback_ready.store(false); + length = 0.0; + memset(&backend_spec, 0, sizeof(backend_spec)); + if (sdl_stream) SDL_FreeAudioStream(sdl_stream); + sdl_stream = nullptr; + bufsize = 0; + if (buf) free((void*)buf); + buf = nullptr; + if (process) delete process; + process = nullptr; + stream_changed.store(false); + file_changed.store(false); + load_requested.store(false); + current_file = {}; + current_stream = -1; + current_title = {}; + seeking.store(false); + pause_changed.store(false); + just_stopped.store(true); + just_started.store(false); + stopped.store(true); set_signal(PlaybackSignalStarted); + load_finished.store(true); } void PlaybackInstance::LoopFunction() { - if (file_changed.exchange(false) || load_requested.exchange(false)) { Unload(); Load(filePath.c_str(), 0); @@ -345,6 +362,8 @@ void PlaybackInstance::LoopFunction() { } else { playback_ready.store(false); } + load_finished.store(true); + just_started.store(true); } if (stream_changed.exchange(false)) { current_file_mutex.lock(); @@ -375,8 +394,8 @@ void PlaybackInstance::LoopFunction() { if (process && process->process_running()) process->set_position(this->position); set_signal(PlaybackSignalSeeked); } - if (pause_changed.exchange(false)) { - SDL_PauseAudioDevice(device, paused ? 1 : 0); + if (pause_changed.exchange(false) || just_stopped.exchange(false) || just_started.exchange(false)) { + SDL_PauseAudioDevice(device, (stopped.load() || paused) ? 1 : 0); if (paused) { set_signal(PlaybackSignalPaused); } else { @@ -402,9 +421,9 @@ void PlaybackInstance::DeinitLoopFunction() { playback_ready.store(false); // ==== Unload(); -#ifndef __ANDROID__ +#ifdef USE_SDL SDL_CloseAudioDevice(device); -#else +#elif defined(USE_OBOE) if (ostream && ostream->getState() != oboe::StreamState::Closed) { ostream->stop(); ostream->close(); @@ -413,7 +432,8 @@ void PlaybackInstance::DeinitLoopFunction() { #endif SDL_QuitSubSystem(SDL_INIT_AUDIO); delete st; - free(buf); + st = nullptr; + if (buf) free(buf); current_file_mutex.lock(); current_file = {}; current_file_mutex.unlock(); @@ -435,14 +455,17 @@ PlaybackInstance::PlaybackInstance() { running = false; paused = true; position = 0; + sdl_stream = nullptr; length = 0; volume = 100.0; + real_volume = 1.0; speed = 1.0; pitch = 1.0; tempo = 1.0; prev_speed = -1.0; prev_pitch = -1.0; prev_tempo = -1.0; + buf = nullptr; tempo_changed.store(true); speed_changed.store(true); pitch_changed.store(true); @@ -450,6 +473,21 @@ PlaybackInstance::PlaybackInstance() { playback_ready = false; bufsize = 0; process = nullptr; + running.store(true); + #if NO_THREADS + start_loop(); + #else + thread = std::thread(&PlaybackInstance::ThreadFunc, this); + loop_started = true; + #endif + while (loop_started && !load_finished.exchange(false)) { + #if NO_THREADS + LoopHook(); + #endif + std::this_thread::sleep_for(20ms); + } + load_finished.store(false); + } std::optional PlaybackInstance::get_current_file() { current_file_mutex.lock(); @@ -465,38 +503,31 @@ std::optional PlaybackInstance::get_current_title() { } PlaybackInstance::~PlaybackInstance() { Stop(); + running.store(false); + #ifdef NO_THREADS + stop_loop(); + #else + thread.join(); + #endif } void PlaybackInstance::Load(std::string filePath) { + load_finished.store(false); this->filePath = filePath; INFO.writefln("Loading %s...", filePath.c_str()); - if (running.exchange(true)) { - load_requested.store(true); - } else { - #if defined(__EMSCRIPTEN__)||defined(__ANDROID__) - start_loop(); - #else - thread = std::thread(&PlaybackInstance::ThreadFunc, this); - loop_started = true; - #endif - } flag_mutex.lock(); this->position = 0.0; seeking.store(true); paused = true; Update(); flag_mutex.unlock(); + file_changed.store(true); + wait([this]() { + return load_finished.load(); + }); } void PlaybackInstance::Start(std::string filePath, int streamIdx) { + load_finished.store(false); Load(filePath); - while (loop_started && !load_finished.exchange(false)) { - #if defined(__EMSCRIPTEN__)||defined(__ANDROID__) - LoopHook(); - #endif - std::this_thread::sleep_for(20ms); - } - if (!loop_started) { - return; - } INFO.writefln("Playing %s...", filePath.c_str()); flag_mutex.lock(); this->position = 0.0; @@ -506,6 +537,9 @@ void PlaybackInstance::Start(std::string filePath, int streamIdx) { stream_changed.store(true); Update(); flag_mutex.unlock(); + wait([this]() { + return load_finished.load(); + }); } void PlaybackInstance::play_stream(int idx) { flag_mutex.lock(); @@ -515,6 +549,7 @@ void PlaybackInstance::play_stream(int idx) { current_stream = idx; stream_changed.store(true); Update(); + just_started.store(true); flag_mutex.unlock(); } double PlaybackInstance::GetPosition() { @@ -543,13 +578,11 @@ bool PlaybackInstance::IsPaused() { } void PlaybackInstance::Stop() { - if (running.exchange(false)) { - #if defined(__EMSCRIPTEN__)||defined(__ANDROID__) - stop_loop(); - #else - thread.join(); - #endif - } + flag_mutex.lock(); + stopped.store(true); + just_started.store(false); + just_stopped.store(true); + flag_mutex.unlock(); } void PlaybackInstance::Update() { if (prev_pitch != pitch) { diff --git a/playback.h b/playback.h index 9798626..15bb366 100644 --- a/playback.h +++ b/playback.h @@ -21,6 +21,7 @@ extern "C" { #include #include "file_backend.hpp" #include "playback_backend.hpp" +#include "config.hpp" #include "playback_process.hpp" using namespace soundtouch; using std::span; @@ -243,6 +244,9 @@ private: 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; @@ -276,6 +280,14 @@ private: float prev_pitch, prev_speed, prev_tempo; FILE_TYPE *file; bool initial_render = false; + inline void wait(std::function fn) { + while (!fn()) { + #ifdef NO_THREADS + LoopHook(); + #endif + std::this_thread::sleep_for(20ms); + } + } public: PlaybackInstance(); ~PlaybackInstance() override;