cellMusic: implement qt music handler

This commit is contained in:
Megamouse 2022-02-20 19:01:21 +01:00
parent 0dbfe314a3
commit 15e74357cf
27 changed files with 885 additions and 24 deletions

View file

@ -3,6 +3,9 @@
#include "Emu/Cell/lv2/sys_lwmutex.h"
#include "Emu/Cell/lv2/sys_lwcond.h"
#include "Emu/Cell/lv2/sys_spu.h"
#include "Emu/Io/music_handler_base.h"
#include "Emu/System.h"
#include "Emu/VFS.h"
#include "cellSearch.h"
#include "cellSpurs.h"
#include "cellSysutil.h"
@ -67,6 +70,14 @@ struct music_state
vm::ptr<void(u32 event, vm::ptr<void> param, vm::ptr<void> userData)> func{};
vm::ptr<void> userData{};
std::mutex mtx;
std::shared_ptr<music_handler_base> handler;
music_selection_context current_selection_context;
music_state()
{
handler = Emu.GetCallbacks().get_music_handler();
}
};
error_code cellMusicGetSelectionContext(vm::ptr<CellMusicSelectionContext> context)
@ -75,6 +86,12 @@ error_code cellMusicGetSelectionContext(vm::ptr<CellMusicSelectionContext> conte
if (!context)
return CELL_MUSIC_ERROR_PARAM;
auto& music = g_fxo->get<music_state>();
std::lock_guard lock(music.mtx);
*context = music.current_selection_context.get();
cellMusic.success("cellMusicGetSelectionContext: selection context = %s", music.current_selection_context.to_string());
return CELL_OK;
}
@ -93,7 +110,17 @@ error_code cellMusicSetSelectionContext2(vm::ptr<CellMusicSelectionContext> cont
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
music.func(ppu, CELL_MUSIC2_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(CELL_OK), music.userData);
bool result = false;
{
std::lock_guard lock(music.mtx);
result = music.current_selection_context.set(*context);
}
const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC2_ERROR_INVALID_CONTEXT};
if (result) cellMusic.success("cellMusicSetSelectionContext2: new selection context = %s)", music.current_selection_context.to_string());
else cellMusic.todo("cellMusicSetSelectionContext2: failed. context = %s)", context->data);
music.func(ppu, CELL_MUSIC2_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData);
return CELL_OK;
});
@ -111,6 +138,8 @@ error_code cellMusicSetVolume2(f32 level)
if (!music.func)
return CELL_MUSIC2_ERROR_GENERIC;
music.handler->set_volume(level);
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
music.func(ppu, CELL_MUSIC2_EVENT_SET_VOLUME_RESULT, vm::addr_t(CELL_OK), music.userData);
@ -127,7 +156,10 @@ error_code cellMusicGetContentsId(vm::ptr<CellSearchContentId> contents_id)
if (!contents_id)
return CELL_MUSIC_ERROR_PARAM;
return CELL_OK;
// HACKY
auto& music = g_fxo->get<music_state>();
std::lock_guard lock(music.mtx);
return music.current_selection_context.find_content_id(contents_id);
}
error_code cellMusicSetSelectionContext(vm::ptr<CellMusicSelectionContext> context)
@ -144,7 +176,17 @@ error_code cellMusicSetSelectionContext(vm::ptr<CellMusicSelectionContext> conte
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
music.func(ppu, CELL_MUSIC_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(CELL_OK), music.userData);
bool result = false;
{
std::lock_guard lock(music.mtx);
result = music.current_selection_context.set(*context);
}
const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC_ERROR_INVALID_CONTEXT};
if (result) cellMusic.success("cellMusicSetSelectionContext: new selection context = %s)", music.current_selection_context.to_string());
else cellMusic.todo("cellMusicSetSelectionContext: failed. context = %s)", context->data);
music.func(ppu, CELL_MUSIC_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData);
return CELL_OK;
});
@ -184,6 +226,9 @@ error_code cellMusicGetPlaybackStatus2(vm::ptr<s32> status)
if (!status)
return CELL_MUSIC2_ERROR_PARAM;
const auto& music = g_fxo->get<music_state>();
*status = music.handler->get_state();
return CELL_OK;
}
@ -194,7 +239,10 @@ error_code cellMusicGetContentsId2(vm::ptr<CellSearchContentId> contents_id)
if (!contents_id)
return CELL_MUSIC2_ERROR_PARAM;
return CELL_OK;
// HACKY
auto& music = g_fxo->get<music_state>();
std::lock_guard lock(music.mtx);
return music.current_selection_context.find_content_id(contents_id);
}
error_code cellMusicFinalize()
@ -292,6 +340,11 @@ error_code cellMusicGetSelectionContext2(vm::ptr<CellMusicSelectionContext> cont
if (!context)
return CELL_MUSIC2_ERROR_PARAM;
auto& music = g_fxo->get<music_state>();
std::lock_guard lock(music.mtx);
*context = music.current_selection_context.get();
cellMusic.success("cellMusicGetSelectionContext2: selection context = %s", music.current_selection_context.to_string());
return CELL_OK;
}
@ -302,6 +355,9 @@ error_code cellMusicGetVolume(vm::ptr<f32> level)
if (!level)
return CELL_MUSIC_ERROR_PARAM;
const auto& music = g_fxo->get<music_state>();
*level = music.handler->get_volume();
return CELL_OK;
}
@ -312,6 +368,9 @@ error_code cellMusicGetPlaybackStatus(vm::ptr<s32> status)
if (!status)
return CELL_MUSIC_ERROR_PARAM;
const auto& music = g_fxo->get<music_state>();
*status = music.handler->get_state();
return CELL_OK;
}
@ -319,7 +378,7 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr<void> param)
{
cellMusic.todo("cellMusicSetPlaybackCommand2(command=0x%x, param=*0x%x)", command, param);
if (command < CELL_MUSIC_PB_CMD_STOP || command > CELL_MUSIC_PB_CMD_FASTREVERSE)
if (command < CELL_MUSIC2_PB_CMD_STOP || command > CELL_MUSIC2_PB_CMD_FASTREVERSE)
return CELL_MUSIC2_ERROR_PARAM;
auto& music = g_fxo->get<music_state>();
@ -329,6 +388,40 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr<void> param)
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
// TODO: play proper song when the context is a playlist
std::string path;
{
std::lock_guard lock(music.mtx);
path = vfs::get(music.current_selection_context.path);
}
switch (command)
{
case CELL_MUSIC2_PB_CMD_STOP:
music.handler->stop();
break;
case CELL_MUSIC2_PB_CMD_PLAY:
music.handler->play(path);
break;
case CELL_MUSIC2_PB_CMD_PAUSE:
music.handler->pause();
break;
case CELL_MUSIC2_PB_CMD_NEXT:
music.handler->play(path);
break;
case CELL_MUSIC2_PB_CMD_PREV:
music.handler->play(path);
break;
case CELL_MUSIC2_PB_CMD_FASTFORWARD:
music.handler->fast_forward();
break;
case CELL_MUSIC2_PB_CMD_FASTREVERSE:
music.handler->fast_reverse();
break;
default:
break;
}
music.func(ppu, CELL_MUSIC2_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_OK), music.userData);
return CELL_OK;
});
@ -350,6 +443,40 @@ error_code cellMusicSetPlaybackCommand(s32 command, vm::ptr<void> param)
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
// TODO: play proper song when the context is a playlist
std::string path;
{
std::lock_guard lock(music.mtx);
path = vfs::get(music.current_selection_context.path);
}
switch (command)
{
case CELL_MUSIC_PB_CMD_STOP:
music.handler->stop();
break;
case CELL_MUSIC_PB_CMD_PLAY:
music.handler->play(path);
break;
case CELL_MUSIC_PB_CMD_PAUSE:
music.handler->pause();
break;
case CELL_MUSIC_PB_CMD_NEXT:
music.handler->play(path);
break;
case CELL_MUSIC_PB_CMD_PREV:
music.handler->play(path);
break;
case CELL_MUSIC_PB_CMD_FASTFORWARD:
music.handler->fast_forward();
break;
case CELL_MUSIC_PB_CMD_FASTREVERSE:
music.handler->fast_reverse();
break;
default:
break;
}
music.func(ppu, CELL_MUSIC_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_OK), music.userData);
return CELL_OK;
});
@ -430,6 +557,8 @@ error_code cellMusicSetVolume(f32 level)
if (!music.func)
return CELL_MUSIC_ERROR_GENERIC;
music.handler->set_volume(level);
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
music.func(ppu, CELL_MUSIC_EVENT_SET_VOLUME_RESULT, vm::addr_t(CELL_OK), music.userData);
@ -446,6 +575,9 @@ error_code cellMusicGetVolume2(vm::ptr<f32> level)
if (!level)
return CELL_MUSIC2_ERROR_PARAM;
const auto& music = g_fxo->get<music_state>();
*level = music.handler->get_volume();
return CELL_OK;
}

View file

@ -1,6 +1,8 @@
#pragma once
#include "Emu/Memory/vm_ptr.h"
#include "Emu/Cell/ErrorCodes.h"
#include "cellSearch.h"
// Error Codes
enum CellMusicError : u32
@ -132,5 +134,65 @@ using CellMusic2Callback = void(u32 event, vm::ptr<void> param, vm::ptr<void> us
struct CellMusicSelectionContext
{
// TODO: find out what fw does with it
// Let's just hijack it with our own data.
char data[CELL_MUSIC_SELECTION_CONTEXT_SIZE];
};
struct music_selection_context
{
char magic[4] = "SUS";
u32 content_type{0};
u32 repeat_mode{0};
u32 context_option{0};
std::string path;
static constexpr u32 max_depth = 2; // root + 1 folder + file
bool set(const CellMusicSelectionContext& in)
{
if (memcmp(in.data, magic, sizeof(magic)) != 0)
{
return false;
}
u32 pos = sizeof(magic);
memcpy(&content_type, &in.data[pos], sizeof(content_type));
pos += sizeof(content_type);
repeat_mode = in.data[pos++];
context_option = in.data[pos++];
path = &in.data[pos];
return true;
}
CellMusicSelectionContext get() const
{
if (path.size() + 2 + sizeof(content_type) + sizeof(magic) > CELL_MUSIC_SELECTION_CONTEXT_SIZE)
{
fmt::throw_exception("Contents of music_selection_context are too large");
}
CellMusicSelectionContext out{};
u32 pos = 0;
std::memset(out.data, 0, CELL_MUSIC_SELECTION_CONTEXT_SIZE);
std::memcpy(out.data, magic, sizeof(magic));
pos += sizeof(magic);
std::memcpy(&out.data[pos], &content_type, sizeof(content_type));
pos += sizeof(content_type);
out.data[pos++] = repeat_mode;
out.data[pos++] = context_option;
std::memcpy(&out.data[pos], path.c_str(), path.size());
return out;
}
std::string to_string() const
{
return fmt::format("{ .magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .path='%s' }", magic, content_type, repeat_mode, context_option, path);
}
// Helper
error_code find_content_id(vm::ptr<CellSearchContentId> contents_id);
};

View file

@ -566,7 +566,7 @@ error_code cellSearchStartListSearch(CellSearchListSearchType type, CellSearchSo
cellSearch.warning("cellSearchStartListSearch(): Directory-Path \"%s\" is too long and will be omitted: %i", item_path, item_path.length());
continue;
// const size_t ext_offset = item.name.find_last_of('.');
// const std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset);
// std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset);
// strcpy_trunc(curr_find->infoPath.contentPath, link);
// std::lock_guard lock(search.links_mutex);
@ -981,7 +981,7 @@ error_code cellSearchStartContentSearch(CellSearchContentSearchType type, CellSe
{
// Create mapping which will be resolved to an actual hard link in VFS by cellSearchPrepareFile
const size_t ext_offset = item.name.find_last_of('.');
const std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset);
std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset);
strcpy_trunc(curr_find->infoPath.contentPath, link);
std::lock_guard lock(search.links_mutex);
@ -1446,8 +1446,10 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
return CELL_SEARCH_ERROR_INVALID_SEARCHID;
}
auto& search = g_fxo->get<search_info>();
// TODO: find out if this check is correct
if (error_code error = check_search_state(g_fxo->get<search_info>().state.load(), search_state::in_progress))
if (error_code error = check_search_state(search.state.load(), search_state::in_progress))
{
return error;
}
@ -1457,6 +1459,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
return CELL_SEARCH_ERROR_CONTENT_NOT_FOUND;
}
music_selection_context context;
// Use the first track in order to get info about this search
const auto& first_content_id = searchObject->content_ids[0];
const auto& first_content = first_content_id.second;
@ -1481,9 +1485,9 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
return { CELL_SEARCH_ERROR_NOT_SUPPORTED_CONTEXT, fmt::format("Type: %d, Expected: %d", +content->second->type, +first_content->type) };
}
// TODO: Use the found content
// first track/playlist of context = content->second;
cellSearch.todo("cellSearchGetMusicSelectionContext(): Hash=%08X, Assign found track: Type=0x%x, Path=%s", content_hash, +content->second->type, content->second->infoPath.contentPath);
// Use the found content
context.path = content->second->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.path);
}
else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
@ -1492,9 +1496,9 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
}
else
{
// TODO: Select the first track by default
// first track of context = first_content;
cellSearch.todo("cellSearchGetMusicSelectionContext(): Hash=%08X, Assign first track: Type=0x%x, Path=%s", content_hash, +first_content->type, first_content->infoPath.contentPath);
// Select the first track by default
context.path = first_content->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.path);
}
}
else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
@ -1504,13 +1508,24 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
}
else
{
// TODO: Select the first track by default
// first track of context = first_content;
cellSearch.todo("cellSearchGetMusicSelectionContext(): Assign first track: Type=0x%x, Path=%s", +first_content->type, first_content->infoPath.contentPath);
// Select the first track by default
context.path = first_content->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.path);
}
// TODO: Use repeatMode and option in our context, depending on the type (list vs single)
// TODO: assign outContext
context.content_type = first_content->type;
context.repeat_mode = repeatMode;
context.context_option = option;
// Resolve hashed paths
if (auto found = search.content_links.find(context.path); found != search.content_links.end())
{
context.path = found->second;
}
*outContext = context.get();
cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %d", context.to_string());
return CELL_OK;
}
@ -1544,7 +1559,12 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptr<CellSearchCo
return CELL_SEARCH_ERROR_INVALID_CONTENTTYPE;
}
// TODO: assign outContext
music_selection_context context;
context.path = content_info->infoPath.contentPath;
*outContext = context.get();
cellSearch.success("cellSearchGetMusicSelectionContextOfSingleTrack: found selection context: %s", context.to_string());
return CELL_OK;
}
@ -1576,7 +1596,7 @@ error_code cellSearchGetContentInfoPath(vm::cptr<CellSearchContentId> contentId,
return CELL_SEARCH_ERROR_CONTENT_NOT_FOUND;
}
cellSearch.success("contentId = %08X contentPath = \"%s\"", id, infoPath->contentPath);
cellSearch.success("contentId=%08X, contentPath=\"%s\"", id, infoPath->contentPath);
return CELL_OK;
}
@ -1781,3 +1801,110 @@ DECLARE(ppu_module_manager::cellSearch)("cellSearchUtility", []()
REG_FUNC(cellSearchUtility, cellSearchCancel);
REG_FUNC(cellSearchUtility, cellSearchEnd);
});
// Helper
error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId> contents_id)
{
if (!contents_id)
return CELL_MUSIC_ERROR_PARAM;
// Search for the content that matches our current selection
auto& content_map = g_fxo->get<content_id_map>();
const u64 hash = std::hash<std::string>()(this->path);
auto found = content_map.map.find(hash);
if (found != content_map.map.end())
{
// TODO: check if the content type is correct
const u128 content_id_128 = found->first;
std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE);
cellSearch.warning("find_content_id: found existing content for %s (path control: '%s')", to_string(), found->second->infoPath.contentPath);
return CELL_OK;
}
// Try to find the content manually
const std::string vpath = vfs::get("/dev_hdd0/music/");
for (auto&& entry : fs::dir(vpath))
{
entry.name = vfs::unescape(entry.name);
if (!entry.is_directory || entry.name == "." || entry.name == "..")
{
continue;
}
std::string dir_path = vpath + entry.name;
if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
const u64 dir_hash = std::hash<std::string>()(dir_path);
if (hash == dir_hash)
{
u32 num_of_items = 0;
for (auto&& file : fs::dir(dir_path))
{
file.name = vfs::unescape(file.name);
if (file.is_directory || file.name == "." || file.name == "..")
{
continue;
}
num_of_items++;
}
// TODO: check for actual content inside the directory
std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>();
curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSICLIST;
strcpy_trunc(curr_find->infoPath.contentPath, entry.name); // only save dir name due to 63 character limit
CellSearchMusicListInfo& info = curr_find->data.music_list;
info.listType = CELL_SEARCH_LISTSEARCHTYPE_MUSIC_ALBUM;
info.numOfItems = num_of_items;
info.duration = 0;
strcpy_trunc(info.title, entry.name);
strcpy_trunc(info.artistName, "ARTIST NAME");
content_map.map.emplace(dir_hash, curr_find);
const u128 content_id_128 = dir_hash;
std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE);
cellSearch.warning("find_content_id: found music list %s (path control: '%s')", to_string(), dir_path);
return CELL_OK;
}
continue;
}
// Search the subfolders. We assume all music is located in a depth of 2 (max_depth, root + 1 folder + file).
for (auto&& item : fs::dir(dir_path))
{
if (item.is_directory || item.name == "." || item.name == "..")
{
continue;
}
const std::string file_path = dir_path + "/" + item.name;
const u64 file_hash = std::hash<std::string>()(file_path);
if (hash == file_hash)
{
const auto [success, mi] = utils::get_media_info(path, 1); // AVMEDIA_TYPE_AUDIO
if (!success)
{
continue;
}
std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>();
curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC;
const std::string short_path = entry.name + "/" + item.name; // only save dir name and item name due to 63 character limit
strcpy_trunc(curr_find->infoPath.contentPath, short_path);
populate_music_info(curr_find->data.music, mi, item);
content_map.map.emplace(file_hash, curr_find);
const u128 content_id_128 = file_hash;
std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE);
cellSearch.warning("find_content_id: found music track %s (path control: '%s')", to_string(), file_path);
return CELL_OK;
}
}
}
// content ID not found
return CELL_SEARCH_ERROR_CONTENT_NOT_FOUND;
}

View file

@ -0,0 +1,20 @@
#pragma once
#include "Emu/Io/music_handler_base.h"
class null_music_handler final : public music_handler_base
{
public:
null_music_handler() : music_handler_base() {}
void stop() override { m_state = 0; } // CELL_MUSIC_PB_STATUS_STOP
void play(const std::string& /*path*/) override { m_state = 1; } // CELL_MUSIC_PB_STATUS_PLAY
void pause() override { m_state = 2; } // CELL_MUSIC_PB_STATUS_PAUSE
void fast_forward() override { m_state = 3; } // CELL_MUSIC_PB_STATUS_FASTFORWARD
void fast_reverse() override { m_state = 4; } // CELL_MUSIC_PB_STATUS_FASTREVERSE
void set_volume(f32 volume) override { m_volume = volume; }
f32 get_volume() const override { return m_volume; }
private:
atomic_t<f32> m_volume{0.0f};
};

View file

@ -0,0 +1,26 @@
#pragma once
#include "util/types.hpp"
#include "util/atomic.hpp"
class music_handler_base
{
public:
virtual ~music_handler_base() = default;
virtual void stop() = 0;
virtual void play(const std::string& path) = 0;
virtual void pause() = 0;
virtual void fast_forward() = 0;
virtual void fast_reverse() = 0;
virtual void set_volume(f32 volume) = 0;
virtual f32 get_volume() const = 0;
s32 get_state() const
{
return m_state;
}
protected:
atomic_t<s32> m_state{0};
};

View file

@ -71,6 +71,7 @@ struct EmuCallbacks
std::function<std::unique_ptr<class GSFrameBase>()> get_gs_frame;
std::function<void()> init_gs_render;
std::function<std::shared_ptr<class camera_handler_base>()> get_camera_handler;
std::function<std::shared_ptr<class music_handler_base>()> get_music_handler;
std::function<std::shared_ptr<class AudioBackend>()> get_audio;
std::function<std::shared_ptr<class MsgDialogBase>()> get_msg_dialog;
std::function<std::shared_ptr<class OskDialogBase>()> get_osk_dialog;

View file

@ -239,6 +239,7 @@ struct cfg_root : cfg::node
cfg::_int<0, 100> time_stretching_threshold{ this, "Time Stretching Threshold", 75, true };
cfg::_enum<microphone_handler> microphone_type{ this, "Microphone Type", microphone_handler::null };
cfg::string microphone_devices{ this, "Microphone Devices", "@@@@@@@@@@@@" };
cfg::_enum<music_handler> music{ this, "Music Handler", music_handler::qt };
} audio{ this };
struct node_io : cfg::node

View file

@ -335,6 +335,21 @@ void fmt_class_string<camera_handler>::format(std::string& out, u64 arg)
});
}
template <>
void fmt_class_string<music_handler>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto value)
{
switch (value)
{
case music_handler::null: return "Null";
case music_handler::qt: return "Qt";
}
return unknown;
});
}
template <>
void fmt_class_string<fake_camera_type>::format(std::string& out, u64 arg)
{

View file

@ -75,6 +75,12 @@ enum class audio_downmix
use_application_settings
};
enum class music_handler
{
null,
qt
};
enum class camera_handler
{
null,

View file

@ -450,7 +450,9 @@
<ClInclude Include="Emu\Cell\Modules\sys_crashdump.h" />
<ClInclude Include="Emu\Io\camera_config.h" />
<ClInclude Include="Emu\Io\camera_handler_base.h" />
<ClInclude Include="Emu\Io\music_handler_base.h" />
<ClInclude Include="Emu\Io\Null\null_camera_handler.h" />
<ClInclude Include="Emu\Io\Null\null_music_handler.h" />
<ClInclude Include="Emu\Io\Turntable.h" />
<ClInclude Include="Emu\Io\GHLtar.h" />
<ClInclude Include="Emu\Io\Buzz.h" />

View file

@ -2041,6 +2041,12 @@
<ClInclude Include="Emu\NP\np_helpers.h">
<Filter>Emu\NP</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\music_handler_base.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\Null\null_music_handler.h">
<Filter>Emu\Io\Null</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Common\Interpreter\FragmentInterpreter.glsl">

View file

@ -6,6 +6,7 @@
#include "Emu/Cell/Modules/cellSaveData.h"
#include "Emu/Cell/Modules/sceNpTrophy.h"
#include "Emu/Io/Null/null_camera_handler.h"
#include "Emu/Io/Null/null_music_handler.h"
#include <clocale>
@ -105,6 +106,22 @@ void headless_application::InitializeCallbacks()
return nullptr;
};
callbacks.get_music_handler = []() -> std::shared_ptr<music_handler_base>
{
switch (g_cfg.audio.music.get())
{
case music_handler::null:
{
return std::make_shared<null_music_handler>();
}
case music_handler::qt:
{
fmt::throw_exception("Headless mode can not be used with this music handler. Current handler: %s", g_cfg.audio.music.get());
}
}
return nullptr;
};
callbacks.get_gs_frame = []() -> std::unique_ptr<GSFrameBase>
{
if (g_cfg.video.renderer != video_renderer::null)

View file

@ -309,6 +309,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_qt_camera_error_handler.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_qt_music_error_handler.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_recvmessage_dialog_frame.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -513,6 +516,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_qt_camera_error_handler.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_qt_music_error_handler.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_recvmessage_dialog_frame.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -609,6 +615,8 @@
<ClCompile Include="rpcs3qt\persistent_settings.cpp" />
<ClCompile Include="rpcs3qt\qt_camera_error_handler.cpp" />
<ClCompile Include="rpcs3qt\qt_camera_handler.cpp" />
<ClCompile Include="rpcs3qt\qt_music_error_handler.cpp" />
<ClCompile Include="rpcs3qt\qt_music_handler.cpp" />
<ClCompile Include="rpcs3qt\recvmessage_dialog_frame.cpp" />
<ClCompile Include="rpcs3qt\render_creator.cpp" />
<ClCompile Include="rpcs3qt\rpcn_settings_dialog.cpp" />
@ -1127,6 +1135,17 @@
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\qt_camera_handler.h" />
<ClInclude Include="rpcs3qt\qt_music_handler.h" />
<CustomBuild Include="rpcs3qt\qt_music_error_handler.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\richtext_item_delegate.h" />
<CustomBuild Include="rpcs3qt\sendmessage_dialog_frame.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>

View file

@ -136,6 +136,9 @@
<Filter Include="Io\camera">
<UniqueIdentifier>{f1996891-48b7-441a-b3fe-52794cbebe80}</UniqueIdentifier>
</Filter>
<Filter Include="Io\music">
<UniqueIdentifier>{44c3b8d8-cc4d-4d5f-8681-21241ab220d0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
@ -807,6 +810,18 @@
<ClCompile Include="rpcs3qt\shortcut_utils.cpp">
<Filter>Gui\utils</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\qt_music_handler.cpp">
<Filter>Io\music</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\qt_music_error_handler.cpp">
<Filter>Io\music</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_qt_music_error_handler.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_qt_music_error_handler.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -953,6 +968,9 @@
<ClInclude Include="rpcs3qt\shortcut_utils.h">
<Filter>Gui\utils</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\qt_music_handler.h">
<Filter>Io\music</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">
@ -1189,6 +1207,9 @@
<CustomBuild Include="rpcs3qt\camera_settings_dialog.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\qt_music_error_handler.h">
<Filter>Io\music</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<Image Include="rpcs3.ico" />

View file

@ -53,6 +53,8 @@ set(SRC_FILES
qt_camera_error_handler.cpp
qt_camera_handler.cpp
qt_camera_video_surface.cpp
qt_music_error_handler.cpp
qt_music_handler.cpp
qt_utils.cpp
register_editor_dialog.cpp
recvmessage_dialog_frame.cpp

View file

@ -1027,6 +1027,13 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_
case camera_handler::qt: return tr("Qt", "Camera handler");
}
break;
case emu_settings_type::MusicHandler:
switch (static_cast<music_handler>(index))
{
case music_handler::null: return tr("Null", "Music handler");
case music_handler::qt: return tr("Qt", "Music handler");
}
break;
case emu_settings_type::PadHandlerMode:
switch (static_cast<pad_handler_mode>(index))
{

View file

@ -126,6 +126,7 @@ enum class emu_settings_type
TimeStretchingThreshold,
MicrophoneType,
MicrophoneDevices,
MusicHandler,
// Input / Output
PadHandlerMode,
@ -290,6 +291,7 @@ inline static const QMap<emu_settings_type, cfg_location> settings_location =
{ emu_settings_type::TimeStretchingThreshold, { "Audio", "Time Stretching Threshold"}},
{ emu_settings_type::MicrophoneType, { "Audio", "Microphone Type" }},
{ emu_settings_type::MicrophoneDevices, { "Audio", "Microphone Devices" }},
{ emu_settings_type::MusicHandler, { "Audio", "Music Handler"}},
// Input / Output
{ emu_settings_type::PadHandlerMode, { "Input/Output", "Pad handler mode"}},

View file

@ -11,12 +11,14 @@
#include "display_sleep_control.h"
#include "localized_emu.h"
#include "qt_camera_handler.h"
#include "qt_music_handler.h"
#ifdef WITH_DISCORD_RPC
#include "_discord_utils.h"
#endif
#include "Emu/Io/Null/null_camera_handler.h"
#include "Emu/Io/Null/null_music_handler.h"
#include "Emu/Cell/Modules/cellAudio.h"
#include "Emu/RSX/Overlays/overlay_perf_metrics.h"
#include "Emu/system_utils.hpp"
@ -379,6 +381,23 @@ void gui_application::InitializeCallbacks()
}
return nullptr;
};
callbacks.get_music_handler = []() -> std::shared_ptr<music_handler_base>
{
switch (g_cfg.audio.music.get())
{
case music_handler::null:
{
return std::make_shared<null_music_handler>();
}
case music_handler::qt:
{
return std::make_shared<qt_music_handler>();
}
}
return nullptr;
};
callbacks.get_gs_frame = [this]() -> std::unique_ptr<GSFrameBase> { return get_gs_frame(); };
callbacks.get_msg_dialog = [this]() -> std::shared_ptr<MsgDialogBase> { return m_show_gui ? std::make_shared<msg_dialog_frame>() : nullptr; };
callbacks.get_osk_dialog = [this]() -> std::shared_ptr<OskDialogBase> { return m_show_gui ? std::make_shared<osk_dialog_frame>() : nullptr; };

View file

@ -0,0 +1,92 @@
#include "qt_music_error_handler.h"
#include "util/logs.hpp"
LOG_CHANNEL(music_log, "Music");
template <>
void fmt_class_string<QMediaPlayer::Error>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](QMediaPlayer::Error value)
{
switch (value)
{
case QMediaPlayer::Error::NoError: return "NoError";
case QMediaPlayer::Error::ResourceError: return "ResourceError";
case QMediaPlayer::Error::FormatError: return "FormatError";
case QMediaPlayer::Error::NetworkError: return "NetworkError";
case QMediaPlayer::Error::AccessDeniedError: return "AccessDeniedError";
case QMediaPlayer::Error::ServiceMissingError: return "ServiceMissingError";
case QMediaPlayer::Error::MediaIsPlaylist: return "MediaIsPlaylist";
}
return unknown;
});
}
template <>
void fmt_class_string<QMediaPlayer::MediaStatus>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](QMediaPlayer::MediaStatus value)
{
switch (value)
{
case QMediaPlayer::MediaStatus::UnknownMediaStatus: return "UnknownMediaStatus";
case QMediaPlayer::MediaStatus::NoMedia: return "NoMedia";
case QMediaPlayer::MediaStatus::LoadingMedia: return "LoadingMedia";
case QMediaPlayer::MediaStatus::LoadedMedia: return "LoadedMedia";
case QMediaPlayer::MediaStatus::StalledMedia: return "StalledMedia";
case QMediaPlayer::MediaStatus::BufferingMedia: return "BufferingMedia";
case QMediaPlayer::MediaStatus::BufferedMedia: return "BufferedMedia";
case QMediaPlayer::MediaStatus::EndOfMedia: return "EndOfMedia";
case QMediaPlayer::MediaStatus::InvalidMedia: return "InvalidMedia";
}
return unknown;
});
}
template <>
void fmt_class_string<QMediaPlayer::State>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](QMediaPlayer::State value)
{
switch (value)
{
case QMediaPlayer::State::StoppedState: return "StoppedState";
case QMediaPlayer::State::PlayingState: return "PlayingState";
case QMediaPlayer::State::PausedState: return "PausedState";
}
return unknown;
});
}
qt_music_error_handler::qt_music_error_handler(std::shared_ptr<QMediaPlayer> media_player)
: m_media_player(std::move(media_player))
{
if (m_media_player)
{
connect(m_media_player.get(), &QMediaPlayer::mediaStatusChanged, this, &qt_music_error_handler::handle_media_status);
connect(m_media_player.get(), &QMediaPlayer::stateChanged, this, &qt_music_error_handler::handle_music_state);
connect(m_media_player.get(), QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error), this, &qt_music_error_handler::handle_music_error);
}
}
qt_music_error_handler::~qt_music_error_handler()
{
}
void qt_music_error_handler::handle_media_status(QMediaPlayer::MediaStatus status)
{
music_log.notice("New media status: %s (status=%d)", status, static_cast<int>(status));
}
void qt_music_error_handler::handle_music_state(QMediaPlayer::State state)
{
music_log.notice("New playback state: %s (state=%d)", state, static_cast<int>(state));
}
void qt_music_error_handler::handle_music_error(QMediaPlayer::Error error)
{
music_log.error("Error event: \"%s\" (error=%s)", m_media_player ? m_media_player->errorString().toStdString() : "", error);
}

View file

@ -0,0 +1,21 @@
#pragma once
#include <QObject>
#include <QMediaPlayer>
class qt_music_error_handler : public QObject
{
Q_OBJECT
public:
qt_music_error_handler(std::shared_ptr<QMediaPlayer> media_player);
virtual ~qt_music_error_handler();
private Q_SLOTS:
void handle_media_status(QMediaPlayer::MediaStatus status);
void handle_music_state(QMediaPlayer::State state);
void handle_music_error(QMediaPlayer::Error error);
private:
std::shared_ptr<QMediaPlayer> m_media_player;
};

View file

@ -0,0 +1,185 @@
#include "qt_music_handler.h"
#include "Emu/Cell/Modules/cellMusic.h"
#include "Emu/System.h"
#include "Utilities/Thread.h"
#include "util/logs.hpp"
#include <QUrl>
LOG_CHANNEL(music_log, "Music");
qt_music_handler::qt_music_handler()
{
music_log.notice("Constructing Qt music handler...");
m_media_player = std::make_shared<QMediaPlayer>();
m_error_handler = std::make_unique<qt_music_error_handler>(m_media_player);
}
qt_music_handler::~qt_music_handler()
{
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]()
{
music_log.notice("Destroying Qt music handler...");
m_media_player->stop();
m_media_player.reset();
m_error_handler.reset();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
}
void qt_music_handler::play(const std::string& path)
{
std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, &path, this]()
{
music_log.notice("Playing music: %s", path);
m_media_player->setAudioRole(QAudio::Role::MusicRole);
m_media_player->setPlaybackRate(1.0);
m_media_player->setMedia(QUrl(QString::fromStdString(path)));
m_media_player->play();
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_PLAY;
}
void qt_music_handler::stop()
{
std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]()
{
music_log.notice("Stopping music...");
m_media_player->stop();
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_STOP;
}
void qt_music_handler::pause()
{
std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]()
{
music_log.notice("Pausing music...");
m_media_player->pause();
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_PAUSE;
}
void qt_music_handler::fast_forward()
{
std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]()
{
music_log.notice("Fast-forwarding music...");
m_media_player->setPlaybackRate(2.0);
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_FASTFORWARD;
}
void qt_music_handler::fast_reverse()
{
std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, this]()
{
music_log.notice("Fast-reversing music...");
m_media_player->setPlaybackRate(-2.0);
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
m_state = CELL_MUSIC_PB_STATUS_FASTREVERSE;
}
void qt_music_handler::set_volume(f32 volume)
{
std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
Emu.CallFromMainThread([&wake_up, &volume, this]()
{
const int new_volume = std::max<int>(0, std::min<int>(volume * 100, 100));
music_log.notice("Setting volume to %d%%", new_volume);
m_media_player->setVolume(new_volume);
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
}
f32 qt_music_handler::get_volume() const
{
std::lock_guard lock(m_mutex);
atomic_t<bool> wake_up = false;
f32 volume = 0.0f;
Emu.CallFromMainThread([&wake_up, &volume, this]()
{
const int current_volume = std::max(0, std::min(m_media_player->volume(), 100));
music_log.notice("Getting volume: %d%%", current_volume);
volume = current_volume / 100.0f;
wake_up = true;
wake_up.notify_one();
});
while (!wake_up && !Emu.IsStopped())
{
thread_ctrl::wait_on(wake_up, false);
}
return volume;
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "Emu/Io/music_handler_base.h"
#include "qt_music_error_handler.h"
#include <QMediaPlayer>
#include <mutex>
class qt_music_handler final : public music_handler_base
{
public:
qt_music_handler();
virtual ~qt_music_handler();
void stop() override;
void play(const std::string& path) override;
void pause() override;
void fast_forward() override;
void fast_reverse() override;
void set_volume(f32 volume) override;
f32 get_volume() const override;
private:
mutable std::mutex m_mutex;
std::unique_ptr<qt_music_error_handler> m_error_handler;
std::shared_ptr<QMediaPlayer> m_media_player;
};

View file

@ -1063,6 +1063,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
SubscribeTooltip(ui->gb_camera_id, tooltips.settings.camera_id);
}
m_emu_settings->EnhanceComboBox(ui->musicHandlerBox, emu_settings_type::MusicHandler);
SubscribeTooltip(ui->gb_music_handler, tooltips.settings.music_handler);
m_emu_settings->EnhanceComboBox(ui->padModeBox, emu_settings_type::PadHandlerMode);
SubscribeTooltip(ui->gb_pad_mode, tooltips.settings.pad_mode);

View file

@ -43,8 +43,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>821</width>
<height>667</height>
<width>838</width>
<height>641</height>
</rect>
</property>
<property name="sizePolicy">
@ -54,7 +54,7 @@
</sizepolicy>
</property>
<property name="currentIndex">
<number>6</number>
<number>2</number>
</property>
<widget class="QWidget" name="coreTab">
<attribute name="title">
@ -1077,6 +1077,34 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_music_handler">
<property name="title">
<string>Music Handler</string>
</property>
<layout class="QVBoxLayout" name="gb_music_handler_layout">
<item>
<widget class="QComboBox" name="musicHandlerBox"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacerAudioLeft">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
@ -1244,6 +1272,22 @@
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacerAudioRight">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>

View file

@ -198,6 +198,7 @@ public:
const QString pad_mode = tr("Single-threaded: All pad handlers run on the same thread sequentially.\nMulti-threaded: Each pad handler has its own thread.\nOnly use multi-threaded if you can spare the extra threads.");
const QString keyboard_handler = tr("Some games support native keyboard input.\nBasic will work in these cases.");
const QString mouse_handler = tr("Some games support native mouse input.\nBasic will work in these cases.");
const QString music_handler = tr("Currently only used for cellMusic emulation.\nSelect Qt to use the default output device of your operating system.\nThis may not be able to play all audio formats.");
const QString camera = tr("Select Qt Camera to use the default camera device of your operating system.");
const QString camera_type = tr("Depending on the game, you may need to select a specific camera type.");
const QString camera_flip = tr("Flips the camera image either horizontally, vertically, or on both axis.");

View file

@ -59,6 +59,7 @@ namespace utils
std::pair<bool, media_info> get_media_info(const std::string& path, s32 av_media_type)
{
media_info info;
info.path = path;
// Only print FFMPEG errors, fatals and panics
av_log_set_level(AV_LOG_ERROR);

View file

@ -6,6 +6,8 @@ namespace utils
{
struct media_info
{
std::string path;
s32 audio_av_codec_id = 0; // 0 = AV_CODEC_ID_NONE
s32 video_av_codec_id = 0; // 0 = AV_CODEC_ID_NONE
s32 audio_bitrate_bps = 0; // Bit rate in bit/s