General improvements, including hidpi support.

This commit is contained in:
Zachary Hall 2023-09-03 11:54:07 -07:00
parent 1bcd761880
commit 70f27722df
6 changed files with 213 additions and 83 deletions

View file

@ -63,6 +63,45 @@ void RendererBackend::SetWindowTitle(const char *title) {
void RendererBackend::GuiFunction() { void RendererBackend::GuiFunction() {
// Do nothing by default. // 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() { int RendererBackend::Run() {
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
bindtextdomain("neko_player", LOCALE_DIR); bindtextdomain("neko_player", LOCALE_DIR);
@ -165,8 +204,7 @@ int RendererBackend::Run() {
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); //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()); //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != nullptr); //IM_ASSERT(font != nullptr);
add_font({FontData {notosans_regular_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_regular_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}); UpdateScale();
title = add_font({FontData {notosans_thin_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_thin_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}, 48);
theme = new Theme(false); theme = new Theme(false);
@ -203,10 +241,13 @@ int RendererBackend::Run() {
done = true; done = true;
if (event.type == SDL_WINDOWEVENT) { if (event.type == SDL_WINDOWEVENT) {
if (event.window.event == SDL_WINDOWEVENT_RESIZED) { if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
window_width = event.window.data1; window_width = event.window.data1 / scale;
window_height = event.window.data2; window_height = event.window.data2 / scale;
//SDL_GetWindowSize(window, &window_width, &window_height); //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)) { if (event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) {
done = true; done = true;
} }

View file

@ -13,6 +13,7 @@
static const char* NAME = "Neko Player"; static const char* NAME = "Neko Player";
class RendererBackend { class RendererBackend {
public: public:
double scale = 1.0;
SDL_Window *window; SDL_Window *window;
int window_width = 475; int window_width = 475;
int window_height = 354; int window_height = 354;
@ -31,6 +32,10 @@ class RendererBackend {
virtual void Init(); virtual void Init();
virtual void GuiFunction(); virtual void GuiFunction();
virtual void Deinit(); virtual void Deinit();
void UpdateScale();
void AddFonts();
void SetWindowSize(int w, int h);
void GetWindowsize(int *w, int *h);
RendererBackend(); RendererBackend();
~RendererBackend(); ~RendererBackend();
}; };

View file

@ -374,7 +374,7 @@ void MainLoop::GuiFunction() {
if (fileDialog.HasSelected()) { if (fileDialog.HasSelected()) {
playback->Start(fileDialog.GetSelected().string()); playback->Start(fileDialog.GetSelected().string());
// Update the window title. // 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. // Make sure to not load the file unnecessarily.
fileDialog.ClearSelected(); fileDialog.ClearSelected();
} }

View file

@ -28,6 +28,7 @@ deps = [
subproject('jsoncpp').get_variable('jsoncpp_dep'), subproject('jsoncpp').get_variable('jsoncpp_dep'),
dependency('soundtouch'), dependency('soundtouch'),
dependency('intl'), dependency('intl'),
dependency('threads'),
smx_subproj.dependency('SDL2_mixer_ext_Static') smx_subproj.dependency('SDL2_mixer_ext_Static')
] ]

View file

@ -4,17 +4,39 @@
#include <SDL.h> #include <SDL.h>
#include <exception> #include <exception>
#include <thread> #include <thread>
#include <cmath>
#ifdef __linux__
#include <pthread.h>
#endif
using namespace std::chrono; using namespace std::chrono;
size_t CalculateBufSize(SDL_AudioSpec *obtained, double max_seconds, size_t samples_override = 0) { size_t CalculateBufSize(SDL_AudioSpec *obtained, double seconds, double max_seconds, size_t samples_override = 0) {
return ((((samples_override == 0) ? obtained->samples : samples_override) * max_seconds) + 1) * sizeof(SAMPLETYPE) * obtained->channels; 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) { 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); 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) { void Playback::SDLCallback(void *userdata, Uint8 *stream, int len) {
((Playback*)userdata)->SDLCallbackInner(stream, len); ((Playback*)userdata)->SDLCallbackInner(stream, len);
@ -34,7 +56,30 @@ void Playback::Unload(Mix_Music *music) {
Mix_HaltMusicStream(music); Mix_HaltMusicStream(music);
Mix_FreeMusic(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() { void Playback::ThreadFunc() {
#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_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
printf("Error initializing SDL: '%s'\n", SDL_GetError()); printf("Error initializing SDL: '%s'\n", SDL_GetError());
@ -55,22 +100,32 @@ void Playback::ThreadFunc() {
desired.callback = Playback::SDLCallback; desired.callback = Playback::SDLCallback;
desired.userdata = this; desired.userdata = this;
st = new SoundTouch(); 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); 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) { 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()); printf("Error opening audio device: '%s'\n", SDL_GetError());
throw std::exception(); throw std::exception();
} }
spec = obtained; spec = obtained;
bufsize = spec.size; st->setSampleRate(spec.freq);
buf = (Uint8*)malloc(bufsize); 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();
}
bufsize = new_bufsize;
general_mixer = Mix_GetGeneralMixer(); general_mixer = Mix_GetGeneralMixer();
Mix_InitMixer(&spec, SDL_TRUE); Mix_InitMixer(&spec, SDL_TRUE);
SDL_PauseAudioDevice(device, 0); SDL_PauseAudioDevice(device, 0);
Mix_Music *music = Load(filePath.c_str()); Mix_Music *music = Load(filePath.c_str());
if (reload) {
while (running) { Seek(position);
}
reload = false;
playback_ready.store(true);
while (running && !reload) {
if (file_changed.exchange(false)) { if (file_changed.exchange(false)) {
Unload(music); Unload(music);
music = Load(filePath.c_str()); music = Load(filePath.c_str());
@ -87,14 +142,25 @@ void Playback::ThreadFunc() {
if (update.exchange(false)) { if (update.exchange(false)) {
Mix_VolumeMusicStream(music, (volume / 100.0 * MIX_MAX_VOLUME)); Mix_VolumeMusicStream(music, (volume / 100.0 * MIX_MAX_VOLUME));
SDL_LockAudioDevice(device); SDL_LockAudioDevice(device);
if (speed > 0.0f) { UpdateST();
st->setRate(speed); 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 (tempo > 0.0f) { if (too_large || too_small) {
st->setTempo(tempo); printf("Resizing buffer...\n");
general_mixer = nullptr;
bufsize = 0;
buf = (Uint8*)realloc((void*)buf, correct_buf_size);
if (buf == NULL) {
break;
} }
if (pitch > 0.0f) { bufsize = correct_buf_size;
st->setPitch(pitch);
} }
SDL_UnlockAudioDevice(device); SDL_UnlockAudioDevice(device);
} }
@ -103,13 +169,16 @@ void Playback::ThreadFunc() {
position = Mix_GetMusicPosition(music); position = Mix_GetMusicPosition(music);
std::this_thread::sleep_for(20ms); std::this_thread::sleep_for(20ms);
} }
playback_ready.store(false);
// ==== // ====
Unload(music); Unload(music);
SDL_CloseAudioDevice(device);
Mix_CloseAudio(); Mix_CloseAudio();
Mix_Quit(); Mix_Quit();
SDL_CloseAudioDevice(device); SDL_QuitSubSystem(SDL_INIT_AUDIO);
delete st; delete st;
free(buf); free(buf);
}
} }
Playback::Playback() { Playback::Playback() {
@ -131,6 +200,8 @@ void Playback::Start(std::string filePath) {
this->filePath = filePath; this->filePath = filePath;
printf("Playing %s...\n", filePath.c_str()); printf("Playing %s...\n", filePath.c_str());
flag_mutex.lock(); flag_mutex.lock();
this->position = 0.0;
seeking.store(true);
paused = false; paused = false;
Update(); Update();
if (running.exchange(true)) { if (running.exchange(true)) {
@ -169,7 +240,6 @@ void Playback::Stop() {
thread.join(); thread.join();
} }
} }
void Playback::Update() { void Playback::Update() {
update.store(true); update.store(true);
} }

View file

@ -9,8 +9,10 @@
#include <mutex> #include <mutex>
#include <SoundTouch.h> #include <SoundTouch.h>
#include <span> #include <span>
#include <optional>
using namespace soundtouch; using namespace soundtouch;
using std::span; using std::span;
using std::optional;
class Playback { class Playback {
private: private:
std::string filePath; std::string filePath;
@ -18,6 +20,8 @@ private:
std::atomic_bool file_changed; std::atomic_bool file_changed;
std::atomic_bool seeking; std::atomic_bool seeking;
std::atomic_bool update; std::atomic_bool update;
std::atomic_bool restart;
std::atomic_bool playback_ready;
std::mutex flag_mutex; std::mutex flag_mutex;
std::thread thread; std::thread thread;
double position; double position;
@ -34,6 +38,8 @@ private:
Mix_Music *Load(const char* file); Mix_Music *Load(const char* file);
void Unload(Mix_Music* music); void Unload(Mix_Music* music);
void ThreadFunc(); void ThreadFunc();
void UpdateST();
double GetMaxSeconds();
public: public:
Playback(); Playback();
~Playback(); ~Playback();
@ -50,4 +56,11 @@ public:
float speed; float speed;
float tempo; float tempo;
float pitch; 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;
}; };