mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 11:36:13 +00:00
cellMusic: implement qt music handler
This commit is contained in:
parent
0dbfe314a3
commit
15e74357cf
27 changed files with 885 additions and 24 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
20
rpcs3/Emu/Io/Null/null_music_handler.h
Normal file
20
rpcs3/Emu/Io/Null/null_music_handler.h
Normal 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};
|
||||
};
|
26
rpcs3/Emu/Io/music_handler_base.h
Normal file
26
rpcs3/Emu/Io/music_handler_base.h
Normal 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};
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -75,6 +75,12 @@ enum class audio_downmix
|
|||
use_application_settings
|
||||
};
|
||||
|
||||
enum class music_handler
|
||||
{
|
||||
null,
|
||||
qt
|
||||
};
|
||||
|
||||
enum class camera_handler
|
||||
{
|
||||
null,
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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"}},
|
||||
|
|
|
@ -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; };
|
||||
|
|
92
rpcs3/rpcs3qt/qt_music_error_handler.cpp
Normal file
92
rpcs3/rpcs3qt/qt_music_error_handler.cpp
Normal 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);
|
||||
}
|
21
rpcs3/rpcs3qt/qt_music_error_handler.h
Normal file
21
rpcs3/rpcs3qt/qt_music_error_handler.h
Normal 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;
|
||||
};
|
185
rpcs3/rpcs3qt/qt_music_handler.cpp
Normal file
185
rpcs3/rpcs3qt/qt_music_handler.cpp
Normal 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;
|
||||
}
|
27
rpcs3/rpcs3qt/qt_music_handler.h
Normal file
27
rpcs3/rpcs3qt/qt_music_handler.h
Normal 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;
|
||||
};
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue