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() {
// 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;
}

View file

@ -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();
};

View file

@ -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();
}

View file

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

View file

@ -4,17 +4,39 @@
#include <SDL.h>
#include <exception>
#include <thread>
#include <cmath>
#ifdef __linux__
#include <pthread.h>
#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);
}

View file

@ -9,8 +9,10 @@
#include <mutex>
#include <SoundTouch.h>
#include <span>
#include <optional>
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;
};