From 70f27722df6f49c6c968f0298e94f8b7d2b375d6 Mon Sep 17 00:00:00 2001 From: Zachary Hall Date: Sun, 3 Sep 2023 11:54:07 -0700 Subject: [PATCH] General improvements, including hidpi support. --- RendererBackend.cpp | 49 +++++++++- RendererBackend.h | 5 + main.cpp | 2 +- meson.build | 1 + playback.cpp | 226 +++++++++++++++++++++++++++++--------------- playback.h | 13 +++ 6 files changed, 213 insertions(+), 83 deletions(-) diff --git a/RendererBackend.cpp b/RendererBackend.cpp index 9ab9e41..6eb6275 100644 --- a/RendererBackend.cpp +++ b/RendererBackend.cpp @@ -63,6 +63,45 @@ void RendererBackend::SetWindowTitle(const char *title) { void RendererBackend::GuiFunction() { // Do nothing by default. } +void RendererBackend::UpdateScale() { + double prevScale = scale; + const double defaultDPI = + #ifdef __APPLE__ + 72.0; + #else + 96.0; + #endif + float dpi = defaultDPI; + if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){ + scale = dpi / defaultDPI; + } else { + printf("WARNING: DPI couldn't be determined!\n"); + scale = 1.0; + } + SDL_SetWindowSize(window, window_width * scale, window_height * scale); + AddFonts(); +} +void RendererBackend::SetWindowSize(int w, int h) { + window_width = w; + window_height = h; + SDL_SetWindowSize(window, w * scale, h * scale); +} +void RendererBackend::GetWindowsize(int *w, int *h) { + int ww, wh; + SDL_GetWindowSize(window, &ww, &wh); + ww /= scale; + wh /= scale; + if (w) *w = ww; + if (h) *h = wh; +} +void RendererBackend::AddFonts() { + ImGui_ImplOpenGL3_DestroyFontsTexture(); + auto& io = ImGui::GetIO(); (void)io; + io.Fonts->Clear(); + add_font({FontData {notosans_regular_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_regular_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}, 13 * scale); + title = add_font({FontData {notosans_thin_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_thin_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}, 48 * scale); + ImGui_ImplOpenGL3_CreateFontsTexture(); +} int RendererBackend::Run() { setlocale(LC_ALL, ""); bindtextdomain("neko_player", LOCALE_DIR); @@ -165,8 +204,7 @@ int RendererBackend::Run() { //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); //IM_ASSERT(font != nullptr); - add_font({FontData {notosans_regular_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_regular_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}); - title = add_font({FontData {notosans_thin_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_thin_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}, 48); + UpdateScale(); theme = new Theme(false); @@ -203,10 +241,13 @@ int RendererBackend::Run() { done = true; if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { - window_width = event.window.data1; - window_height = event.window.data2; + window_width = event.window.data1 / scale; + window_height = event.window.data2 / scale; //SDL_GetWindowSize(window, &window_width, &window_height); } + if (event.window.event == SDL_WINDOWEVENT_DISPLAY_CHANGED) { + UpdateScale(); + } if (event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) { done = true; } diff --git a/RendererBackend.h b/RendererBackend.h index 5f3c9e6..d409c28 100644 --- a/RendererBackend.h +++ b/RendererBackend.h @@ -13,6 +13,7 @@ static const char* NAME = "Neko Player"; class RendererBackend { public: + double scale = 1.0; SDL_Window *window; int window_width = 475; int window_height = 354; @@ -31,6 +32,10 @@ class RendererBackend { virtual void Init(); virtual void GuiFunction(); virtual void Deinit(); + void UpdateScale(); + void AddFonts(); + void SetWindowSize(int w, int h); + void GetWindowsize(int *w, int *h); RendererBackend(); ~RendererBackend(); }; \ No newline at end of file diff --git a/main.cpp b/main.cpp index 8f96ddc..f0e2243 100644 --- a/main.cpp +++ b/main.cpp @@ -374,7 +374,7 @@ void MainLoop::GuiFunction() { if (fileDialog.HasSelected()) { playback->Start(fileDialog.GetSelected().string()); // Update the window title. - SetWindowTitle((fileDialog.GetSelected().filename().replace_extension("").string() + std::string(" - ") + std::string(NAME)).c_str()); + SetWindowTitle((fileDialog.GetSelected().filename().string() + std::string(" - ") + std::string(NAME)).c_str()); // Make sure to not load the file unnecessarily. fileDialog.ClearSelected(); } diff --git a/meson.build b/meson.build index b808d2a..36cff65 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ deps = [ subproject('jsoncpp').get_variable('jsoncpp_dep'), dependency('soundtouch'), dependency('intl'), + dependency('threads'), smx_subproj.dependency('SDL2_mixer_ext_Static') ] diff --git a/playback.cpp b/playback.cpp index 61386aa..81c51a2 100644 --- a/playback.cpp +++ b/playback.cpp @@ -4,17 +4,39 @@ #include #include #include +#include +#ifdef __linux__ +#include +#endif using namespace std::chrono; -size_t CalculateBufSize(SDL_AudioSpec *obtained, double max_seconds, size_t samples_override = 0) { - return ((((samples_override == 0) ? obtained->samples : samples_override) * max_seconds) + 1) * sizeof(SAMPLETYPE) * obtained->channels; +size_t CalculateBufSize(SDL_AudioSpec *obtained, double seconds, double max_seconds, size_t samples_override = 0) { + return ((((samples_override == 0) ? obtained->samples : samples_override) * std::min(seconds, max_seconds)) + 1) * sizeof(SAMPLETYPE) * obtained->channels; } void Playback::SDLCallbackInner(Uint8 *stream, int len) { - while (st->numSamples() <= (uint)len) { - general_mixer(NULL, buf, bufsize); - st->putSamples((SAMPLETYPE*)buf, bufsize / sizeof(SAMPLETYPE) / spec.channels); - } SDL_memset((void*)stream, 0, len); - st->receiveSamples((SAMPLETYPE*)stream, len / sizeof(SAMPLETYPE) / spec.channels); + if (!playback_ready.load()) { + return; + } + size_t i = 0; + size_t max = 0; + size_t unit = sizeof(SAMPLETYPE) * spec.channels; + size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)len); + while (st->numSamples() <= (uint)len) { + if (general_mixer == nullptr) { + return; + } + general_mixer(NULL, buf + i, bytes_per_iter); + i += bytes_per_iter; + max = i + bytes_per_iter; + if (i + bytes_per_iter >= bufsize) { + st->putSamples((SAMPLETYPE*)buf, i / unit); + max = 0; + i = 0; + SDL_memset((void*)buf, 0, bufsize); + } + } + st->putSamples((SAMPLETYPE*)buf, max / unit); + st->receiveSamples((SAMPLETYPE*)stream, len / unit); } void Playback::SDLCallback(void *userdata, Uint8 *stream, int len) { ((Playback*)userdata)->SDLCallbackInner(stream, len); @@ -34,82 +56,129 @@ void Playback::Unload(Mix_Music *music) { Mix_HaltMusicStream(music); Mix_FreeMusic(music); } +void Playback::UpdateST() { + if (speed > 0.0f) { + st->setRate(speed); + } + if (tempo > 0.0f) { + st->setTempo(tempo); + } + if (pitch > 0.0f) { + st->setPitch(pitch); + } +} +double Playback::GetMaxSeconds() { + return std::max(MaxSpeed * MaxTempo, st->getInputOutputSampleRatio()); +} void Playback::ThreadFunc() { - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - printf("Error initializing SDL: '%s'\n", SDL_GetError()); + #ifdef __linux__ + pthread_setname_np(pthread_self(), "Playback control thread"); + #endif + bool reload = false; + while (running) { + playback_ready.store(false); + if (reload) { + printf("Resuming playback...\n"); + } + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + printf("Error initializing SDL: '%s'\n", SDL_GetError()); + throw std::exception(); + } + } + SDL_AudioSpec obtained; + SDL_AudioSpec desired; + desired.format = + #ifdef SOUNDTOUCH_INTEGER_SAMPLES + AUDIO_S16SYS; + #else + AUDIO_F32SYS; + #endif + desired.freq = 48000; + desired.samples = 1024; + desired.channels = 2; + desired.callback = Playback::SDLCallback; + desired.userdata = this; + st = new SoundTouch(); + Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK); + if ((device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) { + printf("Error opening audio device: '%s'\n", SDL_GetError()); throw std::exception(); } - } - SDL_AudioSpec obtained; - SDL_AudioSpec desired; - desired.format = - #ifdef SOUNDTOUCH_INTEGER_SAMPLES - AUDIO_S16SYS; - #else - AUDIO_F32SYS; - #endif - desired.freq = 48000; - desired.samples = 1024; - desired.channels = 2; - desired.callback = Playback::SDLCallback; - desired.userdata = this; - st = new SoundTouch(); - st->setSampleRate(desired.freq); - st->setChannels(desired.channels); - Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK); - if ((device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0)) == 0) { - printf("Error opening audio device: '%s'\n", SDL_GetError()); - throw std::exception(); - } - spec = obtained; - bufsize = spec.size; - buf = (Uint8*)malloc(bufsize); - general_mixer = Mix_GetGeneralMixer(); - Mix_InitMixer(&spec, SDL_TRUE); - SDL_PauseAudioDevice(device, 0); - Mix_Music *music = Load(filePath.c_str()); - - while (running) { - if (file_changed.exchange(false)) { - Unload(music); - music = Load(filePath.c_str()); + spec = obtained; + st->setSampleRate(spec.freq); + st->setChannels(spec.channels); + UpdateST(); + bufsize = 0; + size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds); + buf = (Uint8*)malloc(new_bufsize); + if (buf == NULL) { + throw std::exception(); } - if (flag_mutex.try_lock()) { - if (seeking.exchange(false)) { - Mix_SetMusicPositionStream(music, position); - } - if (paused) { - Mix_PauseMusicStream(music); - } else { - Mix_ResumeMusicStream(music); - } - if (update.exchange(false)) { - Mix_VolumeMusicStream(music, (volume / 100.0 * MIX_MAX_VOLUME)); - SDL_LockAudioDevice(device); - if (speed > 0.0f) { - st->setRate(speed); - } - if (tempo > 0.0f) { - st->setTempo(tempo); - } - if (pitch > 0.0f) { - st->setPitch(pitch); - } - SDL_UnlockAudioDevice(device); - } - flag_mutex.unlock(); + bufsize = new_bufsize; + general_mixer = Mix_GetGeneralMixer(); + Mix_InitMixer(&spec, SDL_TRUE); + SDL_PauseAudioDevice(device, 0); + Mix_Music *music = Load(filePath.c_str()); + if (reload) { + Seek(position); } - position = Mix_GetMusicPosition(music); - std::this_thread::sleep_for(20ms); + reload = false; + playback_ready.store(true); + while (running && !reload) { + if (file_changed.exchange(false)) { + Unload(music); + music = Load(filePath.c_str()); + } + if (flag_mutex.try_lock()) { + if (seeking.exchange(false)) { + Mix_SetMusicPositionStream(music, position); + } + if (paused) { + Mix_PauseMusicStream(music); + } else { + Mix_ResumeMusicStream(music); + } + if (update.exchange(false)) { + Mix_VolumeMusicStream(music, (volume / 100.0 * MIX_MAX_VOLUME)); + SDL_LockAudioDevice(device); + UpdateST(); + size_t correct_buf_size = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds); + size_t max_buf_size = correct_buf_size * 10; + bool too_large = max_buf_size < bufsize; + bool too_small = correct_buf_size > bufsize; + if (too_large) { + printf("Bufsize is too large - "); + } else if (too_small) { + printf("Bufsize is too small - "); + } + if (too_large || too_small) { + printf("Resizing buffer...\n"); + general_mixer = nullptr; + bufsize = 0; + buf = (Uint8*)realloc((void*)buf, correct_buf_size); + if (buf == NULL) { + break; + } + bufsize = correct_buf_size; + } + SDL_UnlockAudioDevice(device); + } + flag_mutex.unlock(); + } + position = Mix_GetMusicPosition(music); + std::this_thread::sleep_for(20ms); + } + playback_ready.store(false); + // ==== + Unload(music); + SDL_CloseAudioDevice(device); + Mix_CloseAudio(); + Mix_Quit(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + delete st; + free(buf); } - // ==== - Unload(music); - Mix_CloseAudio(); - Mix_Quit(); - SDL_CloseAudioDevice(device); - delete st; - free(buf); } Playback::Playback() { @@ -131,6 +200,8 @@ void Playback::Start(std::string filePath) { this->filePath = filePath; printf("Playing %s...\n", filePath.c_str()); flag_mutex.lock(); + this->position = 0.0; + seeking.store(true); paused = false; Update(); if (running.exchange(true)) { @@ -169,7 +240,6 @@ void Playback::Stop() { thread.join(); } } - void Playback::Update() { update.store(true); } diff --git a/playback.h b/playback.h index 47f7d73..dfb7b87 100644 --- a/playback.h +++ b/playback.h @@ -9,8 +9,10 @@ #include #include #include +#include using namespace soundtouch; using std::span; +using std::optional; class Playback { private: std::string filePath; @@ -18,6 +20,8 @@ private: std::atomic_bool file_changed; std::atomic_bool seeking; std::atomic_bool update; + std::atomic_bool restart; + std::atomic_bool playback_ready; std::mutex flag_mutex; std::thread thread; double position; @@ -34,6 +38,8 @@ private: Mix_Music *Load(const char* file); void Unload(Mix_Music* music); void ThreadFunc(); + void UpdateST(); + double GetMaxSeconds(); public: Playback(); ~Playback(); @@ -50,4 +56,11 @@ public: float speed; float tempo; float pitch; + double MaxSeconds = 100.0; + double MaxSpeed = 4.0; + double MaxPitch = 4.0; + double MaxTempo = 4.0; + double MinSpeed = 0.25; + double MinPitch = 0.25; + double MinTempo = 0.25; }; \ No newline at end of file