mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
Play snd0.at9 during the startup splash screen (#1545)
launcher: play snd0.at9 on title selection
This commit is contained in:
@@ -21,7 +21,8 @@ add_executable(launcher WIN32
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL3::SDL3-static kyty_git_version common)
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL3::SDL3-static
|
||||
LibAtrac9 kyty_git_version common)
|
||||
if(LINUX)
|
||||
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
||||
target_link_libraries(launcher Qt6::DBus)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFutureWatcher>
|
||||
#include <QObject>
|
||||
#include <QPromise>
|
||||
#include <QString>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct SDL_AudioStream;
|
||||
|
||||
class TitleSound final: public QObject {
|
||||
public:
|
||||
explicit TitleSound(QObject* parent = nullptr);
|
||||
~TitleSound() override;
|
||||
|
||||
void SetTitle(const QString& base);
|
||||
|
||||
private:
|
||||
struct Sound {
|
||||
std::vector<float> pcm;
|
||||
int channels = 0;
|
||||
int rate = 0;
|
||||
};
|
||||
|
||||
static Sound Decode(const QByteArray& data, const QPromise<Sound>& promise);
|
||||
void Stop();
|
||||
|
||||
QThreadPool m_decode_pool;
|
||||
QFutureWatcher<Sound> m_decode;
|
||||
QString m_base;
|
||||
SDL_AudioStream* m_stream = nullptr;
|
||||
bool m_audio_initialized = false;
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "configurationListWidget.h"
|
||||
#include "controllerPreview.h"
|
||||
#include "gameContent.h"
|
||||
#include "titleSound.h"
|
||||
#include "updateChecker.h"
|
||||
|
||||
#include <QApplication>
|
||||
@@ -90,6 +91,7 @@ private:
|
||||
MainDialog* m_main_dialog = nullptr;
|
||||
UpdateChecker* m_update_checker = nullptr;
|
||||
ControllerPreview m_controller_preview;
|
||||
TitleSound m_title_sound;
|
||||
QString m_interpreter;
|
||||
|
||||
QProcess m_process;
|
||||
@@ -124,7 +126,13 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
|
||||
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Select, this, [this]() {
|
||||
Update();
|
||||
const auto* item = m_ui->widget->GetSelectedItem();
|
||||
m_title_sound.SetTitle(m_process.state() == QProcess::NotRunning && item != nullptr
|
||||
? item->GetInfo().basedir
|
||||
: QString());
|
||||
});
|
||||
connect(m_ui->widget, &ConfigurationListWidget::PreviewControllerColor, this,
|
||||
[this](const QString& color) {
|
||||
if (m_process.state() == QProcess::NotRunning) {
|
||||
@@ -530,6 +538,7 @@ void MainDialogPrivate::Run() {
|
||||
|
||||
m_running_item->SetRunning(true);
|
||||
m_controller_preview.SetSuspended(true);
|
||||
m_title_sound.SetTitle({});
|
||||
|
||||
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
|
||||
m_main_dialog->RunInterpreter(&m_process, *info);
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "titleSound.h"
|
||||
|
||||
#include "gameContent.h"
|
||||
#include "libs/ajm/atrac9_decoder.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <array>
|
||||
|
||||
TitleSound::Sound TitleSound::Decode(const QByteArray& data, const QPromise<Sound>& promise) {
|
||||
using namespace Libs::Audio::Ajm;
|
||||
AjmAt9Decoder decoder(0, 0, AjmSampleEncoding::Float,
|
||||
AJM_INSTANCE_FLAG_DEC_AT9_PARSE_RIFF_HEADER);
|
||||
AjmGaplessState gapless;
|
||||
std::array<float, 16 * 1024> batch;
|
||||
Sound sound;
|
||||
size_t offset = 0;
|
||||
while (offset < static_cast<size_t>(data.size()) && !gapless.IsEnd()) {
|
||||
if (promise.isCanceled()) {
|
||||
return {};
|
||||
}
|
||||
const auto result = decoder.Decode(data.constData() + offset, data.size() - offset,
|
||||
batch.data(), sizeof(batch), true, &gapless);
|
||||
if ((result.result != 0 && result.result != AJM_RESULT_NOT_ENOUGH_ROOM) ||
|
||||
result.input_consumed == 0) {
|
||||
return {};
|
||||
}
|
||||
const auto samples = result.output_written / sizeof(float);
|
||||
constexpr size_t MaxSamples = (128u << 20u) / sizeof(float);
|
||||
if (samples > MaxSamples - sound.pcm.size()) {
|
||||
return {};
|
||||
}
|
||||
sound.pcm.insert(sound.pcm.end(), batch.begin(), batch.begin() + samples);
|
||||
offset += result.input_consumed;
|
||||
}
|
||||
if (promise.isCanceled()) {
|
||||
return {};
|
||||
}
|
||||
const auto format = decoder.GetFormat();
|
||||
sound.channels = static_cast<int>(format.channel_num);
|
||||
sound.rate = static_cast<int>(format.sampling_frequency);
|
||||
return sound;
|
||||
}
|
||||
|
||||
TitleSound::TitleSound(QObject* parent): QObject(parent) {
|
||||
m_decode_pool.setMaxThreadCount(1);
|
||||
connect(&m_decode, &QFutureWatcher<Sound>::finished, this, [this] {
|
||||
if (m_decode.isCanceled() || m_decode.future().resultCount() == 0) {
|
||||
return;
|
||||
}
|
||||
auto sound = m_decode.future().takeResult();
|
||||
if (sound.pcm.empty()) {
|
||||
qWarning() << "Cannot decode title sound:" << m_base;
|
||||
return;
|
||||
}
|
||||
m_audio_initialized = SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
if (m_audio_initialized) {
|
||||
const SDL_AudioSpec spec {SDL_AUDIO_F32, sound.channels, sound.rate};
|
||||
m_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr,
|
||||
nullptr);
|
||||
}
|
||||
if (m_stream == nullptr ||
|
||||
!SDL_PutAudioStreamData(m_stream, sound.pcm.data(),
|
||||
static_cast<int>(sound.pcm.size() * sizeof(float))) ||
|
||||
!SDL_FlushAudioStream(m_stream) || !SDL_ResumeAudioStreamDevice(m_stream)) {
|
||||
qWarning() << "Cannot play title sound:" << SDL_GetError();
|
||||
Stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TitleSound::~TitleSound() {
|
||||
m_decode.cancel();
|
||||
Stop();
|
||||
}
|
||||
|
||||
void TitleSound::SetTitle(const QString& base) {
|
||||
if (base == m_base) {
|
||||
return;
|
||||
}
|
||||
m_decode.cancel();
|
||||
Stop();
|
||||
m_base = base;
|
||||
if (!base.isEmpty()) {
|
||||
m_decode.setFuture(QtConcurrent::run(&m_decode_pool, [base](QPromise<Sound>& promise) {
|
||||
const auto data =
|
||||
GameContent::ReadFile(base, QStringLiteral("sce_sys/snd0.at9"), 32u << 20u);
|
||||
if (!data.isEmpty() && !promise.isCanceled()) {
|
||||
promise.addResult(Decode(data, promise));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void TitleSound::Stop() {
|
||||
if (m_stream != nullptr) {
|
||||
SDL_DestroyAudioStream(m_stream);
|
||||
m_stream = nullptr;
|
||||
}
|
||||
if (m_audio_initialized) {
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
m_audio_initialized = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user