Skip to content

Commit

Permalink
gui/themes: Add support of BGM.
Browse files Browse the repository at this point in the history
config & gui/settings dialog: Add control of bgm volume.
  • Loading branch information
Zangetsu38 committed Oct 24, 2024
1 parent 52b82dd commit 53ec272
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 5 deletions.
1 change: 1 addition & 0 deletions vita3k/config/include/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ enum ScreenshotFormat {
code(std::string, "audio-backend", "SDL", audio_backend) \
code(int, "audio-volume", 100, audio_volume) \
code(bool, "ngs-enable", true, ngs_enable) \
code(int, "bgm-volume", 100, bgm_volume) \
code(int, "sys-button", static_cast<int>(SCE_SYSTEM_PARAM_ENTER_BUTTON_CROSS), sys_button) \
code(int, "sys-lang", static_cast<int>(SCE_SYSTEM_PARAM_LANG_ENGLISH_US), sys_lang) \
code(int, "sys-date-format", (int)SCE_SYSTEM_PARAM_DATE_FORMAT_MMDDYYYY, sys_date_format) \
Expand Down
4 changes: 2 additions & 2 deletions vita3k/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ add_library(
)

target_include_directories(gui PUBLIC include ${CMAKE_SOURCE_DIR}/vita3k)
target_link_libraries(gui PUBLIC app compat config dialog emuenv ime imgui lang regmgr np)
target_link_libraries(gui PRIVATE audio cppcommon ctrl kernel miniz psvpfsparser pugixml::pugixml stb renderer packages sdl2 touch vkutil host::dialog concurrentqueue)
target_link_libraries(gui PUBLIC app codec compat config dialog emuenv ime imgui lang regmgr np)
target_link_libraries(gui PRIVATE audio cubeb cppcommon ctrl kernel miniz psvpfsparser pugixml::pugixml stb renderer packages sdl2 touch vkutil host::dialog concurrentqueue)
target_link_libraries(gui PUBLIC tracy)
4 changes: 4 additions & 0 deletions vita3k/gui/include/gui/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void init(GuiState &gui, EmuEnvState &emuenv);
void init_app_background(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path);
void init_app_icon(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path);
void init_apps_icon(GuiState &gui, EmuEnvState &emuenv, const std::vector<gui::App> &app_list);
void init_bgm_player(const float vol);
void init_config(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path);
void init_content_manager(GuiState &gui, EmuEnvState &emuenv);
vfs::FileBuffer init_default_icon(GuiState &gui, EmuEnvState &emuenv);
Expand Down Expand Up @@ -103,6 +104,9 @@ void save_apps_cache(GuiState &gui, EmuEnvState &emuenv);
void save_user(GuiState &gui, EmuEnvState &emuenv, const std::string &user_id);
void set_config(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path);
void set_shaders_compiled_display(GuiState &gui, EmuEnvState &emuenv);
void set_bgm_volume(const float volume);
void stop_bgm_theme();
void switch_bgm_state(const bool pause);
void update_app(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path);
void update_last_time_app_used(GuiState &gui, EmuEnvState &emuenv, const std::string &app);
void update_live_area_current_open_apps_list(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path);
Expand Down
2 changes: 1 addition & 1 deletion vita3k/gui/src/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,9 @@ void init_home(GuiState &gui, EmuEnvState &emuenv) {
}

init_app_background(gui, emuenv, "NPXS10015");
init_bgm_player(emuenv.cfg.bgm_volume / 100.f);

regmgr::init_regmgr(emuenv.regmgr, emuenv.pref_path);

const auto is_cmd = emuenv.cfg.run_app_path || emuenv.cfg.content_path;
if (!gui.users.empty() && gui.users.contains(emuenv.cfg.user_id) && (is_cmd || emuenv.cfg.auto_user_login)) {
init_user(gui, emuenv, emuenv.cfg.user_id);
Expand Down
3 changes: 3 additions & 0 deletions vita3k/gui/src/home_screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ void pre_load_app(GuiState &gui, EmuEnvState &emuenv, bool live_area, const std:
}

void pre_run_app(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) {
switch_bgm_state(true);
const auto is_sys = app_path.starts_with("NPXS") && (app_path != "NPXS10007");
if (!is_sys) {
if (emuenv.io.app_path != app_path) {
Expand Down Expand Up @@ -213,6 +214,7 @@ void close_system_app(GuiState &gui, EmuEnvState &emuenv) {
gui.vita_area.information_bar = true;
gui.vita_area.home_screen = true;
}
switch_bgm_state(false);
}

void close_and_run_new_app(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) {
Expand Down Expand Up @@ -544,6 +546,7 @@ void draw_home_screen(GuiState &gui, EmuEnvState &emuenv) {
gui.vita_area.home_screen = false;
gui.vita_area.information_bar = true;
gui.vita_area.start_screen = true;
switch_bgm_state(true);
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion vita3k/gui/src/settings_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ static void save_config(GuiState &gui, EmuEnvState &emuenv) {
app::update_viewport(emuenv);
}

set_bgm_volume(emuenv.cfg.bgm_volume / 100.f);
config::serialize_config(emuenv.cfg, emuenv.cfg.config_path);
}

Expand Down Expand Up @@ -813,6 +814,8 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
ImGui::Checkbox(lang.audio["enable_ngs_support"].c_str(), &config.ngs_enable);
SetTooltipEx(lang.audio["ngs_description"].c_str());
ImGui::Spacing();
ImGui::SliderInt("Bgm Volume", &emuenv.cfg.bgm_volume, 0, 100, "%d %%", ImGuiSliderFlags_AlwaysClamp);
SetTooltipEx("Adjusts the background music volume percentage of the theme");
ImGui::Separator();
ImGui::Spacing();
ImGui::EndTabItem();
Expand Down Expand Up @@ -1065,7 +1068,7 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
SetTooltipEx(lang.gui["select_delay_background"].c_str());
}
ImGui::Spacing();
ImGui::SliderInt(lang.gui["delay_start"].c_str(), &emuenv.cfg.delay_start, 10, 60);
ImGui::SliderInt(lang.gui["delay_start"].c_str(), &emuenv.cfg.delay_start, 10, 240);
SetTooltipEx(lang.gui["select_delay_start"].c_str());
ImGui::EndTabItem();
} else
Expand Down
247 changes: 246 additions & 1 deletion vita3k/gui/src/themes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

#include "private.h"

#include <audio/state.h>
#include <codec/state.h>
#include <config/state.h>
#include <cubeb/cubeb.h>
#include <gui/functions.h>
#include <io/VitaIoDevice.h>
#include <io/state.h>
Expand Down Expand Up @@ -211,6 +214,236 @@ bool init_user_start_background(GuiState &gui, const std::string &image_path) {
return gui.start_background;
}

// Structure to store PCM data
struct PcmData {
std::vector<uint8_t> data;
uint32_t position;
std::mutex mutex;
};

static long data_callback(cubeb_stream *stream, void *user_ptr, const void *input_buffer, void *output_buffer, long nframes) {
PcmData *pcm = static_cast<PcmData *>(user_ptr);
// Lock the mutex to access pcm->position and pcm->data
std::lock_guard<std::mutex> lock(pcm->mutex);

// Calculate the number of bytes to copy (2 bytes per sample, 2 channels) and the length of the PCM data
const uint32_t bytes_to_copy = nframes * 2 * sizeof(uint16_t);
const uint32_t length = pcm->data.size();

// If we reach the end of the buffer, return to zero (loop)
if (pcm->position >= length) {
pcm->position = 0;
}

// Calculate how much data is left to copy
const size_t bytes_to_copy_now = std::min(bytes_to_copy, length - pcm->position);

// Copy the PCM data into the output buffer
std::memcpy(output_buffer, pcm->data.data() + pcm->position, bytes_to_copy_now);

// Update the position
pcm->position += bytes_to_copy_now;

// If we copied less data than the buffer, fill the rest with silence
if (bytes_to_copy_now < bytes_to_copy)
std::memset((uint8_t *)output_buffer + bytes_to_copy_now, 0, bytes_to_copy - bytes_to_copy_now);

// Return the number of frames copied
return nframes;
}

// Callback called when the stream state changes
void state_callback(cubeb_stream *stream, void *user_ptr, cubeb_state state) {
switch (state) {
case CUBEB_STATE_DRAINED:
LOG_INFO("Playback drained.");
break;
case CUBEB_STATE_ERROR:
LOG_ERROR("Playback error.");
break;
default:
break;
}
}

std::thread playback_thread;
cubeb_stream *stream = nullptr;
bool stop_requested;
std::condition_variable stop_condition;
cubeb *ctx = nullptr;
PcmData pcm_data;

void stop_bgm_theme() {
if (!stream) {
return;
}

{
// We lock the mutex to protect access to stop_requested
std::lock_guard<std::mutex> lock(pcm_data.mutex);
stop_requested = true;
}

// Notify the condition to wake up the thread
stop_condition.notify_one();

// Wait for the playback thread to finish
if (playback_thread.joinable()) {
playback_thread.join();
}

// Stop and destroy the stream
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
stream = nullptr;

// Reset the stop indicator
stop_requested = false;
}

void switch_bgm_state(const bool pause) {
if (!stream) {
LOG_ERROR("The background music stream is not initialized!");
return;
}

if (pause)
cubeb_stream_stop(stream);
else
cubeb_stream_start(stream);
}

void set_bgm_volume(const float volume) {
if (!stream) {
LOG_ERROR("The background music stream is not initialized!");
return;
}

cubeb_stream_set_volume(stream, volume);
}

static void pcm_playback_thread() {
std::unique_lock<std::mutex> lock(pcm_data.mutex);

// Wait until stop is requested
stop_condition.wait(lock, [] { return stop_requested; });

// Destroy the context
cubeb_destroy(ctx);
}

void init_bgm_player(const float vol) {
if (cubeb_init(&ctx, "BGM Player", nullptr) != CUBEB_OK) {
LOG_ERROR("Failed to initialize Cubeb context");
return;
}

// Configure the audio output parameters
cubeb_stream_params output_params;
output_params.format = CUBEB_SAMPLE_S16LE; // Format PCM 16-bit, little-endian
output_params.rate = 48000; // Sample rate
output_params.channels = 2; // Stereo
output_params.layout = CUBEB_LAYOUT_STEREO;

if (cubeb_stream_init(ctx, &stream, "Bgm Stream", nullptr, nullptr,
nullptr, &output_params, 4096, data_callback, state_callback, &pcm_data)
!= CUBEB_OK) {
LOG_ERROR("Failed to initialize Cubeb stream");
return;
}

set_bgm_volume(vol);

// Start playback in a new thread
playback_thread = std::thread(pcm_playback_thread);
}

struct At9Header {
char magic[4];
uint32_t file_size;
char id[4];
char fmt[4];
uint32_t fmt_size;
uint16_t format_tag;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
uint16_t extension_size;
uint16_t samples_per_block;
uint32_t channel_mask;
char codec_id[16];
uint32_t version;
uint32_t config_data;
};

void load_bgm_theme(std::vector<uint8_t> at9_data) {
uint8_t *es_data = at9_data.data();

// Get the header of the AT9 file
At9Header header;
memcpy(&header, es_data, sizeof(At9Header));

const uint32_t es_size = at9_data.size();

// Create a new decoder with the configuration data from the AT9 header
Atrac9DecoderState decoder(header.config_data);

const uint32_t super_frame_size = decoder.get(DecoderQuery::AT9_SUPERFRAME_SIZE);
const auto es_size_max = std::min(super_frame_size, 1024u);

uint32_t total_bytes_read = 0;

// Set offset of data start
const auto data_start = 168;

// Set data size
const auto data_size = es_size - data_start;

// Set data start pointer
es_data += data_start;

// Get the maximum size of the PCM buffer
const auto max_pcm_size = decoder.get(DecoderQuery::AT9_SAMPLE_PER_FRAME) * decoder.get(DecoderQuery::CHANNELS) * sizeof(int16_t);
std::vector<uint8_t> bgm_pcm_data{};

// Decode the AT9 data
while (total_bytes_read < data_size) {
// Set the number of bytes to send to the decoder
const size_t bytes_to_send = std::min(es_size_max, data_size - total_bytes_read);
DecoderSize size;
std::vector<uint8_t> pcm_buffer(max_pcm_size);

// Send the data to the decoder and receive the decoded PCM data
if (!decoder.send(es_data, bytes_to_send)
|| !decoder.receive(pcm_buffer.data(), &size)) {
LOG_ERROR("Error at offset {} while sending or decoding AT9 after sending {} bytes.", total_bytes_read, bytes_to_send);
return;
}

// Get es size used, update the total bytes read and the es data to the next super frame
const uint32_t es_size_used = std::min(decoder.get_es_size(), es_size_max);
total_bytes_read += es_size_used;
es_data += es_size_used;

// Get the size of the PCM data given by the decoder and insert it into the PCM data buffer
const uint32_t pcm_size_given = size.samples * decoder.get(DecoderQuery::CHANNELS) * sizeof(int16_t);
bgm_pcm_data.insert(bgm_pcm_data.end(), pcm_buffer.begin(), pcm_buffer.begin() + pcm_size_given);
}

// Check if PCM data is valid
if (bgm_pcm_data.empty()) {
LOG_ERROR("Error: PCM data is empty!");
return;
}

// Change the BGM theme with the new PCM data
std::lock_guard<std::mutex> lock(pcm_data.mutex);
pcm_data.data = bgm_pcm_data;
pcm_data.position = 0;
}

bool init_theme(GuiState &gui, EmuEnvState &emuenv, const std::string &content_id) {
std::vector<std::string> theme_bg_name;
std::map<std::string, std::string> theme_icon_name = {
Expand All @@ -226,7 +459,9 @@ bool init_theme(GuiState &gui, EmuEnvState &emuenv, const std::string &content_i
gui.theme_backgrounds.clear();
gui.theme_backgrounds_font_color.clear();
gui.theme_information_bar_notice.clear();

pcm_data.data.clear();
pcm_data.position = 0;

const auto content_id_path = fs_utils::utf8_to_path(content_id);

if (content_id != "default") {
Expand All @@ -249,6 +484,15 @@ bool init_theme(GuiState &gui, EmuEnvState &emuenv, const std::string &content_i
if (!home_property.child("m_hostCollabo").child("m_iconFilePath").text().empty())
theme_icon_name["NPXS10026"] = home_property.child("m_hostCollabo").child("m_iconFilePath").text().as_string();

// Bgm theme
if (!home_property.child("m_bgmFilePath").text().empty()) {
std::vector<uint8_t> bgm_buffer;
if (!vfs::read_file(VitaIoDevice::ux0, bgm_buffer, emuenv.pref_path, fs::path("theme") / content_id_path / home_property.child("m_bgmFilePath").text().as_string()))
LOG_ERROR("BGM file not found for Content ID: {}, in path: {}", content_id, THEME_XML_PATH.stem() / home_property.child("m_bgmFilePath").text().as_string());
else
load_bgm_theme(bgm_buffer);
}

// Home
for (const auto &param : home_property.child("m_bgParam")) {
// Theme Background
Expand Down Expand Up @@ -504,6 +748,7 @@ void draw_start_screen(GuiState &gui, EmuEnvState &emuenv) {

if ((ImGui::IsWindowHovered(ImGuiFocusedFlags_RootWindow) && ImGui::IsMouseClicked(0))) {
gui.vita_area.start_screen = false;
switch_bgm_state(false);
gui.vita_area.home_screen = true;
if (emuenv.cfg.show_info_bar)
gui.vita_area.information_bar = true;
Expand Down
2 changes: 2 additions & 0 deletions vita3k/interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ bool handle_events(EmuEnvState &emuenv, GuiState &gui) {
}

app::switch_state(emuenv, !emuenv.kernel.is_threads_paused());
gui::switch_bgm_state(!emuenv.kernel.is_threads_paused());

} else if (!gui::get_sys_apps_state(gui))
gui::close_system_app(gui, emuenv);
Expand All @@ -652,6 +653,7 @@ bool handle_events(EmuEnvState &emuenv, GuiState &gui) {
ImGui_ImplSdl_ProcessEvent(gui.imgui_state.get(), &event);
switch (event.type) {
case SDL_QUIT:
gui::stop_bgm_theme();
if (!emuenv.io.app_path.empty())
gui::update_time_app_used(gui, emuenv, emuenv.io.app_path);
emuenv.kernel.exit_delete_all_threads();
Expand Down
Loading

0 comments on commit 53ec272

Please sign in to comment.