Skip to content

Commit

Permalink
gui/themes: Add support of BGM.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zangetsu38 committed Oct 24, 2024
1 parent 52b82dd commit 07d9d26
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 2 deletions.
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)
2 changes: 2 additions & 0 deletions vita3k/gui/include/gui/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ 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 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
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
240 changes: 240 additions & 0 deletions 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,231 @@ 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("Stream is not initialized!");
return;
}

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

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

static void init_pcm_player() {
if (cubeb_init(&ctx, "PCM 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, "Pcm Stream", nullptr, nullptr,
nullptr, &output_params, 4096, data_callback, state_callback, &pcm_data)
!= CUBEB_OK) {
LOG_ERROR("Failed to initialize Cubeb stream");
return;
}

// Start playback in a new thread and load the PCM data into the audio player
playback_thread = std::thread(pcm_playback_thread);
}

static void change_theme(const std::vector<uint8_t> &new_pcm_data) {
std::lock_guard<std::mutex> lock(pcm_data.mutex);
pcm_data.data = new_pcm_data;
pcm_data.position = 0;
}

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> 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);
pcm_data.insert(pcm_data.end(), pcm_buffer.begin(), pcm_buffer.begin() + pcm_size_given);
}

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

// Change the theme with the new PCM data
change_theme(pcm_data);
}

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 Down Expand Up @@ -249,6 +477,17 @@ 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 (!stream)
init_pcm_player();
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 +743,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
1 change: 1 addition & 0 deletions vita3k/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ int main(int argc, char *argv[]) {
gui.imgui_state->do_clear_screen = false;
}

gui::switch_bgm_state(true);
gui::init_app_background(gui, emuenv, emuenv.io.app_path);
gui::update_last_time_app_used(gui, emuenv, emuenv.io.app_path);

Expand Down

0 comments on commit 07d9d26

Please sign in to comment.