cellMusic: improve music list playback

This commit is contained in:
Megamouse 2026-05-31 00:32:19 +02:00
parent 9c719a5857
commit 059e70069d
7 changed files with 98 additions and 44 deletions

View File

@ -79,33 +79,38 @@ struct music_state
music_state()
{
handler = Emu.GetCallbacks().get_music_handler();
handler->set_status_callback([this](music_handler_base::player_status status)
handler->set_event_status_callback([this](u32 status)
{
// TODO: disabled until I find a game that uses CELL_MUSIC_EVENT_STATUS_NOTIFICATION
return;
if (!func)
{
return;
}
s32 result = CELL_OK;
// Known to be used by NFS: Hot Pursuit
sysutil_register_cb([this, state = status](ppu_thread& ppu) -> s32
{
cellMusic.notice("Sending status notification %d", state);
func(ppu, CELL_MUSIC_EVENT_STATUS_NOTIFICATION, vm::addr_t(state), userData);
return CELL_OK;
});
});
handler->set_playback_status_callback([this](music_handler_base::player_status status)
{
switch (status)
{
case music_handler_base::player_status::end_of_media:
result = CELL_MUSIC_PLAYBACK_FINISHED;
// Let's just play the next song for now
if (current_selection_context.content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST && handler->get_state() == CELL_MUSIC_PB_STATUS_PLAY)
{
if (const error_code error = set_playback_command(CELL_MUSIC_PB_CMD_NEXT))
{
cellMusic.error("Failed to play next track. error=0x%x", +error);
}
}
break;
default:
return;
}
sysutil_register_cb([this, &result](ppu_thread& ppu) -> s32
{
cellMusic.notice("Sending status notification %d", result);
func(ppu, CELL_MUSIC_EVENT_STATUS_NOTIFICATION, vm::addr_t(result), userData);
return CELL_OK;
});
});
}

View File

@ -287,6 +287,8 @@ u32 music_selection_context::step_track(bool next)
return umax;
}
const std::string last_track = ::at32(playlist, current_track);
switch (repeat_mode)
{
case CELL_SEARCH_REPEATMODE_NONE:
@ -365,6 +367,12 @@ u32 music_selection_context::step_track(bool next)
std::random_device rd;
auto engine = std::default_random_engine{rd()};
std::shuffle(std::begin(playlist), std::end(playlist), engine);
// Don't play the same track twice
if (last_track == ::at32(playlist, current_track))
{
current_track = (current_track + 1) % playlist.size();
}
}
}

View File

@ -1711,12 +1711,6 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
auto content = std::find_if(searchObject->content_ids.begin(), searchObject->content_ids.end(), [&content_hash](const content_id_type& cid){ return cid.first == content_hash; });
if (content != searchObject->content_ids.cend() && content->second)
{
// Check if the type of the found content is correct
if (content->second->type != CELL_SEARCH_CONTENTTYPE_MUSIC)
{
return { CELL_SEARCH_ERROR_INVALID_CONTENTTYPE, "Type: %d, Expected: CELL_SEARCH_CONTENTTYPE_MUSIC"};
}
// Check if the type of the found content matches our search content type
if (content->second->type != first_content->type)
{
@ -1724,8 +1718,36 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
}
// Use the found content
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());
if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
const std::string path = content->second->infoPath.contentPath;
const std::string vfs_path = vfs::get(path);
if (!fs::is_dir(vfs_path))
{
return { CELL_SEARCH_ERROR_CONTENT_NOT_FOUND, "Not a directory: Path='%s'", vfs_path };
}
for (auto&& dir_entry : fs::dir{vfs_path})
{
if (dir_entry.name == "." || dir_entry.name == "..")
{
continue;
}
std::string track = path + "/" + dir_entry.name;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path='%s'", content_hash, +content->second->type, track);
context.playlist.push_back(std::move(track));
}
}
else if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSIC)
{
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
{
return { CELL_SEARCH_ERROR_INVALID_CONTENTTYPE, "Type: %d, Expected: CELL_SEARCH_CONTENTTYPE_MUSIC or CELL_SEARCH_CONTENTTYPE_MUSICLIST", +content->second->type };
}
}
else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
@ -1738,14 +1760,14 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
// TODO: whole playlist
shared_ptr<search_content_t> content = get_random_content();
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());
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
// 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());
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)
@ -1759,14 +1781,14 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
// TODO: whole playlist
shared_ptr<search_content_t> content = get_random_content();
context.playlist.push_back(content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path=%s", +content->type, context.playlist.back());
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path='%s'", +content->type, context.playlist.back());
}
else
{
// Select the first track by default
// 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());
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path='%s'", +first_content->type, context.playlist.back());
}
context.content_type = first_content->type;

View File

@ -7,11 +7,11 @@ 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 pause() override { m_state = 2; } // CELL_MUSIC_PB_STATUS_PAUSE
void play(const std::string& /*path*/) override { m_state = 1; } // CELL_MUSIC_PB_STATUS_PLAY
void fast_forward(const std::string& /*path*/) override { m_state = 3; } // CELL_MUSIC_PB_STATUS_FASTFORWARD
void fast_reverse(const std::string& /*path*/) override { m_state = 4; } // CELL_MUSIC_PB_STATUS_FASTREVERSE
void stop() override { set_state(0); } // CELL_MUSIC_PB_STATUS_STOP
void pause() override { set_state(2); } // CELL_MUSIC_PB_STATUS_PAUSE
void play(const std::string& /*path*/) override { set_state(1); } // CELL_MUSIC_PB_STATUS_PLAY
void fast_forward(const std::string& /*path*/) override { set_state(3); } // CELL_MUSIC_PB_STATUS_FASTFORWARD
void fast_reverse(const std::string& /*path*/) override { set_state(4); } // CELL_MUSIC_PB_STATUS_FASTREVERSE
void set_volume(f32 volume) override { m_volume = volume; }
f32 get_volume() const override { return m_volume; }

View File

@ -21,17 +21,35 @@ public:
virtual void set_volume(f32 volume) = 0;
virtual f32 get_volume() const = 0;
s32 get_state() const
void set_state(u32 state)
{
m_state = state;
if (m_event_status_callback)
{
m_event_status_callback(state);
}
}
u32 get_state() const
{
return m_state;
}
void set_status_callback(std::function<void(player_status)> status_callback)
void set_event_status_callback(std::function<void(u32)> status_callback)
{
m_status_callback = std::move(status_callback);
m_event_status_callback = std::move(status_callback);
}
void set_playback_status_callback(std::function<void(player_status)> status_callback)
{
m_playback_status_callback = std::move(status_callback);
}
private:
atomic_t<u32> m_state{0};
std::function<void(u32)> m_event_status_callback;
protected:
atomic_t<s32> m_state{0};
std::function<void(player_status status)> m_status_callback;
std::function<void(player_status)> m_playback_status_callback;
};

View File

@ -97,7 +97,7 @@ void qt_music_handler::stop()
m_media_player->stop();
});
m_state = CELL_MUSIC_PB_STATUS_STOP;
set_state(CELL_MUSIC_PB_STATUS_STOP);
}
void qt_music_handler::pause()
@ -110,7 +110,7 @@ void qt_music_handler::pause()
m_media_player->pause();
});
m_state = CELL_MUSIC_PB_STATUS_PAUSE;
set_state(CELL_MUSIC_PB_STATUS_PAUSE);
}
void qt_music_handler::play(const std::string& path)
@ -135,7 +135,7 @@ void qt_music_handler::play(const std::string& path)
m_media_player->play();
});
m_state = CELL_MUSIC_PB_STATUS_PLAY;
set_state(CELL_MUSIC_PB_STATUS_PLAY);
}
void qt_music_handler::fast_forward(const std::string& path)
@ -160,7 +160,7 @@ void qt_music_handler::fast_forward(const std::string& path)
m_media_player->play();
});
m_state = CELL_MUSIC_PB_STATUS_FASTFORWARD;
set_state(CELL_MUSIC_PB_STATUS_FASTFORWARD);
}
void qt_music_handler::fast_reverse(const std::string& path)
@ -185,7 +185,7 @@ void qt_music_handler::fast_reverse(const std::string& path)
m_media_player->play();
});
m_state = CELL_MUSIC_PB_STATUS_FASTREVERSE;
set_state(CELL_MUSIC_PB_STATUS_FASTREVERSE);
}
void qt_music_handler::set_volume(f32 volume)
@ -218,7 +218,7 @@ void qt_music_handler::handle_media_status(QMediaPlayer::MediaStatus status)
{
music_log.notice("New media status: %s (status=%d)", status, static_cast<int>(status));
if (!m_status_callback)
if (!m_playback_status_callback)
{
return;
}
@ -234,7 +234,7 @@ void qt_music_handler::handle_media_status(QMediaPlayer::MediaStatus status)
case QMediaPlayer::MediaStatus::InvalidMedia:
break;
case QMediaPlayer::MediaStatus::EndOfMedia:
m_status_callback(player_status::end_of_media);
m_playback_status_callback(player_status::end_of_media);
break;
default:
music_log.error("Ignoring unknown status %d", static_cast<int>(status));

View File

@ -720,7 +720,8 @@ namespace utils
{
// Shuffle once if necessary
media_log.notice("audio_decoder: shuffling initial playlist...");
auto engine = std::default_random_engine{};
std::random_device rd;
auto engine = std::default_random_engine{rd()};
std::shuffle(std::begin(m_context.playlist), std::end(m_context.playlist), engine);
}