Skip to content

Commit

Permalink
gui/(bgm player & themes): Add support of BGM.
Browse files Browse the repository at this point in the history
- Some small improvement and comment added.
config & gui/settings dialog: Add control of bgm volume.
- Change start screen delay min to 30 sec and max to 5 min.
codec: Set default value to 0 for send of atrac 9.
  • Loading branch information
Zangetsu38 committed Oct 26, 2024
1 parent 52b82dd commit 3730ae4
Show file tree
Hide file tree
Showing 12 changed files with 375 additions and 9 deletions.
2 changes: 1 addition & 1 deletion vita3k/codec/include/codec/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ struct Atrac9DecoderState : public DecoderState {
uint32_t get(DecoderQuery query) override;
uint32_t get_es_size() override;

bool send(const uint8_t *data, uint32_t size) override;
bool send(const uint8_t *data, uint32_t size = 0) override;
bool receive(uint8_t *data, DecoderSize *size) override;
void flush() override;

Expand Down
3 changes: 2 additions & 1 deletion vita3k/config/include/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,15 @@ 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) \
code(int, "sys-time-format", (int)SCE_SYSTEM_PARAM_TIME_FORMAT_12HOUR, sys_time_format) \
code(int, "cpu-pool-size", 10, cpu_pool_size) \
code(int, "modules-mode", static_cast<int>(ModulesMode::AUTOMATIC), modules_mode) \
code(int, "delay-background", 4, delay_background) \
code(int, "delay-start", 10, delay_start) \
code(int, "delay-start", 30, delay_start) \
code(float, "background-alpha", .300f, background_alpha) \
code(int, "log-level", static_cast<int>(spdlog::level::trace), log_level) \
code(std::string, "cpu-backend", "Dynarmic", cpu_backend) \
Expand Down
5 changes: 3 additions & 2 deletions vita3k/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_library(
include/gui/state.h
src/app_context_menu.cpp
src/archive_install_dialog.cpp
src/bgm_player
src/common_dialog.cpp
src/compile_shaders.cpp
src/condvars_dialog.cpp
Expand Down Expand Up @@ -50,6 +51,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)
5 changes: 5 additions & 0 deletions vita3k/gui/include/gui/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ 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);
bool init_bgm(EmuEnvState &emuenv, const std::pair<std::string, std::string> path_bgm);
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 @@ -101,8 +103,11 @@ void pre_run_app(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path
void reset_controller_binding(EmuEnvState &emuenv);
void save_apps_cache(GuiState &gui, EmuEnvState &emuenv);
void save_user(GuiState &gui, EmuEnvState &emuenv, const std::string &user_id);
void set_bgm_volume(const float vol);
void set_config(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path);
void set_shaders_compiled_display(GuiState &gui, EmuEnvState &emuenv);
void stop_bgm();
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
321 changes: 321 additions & 0 deletions vita3k/gui/src/bgm_player.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
// Vita3K emulator project
// Copyright (C) 2024 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

#include "private.h"

#include <audio/state.h>
#include <codec/state.h>
#include <cubeb/cubeb.h>
#include <gui/functions.h>
#include <io/VitaIoDevice.h>

namespace gui {

// 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_handle_thread;
cubeb_stream *stream = nullptr;
bool stop_requested;
std::condition_variable stop_condition;
cubeb *ctx = nullptr;
PcmData pcm_data;

void stop_bgm() {
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 handle thread to finish
if (playback_handle_thread.joinable())
playback_handle_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 vol) {
if (!stream) {
LOG_ERROR("The background music stream is not initialized!");
return;
}

cubeb_stream_set_volume(stream, vol / 100.f);
}

static void pcm_playback_handle_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) {
// Create a new Cubeb context
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 48 kHz
output_params.channels = 2; // Stereo
output_params.layout = CUBEB_LAYOUT_STEREO;

// Get the minimum latency for the output parameters
uint32_t latency;
cubeb_get_min_latency(ctx, &output_params, &latency);

// Lock the mutex to protect initialization of the Cubeb stream
std::lock_guard<std::mutex> lock(pcm_data.mutex);
if (cubeb_stream_init(ctx, &stream, "BGM Stream", nullptr, nullptr,
nullptr, &output_params, latency, 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_handle_thread = std::thread(pcm_playback_handle_thread);
}

struct RiffHeader {
unsigned char chunk_id[4]; // Identifier for the "RIFF" chunk
uint32_t chunk_size; // Size of the chunk
unsigned char format[4]; // Format of the file
};

struct FmtChunk {
unsigned char chunk_id[4]; // Identifier for the "fmt" chunk
uint32_t chunk_data_size; // Size of the chunk
uint16_t format_tag; // Format of the audio data
uint16_t num_channels; // Number of channels
uint32_t sample_rate; // Sample rate
uint32_t byte_rate; // Byte rate
uint16_t block_align; // Block alignment
uint16_t bits_per_sample; // Bits per sample
uint16_t extension_size; // Size of the extension
uint16_t samples_per_block; // Samples per block
uint32_t channel_mask; // Channel mask
char codec_id[16]; // Codec ID
uint32_t version; // Version
uint32_t config_data; // Configuration data
uint32_t reserved; // Reserved
};

struct FactChunk {
unsigned char chunk_id[4]; // Identifier for the "fact" chunk
uint32_t chunk_data_size; // Size of the chunk
uint32_t total_samples; // Total number of samples in the file
uint32_t input_overlap_delay; // Input overlap delay
uint32_t input_overlap_encoder_delay; // Input overlap encoder delay
};

struct SmplChunk {
unsigned char chunk_id[4]; // Identifier for the "smpl" chunk
uint32_t chunk_size; // Size of the chunk
uint32_t manufacturer; // Manufacturer code (MMA Manufacturer code)
uint32_t product; // Product code
uint32_t sample_period; // Period of one sample in nanoseconds
uint32_t midi_unity_note; // MIDI note to play the sample at its original pitch
uint32_t midi_pitch_fraction; // Fraction of the MIDI note
uint32_t smpte_format; // SMPTE format for synchronization
uint32_t smpte_offset; // SMPTE offset
uint32_t num_sample_loops; // Number of sample loops
uint32_t sampler_data; // Size of sampler-specific data (in bytes)
uint32_t identifier; // Unique identifier for the loop
uint32_t type; // Loop type (e.g., 0 for forward)
uint32_t start; // Loop start point (in samples)
uint32_t end; // Loop end point (in samples)
uint32_t fraction; // Fraction of a sample length for fine-tuning
uint32_t play_count; // Number of times to repeat the loop (0 for infinite)
};

struct DataChunk {
unsigned char chunk_id[4]; // Identifier for the "data" chunk
uint32_t size; // Size of the audio data
};

struct At9Header {
RiffHeader riff;
FmtChunk fmt;
FactChunk fact;
SmplChunk smpl;
DataChunk data;
};

// Size of the AT9 header
static constexpr uint32_t AT9_HEADER_SIZE = sizeof(At9Header);

static bool decode_bgm(uint8_t *at9_data) {
// Get the header of the AT9 file
At9Header header;
memcpy(&header, at9_data, AT9_HEADER_SIZE);

// Set data size
const uint32_t data_size = header.data.size;

// Set the pointer to the ES data after the header
at9_data += AT9_HEADER_SIZE;

// Create a new decoder with the configuration data from the AT9 header
Atrac9DecoderState decoder(header.fmt.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;

// Get the maximum size of the PCM buffer for a single super frame
const uint64_t max_pcm_size = static_cast<uint64_t>(decoder.get(DecoderQuery::AT9_SAMPLE_PER_FRAME) * decoder.get(DecoderQuery::CHANNELS)) * sizeof(int16_t);
std::vector<uint8_t> bgm_data;

// Decode the AT9 data
while (total_bytes_read < data_size) {
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(at9_data)
|| !decoder.receive(pcm_buffer.data(), &size)) {
LOG_ERROR("Error at offset {} while sending or decoding AT9.", total_bytes_read);
return false;
}

// 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;
at9_data += es_size_used;

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

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

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

return true;
}

bool init_bgm(EmuEnvState &emuenv, const std::pair<std::string, std::string> path_bgm) {
// Clear the PCM data buffer and reset the position
pcm_data.position = 0;
pcm_data.data.clear();

// Read the At9 file
vfs::FileBuffer at9_buffer;
const auto device = VitaIoDevice::_from_string(path_bgm.first.c_str());
const auto path = path_bgm.second;
if (!vfs::read_file(device, at9_buffer, emuenv.pref_path, path)) {
LOG_ERROR_IF(device == VitaIoDevice::ux0, "Failed to read theme BGM file: {}:{}", path_bgm.first, path);
return false;
}

// Decode the At9 buffer to PCM data
return decode_bgm(at9_buffer.data());
}

} // namespace gui
1 change: 1 addition & 0 deletions vita3k/gui/src/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ void pre_init(GuiState &gui, EmuEnvState &emuenv) {

init_style(emuenv);
init_font(gui, emuenv);
gui::init_bgm_player(emuenv.cfg.bgm_volume);
lang::init_lang(gui.lang, emuenv);

bool result = ImGui_ImplSdl_CreateDeviceObjects(gui.imgui_state.get());
Expand Down
Loading

0 comments on commit 3730ae4

Please sign in to comment.