diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index fe1f4bb75a..8edf2da28c 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -264,6 +264,7 @@ target_sources(rpcs3_emu PRIVATE Cell/Modules/cellMsgDialog.cpp Cell/Modules/cellMusic.cpp Cell/Modules/cellMusicDecode.cpp + Cell/Modules/cellMusicSelectionContext.cpp Cell/Modules/cellMusicExport.cpp Cell/Modules/cellNetAoi.cpp Cell/Modules/cellNetCtl.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.cpp b/rpcs3/Emu/Cell/Modules/cellMusic.cpp index d9cb4c723c..f35fdda01a 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusic.cpp @@ -72,7 +72,7 @@ struct music_state vm::ptr userData{}; std::mutex mtx; std::shared_ptr handler; - music_selection_context current_selection_context; + music_selection_context current_selection_context{}; SAVESTATE_INIT_POS(16); @@ -118,17 +118,17 @@ error_code cell_music_select_contents() { sysutil_register_cb([&music, dir_path, vfs_dir_path, info, status](ppu_thread& ppu) -> s32 { + std::lock_guard lock(music.mtx); const u32 result = status >= 0 ? u32{CELL_OK} : u32{CELL_MUSIC_CANCELED}; if (result == CELL_OK) { - music_selection_context context; - context.content_path = info.path; - context.content_path = dir_path + info.path.substr(vfs_dir_path.length()); // We need the non-vfs path here - context.content_type = fs::is_dir(info.path) ? CELL_SEARCH_CONTENTTYPE_MUSICLIST : CELL_SEARCH_CONTENTTYPE_MUSIC; + music_selection_context context{}; + context.set_playlist(info.path); // TODO: context.repeat_mode = CELL_SEARCH_REPEATMODE_NONE; // TODO: context.context_option = CELL_SEARCH_CONTEXTOPTION_NONE; music.current_selection_context = context; - cellMusic.success("Media list dialog: selected entry '%s'", context.content_path); + music.current_selection_context.create_playlist(music_selection_context::get_next_hash()); + cellMusic.success("Media list dialog: selected entry '%s'", context.playlist.front()); } else { @@ -178,21 +178,8 @@ error_code cellMusicSetSelectionContext2(vm::ptr cont } 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 - { - std::string dahex; - - for (usz i = 0; i < CELL_MUSIC_SELECTION_CONTEXT_SIZE; i++) - { - fmt::append(dahex, " %.2x", context.data[i]); - } - - cellMusic.todo("cellMusicSetSelectionContext2: failed. context = %s", dahex); - } + if (result) cellMusic.success("cellMusicSetSelectionContext2: new selection context = %s", music.current_selection_context.to_string()); + else cellMusic.todo("cellMusicSetSelectionContext2: failed. context = %s", music_selection_context::context_to_hex(context)); music.func(ppu, CELL_MUSIC2_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData); return CELL_OK; @@ -248,17 +235,17 @@ error_code cellMusicSetSelectionContext(vm::ptr conte if (!music.func) return CELL_MUSIC_ERROR_GENERIC; - sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 + sysutil_register_cb([context = *context, &music](ppu_thread& ppu) -> s32 { bool result = false; { std::lock_guard lock(music.mtx); - result = music.current_selection_context.set(*context); + 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); + else cellMusic.todo("cellMusicSetSelectionContext: failed. context = %s)", music_selection_context::context_to_hex(context)); music.func(ppu, CELL_MUSIC_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData); return CELL_OK; @@ -404,6 +391,7 @@ error_code cellMusicGetSelectionContext2(vm::ptr cont auto& music = g_fxo->get(); std::lock_guard lock(music.mtx); + *context = music.current_selection_context.get(); cellMusic.success("cellMusicGetSelectionContext2: selection context = %s", music.current_selection_context.to_string()); @@ -450,31 +438,53 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr 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.content_path); - cellMusic.notice("cellMusicSetPlaybackCommand2: current vfs path: '%s' (unresolved='%s')", path, music.current_selection_context.content_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_PLAY: case CELL_MUSIC2_PB_CMD_NEXT: - music.handler->play(path); - break; case CELL_MUSIC2_PB_CMD_PREV: + { + std::string path; + bool playback_finished = false; + { + std::lock_guard lock(music.mtx); + const std::vector& playlist = music.current_selection_context.playlist; + u32 next_track = music.current_selection_context.current_track; + + if (command != CELL_MUSIC2_PB_CMD_PLAY) + { + next_track = music.current_selection_context.step_track(command == CELL_MUSIC2_PB_CMD_NEXT); + } + + if (next_track < playlist.size()) + { + path = vfs::get(playlist.at(next_track)); + cellMusic.notice("cellMusicSetPlaybackCommand2: current vfs path: '%s' (unresolved='%s')", path, playlist.at(next_track)); + } + else + { + playback_finished = true; + } + } + + if (playback_finished) + { + // TODO: is CELL_MUSIC2_PLAYBACK_FINISHED correct here ? + cellMusic.notice("cellMusicSetPlaybackCommand2: no more tracks to play"); + music.handler->stop(); + music.func(ppu, CELL_MUSIC2_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_MUSIC2_PLAYBACK_FINISHED), music.userData); + return CELL_OK; + } + music.handler->play(path); break; + } case CELL_MUSIC2_PB_CMD_FASTFORWARD: music.handler->fast_forward(); break; @@ -506,31 +516,53 @@ error_code cellMusicSetPlaybackCommand(s32 command, vm::ptr 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.content_path); - cellMusic.notice("cellMusicSetPlaybackCommand: current vfs path: '%s' (unresolved='%s')", path, music.current_selection_context.content_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_PLAY: case CELL_MUSIC_PB_CMD_NEXT: - music.handler->play(path); - break; case CELL_MUSIC_PB_CMD_PREV: + { + std::string path; + bool playback_finished = false; + { + std::lock_guard lock(music.mtx); + const std::vector& playlist = music.current_selection_context.playlist; + u32 next_track = music.current_selection_context.current_track; + + if (command != CELL_MUSIC_PB_CMD_PLAY) + { + next_track = music.current_selection_context.step_track(command == CELL_MUSIC_PB_CMD_NEXT); + } + + if (next_track < playlist.size()) + { + path = vfs::get(playlist.at(next_track)); + cellMusic.notice("cellMusicSetPlaybackCommand: current vfs path: '%s' (unresolved='%s')", path, playlist.at(next_track)); + } + else + { + playback_finished = true; + } + } + + if (playback_finished) + { + // TODO: is CELL_MUSIC_PLAYBACK_FINISHED correct here ? + cellMusic.notice("cellMusicSetPlaybackCommand: no more tracks to play"); + music.handler->stop(); + music.func(ppu, CELL_MUSIC_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_MUSIC_PLAYBACK_FINISHED), music.userData); + return CELL_OK; + } + music.handler->play(path); break; + } case CELL_MUSIC_PB_CMD_FASTFORWARD: music.handler->fast_forward(); break; diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.h b/rpcs3/Emu/Cell/Modules/cellMusic.h index 577ff2fa3f..625b4768c3 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.h +++ b/rpcs3/Emu/Cell/Modules/cellMusic.h @@ -142,56 +142,30 @@ struct CellMusicSelectionContext struct music_selection_context { char magic[4] = "SUS"; - u32 content_type{0}; - u32 repeat_mode{0}; - u32 context_option{0}; - std::string content_path; + std::string hash; + CellSearchContentType content_type = CELL_SEARCH_CONTENTTYPE_MUSIC; + CellSearchRepeatMode repeat_mode = CELL_SEARCH_REPEATMODE_NONE; + CellSearchContextOption context_option = CELL_SEARCH_CONTEXTOPTION_NONE; + u32 first_track{0}; + u32 current_track{0}; + std::vector playlist; static constexpr u32 max_depth = 2; // root + 1 folder + file + static constexpr const char* target_file_type = "Music Playlist"; + static constexpr const char* target_version = "1.0"; + static std::string get_next_hash(); + static std::string context_to_hex(const CellMusicSelectionContext& context); - bool set(const CellMusicSelectionContext& in) - { - if (memcmp(in.data, magic, sizeof(magic)) != 0) - { - return false; - } + bool set(const CellMusicSelectionContext& in); + CellMusicSelectionContext get() const; - 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++]; - content_path = &in.data[pos]; + std::string to_string() const; + std::string get_yaml_path() const; - return true; - } - - CellMusicSelectionContext get() const - { - if (content_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], content_path.c_str(), content_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, content_path); - } + void set_playlist(const std::string& path); + void create_playlist(const std::string& new_hash); + bool load_playlist(); + u32 step_track(bool next); // Helper error_code find_content_id(vm::ptr contents_id); diff --git a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp index 1b04637c33..66f52680bc 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp @@ -5,6 +5,7 @@ #include "Emu/Cell/lv2/sys_spu.h" #include "Emu/RSX/Overlays/overlay_media_list_dialog.h" #include "Emu/VFS.h" +#include "cellMusicDecode.h" #include "cellMusic.h" #include "cellSearch.h" #include "cellSpurs.h" @@ -16,25 +17,6 @@ LOG_CHANNEL(cellMusicDecode); -// Return Codes (CELL_MUSIC_DECODE2 codes are omitted if they are identical) -enum CellMusicDecodeError : u32 -{ - CELL_MUSIC_DECODE_CANCELED = 1, - CELL_MUSIC_DECODE_DECODE_FINISHED = 0x8002C101, - CELL_MUSIC_DECODE_ERROR_PARAM = 0x8002C102, - CELL_MUSIC_DECODE_ERROR_BUSY = 0x8002C103, - CELL_MUSIC_DECODE_ERROR_NO_ACTIVE_CONTENT = 0x8002C104, - CELL_MUSIC_DECODE_ERROR_NO_MATCH_FOUND = 0x8002C105, - CELL_MUSIC_DECODE_ERROR_INVALID_CONTEXT = 0x8002C106, - CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE = 0x8002C107, - CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT = 0x8002C108, - CELL_MUSIC_DECODE_DIALOG_OPEN = 0x8002C109, - CELL_MUSIC_DECODE_DIALOG_CLOSE = 0x8002C10A, - CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA = 0x8002C10B, - CELL_MUSIC_DECODE_NEXT_CONTENTS_READY = 0x8002C10C, - CELL_MUSIC_DECODE_ERROR_GENERIC = 0x8002C1FF, -}; - template<> void fmt_class_string::format(std::string& out, u64 arg) { @@ -62,55 +44,6 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -// Constants (CELL_MUSIC_DECODE2 codes are omitted if they are identical) -enum -{ - CELL_MUSIC_DECODE_EVENT_STATUS_NOTIFICATION = 0, - CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT = 1, - CELL_MUSIC_DECODE_EVENT_FINALIZE_RESULT = 2, - CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT = 3, - CELL_MUSIC_DECODE_EVENT_SET_DECODE_COMMAND_RESULT = 4, - CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT = 5, - CELL_MUSIC_DECODE_EVENT_UI_NOTIFICATION = 6, - CELL_MUSIC_DECODE_EVENT_NEXT_CONTENTS_READY_RESULT = 7, - - CELL_MUSIC_DECODE_MODE_NORMAL = 0, - - CELL_MUSIC_DECODE_CMD_STOP = 0, - CELL_MUSIC_DECODE_CMD_START = 1, - CELL_MUSIC_DECODE_CMD_NEXT = 2, - CELL_MUSIC_DECODE_CMD_PREV = 3, - - CELL_MUSIC_DECODE_STATUS_DORMANT = 0, - CELL_MUSIC_DECODE_STATUS_DECODING = 1, - - CELL_MUSIC_DECODE_POSITION_NONE = 0, - CELL_MUSIC_DECODE_POSITION_START = 1, - CELL_MUSIC_DECODE_POSITION_MID = 2, - CELL_MUSIC_DECODE_POSITION_END = 3, - CELL_MUSIC_DECODE_POSITION_END_LIST_END = 4, - - CELL_MUSIC_DECODE2_MODE_NORMAL = 0, - - CELL_MUSIC_DECODE2_SPEED_MAX = 0, - CELL_MUSIC_DECODE2_SPEED_2 = 2, - - CELL_SYSUTIL_MUSIC_DECODE2_INITIALIZING_FINISHED = 1, - CELL_SYSUTIL_MUSIC_DECODE2_SHUTDOWN_FINISHED = 4, // 3(SDK103) -> 4(SDK110) - CELL_SYSUTIL_MUSIC_DECODE2_LOADING_FINISHED = 5, - CELL_SYSUTIL_MUSIC_DECODE2_UNLOADING_FINISHED = 7, - CELL_SYSUTIL_MUSIC_DECODE2_RELEASED = 9, - CELL_SYSUTIL_MUSIC_DECODE2_GRABBED = 11, - - CELL_MUSIC_DECODE2_MIN_BUFFER_SIZE = 448 * 1024, - CELL_MUSIC_DECODE2_MANAGEMENT_SIZE = 64 * 1024, - CELL_MUSIC_DECODE2_PAGESIZE_64K = 64 * 1024, - CELL_MUSIC_DECODE2_PAGESIZE_1M = 1 * 1024 * 1024, -}; - -using CellMusicDecodeCallback = void(u32, vm::ptr param, vm::ptr userData); -using CellMusicDecode2Callback = void(u32, vm::ptr param, vm::ptr userData); - struct music_decode { vm::ptr func{}; @@ -118,7 +51,7 @@ struct music_decode music_selection_context current_selection_context{}; s32 decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT; s32 decode_command = CELL_MUSIC_DECODE_CMD_STOP; - u64 readPos = 0; + u64 read_pos = 0; utils::audio_decoder decoder{}; shared_mutex mutex; @@ -138,21 +71,41 @@ struct music_decode case CELL_MUSIC_DECODE_CMD_START: { decode_status = CELL_MUSIC_DECODE_STATUS_DECODING; - readPos = 0; + read_pos = 0; // Decode data. The format of the decoded data is 48kHz, float 32bit, 2ch LPCM data interleaved in order from left to right. - const std::string path = vfs::get(current_selection_context.content_path); - cellMusicDecode.notice("set_decode_command(START): Setting vfs path: '%s' (unresolved='%s')", path, current_selection_context.content_path); + cellMusicDecode.notice("set_decode_command(START): context: %s", current_selection_context.to_string()); - decoder.set_path(path); + music_selection_context context = current_selection_context; + + for (usz i = 0; i < context.playlist.size(); i++) + { + context.playlist[i] = vfs::get(context.playlist[i]); + } + + // TODO: set speed if small-memory decoding is used (music_decode2) + decoder.set_context(std::move(context)); decoder.set_swap_endianness(true); decoder.decode(); break; } - case CELL_MUSIC_DECODE_CMD_NEXT: // TODO: set path of next file if possible - case CELL_MUSIC_DECODE_CMD_PREV: // TODO: set path of prev file if possible + case CELL_MUSIC_DECODE_CMD_NEXT: + case CELL_MUSIC_DECODE_CMD_PREV: { - return CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT; + decoder.stop(); + + if (decoder.set_next_index(command == CELL_MUSIC_DECODE_CMD_NEXT) == umax) + { + decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT; + return CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT; + } + + decoder.decode(); + break; + } + default: + { + fmt::throw_exception("Unknown decode command %d", command); } } return CELL_OK; @@ -163,7 +116,7 @@ struct music_decode decoder.stop(); decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT; decode_command = CELL_MUSIC_DECODE_CMD_STOP; - readPos = 0; + read_pos = 0; return CELL_OK; } }; @@ -194,13 +147,13 @@ error_code cell_music_decode_select_contents() const u32 result = status >= 0 ? u32{CELL_OK} : u32{CELL_MUSIC_DECODE_CANCELED}; if (result == CELL_OK) { - music_selection_context context; - context.content_path = dir_path + info.path.substr(vfs_dir_path.length()); // We need the non-vfs path here - context.content_type = fs::is_dir(info.path) ? CELL_SEARCH_CONTENTTYPE_MUSICLIST : CELL_SEARCH_CONTENTTYPE_MUSIC; + music_selection_context context{}; + context.set_playlist(info.path); // TODO: context.repeat_mode = CELL_SEARCH_REPEATMODE_NONE; // TODO: context.context_option = CELL_SEARCH_CONTEXTOPTION_NONE; dec.current_selection_context = context; - cellMusicDecode.success("Media list dialog: selected entry '%s'", context.content_path); + dec.current_selection_context.create_playlist(music_selection_context::get_next_hash()); + cellMusicDecode.success("Media list dialog: selected entry '%s'", context.playlist.front()); } else { @@ -217,49 +170,73 @@ template error_code cell_music_decode_read(vm::ptr buf, vm::ptr startTime, u64 reqSize, vm::ptr readSize, vm::ptr position) { if (!buf || !startTime || !position || !reqSize || !readSize) + { return CELL_MUSIC_DECODE_ERROR_PARAM; + } + + *position = CELL_MUSIC_DECODE_POSITION_NONE; + *readSize = 0; + *startTime = 0; auto& dec = g_fxo->get(); std::lock_guard lock(dec.mutex); std::scoped_lock slock(dec.decoder.m_mtx); if (dec.decoder.has_error) + { return CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE; + } if (dec.decoder.m_size == 0) { - *position = CELL_MUSIC_DECODE_POSITION_NONE; - *readSize = 0; return CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA; } - if (dec.readPos == 0) + const u64 size_left = dec.decoder.m_size - dec.read_pos; + + if (dec.read_pos == 0) { + cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_START, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load()); *position = CELL_MUSIC_DECODE_POSITION_START; } - else if ((dec.readPos + reqSize) >= dec.decoder.m_size) + else if (!dec.decoder.track_fully_decoded || size_left > reqSize) // track_fully_decoded is not guarded by a mutex, but since it is set to true after the decode, it should be fine. { - // TODO: CELL_MUSIC_DECODE_POSITION_END - *position = CELL_MUSIC_DECODE_POSITION_END_LIST_END; + cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_MID, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load()); + *position = CELL_MUSIC_DECODE_POSITION_MID; } else { - *position = CELL_MUSIC_DECODE_POSITION_MID; + if (dec.decoder.set_next_index(true) == umax) + { + cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END_LIST_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load()); + *position = CELL_MUSIC_DECODE_POSITION_END_LIST_END; + } + else + { + cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load()); + *position = CELL_MUSIC_DECODE_POSITION_END; + } } - const u64 size_to_read = (dec.readPos + reqSize) <= dec.decoder.m_size ? reqSize : dec.decoder.m_size - dec.readPos; - std::memcpy(buf.get_ptr(), &dec.decoder.data[dec.readPos], size_to_read); - - dec.readPos += size_to_read; + const u64 size_to_read = std::min(reqSize, size_left); *readSize = size_to_read; + if (size_to_read == 0) + { + return CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA; // TODO: speculative + } + + std::memcpy(buf.get_ptr(), &dec.decoder.data[dec.read_pos], size_to_read); + + dec.read_pos += size_to_read; + s64 start_time_ms = 0; if (!dec.decoder.timestamps_ms.empty()) { start_time_ms = dec.decoder.timestamps_ms.front().second; - while (dec.decoder.timestamps_ms.size() > 1 && dec.readPos >= dec.decoder.timestamps_ms.at(1).first) + while (dec.decoder.timestamps_ms.size() > 1 && dec.read_pos >= dec.decoder.timestamps_ms.at(1).first) { dec.decoder.timestamps_ms.pop_front(); } @@ -267,6 +244,29 @@ error_code cell_music_decode_read(vm::ptr buf, vm::ptr startTime, u64 *startTime = static_cast(start_time_ms); // startTime is milliseconds + switch (*position) + { + case CELL_MUSIC_DECODE_POSITION_END_LIST_END: + { + // Reset the decoder and the decode status + ensure(dec.set_decode_command(CELL_MUSIC_DECODE_CMD_STOP) == CELL_OK); + dec.read_pos = 0; + break; + } + case CELL_MUSIC_DECODE_POSITION_END: + { + dec.read_pos = 0; + dec.decoder.clear(); + dec.decoder.track_fully_consumed = true; + dec.decoder.track_fully_consumed.notify_one(); + break; + } + default: + { + break; + } + } + cellMusicDecode.trace("cell_music_decode_read(size_to_read=%d, samples=%d, start_time_ms=%d)", size_to_read, size_to_read / sizeof(u64), start_time_ms); return CELL_OK; @@ -360,7 +360,11 @@ error_code cellMusicDecodeSetDecodeCommand(s32 command) if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; - const error_code result = dec.set_decode_command(command); + error_code result = CELL_OK; + { + std::scoped_lock slock(dec.decoder.m_mtx); + result = dec.set_decode_command(command); + } sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { @@ -402,6 +406,7 @@ error_code cellMusicDecodeGetSelectionContext(vm::ptr auto& dec = g_fxo->get(); std::lock_guard lock(dec.mutex); + *context = dec.current_selection_context.get(); cellMusicDecode.warning("cellMusicDecodeGetSelectionContext: selection_context = %s", dec.current_selection_context.to_string()); @@ -423,7 +428,7 @@ error_code cellMusicDecodeSetSelectionContext(vm::ptr const bool result = dec.current_selection_context.set(*context); if (result) cellMusicDecode.warning("cellMusicDecodeSetSelectionContext: new selection_context = %s", dec.current_selection_context.to_string()); - else cellMusicDecode.error("cellMusicDecodeSetSelectionContext: failed. context = '%s'", context->data); + else cellMusicDecode.error("cellMusicDecodeSetSelectionContext: failed. context = '%s'", music_selection_context::context_to_hex(*context)); sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { @@ -540,7 +545,11 @@ error_code cellMusicDecodeSetDecodeCommand2(s32 command) if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; - const error_code result = dec.set_decode_command(command); + error_code result = CELL_OK; + { + std::scoped_lock slock(dec.decoder.m_mtx); + result = dec.set_decode_command(command); + } sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { @@ -582,6 +591,7 @@ error_code cellMusicDecodeGetSelectionContext2(vm::ptrget(); std::lock_guard lock(dec.mutex); + *context = dec.current_selection_context.get(); cellMusicDecode.warning("cellMusicDecodeGetSelectionContext2: selection context = %s)", dec.current_selection_context.to_string()); @@ -603,7 +613,7 @@ error_code cellMusicDecodeSetSelectionContext2(vm::ptrdata); + else cellMusicDecode.error("cellMusicDecodeSetSelectionContext2: failed. context = '%s'", music_selection_context::context_to_hex(*context)); sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { diff --git a/rpcs3/Emu/Cell/Modules/cellMusicDecode.h b/rpcs3/Emu/Cell/Modules/cellMusicDecode.h new file mode 100644 index 0000000000..dace5ddf1a --- /dev/null +++ b/rpcs3/Emu/Cell/Modules/cellMusicDecode.h @@ -0,0 +1,69 @@ +#pragma once + +// Return Codes (CELL_MUSIC_DECODE2 codes are omitted if they are identical) +enum CellMusicDecodeError : u32 +{ + CELL_MUSIC_DECODE_CANCELED = 1, + CELL_MUSIC_DECODE_DECODE_FINISHED = 0x8002C101, + CELL_MUSIC_DECODE_ERROR_PARAM = 0x8002C102, + CELL_MUSIC_DECODE_ERROR_BUSY = 0x8002C103, + CELL_MUSIC_DECODE_ERROR_NO_ACTIVE_CONTENT = 0x8002C104, + CELL_MUSIC_DECODE_ERROR_NO_MATCH_FOUND = 0x8002C105, + CELL_MUSIC_DECODE_ERROR_INVALID_CONTEXT = 0x8002C106, + CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE = 0x8002C107, + CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT = 0x8002C108, + CELL_MUSIC_DECODE_DIALOG_OPEN = 0x8002C109, + CELL_MUSIC_DECODE_DIALOG_CLOSE = 0x8002C10A, + CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA = 0x8002C10B, + CELL_MUSIC_DECODE_NEXT_CONTENTS_READY = 0x8002C10C, + CELL_MUSIC_DECODE_ERROR_GENERIC = 0x8002C1FF, +}; + +// Constants (CELL_MUSIC_DECODE2 codes are omitted if they are identical) +enum +{ + CELL_MUSIC_DECODE_EVENT_STATUS_NOTIFICATION = 0, + CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT = 1, + CELL_MUSIC_DECODE_EVENT_FINALIZE_RESULT = 2, + CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT = 3, + CELL_MUSIC_DECODE_EVENT_SET_DECODE_COMMAND_RESULT = 4, + CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT = 5, + CELL_MUSIC_DECODE_EVENT_UI_NOTIFICATION = 6, + CELL_MUSIC_DECODE_EVENT_NEXT_CONTENTS_READY_RESULT = 7, + + CELL_MUSIC_DECODE_MODE_NORMAL = 0, + + CELL_MUSIC_DECODE_CMD_STOP = 0, + CELL_MUSIC_DECODE_CMD_START = 1, + CELL_MUSIC_DECODE_CMD_NEXT = 2, + CELL_MUSIC_DECODE_CMD_PREV = 3, + + CELL_MUSIC_DECODE_STATUS_DORMANT = 0, + CELL_MUSIC_DECODE_STATUS_DECODING = 1, + + CELL_MUSIC_DECODE_POSITION_NONE = 0, + CELL_MUSIC_DECODE_POSITION_START = 1, + CELL_MUSIC_DECODE_POSITION_MID = 2, + CELL_MUSIC_DECODE_POSITION_END = 3, + CELL_MUSIC_DECODE_POSITION_END_LIST_END = 4, + + CELL_MUSIC_DECODE2_MODE_NORMAL = 0, + + CELL_MUSIC_DECODE2_SPEED_MAX = 0, + CELL_MUSIC_DECODE2_SPEED_2 = 2, + + CELL_SYSUTIL_MUSIC_DECODE2_INITIALIZING_FINISHED = 1, + CELL_SYSUTIL_MUSIC_DECODE2_SHUTDOWN_FINISHED = 4, // 3(SDK103) -> 4(SDK110) + CELL_SYSUTIL_MUSIC_DECODE2_LOADING_FINISHED = 5, + CELL_SYSUTIL_MUSIC_DECODE2_UNLOADING_FINISHED = 7, + CELL_SYSUTIL_MUSIC_DECODE2_RELEASED = 9, + CELL_SYSUTIL_MUSIC_DECODE2_GRABBED = 11, + + CELL_MUSIC_DECODE2_MIN_BUFFER_SIZE = 448 * 1024, + CELL_MUSIC_DECODE2_MANAGEMENT_SIZE = 64 * 1024, + CELL_MUSIC_DECODE2_PAGESIZE_64K = 64 * 1024, + CELL_MUSIC_DECODE2_PAGESIZE_1M = 1 * 1024 * 1024, +}; + +using CellMusicDecodeCallback = void(u32, vm::ptr param, vm::ptr userData); +using CellMusicDecode2Callback = void(u32, vm::ptr param, vm::ptr userData); diff --git a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp new file mode 100644 index 0000000000..376d673b2c --- /dev/null +++ b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp @@ -0,0 +1,333 @@ +#include "stdafx.h" +#include "cellMusic.h" +#include "util/yaml.hpp" +#include "Emu/VFS.h" + +#include + +// This is just a helper and not a real cell entity + +LOG_CHANNEL(cellMusicSelectionContext); + +bool music_selection_context::set(const CellMusicSelectionContext& in) +{ + if (memcmp(in.data, magic, sizeof(magic)) != 0) + { + return false; + } + + u32 pos = sizeof(magic); + hash = &in.data[pos]; + + return load_playlist(); +} + +CellMusicSelectionContext music_selection_context::get() const +{ + if (hash.size() + 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], hash.c_str(), hash.size()); + + return out; +} + +std::string music_selection_context::to_string() const +{ + return fmt::format("{ .magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .first_track=%d, .tracks=%d, .hash='%s' }", + magic, static_cast(content_type), static_cast(repeat_mode), static_cast(context_option), first_track, playlist.size(), hash); +} + +std::string music_selection_context::get_next_hash() +{ + static u64 hash_counter = 0; + return fmt::format("music_selection_context_%d", hash_counter++); +} + +std::string music_selection_context::context_to_hex(const CellMusicSelectionContext& context) +{ + std::string dahex; + + for (usz i = 0; i < CELL_MUSIC_SELECTION_CONTEXT_SIZE; i++) + { + fmt::append(dahex, " %.2x", context.data[i]); + } + + return dahex; +} + +std::string music_selection_context::get_yaml_path() const +{ + std::string path = fs::get_cache_dir() + "cache/playlists/"; + + if (!fs::create_path(path)) + { + cellMusicSelectionContext.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error); + } + + return path + hash + ".yml"; +} + +void music_selection_context::set_playlist(const std::string& path) +{ + playlist.clear(); + + const std::string dir_path = "/dev_hdd0/music"; + const std::string vfs_dir_path = vfs::get("/dev_hdd0/music"); + + if (fs::is_dir(path)) + { + content_type = CELL_SEARCH_CONTENTTYPE_MUSICLIST; + + for (auto&& dir_entry : fs::dir{path}) + { + dir_entry.name = vfs::unescape(dir_entry.name); + + if (dir_entry.name == "." || dir_entry.name == "..") + { + continue; + } + + playlist.push_back(dir_path + std::string(path + "/" + dir_entry.name).substr(vfs_dir_path.length())); + } + } + else + { + content_type = CELL_SEARCH_CONTENTTYPE_MUSIC; + playlist.push_back(dir_path + path.substr(vfs_dir_path.length())); + } +} + +void music_selection_context::create_playlist(const std::string& new_hash) +{ + hash = new_hash; + + const std::string yaml_path = get_yaml_path(); + cellMusicSelectionContext.notice("Saving music playlist file %s", yaml_path); + + YAML::Emitter out; + out << YAML::BeginMap; + out << "Version" << target_version; + out << "FileType" << target_file_type; + out << "ContentType" << content_type; + out << "ContextOption" << context_option; + out << "RepeatMode" << repeat_mode; + out << "FirstTrack" << first_track; + out << "Tracks" << YAML::BeginSeq; + + for (const std::string& track : playlist) + { + out << track; + } + + out << YAML::EndSeq; + out << YAML::EndMap; + + fs::pending_file file(yaml_path); + + if (!file.file || (file.file.write(out.c_str(), out.size()), !file.commit())) + { + cellMusicSelectionContext.error("Failed to create music playlist file %s (%s)", yaml_path, fs::g_tls_error); + } +} + +bool music_selection_context::load_playlist() +{ + playlist.clear(); + + const std::string path = get_yaml_path(); + cellMusicSelectionContext.notice("Loading music playlist file %s", path); + + std::string content; + { + // Load patch file + fs::file file{path}; + + if (!file) + { + cellMusicSelectionContext.error("Failed to load music playlist file %s: %s", path, fs::g_tls_error); + return false; + } + + content = file.to_string(); + } + + auto [root, error] = yaml_load(content); + + if (!error.empty() || !root) + { + cellMusicSelectionContext.error("Failed to load music playlist file %s:\n%s", path, error); + return false; + } + + std::string err; + + const std::string version = get_yaml_node_value(root["Version"], err); + if (!err.empty()) + { + cellMusicSelectionContext.error("No Version entry found. Error: '%s' (file: %s)", err, path); + return false; + } + + if (version != target_version) + { + cellMusicSelectionContext.error("Version '%s' does not match music playlist target '%s' (file: %s)", version, target_version, path); + return false; + } + + const std::string file_type = get_yaml_node_value(root["FileType"], err); + if (!err.empty()) + { + cellMusicSelectionContext.error("No FileType entry found. Error: '%s' (file: %s)", err, path); + return false; + } + + if (file_type != target_file_type) + { + cellMusicSelectionContext.error("FileType '%s' does not match music playlist target '%s' (file: %s)", file_type, target_file_type, path); + return false; + } + + content_type = static_cast(get_yaml_node_value(root["ContentType"], err)); + if (!err.empty()) + { + cellMusicSelectionContext.error("No ContentType entry found. Error: '%s' (file: %s)", err, path); + return false; + } + + context_option = static_cast(get_yaml_node_value(root["ContextOption"], err)); + if (!err.empty()) + { + cellMusicSelectionContext.error("No ContextOption entry found. Error: '%s' (file: %s)", err, path); + return false; + } + + repeat_mode = static_cast(get_yaml_node_value(root["RepeatMode"], err)); + if (!err.empty()) + { + cellMusicSelectionContext.error("No RepeatMode entry found. Error: '%s' (file: %s)", err, path); + return false; + } + + first_track = get_yaml_node_value(root["FirstTrack"], err); + if (!err.empty()) + { + cellMusicSelectionContext.error("No FirstTrack entry found. Error: '%s' (file: %s)", err, path); + return false; + } + + const YAML::Node& track_node = root["Tracks"]; + + if (!track_node || track_node.Type() != YAML::NodeType::Sequence) + { + cellMusicSelectionContext.error("No Tracks entry found or Tracks is not a Sequence. (file: %s)", path); + return false; + } + + for (usz i = 0; i < track_node.size(); i++) + { + playlist.push_back(track_node[i].Scalar()); + } + + return true; +} + +u32 music_selection_context::step_track(bool next) +{ + if (playlist.empty()) + { + cellMusicSelectionContext.error("No tracks to play..."); + current_track = umax; + return umax; + } + + switch (repeat_mode) + { + case CELL_SEARCH_REPEATMODE_NONE: + { + if (next) + { + // Try to play the next track. + if (++current_track >= playlist.size()) + { + // We are at the end of the playlist. + cellMusicSelectionContext.notice("No more tracks to play in playlist..."); + current_track = umax; + return umax; + } + } + else + { + // Try to play the previous track. + if (current_track == 0) + { + // We are at the start of the playlist. + cellMusicSelectionContext.notice("No more tracks to play in playlist..."); + current_track = umax; + return umax; + } + + current_track--; + } + break; + } + case CELL_SEARCH_REPEATMODE_REPEAT1: + { + // Keep decoding the same track. + break; + } + case CELL_SEARCH_REPEATMODE_ALL: + { + if (next) + { + // Play the next track. Start with the first track if we reached the end of the playlist. + current_track = (current_track + 1) % playlist.size(); + } + else + { + // Play the previous track. Start with the last track if we reached the start of the playlist. + if (current_track == 0) + { + current_track = playlist.size() - 1; + } + else + { + current_track--; + } + } + break; + } + case CELL_SEARCH_REPEATMODE_NOREPEAT1: + { + // We are done. We only wanted to decode a single track. + cellMusicSelectionContext.notice("No more tracks to play..."); + current_track = umax; + return umax; + } + default: + { + fmt::throw_exception("Unknown repeat mode %d", static_cast(repeat_mode)); + } + } + + if (context_option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE && repeat_mode == CELL_SEARCH_REPEATMODE_ALL && playlist.size() > 1) + { + if (next ? current_track == 0 : current_track == (playlist.size() - 1)) + { + // We reached the first or last track again. Let's shuffle! + cellMusicSelectionContext.notice("Shuffling playlist..."); + auto engine = std::default_random_engine{}; + std::shuffle(std::begin(playlist), std::end(playlist), engine); + } + } + + return current_track; +} diff --git a/rpcs3/Emu/Cell/Modules/cellSearch.cpp b/rpcs3/Emu/Cell/Modules/cellSearch.cpp index 8ce1f4fda3..a24219a44d 100644 --- a/rpcs3/Emu/Cell/Modules/cellSearch.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSearch.cpp @@ -71,7 +71,9 @@ struct search_info struct search_content_t { CellSearchContentType type = CELL_SEARCH_CONTENTTYPE_NONE; - CellSearchTimeInfo timeInfo; + CellSearchRepeatMode repeat_mode = CELL_SEARCH_REPEATMODE_NONE; + CellSearchContextOption context_option = CELL_SEARCH_CONTEXTOPTION_NONE; + CellSearchTimeInfo time_info; CellSearchContentInfoPath infoPath; union { @@ -1555,7 +1557,7 @@ error_code cellSearchGetContentIdByOffset(CellSearchId searchId, s32 offset, vm: if (outTimeInfo) { - std::memcpy(outTimeInfo.get_ptr(), &content_id.second->timeInfo, sizeof(content_id.second->timeInfo)); + std::memcpy(outTimeInfo.get_ptr(), &content_id.second->time_info, sizeof(content_id.second->time_info)); } } @@ -1644,7 +1646,7 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrcontent_ids[0]; @@ -1685,8 +1687,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrsecond->infoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.content_path); + context.playlist.push_back(content->second->infoPath.contentPath); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.playlist.back()); } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) { @@ -1696,15 +1698,17 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr content = get_random_content(); - context.content_path = content->infoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path=%s", content_hash, +content->type, context.content_path); + context.playlist.push_back(content->infoPath.contentPath); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path=%s", content_hash, +content->type, context.playlist.back()); } else { // Select the first track by default - context.content_path = first_content->infoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.content_path); + // TODO: whole playlist + context.playlist.push_back(first_content->infoPath.contentPath); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.playlist.back()); } } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) @@ -1715,27 +1719,34 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr content = get_random_content(); - context.content_path = content->infoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path=%s", +content->type, context.content_path); + context.playlist.push_back(content->infoPath.contentPath); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path=%s", +content->type, context.playlist.back()); } else { // Select the first track by default - context.content_path = first_content->infoPath.contentPath; - cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.content_path); + // TODO: whole playlist + context.playlist.push_back(first_content->infoPath.contentPath); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.playlist.back()); } context.content_type = first_content->type; context.repeat_mode = repeatMode; context.context_option = option; + // TODO: context.first_track = ?; // Resolve hashed paths - if (auto found = search.content_links.find(context.content_path); found != search.content_links.end()) + for (std::string& track : context.playlist) { - context.content_path = found->second; + if (auto found = search.content_links.find(track); found != search.content_links.end()) + { + track = found->second; + } } + context.create_playlist(music_selection_context::get_next_hash()); *outContext = context.get(); cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %d", context.to_string()); @@ -1780,15 +1791,21 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptrinfoPath.contentPath; + music_selection_context context{}; + context.playlist.push_back(content_info->infoPath.contentPath); + context.repeat_mode = content_info->repeat_mode; + context.context_option = content_info->context_option; // Resolve hashed paths - if (auto found = search.content_links.find(context.content_path); found != search.content_links.end()) + for (std::string& track : context.playlist) { - context.content_path = found->second; + if (auto found = search.content_links.find(track); found != search.content_links.end()) + { + track = found->second; + } } + context.create_playlist(music_selection_context::get_next_hash()); *outContext = context.get(); cellSearch.success("cellSearchGetMusicSelectionContextOfSingleTrack: found selection context: %s", context.to_string()); @@ -2066,14 +2083,33 @@ error_code music_selection_context::find_content_id(vm::ptr // Search for the content that matches our current selection auto& content_map = g_fxo->get(); - const u64 hash = std::hash()(content_path); - auto found = content_map.map.find(hash); - if (found != content_map.map.end()) + std::shared_ptr found_content; + u64 hash = 0; + + for (const std::string& track : playlist) + { + if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) + { + hash = std::hash()(fs::get_parent_dir(track)); + } + else + { + hash = std::hash()(track); + } + + if (auto found = content_map.map.find(hash); found != content_map.map.end()) + { + found_content = found->second; + break; + } + } + + if (found_content) { // TODO: check if the content type is correct - const u128 content_id_128 = found->first; + const u128 content_id_128 = hash; 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); + cellSearch.warning("find_content_id: found existing content for %s (path control: '%s')", to_string(), found_content->infoPath.contentPath); return CELL_OK; } @@ -2116,6 +2152,8 @@ error_code music_selection_context::find_content_id(vm::ptr // TODO: check for actual content inside the directory std::shared_ptr curr_find = std::make_shared(); curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSICLIST; + curr_find->repeat_mode = repeat_mode; + curr_find->context_option = context_option; if (dir_path.length() > CELL_SEARCH_PATH_LEN_MAX) { @@ -2170,6 +2208,8 @@ error_code music_selection_context::find_content_id(vm::ptr std::shared_ptr curr_find = std::make_shared(); curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC; + curr_find->repeat_mode = repeat_mode; + curr_find->context_option = context_option; if (file_path.length() > CELL_SEARCH_PATH_LEN_MAX) { diff --git a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp index aac6e692fb..edfe3b9b02 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp @@ -282,6 +282,7 @@ namespace rsx { rsx_log.notice("parse_media_recursive: Found %d matches in directory '%s'", current_entry.children.size(), media_path); current_entry.type = media_list_dialog::media_type::directory; + current_entry.info.path = media_path; } } else if (type == media_list_dialog::media_type::photo) @@ -352,32 +353,32 @@ namespace rsx ensure(static_cast(result) < media->children.size()); media = &media->children[result]; rsx_log.notice("Left media list dialog with entry: '%s' ('%s')", media->name, media->path); + continue; } - else if (result == user_interface::selection_code::canceled) + + if (result == user_interface::selection_code::canceled) { if (media == &root_media_entry) { rsx_log.notice("Media list dialog canceled"); + break; } - else - { - focused = media->index; - media = media->parent; - result = 0; - rsx_log.notice("Media list dialog moving to parent directory (focused=%d)", focused); - } - } - else - { - rsx_log.error("Left media list dialog with error: '%d'", result); + + focused = media->index; + media = media->parent; + result = 0; + rsx_log.notice("Media list dialog moving to parent directory (focused=%d)", focused); + continue; } + + rsx_log.error("Left media list dialog with error: '%d'", result); + break; } - else - { - media = nullptr; - result = user_interface::selection_code::canceled; - rsx_log.error("Media selection is only possible when the native user interface is enabled in the settings. The action will be canceled."); - } + + media = nullptr; + result = user_interface::selection_code::canceled; + rsx_log.error("Media selection is only possible when the native user interface is enabled in the settings. The action will be canceled."); + break; } if (result >= 0 && media && media->type == type) diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 475b4236bc..65cd5e1c54 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -61,6 +61,7 @@ true + @@ -470,6 +471,7 @@ + @@ -843,4 +845,4 @@ - + \ No newline at end of file diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index a640af5b94..e7a4f5e2a6 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1087,6 +1087,9 @@ Emu + + Emu\Cell\Modules + @@ -2164,6 +2167,9 @@ Emu\GPU\RSX\Overlays + + Emu\Cell\Modules + @@ -2194,4 +2200,4 @@ Emu\GPU\RSX\Program\Snippets - + \ No newline at end of file diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index 1fb20e0620..5808b12e87 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -2,6 +2,9 @@ #include "media_utils.h" #include "logs.hpp" #include "Utilities/StrUtil.h" +#include "Emu/Cell/Modules/cellSearch.h" + +#include #ifdef _MSC_VER #pragma warning(push, 0) @@ -196,9 +199,9 @@ namespace utils stop(); } - void audio_decoder::set_path(const std::string& path) + void audio_decoder::set_context(music_selection_context context) { - m_path = path; + m_context = std::move(context); } void audio_decoder::set_swap_endianness(bool swapped) @@ -206,27 +209,41 @@ namespace utils m_swap_endianness = swapped; } + void audio_decoder::clear() + { + track_fully_decoded = false; + track_fully_consumed = false; + has_error = false; + m_size = 0; + duration_ms = 0; + timestamps_ms.clear(); + data.clear(); + } + void audio_decoder::stop() { if (m_thread) { auto& thread = *m_thread; thread = thread_state::aborting; + track_fully_consumed = true; + track_fully_consumed.notify_one(); thread(); + m_thread.reset(); } - has_error = false; - m_size = 0; - timestamps_ms.clear(); - data.clear(); + clear(); } void audio_decoder::decode() { stop(); - m_thread = std::make_unique>>("Music Decode Thread", [this, path = m_path]() + media_log.notice("audio_decoder: %d entries in playlist. Start decoding...", m_context.playlist.size()); + + const auto decode_track = [this](const std::string& path) { + media_log.notice("audio_decoder: decoding %s", path); scoped_av av; // Get format from audio file @@ -411,9 +428,61 @@ namespace utils if (buffer) av_free(buffer); - media_log.trace("audio_decoder: decoded frame_count=%d buffer_size=%d timestamp_us=%d", frame_count, buffer_size, av.frame->best_effort_timestamp); + media_log.notice("audio_decoder: decoded frame_count=%d buffer_size=%d timestamp_us=%d", frame_count, buffer_size, av.frame->best_effort_timestamp); } } + }; + + m_thread = std::make_unique>>("Music Decode Thread", [this, decode_track]() + { + for (const std::string& track : m_context.playlist) + { + media_log.notice("audio_decoder: playlist entry: %s", track); + } + + if (m_context.playlist.empty()) + { + media_log.error("audio_decoder: Can not play empty playlist"); + has_error = true; + return; + } + + m_context.current_track = m_context.first_track; + + if (m_context.context_option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE && m_context.playlist.size() > 1) + { + // Shuffle once if necessary + media_log.notice("audio_decoder: shuffling initial playlist..."); + auto engine = std::default_random_engine{}; + std::shuffle(std::begin(m_context.playlist), std::end(m_context.playlist), engine); + } + + while (thread_ctrl::state() != thread_state::aborting) + { + ensure(m_context.current_track < m_context.playlist.size()); + media_log.notice("audio_decoder: about to decode: %s (index=%d)", m_context.playlist.at(m_context.current_track), m_context.current_track); + + decode_track(m_context.playlist.at(m_context.current_track)); + track_fully_decoded = true; + + if (has_error) + { + media_log.notice("audio_decoder: stopping with error..."); + break; + } + + // Let's only decode one track at a time. Wait for the consumer to finish reading the track. + media_log.notice("audio_decoder: waiting until track is consumed..."); + thread_ctrl::wait_on(track_fully_consumed, false); + track_fully_consumed = false; + } + + media_log.notice("audio_decoder: finished playlist"); }); } + + u32 audio_decoder::set_next_index(bool next) + { + return m_context.step_track(next); + } } diff --git a/rpcs3/util/media_utils.h b/rpcs3/util/media_utils.h index 58c49f3129..02d8cc49d4 100644 --- a/rpcs3/util/media_utils.h +++ b/rpcs3/util/media_utils.h @@ -7,6 +7,7 @@ #include #include "Utilities/StrUtil.h" #include "Utilities/Thread.h" +#include "Emu/Cell/Modules/cellMusic.h" namespace utils { @@ -49,22 +50,26 @@ namespace utils audio_decoder(); ~audio_decoder(); - void set_path(const std::string& path); + void set_context(music_selection_context context); void set_swap_endianness(bool swapped); + void clear(); void stop(); void decode(); + u32 set_next_index(bool next); - std::mutex m_mtx; + shared_mutex m_mtx; const s32 sample_rate = 48000; std::vector data; atomic_t m_size = 0; atomic_t duration_ms = 0; + atomic_t track_fully_decoded{false}; + atomic_t track_fully_consumed{false}; atomic_t has_error{false}; std::deque> timestamps_ms; private: bool m_swap_endianness = false; - std::string m_path; + music_selection_context m_context{}; std::unique_ptr>> m_thread; }; } diff --git a/rpcs3/util/yaml.cpp b/rpcs3/util/yaml.cpp index a8f11cfdc7..dd3a47b6fe 100644 --- a/rpcs3/util/yaml.cpp +++ b/rpcs3/util/yaml.cpp @@ -80,4 +80,5 @@ template u32 get_yaml_node_value(YAML::Node, std::string&); template u64 get_yaml_node_value(YAML::Node, std::string&); template s64 get_yaml_node_value(YAML::Node, std::string&); template f64 get_yaml_node_value(YAML::Node, std::string&); +template std::string get_yaml_node_value(YAML::Node, std::string&); template cheat_info get_yaml_node_value(YAML::Node, std::string&);