This commit is contained in:
Carl Philipp Klemm 2026-03-06 00:06:09 +01:00
parent 10769eef09
commit 58e8345542
20 changed files with 2063 additions and 1901 deletions

View file

@ -55,3 +55,13 @@ target_include_directories(${PROJECT_NAME} PRIVATE
) )
# Note: acestep.cpp binaries (ace-qwen3, dit-vae) and models should be provided at runtime # Note: acestep.cpp binaries (ace-qwen3, dit-vae) and models should be provided at runtime
# Install targets
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
# Install .desktop file
install(FILES aceradio.desktop DESTINATION share/applications)
# Install icon files
install(FILES res/xyz.uvos.aceradio.png DESTINATION share/icons/hicolor/256x256/apps RENAME xyz.uvos.aceradio.png)
install(FILES res/xyz.uvos.aceradio.svg DESTINATION share/icons/hicolor/scalable/apps RENAME xyz.uvos.aceradio.svg)

View file

@ -11,44 +11,46 @@
#include <cstdint> #include <cstdint>
AceStepWorker::AceStepWorker(QObject *parent) AceStepWorker::AceStepWorker(QObject *parent)
: QObject(parent), : QObject(parent),
currentWorker(nullptr) currentWorker(nullptr)
{ {
} }
AceStepWorker::~AceStepWorker() AceStepWorker::~AceStepWorker()
{ {
cancelGeneration(); cancelGeneration();
} }
void AceStepWorker::generateSong(const SongItem& song, const QString &jsonTemplate, void AceStepWorker::generateSong(const SongItem& song, const QString &jsonTemplate,
const QString &aceStepPath, const QString &qwen3ModelPath, const QString &aceStepPath, const QString &qwen3ModelPath,
const QString &textEncoderModelPath, const QString &ditModelPath, const QString &textEncoderModelPath, const QString &ditModelPath,
const QString &vaeModelPath) const QString &vaeModelPath)
{ {
// Cancel any ongoing generation // Cancel any ongoing generation
cancelGeneration(); cancelGeneration();
// Create worker and start it // Create worker and start it
currentWorker = new Worker(this, song, jsonTemplate, aceStepPath, qwen3ModelPath, currentWorker = new Worker(this, song, jsonTemplate, aceStepPath, qwen3ModelPath,
textEncoderModelPath, ditModelPath, vaeModelPath); textEncoderModelPath, ditModelPath, vaeModelPath);
currentWorker->setAutoDelete(true); currentWorker->setAutoDelete(true);
QThreadPool::globalInstance()->start(currentWorker); QThreadPool::globalInstance()->start(currentWorker);
} }
void AceStepWorker::cancelGeneration() void AceStepWorker::cancelGeneration()
{ {
currentWorker = nullptr; currentWorker = nullptr;
} }
bool AceStepWorker::songGenerateing(SongItem* song) bool AceStepWorker::songGenerateing(SongItem* song)
{ {
workerMutex.lock(); workerMutex.lock();
if(!currentWorker) { if(!currentWorker)
{
workerMutex.unlock(); workerMutex.unlock();
return false; return false;
} }
else { else
{
SongItem workerSong = currentWorker->getSong(); SongItem workerSong = currentWorker->getSong();
workerMutex.unlock(); workerMutex.unlock();
if(song) if(song)
@ -60,186 +62,209 @@ bool AceStepWorker::songGenerateing(SongItem* song)
// Worker implementation // Worker implementation
void AceStepWorker::Worker::run() void AceStepWorker::Worker::run()
{ {
uint64_t uid = QRandomGenerator::global()->generate(); uint64_t uid = QRandomGenerator::global()->generate();
// Create temporary JSON file for the request // Create temporary JSON file for the request
QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString requestFile = tempDir + "/request_" + QString::number(uid) + ".json"; QString requestFile = tempDir + "/request_" + QString::number(uid) + ".json";
// Parse and modify the template // Parse and modify the template
QJsonParseError parseError; QJsonParseError parseError;
QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError); QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError);
if (!templateDoc.isObject()) { if (!templateDoc.isObject())
emit parent->generationError("Invalid JSON template: " + QString(parseError.errorString())); {
return; emit parent->generationError("Invalid JSON template: " + QString(parseError.errorString()));
} return;
}
QJsonObject requestObj = templateDoc.object(); QJsonObject requestObj = templateDoc.object();
requestObj["caption"] = song.caption; requestObj["caption"] = song.caption;
if (!song.lyrics.isEmpty()) { if (!song.lyrics.isEmpty())
{
requestObj["lyrics"] = song.lyrics; requestObj["lyrics"] = song.lyrics;
} else { }
// Remove lyrics field if empty to let the LLM generate them else
requestObj.remove("lyrics"); {
} // Remove lyrics field if empty to let the LLM generate them
requestObj.remove("lyrics");
}
// Apply vocal language override if set // Apply vocal language override if set
if (!song.vocalLanguage.isEmpty()) { if (!song.vocalLanguage.isEmpty())
requestObj["vocal_language"] = song.vocalLanguage; {
} requestObj["vocal_language"] = song.vocalLanguage;
}
// Write the request file // Write the request file
QFile requestFileHandle(requestFile); QFile requestFileHandle(requestFile);
if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text)) { if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text))
emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString()); {
return; emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString());
} return;
}
requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented)); requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented));
requestFileHandle.close(); requestFileHandle.close();
// Use provided paths for acestep.cpp binaries // Use provided paths for acestep.cpp binaries
QString qwen3Binary = this->aceStepPath + "/ace-qwen3"; QString qwen3Binary = this->aceStepPath + "/ace-qwen3";
QString ditVaeBinary = this->aceStepPath + "/dit-vae"; QString ditVaeBinary = this->aceStepPath + "/dit-vae";
// Check if binaries exist // Check if binaries exist
QFileInfo qwen3Info(qwen3Binary); QFileInfo qwen3Info(qwen3Binary);
QFileInfo ditVaeInfo(ditVaeBinary); QFileInfo ditVaeInfo(ditVaeBinary);
if (!qwen3Info.exists() || !qwen3Info.isExecutable()) { if (!qwen3Info.exists() || !qwen3Info.isExecutable())
emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary); {
return; emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary);
} return;
}
if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable()) { if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable())
emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary); {
return; emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary);
} return;
}
// Use provided model paths // Use provided model paths
QString qwen3Model = this->qwen3ModelPath; QString qwen3Model = this->qwen3ModelPath;
QString textEncoderModel = this->textEncoderModelPath; QString textEncoderModel = this->textEncoderModelPath;
QString ditModel = this->ditModelPath; QString ditModel = this->ditModelPath;
QString vaeModel = this->vaeModelPath; QString vaeModel = this->vaeModelPath;
if (!QFileInfo::exists(qwen3Model)) { if (!QFileInfo::exists(qwen3Model))
emit parent->generationError("Qwen3 model not found: " + qwen3Model); {
return; emit parent->generationError("Qwen3 model not found: " + qwen3Model);
} return;
}
if (!QFileInfo::exists(textEncoderModel)) { if (!QFileInfo::exists(textEncoderModel))
emit parent->generationError("Text encoder model not found: " + textEncoderModel); {
return; emit parent->generationError("Text encoder model not found: " + textEncoderModel);
} return;
}
if (!QFileInfo::exists(ditModel)) { if (!QFileInfo::exists(ditModel))
emit parent->generationError("DiT model not found: " + ditModel); {
return; emit parent->generationError("DiT model not found: " + ditModel);
} return;
}
if (!QFileInfo::exists(vaeModel)) { if (!QFileInfo::exists(vaeModel))
emit parent->generationError("VAE model not found: " + vaeModel); {
return; emit parent->generationError("VAE model not found: " + vaeModel);
} return;
}
// Step 1: Run ace-qwen3 to generate lyrics and audio codes // Step 1: Run ace-qwen3 to generate lyrics and audio codes
QProcess qwen3Process; QProcess qwen3Process;
QStringList qwen3Args; QStringList qwen3Args;
qwen3Args << "--request" << requestFile; qwen3Args << "--request" << requestFile;
qwen3Args << "--model" << qwen3Model; qwen3Args << "--model" << qwen3Model;
emit parent->progressUpdate(20); emit parent->progressUpdate(20);
qwen3Process.start(qwen3Binary, qwen3Args); qwen3Process.start(qwen3Binary, qwen3Args);
if (!qwen3Process.waitForStarted()) { if (!qwen3Process.waitForStarted())
emit parent->generationError("Failed to start ace-qwen3: " + qwen3Process.errorString()); {
return; emit parent->generationError("Failed to start ace-qwen3: " + qwen3Process.errorString());
} return;
}
if (!qwen3Process.waitForFinished(60000)) { // 60 second timeout if (!qwen3Process.waitForFinished(60000)) // 60 second timeout
qwen3Process.terminate(); {
qwen3Process.waitForFinished(5000); qwen3Process.terminate();
emit parent->generationError("ace-qwen3 timed out after 60 seconds"); qwen3Process.waitForFinished(5000);
return; emit parent->generationError("ace-qwen3 timed out after 60 seconds");
} return;
}
int exitCode = qwen3Process.exitCode(); int exitCode = qwen3Process.exitCode();
if (exitCode != 0) { if (exitCode != 0)
QString errorOutput = qwen3Process.readAllStandardError(); {
emit parent->generationError("ace-qwen3 exited with code " + QString::number(exitCode) + ": " + errorOutput); QString errorOutput = qwen3Process.readAllStandardError();
return; emit parent->generationError("ace-qwen3 exited with code " + QString::number(exitCode) + ": " + errorOutput);
} return;
}
QString requestLmOutputFile = tempDir + "/request_" + QString::number(uid) + "0.json"; QString requestLmOutputFile = tempDir + "/request_" + QString::number(uid) + "0.json";
if (!QFileInfo::exists(requestLmOutputFile)) { if (!QFileInfo::exists(requestLmOutputFile))
emit parent->generationError("ace-qwen3 failed to create enhaced request file "+requestLmOutputFile); {
return; emit parent->generationError("ace-qwen3 failed to create enhaced request file "+requestLmOutputFile);
} return;
}
// Load lyrics from the enhanced request file // Load lyrics from the enhanced request file
QFile lmOutputFile(requestLmOutputFile); QFile lmOutputFile(requestLmOutputFile);
if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text))
QJsonParseError parseError; {
song.json = lmOutputFile.readAll(); QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(song.json.toUtf8(), &parseError); song.json = lmOutputFile.readAll();
lmOutputFile.close(); QJsonDocument doc = QJsonDocument::fromJson(song.json.toUtf8(), &parseError);
lmOutputFile.close();
if (doc.isObject() && !parseError.error) { if (doc.isObject() && !parseError.error)
QJsonObject obj = doc.object(); {
if (obj.contains("lyrics") && obj["lyrics"].isString()) { QJsonObject obj = doc.object();
song.lyrics = obj["lyrics"].toString(); if (obj.contains("lyrics") && obj["lyrics"].isString())
} {
} song.lyrics = obj["lyrics"].toString();
} }
}
}
emit parent->progressUpdate(50); emit parent->progressUpdate(50);
// Step 2: Run dit-vae to generate audio // Step 2: Run dit-vae to generate audio
QProcess ditVaeProcess; QProcess ditVaeProcess;
QStringList ditVaeArgs; QStringList ditVaeArgs;
ditVaeArgs << "--request" << requestLmOutputFile; ditVaeArgs << "--request" << requestLmOutputFile;
ditVaeArgs << "--text-encoder" << textEncoderModel; ditVaeArgs << "--text-encoder" << textEncoderModel;
ditVaeArgs << "--dit" << ditModel; ditVaeArgs << "--dit" << ditModel;
ditVaeArgs << "--vae" << vaeModel; ditVaeArgs << "--vae" << vaeModel;
emit parent->progressUpdate(60); emit parent->progressUpdate(60);
ditVaeProcess.start(ditVaeBinary, ditVaeArgs); ditVaeProcess.start(ditVaeBinary, ditVaeArgs);
if (!ditVaeProcess.waitForStarted()) { if (!ditVaeProcess.waitForStarted())
emit parent->generationError("Failed to start dit-vae: " + ditVaeProcess.errorString()); {
return; emit parent->generationError("Failed to start dit-vae: " + ditVaeProcess.errorString());
} return;
}
if (!ditVaeProcess.waitForFinished(120000)) { // 2 minute timeout if (!ditVaeProcess.waitForFinished(120000)) // 2 minute timeout
ditVaeProcess.terminate(); {
ditVaeProcess.waitForFinished(5000); ditVaeProcess.terminate();
emit parent->generationError("dit-vae timed out after 2 minutes"); ditVaeProcess.waitForFinished(5000);
return; emit parent->generationError("dit-vae timed out after 2 minutes");
} return;
}
exitCode = ditVaeProcess.exitCode(); exitCode = ditVaeProcess.exitCode();
if (exitCode != 0) { if (exitCode != 0)
QString errorOutput = ditVaeProcess.readAllStandardError(); {
emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput); QString errorOutput = ditVaeProcess.readAllStandardError();
return; emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput);
} return;
}
emit parent->progressUpdate(90); emit parent->progressUpdate(90);
// Find the generated WAV file // Find the generated WAV file
QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav"; QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav";
if (!QFileInfo::exists(wavFile)) { if (!QFileInfo::exists(wavFile))
emit parent->generationError("No WAV file generated at "+wavFile); {
return; emit parent->generationError("No WAV file generated at "+wavFile);
} return;
}
// Clean up temporary files // Clean up temporary files
QFile::remove(requestLmOutputFile); QFile::remove(requestLmOutputFile);
QFile::remove(requestFile); QFile::remove(requestFile);
emit parent->progressUpdate(100); emit parent->progressUpdate(100);
song.file = wavFile; song.file = wavFile;
emit parent->songGenerated(song); emit parent->songGenerated(song);
parent->workerMutex.lock(); parent->workerMutex.lock();
parent->currentWorker = nullptr; parent->currentWorker = nullptr;
parent->workerMutex.unlock(); parent->workerMutex.unlock();

View file

@ -12,53 +12,54 @@
class AceStepWorker : public QObject class AceStepWorker : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit AceStepWorker(QObject *parent = nullptr); explicit AceStepWorker(QObject *parent = nullptr);
~AceStepWorker(); ~AceStepWorker();
void generateSong(const SongItem& song, const QString &jsonTemplate, void generateSong(const SongItem& song, const QString &jsonTemplate,
const QString &aceStepPath, const QString &qwen3ModelPath, const QString &aceStepPath, const QString &qwen3ModelPath,
const QString &textEncoderModelPath, const QString &ditModelPath, const QString &textEncoderModelPath, const QString &ditModelPath,
const QString &vaeModelPath); const QString &vaeModelPath);
void cancelGeneration(); void cancelGeneration();
bool songGenerateing(SongItem* song); bool songGenerateing(SongItem* song);
signals: signals:
void songGenerated(const SongItem& song); void songGenerated(const SongItem& song);
void generationFinished(); void generationFinished();
void generationError(const QString &error); void generationError(const QString &error);
void progressUpdate(int percent); void progressUpdate(int percent);
private: private:
class Worker : public QRunnable { class Worker : public QRunnable
public: {
Worker(AceStepWorker *parent, const SongItem& song, const QString &jsonTemplate, public:
const QString &aceStepPath, const QString &qwen3ModelPath, Worker(AceStepWorker *parent, const SongItem& song, const QString &jsonTemplate,
const QString &textEncoderModelPath, const QString &ditModelPath, const QString &aceStepPath, const QString &qwen3ModelPath,
const QString &vaeModelPath) const QString &textEncoderModelPath, const QString &ditModelPath,
: parent(parent), song(song), jsonTemplate(jsonTemplate), const QString &vaeModelPath)
aceStepPath(aceStepPath), qwen3ModelPath(qwen3ModelPath), : parent(parent), song(song), jsonTemplate(jsonTemplate),
textEncoderModelPath(textEncoderModelPath), ditModelPath(ditModelPath), aceStepPath(aceStepPath), qwen3ModelPath(qwen3ModelPath),
vaeModelPath(vaeModelPath) {} textEncoderModelPath(textEncoderModelPath), ditModelPath(ditModelPath),
vaeModelPath(vaeModelPath) {}
void run() override; void run() override;
const SongItem& getSong(); const SongItem& getSong();
private: private:
AceStepWorker *parent; AceStepWorker *parent;
SongItem song; SongItem song;
QString jsonTemplate; QString jsonTemplate;
QString aceStepPath; QString aceStepPath;
QString qwen3ModelPath; QString qwen3ModelPath;
QString textEncoderModelPath; QString textEncoderModelPath;
QString ditModelPath; QString ditModelPath;
QString vaeModelPath; QString vaeModelPath;
}; };
QMutex workerMutex; QMutex workerMutex;
Worker *currentWorker; Worker *currentWorker;
}; };
#endif // ACESTEPWORKER_H #endif // ACESTEPWORKER_H

View file

@ -6,113 +6,120 @@
#include <QJsonParseError> #include <QJsonParseError>
AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent) AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent)
: QDialog(parent), : QDialog(parent),
ui(new Ui::AdvancedSettingsDialog) ui(new Ui::AdvancedSettingsDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
} }
AdvancedSettingsDialog::~AdvancedSettingsDialog() AdvancedSettingsDialog::~AdvancedSettingsDialog()
{ {
delete ui; delete ui;
} }
QString AdvancedSettingsDialog::getJsonTemplate() const QString AdvancedSettingsDialog::getJsonTemplate() const
{ {
return ui->jsonTemplateEdit->toPlainText(); return ui->jsonTemplateEdit->toPlainText();
} }
QString AdvancedSettingsDialog::getAceStepPath() const QString AdvancedSettingsDialog::getAceStepPath() const
{ {
return ui->aceStepPathEdit->text(); return ui->aceStepPathEdit->text();
} }
QString AdvancedSettingsDialog::getQwen3ModelPath() const QString AdvancedSettingsDialog::getQwen3ModelPath() const
{ {
return ui->qwen3ModelEdit->text(); return ui->qwen3ModelEdit->text();
} }
QString AdvancedSettingsDialog::getTextEncoderModelPath() const QString AdvancedSettingsDialog::getTextEncoderModelPath() const
{ {
return ui->textEncoderEdit->text(); return ui->textEncoderEdit->text();
} }
QString AdvancedSettingsDialog::getDiTModelPath() const QString AdvancedSettingsDialog::getDiTModelPath() const
{ {
return ui->ditModelEdit->text(); return ui->ditModelEdit->text();
} }
QString AdvancedSettingsDialog::getVAEModelPath() const QString AdvancedSettingsDialog::getVAEModelPath() const
{ {
return ui->vaeModelEdit->text(); return ui->vaeModelEdit->text();
} }
void AdvancedSettingsDialog::setJsonTemplate(const QString &templateStr) void AdvancedSettingsDialog::setJsonTemplate(const QString &templateStr)
{ {
ui->jsonTemplateEdit->setPlainText(templateStr); ui->jsonTemplateEdit->setPlainText(templateStr);
} }
void AdvancedSettingsDialog::setAceStepPath(const QString &path) void AdvancedSettingsDialog::setAceStepPath(const QString &path)
{ {
ui->aceStepPathEdit->setText(path); ui->aceStepPathEdit->setText(path);
} }
void AdvancedSettingsDialog::setQwen3ModelPath(const QString &path) void AdvancedSettingsDialog::setQwen3ModelPath(const QString &path)
{ {
ui->qwen3ModelEdit->setText(path); ui->qwen3ModelEdit->setText(path);
} }
void AdvancedSettingsDialog::setTextEncoderModelPath(const QString &path) void AdvancedSettingsDialog::setTextEncoderModelPath(const QString &path)
{ {
ui->textEncoderEdit->setText(path); ui->textEncoderEdit->setText(path);
} }
void AdvancedSettingsDialog::setDiTModelPath(const QString &path) void AdvancedSettingsDialog::setDiTModelPath(const QString &path)
{ {
ui->ditModelEdit->setText(path); ui->ditModelEdit->setText(path);
} }
void AdvancedSettingsDialog::setVAEModelPath(const QString &path) void AdvancedSettingsDialog::setVAEModelPath(const QString &path)
{ {
ui->vaeModelEdit->setText(path); ui->vaeModelEdit->setText(path);
} }
void AdvancedSettingsDialog::on_aceStepBrowseButton_clicked() void AdvancedSettingsDialog::on_aceStepBrowseButton_clicked()
{ {
QString dir = QFileDialog::getExistingDirectory(this, "Select AceStep Build Directory", ui->aceStepPathEdit->text()); QString dir = QFileDialog::getExistingDirectory(this, "Select AceStep Build Directory", ui->aceStepPathEdit->text());
if (!dir.isEmpty()) { if (!dir.isEmpty())
ui->aceStepPathEdit->setText(dir); {
} ui->aceStepPathEdit->setText(dir);
}
} }
void AdvancedSettingsDialog::on_qwen3BrowseButton_clicked() void AdvancedSettingsDialog::on_qwen3BrowseButton_clicked()
{ {
QString file = QFileDialog::getOpenFileName(this, "Select Qwen3 Model", ui->qwen3ModelEdit->text(), "GGUF Files (*.gguf)"); QString file = QFileDialog::getOpenFileName(this, "Select Qwen3 Model", ui->qwen3ModelEdit->text(),
if (!file.isEmpty()) { "GGUF Files (*.gguf)");
ui->qwen3ModelEdit->setText(file); if (!file.isEmpty())
} {
ui->qwen3ModelEdit->setText(file);
}
} }
void AdvancedSettingsDialog::on_textEncoderBrowseButton_clicked() void AdvancedSettingsDialog::on_textEncoderBrowseButton_clicked()
{ {
QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(), "GGUF Files (*.gguf)"); QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(),
if (!file.isEmpty()) { "GGUF Files (*.gguf)");
ui->textEncoderEdit->setText(file); if (!file.isEmpty())
} {
ui->textEncoderEdit->setText(file);
}
} }
void AdvancedSettingsDialog::on_ditBrowseButton_clicked() void AdvancedSettingsDialog::on_ditBrowseButton_clicked()
{ {
QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)"); QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)");
if (!file.isEmpty()) { if (!file.isEmpty())
ui->ditModelEdit->setText(file); {
} ui->ditModelEdit->setText(file);
}
} }
void AdvancedSettingsDialog::on_vaeBrowseButton_clicked() void AdvancedSettingsDialog::on_vaeBrowseButton_clicked()
{ {
QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)"); QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)");
if (!file.isEmpty()) { if (!file.isEmpty())
ui->vaeModelEdit->setText(file); {
} ui->vaeModelEdit->setText(file);
}
} }

View file

@ -4,43 +4,44 @@
#include <QDialog> #include <QDialog>
#include <QString> #include <QString>
namespace Ui { namespace Ui
{
class AdvancedSettingsDialog; class AdvancedSettingsDialog;
} }
class AdvancedSettingsDialog : public QDialog class AdvancedSettingsDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit AdvancedSettingsDialog(QWidget *parent = nullptr); explicit AdvancedSettingsDialog(QWidget *parent = nullptr);
~AdvancedSettingsDialog(); ~AdvancedSettingsDialog();
// Getters for settings // Getters for settings
QString getJsonTemplate() const; QString getJsonTemplate() const;
QString getAceStepPath() const; QString getAceStepPath() const;
QString getQwen3ModelPath() const; QString getQwen3ModelPath() const;
QString getTextEncoderModelPath() const; QString getTextEncoderModelPath() const;
QString getDiTModelPath() const; QString getDiTModelPath() const;
QString getVAEModelPath() const; QString getVAEModelPath() const;
// Setters for settings // Setters for settings
void setJsonTemplate(const QString &templateStr); void setJsonTemplate(const QString &templateStr);
void setAceStepPath(const QString &path); void setAceStepPath(const QString &path);
void setQwen3ModelPath(const QString &path); void setQwen3ModelPath(const QString &path);
void setTextEncoderModelPath(const QString &path); void setTextEncoderModelPath(const QString &path);
void setDiTModelPath(const QString &path); void setDiTModelPath(const QString &path);
void setVAEModelPath(const QString &path); void setVAEModelPath(const QString &path);
private slots: private slots:
void on_aceStepBrowseButton_clicked(); void on_aceStepBrowseButton_clicked();
void on_qwen3BrowseButton_clicked(); void on_qwen3BrowseButton_clicked();
void on_textEncoderBrowseButton_clicked(); void on_textEncoderBrowseButton_clicked();
void on_ditBrowseButton_clicked(); void on_ditBrowseButton_clicked();
void on_vaeBrowseButton_clicked(); void on_vaeBrowseButton_clicked();
private: private:
Ui::AdvancedSettingsDialog *ui; Ui::AdvancedSettingsDialog *ui;
}; };
#endif // ADVANCEDSETTINGSDIALOG_H #endif // ADVANCEDSETTINGSDIALOG_H

View file

@ -1,203 +1,203 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>AdvancedSettingsDialog</class> <class>AdvancedSettingsDialog</class>
<widget class="QDialog" name="AdvancedSettingsDialog"> <widget class="QDialog" name="AdvancedSettingsDialog">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>600</width> <width>600</width>
<height>450</height> <height>450</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Advanced Settings</string> <string>Advanced Settings</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="jsonTab"> <widget class="QWidget" name="jsonTab">
<attribute name="title"> <attribute name="title">
<string>JSON Template</string> <string>JSON Template</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="jsonLayout"> <layout class="QVBoxLayout" name="jsonLayout">
<item> <item>
<widget class="QLabel" name="jsonLabel"> <widget class="QLabel" name="jsonLabel">
<property name="text"> <property name="text">
<string>JSON Template for AceStep generation:</string> <string>JSON Template for AceStep generation:</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTextEdit" name="jsonTemplateEdit"/> <widget class="QTextEdit" name="jsonTemplateEdit"/>
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pathsTab"> <widget class="QWidget" name="pathsTab">
<attribute name="title"> <attribute name="title">
<string>Model Paths</string> <string>Model Paths</string>
</attribute> </attribute>
<layout class="QFormLayout" name="pathsLayout"> <layout class="QFormLayout" name="pathsLayout">
<property name="fieldGrowthPolicy"> <property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow</enum> <enum>QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow</enum>
</property> </property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="aceStepLabel"> <widget class="QLabel" name="aceStepLabel">
<property name="text"> <property name="text">
<string>AceStep Path:</string> <string>AceStep Path:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<layout class="QHBoxLayout" name="aceStepLayout"> <layout class="QHBoxLayout" name="aceStepLayout">
<item> <item>
<widget class="QLineEdit" name="aceStepPathEdit"/> <widget class="QLineEdit" name="aceStepPathEdit"/>
</item> </item>
<item> <item>
<widget class="QPushButton" name="aceStepBrowseButton"> <widget class="QPushButton" name="aceStepBrowseButton">
<property name="text"> <property name="text">
<string>Browse...</string> <string>Browse...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="qwen3Label"> <widget class="QLabel" name="qwen3Label">
<property name="text"> <property name="text">
<string>Qwen3 Model:</string> <string>Qwen3 Model:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<layout class="QHBoxLayout" name="qwen3Layout"> <layout class="QHBoxLayout" name="qwen3Layout">
<item> <item>
<widget class="QLineEdit" name="qwen3ModelEdit"/> <widget class="QLineEdit" name="qwen3ModelEdit"/>
</item> </item>
<item> <item>
<widget class="QPushButton" name="qwen3BrowseButton"> <widget class="QPushButton" name="qwen3BrowseButton">
<property name="text"> <property name="text">
<string>Browse...</string> <string>Browse...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="textEncoderLabel"> <widget class="QLabel" name="textEncoderLabel">
<property name="text"> <property name="text">
<string>Text Encoder Model:</string> <string>Text Encoder Model:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<layout class="QHBoxLayout" name="textEncoderLayout"> <layout class="QHBoxLayout" name="textEncoderLayout">
<item> <item>
<widget class="QLineEdit" name="textEncoderEdit"/> <widget class="QLineEdit" name="textEncoderEdit"/>
</item> </item>
<item> <item>
<widget class="QPushButton" name="textEncoderBrowseButton"> <widget class="QPushButton" name="textEncoderBrowseButton">
<property name="text"> <property name="text">
<string>Browse...</string> <string>Browse...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="ditLabel"> <widget class="QLabel" name="ditLabel">
<property name="text"> <property name="text">
<string>DiT Model:</string> <string>DiT Model:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<layout class="QHBoxLayout" name="ditLayout"> <layout class="QHBoxLayout" name="ditLayout">
<item> <item>
<widget class="QLineEdit" name="ditModelEdit"/> <widget class="QLineEdit" name="ditModelEdit"/>
</item> </item>
<item> <item>
<widget class="QPushButton" name="ditBrowseButton"> <widget class="QPushButton" name="ditBrowseButton">
<property name="text"> <property name="text">
<string>Browse...</string> <string>Browse...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="vaeLabel"> <widget class="QLabel" name="vaeLabel">
<property name="text"> <property name="text">
<string>VAE Model:</string> <string>VAE Model:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<layout class="QHBoxLayout" name="vaeLayout"> <layout class="QHBoxLayout" name="vaeLayout">
<item> <item>
<widget class="QLineEdit" name="vaeModelEdit"/> <widget class="QLineEdit" name="vaeModelEdit"/>
</item> </item>
<item> <item>
<widget class="QPushButton" name="vaeBrowseButton"> <widget class="QPushButton" name="vaeBrowseButton">
<property name="text"> <property name="text">
<string>Browse...</string> <string>Browse...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set> <set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>
<sender>buttonBox</sender> <sender>buttonBox</sender>
<signal>accepted()</signal> <signal>accepted()</signal>
<receiver>AdvancedSettingsDialog</receiver> <receiver>AdvancedSettingsDialog</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>248</x> <x>248</x>
<y>254</y> <y>254</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>254</y> <y>254</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection> <connection>
<sender>buttonBox</sender> <sender>buttonBox</sender>
<signal>rejected()</signal> <signal>rejected()</signal>
<receiver>AdvancedSettingsDialog</receiver> <receiver>AdvancedSettingsDialog</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>316</x> <x>316</x>
<y>260</y> <y>260</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>286</x> <x>286</x>
<y>260</y> <y>260</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>
</connections> </connections>
</ui> </ui>

View file

@ -2,113 +2,128 @@
#include <QDebug> #include <QDebug>
AudioPlayer::AudioPlayer(QObject *parent) AudioPlayer::AudioPlayer(QObject *parent)
: QObject(parent), : QObject(parent),
mediaPlayer(new QMediaPlayer(this)), mediaPlayer(new QMediaPlayer(this)),
audioOutput(new QAudioOutput(this)), audioOutput(new QAudioOutput(this)),
positionTimer(new QTimer(this)) positionTimer(new QTimer(this))
{ {
// Set up audio output with default device // Set up audio output with default device
mediaPlayer->setAudioOutput(audioOutput); mediaPlayer->setAudioOutput(audioOutput);
connect(mediaPlayer, &QMediaPlayer::playbackStateChanged, connect(mediaPlayer, &QMediaPlayer::playbackStateChanged,
this, &AudioPlayer::handlePlaybackStateChanged); this, &AudioPlayer::handlePlaybackStateChanged);
connect(mediaPlayer, &QMediaPlayer::mediaStatusChanged, connect(mediaPlayer, &QMediaPlayer::mediaStatusChanged,
this, &AudioPlayer::handleMediaStatusChanged); this, &AudioPlayer::handleMediaStatusChanged);
// Set up position timer for updating playback position // Set up position timer for updating playback position
positionTimer->setInterval(500); // Update every 500ms positionTimer->setInterval(500); // Update every 500ms
connect(positionTimer, &QTimer::timeout, [this]() { connect(positionTimer, &QTimer::timeout, [this]()
if (isPlaying()) { {
emit positionChanged(mediaPlayer->position()); if (isPlaying())
} {
}); emit positionChanged(mediaPlayer->position());
}
});
} }
AudioPlayer::~AudioPlayer() AudioPlayer::~AudioPlayer()
{ {
stop(); stop();
} }
void AudioPlayer::play(const QString &filePath) void AudioPlayer::play(const QString &filePath)
{ {
if (isPlaying()) { if (isPlaying())
stop(); {
} stop();
}
mediaPlayer->setSource(QUrl::fromLocalFile(filePath)); mediaPlayer->setSource(QUrl::fromLocalFile(filePath));
mediaPlayer->play(); mediaPlayer->play();
// Start position timer // Start position timer
positionTimer->start(); positionTimer->start();
} }
void AudioPlayer::play() void AudioPlayer::play()
{ {
if (!isPlaying()) { if (!isPlaying())
mediaPlayer->play(); {
positionTimer->start(); mediaPlayer->play();
} positionTimer->start();
}
} }
void AudioPlayer::pause() void AudioPlayer::pause()
{ {
if (isPlaying()) { if (isPlaying())
mediaPlayer->pause(); {
positionTimer->stop(); mediaPlayer->pause();
} positionTimer->stop();
}
} }
void AudioPlayer::setPosition(int position) void AudioPlayer::setPosition(int position)
{ {
mediaPlayer->setPosition(position); mediaPlayer->setPosition(position);
} }
void AudioPlayer::stop() void AudioPlayer::stop()
{ {
mediaPlayer->stop(); mediaPlayer->stop();
positionTimer->stop(); positionTimer->stop();
} }
bool AudioPlayer::isPlaying() const bool AudioPlayer::isPlaying() const
{ {
return mediaPlayer->playbackState() == QMediaPlayer::PlayingState; return mediaPlayer->playbackState() == QMediaPlayer::PlayingState;
} }
int AudioPlayer::duration() const int AudioPlayer::duration() const
{ {
return mediaPlayer->duration(); return mediaPlayer->duration();
} }
int AudioPlayer::position() const int AudioPlayer::position() const
{ {
return mediaPlayer->position(); return mediaPlayer->position();
} }
void AudioPlayer::handlePlaybackStateChanged(QMediaPlayer::PlaybackState state) void AudioPlayer::handlePlaybackStateChanged(QMediaPlayer::PlaybackState state)
{ {
if (state == QMediaPlayer::PlayingState) { if (state == QMediaPlayer::PlayingState)
emit playbackStarted(); {
} else if (state == QMediaPlayer::StoppedState || emit playbackStarted();
state == QMediaPlayer::PausedState) { }
// Check if we reached the end else if (state == QMediaPlayer::StoppedState ||
if (mediaPlayer->position() >= mediaPlayer->duration() - 100) { state == QMediaPlayer::PausedState)
emit playbackFinished(); {
} // Check if we reached the end
} if (mediaPlayer->position() >= mediaPlayer->duration() - 100)
{
emit playbackFinished();
}
}
} }
void AudioPlayer::handleMediaStatusChanged(QMediaPlayer::MediaStatus status) void AudioPlayer::handleMediaStatusChanged(QMediaPlayer::MediaStatus status)
{ {
if (status == QMediaPlayer::EndOfMedia) { if (status == QMediaPlayer::EndOfMedia)
emit playbackFinished(); {
} else if (status == QMediaPlayer::LoadedMedia || emit playbackFinished();
status == QMediaPlayer::BufferedMedia) { }
// Media loaded successfully, emit duration else if (status == QMediaPlayer::LoadedMedia ||
int duration = mediaPlayer->duration(); status == QMediaPlayer::BufferedMedia)
if (duration > 0) { {
emit durationChanged(duration); // Media loaded successfully, emit duration
} int duration = mediaPlayer->duration();
} else if (status == QMediaPlayer::InvalidMedia) { if (duration > 0)
emit playbackError(mediaPlayer->errorString()); {
} emit durationChanged(duration);
}
}
else if (status == QMediaPlayer::InvalidMedia)
{
emit playbackError(mediaPlayer->errorString());
}
} }

View file

@ -12,35 +12,35 @@
class AudioPlayer : public QObject class AudioPlayer : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit AudioPlayer(QObject *parent = nullptr); explicit AudioPlayer(QObject *parent = nullptr);
~AudioPlayer(); ~AudioPlayer();
void play(const QString &filePath); void play(const QString &filePath);
void play(); void play();
void stop(); void stop();
void pause(); void pause();
void setPosition(int position); void setPosition(int position);
bool isPlaying() const; bool isPlaying() const;
int duration() const; int duration() const;
int position() const; int position() const;
signals: signals:
void playbackStarted(); void playbackStarted();
void playbackFinished(); void playbackFinished();
void playbackError(const QString &error); void playbackError(const QString &error);
void positionChanged(int position); void positionChanged(int position);
void durationChanged(int duration); void durationChanged(int duration);
private slots: private slots:
void handlePlaybackStateChanged(QMediaPlayer::PlaybackState state); void handlePlaybackStateChanged(QMediaPlayer::PlaybackState state);
void handleMediaStatusChanged(QMediaPlayer::MediaStatus status); void handleMediaStatusChanged(QMediaPlayer::MediaStatus status);
private: private:
QMediaPlayer *mediaPlayer; QMediaPlayer *mediaPlayer;
QAudioOutput *audioOutput; QAudioOutput *audioOutput;
QTimer *positionTimer; QTimer *positionTimer;
}; };
#endif // AUDIOPLAYER_H #endif // AUDIOPLAYER_H

File diff suppressed because it is too large Load diff

View file

@ -17,90 +17,93 @@
#include "AceStepWorker.h" #include "AceStepWorker.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; } namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE QT_END_NAMESPACE
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
public: public:
MainWindow(QWidget *parent = nullptr); MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
private slots: private slots:
void on_playButton_clicked(); void on_playButton_clicked();
void on_pauseButton_clicked(); void on_pauseButton_clicked();
void on_skipButton_clicked(); void on_skipButton_clicked();
void on_stopButton_clicked(); void on_stopButton_clicked();
void on_shuffleButton_clicked(); void on_shuffleButton_clicked();
void on_positionSlider_sliderMoved(int position); void on_positionSlider_sliderMoved(int position);
void updatePosition(int position); void updatePosition(int position);
void updateDuration(int duration); void updateDuration(int duration);
void on_addSongButton_clicked(); void on_addSongButton_clicked();
void on_removeSongButton_clicked(); void on_removeSongButton_clicked();
void on_advancedSettingsButton_clicked(); void on_advancedSettingsButton_clicked();
void on_songListView_doubleClicked(const QModelIndex &index); void on_songListView_doubleClicked(const QModelIndex &index);
void songGenerated(const SongItem& song); void songGenerated(const SongItem& song);
void playNextSong(); void playNextSong();
void playbackStarted(); void playbackStarted();
void updatePlaybackStatus(bool playing); void updatePlaybackStatus(bool playing);
void generationError(const QString &error); void generationError(const QString &error);
void on_actionSavePlaylist(); void on_actionSavePlaylist();
void on_actionLoadPlaylist(); void on_actionLoadPlaylist();
void on_actionAppendPlaylist(); void on_actionAppendPlaylist();
void on_actionSaveSong(); void on_actionSaveSong();
private: private:
void startNextSongGeneration(); void startNextSongGeneration();
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
SongListModel *songModel; SongListModel *songModel;
AudioPlayer *audioPlayer; AudioPlayer *audioPlayer;
AceStepWorker *aceStepWorker; AceStepWorker *aceStepWorker;
QTimer *playbackTimer; QTimer *playbackTimer;
QString formatTime(int milliseconds); QString formatTime(int milliseconds);
SongItem currentSong; SongItem currentSong;
bool isPlaying; bool isPlaying;
bool isPaused; bool isPaused;
bool shuffleMode; bool shuffleMode;
bool isGeneratingNext; bool isGeneratingNext;
QString jsonTemplate; QString jsonTemplate;
// Path settings // Path settings
QString aceStepPath; QString aceStepPath;
QString qwen3ModelPath; QString qwen3ModelPath;
QString textEncoderModelPath; QString textEncoderModelPath;
QString ditModelPath; QString ditModelPath;
QString vaeModelPath; QString vaeModelPath;
// Queue for generated songs // Queue for generated songs
static constexpr int generationTresh = 2; static constexpr int generationTresh = 2;
QQueue<SongItem> generatedSongQueue; QQueue<SongItem> generatedSongQueue;
private: private:
void loadSettings(); void loadSettings();
void saveSettings(); void saveSettings();
void loadPlaylist(const QString &filePath); void loadPlaylist(const QString &filePath);
void savePlaylist(const QString &filePath); void savePlaylist(const QString &filePath);
void autoSavePlaylist(); void autoSavePlaylist();
void autoLoadPlaylist(); void autoLoadPlaylist();
void playSong(const SongItem& song); void playSong(const SongItem& song);
bool savePlaylistToJson(const QString &filePath, const QList<SongItem> &songs); bool savePlaylistToJson(const QString &filePath, const QList<SongItem> &songs);
bool loadPlaylistFromJson(const QString &filePath, QList<SongItem> &songs); bool loadPlaylistFromJson(const QString &filePath, QList<SongItem> &songs);
void setupUI(); void setupUI();
void updateControls(); void updateControls();
void ensureSongsInQueue(bool enqeueCurrent = false); void ensureSongsInQueue(bool enqeueCurrent = false);
void flushGenerationQueue(); void flushGenerationQueue();
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

View file

@ -1,359 +1,359 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>MainWindow</class> <class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow"> <widget class="QMainWindow" name="MainWindow">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>600</height> <height>600</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Aceradio</string> <string>Aceradio</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../res/resources.qrc"> <iconset resource="../res/resources.qrc">
<normaloff>:/icons/xyz.uvos.aceradio.png</normaloff>:/icons/xyz.uvos.aceradio.png</iconset> <normaloff>:/icons/xyz.uvos.aceradio.png</normaloff>:/icons/xyz.uvos.aceradio.png</iconset>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QTabWidget" name="mainTabWidget"> <widget class="QTabWidget" name="mainTabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="songsTab"> <widget class="QWidget" name="songsTab">
<attribute name="title"> <attribute name="title">
<string>Songs</string> <string>Songs</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QTableView" name="songListView"> <widget class="QTableView" name="songListView">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>200</height> <height>200</height>
</size> </size>
</property> </property>
<property name="editTriggers"> <property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set> <set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property> </property>
<property name="selectionBehavior"> <property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum> <enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="infoTab"> <widget class="QWidget" name="infoTab">
<attribute name="title"> <attribute name="title">
<string>Info</string> <string>Info</string>
</attribute> </attribute>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QPlainTextEdit" name="jsonTextEdit"> <widget class="QPlainTextEdit" name="jsonTextEdit">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="lyricsTab"> <widget class="QWidget" name="lyricsTab">
<attribute name="title"> <attribute name="title">
<string>Lyrics</string> <string>Lyrics</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QTextEdit" name="lyricsTextEdit"> <widget class="QTextEdit" name="lyricsTextEdit">
<property name="font"> <property name="font">
<font> <font>
<family>Monospace</family> <family>Monospace</family>
</font> </font>
</property> </property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="buttonLayout"> <layout class="QHBoxLayout" name="buttonLayout">
<item> <item>
<widget class="QPushButton" name="addSongButton"> <widget class="QPushButton" name="addSongButton">
<property name="text"> <property name="text">
<string>Add Song</string> <string>Add Song</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="removeSongButton"> <widget class="QPushButton" name="removeSongButton">
<property name="text"> <property name="text">
<string>Remove Song</string> <string>Remove Song</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>40</width> <width>40</width>
<height>20</height> <height>20</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QLabel" name="nowPlayingLabel"> <widget class="QLabel" name="nowPlayingLabel">
<property name="layoutDirection"> <property name="layoutDirection">
<enum>Qt::LayoutDirection::LeftToRight</enum> <enum>Qt::LayoutDirection::LeftToRight</enum>
</property> </property>
<property name="text"> <property name="text">
<string>Now Playing:</string> <string>Now Playing:</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="timeControlsLayout"> <layout class="QHBoxLayout" name="timeControlsLayout">
<item> <item>
<widget class="QLabel" name="elapsedTimeLabel"> <widget class="QLabel" name="elapsedTimeLabel">
<property name="text"> <property name="text">
<string>0:00</string> <string>0:00</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="ClickableSlider" name="positionSlider" native="true"> <widget class="ClickableSlider" name="positionSlider" native="true">
<property name="mouseTracking"> <property name="mouseTracking">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="tabletTracking"> <property name="tabletTracking">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="durationLabel"> <widget class="QLabel" name="durationLabel">
<property name="text"> <property name="text">
<string>0:00</string> <string>0:00</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QFrame" name="controlsFrame"> <widget class="QFrame" name="controlsFrame">
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum> <enum>QFrame::Shape::StyledPanel</enum>
</property> </property>
<property name="frameShadow"> <property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum> <enum>QFrame::Shadow::Raised</enum>
</property> </property>
<layout class="QHBoxLayout" name="controlsLayout"> <layout class="QHBoxLayout" name="controlsLayout">
<item> <item>
<widget class="QPushButton" name="playButton"> <widget class="QPushButton" name="playButton">
<property name="text"> <property name="text">
<string>Play</string> <string>Play</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="media-playback-start"/> <iconset theme="media-playback-start"/>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="pauseButton"> <widget class="QPushButton" name="pauseButton">
<property name="text"> <property name="text">
<string>Pause</string> <string>Pause</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="media-playback-pause"/> <iconset theme="media-playback-pause"/>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="skipButton"> <widget class="QPushButton" name="skipButton">
<property name="text"> <property name="text">
<string>Skip</string> <string>Skip</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="media-skip-forward"/> <iconset theme="media-skip-forward"/>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="stopButton"> <widget class="QPushButton" name="stopButton">
<property name="text"> <property name="text">
<string>Stop</string> <string>Stop</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="media-playback-stop"/> <iconset theme="media-playback-stop"/>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="shuffleButton"> <widget class="QPushButton" name="shuffleButton">
<property name="text"> <property name="text">
<string>Shuffle</string> <string>Shuffle</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="media-playlist-shuffle"/> <iconset theme="media-playlist-shuffle"/>
</property> </property>
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>40</width> <width>40</width>
<height>20</height> <height>20</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="statusLayout"> <layout class="QVBoxLayout" name="statusLayout">
<item> <item>
<widget class="QProgressBar" name="progressBar"> <widget class="QProgressBar" name="progressBar">
<property name="value"> <property name="value">
<number>0</number> <number>0</number>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="statusLabel"> <widget class="QLabel" name="statusLabel">
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QMenuBar" name="menubar"> <widget class="QMenuBar" name="menubar">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>32</height> <height>32</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
<property name="title"> <property name="title">
<string>File</string> <string>File</string>
</property> </property>
<addaction name="actionSaveSong"/> <addaction name="actionSaveSong"/>
<addaction name="actionSavePlaylist"/> <addaction name="actionSavePlaylist"/>
<addaction name="actionLoadPlaylist"/> <addaction name="actionLoadPlaylist"/>
<addaction name="actionAppendPlaylist"/> <addaction name="actionAppendPlaylist"/>
<addaction name="actionClearPlaylist"/> <addaction name="actionClearPlaylist"/>
<addaction name="actionQuit"/> <addaction name="actionQuit"/>
</widget> </widget>
<widget class="QMenu" name="menuSettings"> <widget class="QMenu" name="menuSettings">
<property name="title"> <property name="title">
<string>Settings</string> <string>Settings</string>
</property> </property>
<addaction name="actionAdvancedSettings"/> <addaction name="actionAdvancedSettings"/>
</widget> </widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuSettings"/> <addaction name="menuSettings"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
<action name="actionSavePlaylist"> <action name="actionSavePlaylist">
<property name="icon"> <property name="icon">
<iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/> <iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/>
</property> </property>
<property name="text"> <property name="text">
<string>Save Playlist</string> <string>Save Playlist</string>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>Ctrl+S</string> <string>Ctrl+S</string>
</property> </property>
</action> </action>
<action name="actionLoadPlaylist"> <action name="actionLoadPlaylist">
<property name="icon"> <property name="icon">
<iconset theme="QIcon::ThemeIcon::DocumentOpen"/> <iconset theme="QIcon::ThemeIcon::DocumentOpen"/>
</property> </property>
<property name="text"> <property name="text">
<string>Load Playlist...</string> <string>Load Playlist...</string>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>Ctrl+O</string> <string>Ctrl+O</string>
</property> </property>
</action> </action>
<action name="actionAdvancedSettings"> <action name="actionAdvancedSettings">
<property name="text"> <property name="text">
<string>Ace Step</string> <string>Ace Step</string>
</property> </property>
</action> </action>
<action name="actionQuit"> <action name="actionQuit">
<property name="icon"> <property name="icon">
<iconset theme="QIcon::ThemeIcon::ApplicationExit"/> <iconset theme="QIcon::ThemeIcon::ApplicationExit"/>
</property> </property>
<property name="text"> <property name="text">
<string>Quit</string> <string>Quit</string>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>Ctrl+Q</string> <string>Ctrl+Q</string>
</property> </property>
</action> </action>
<action name="actionClearPlaylist"> <action name="actionClearPlaylist">
<property name="icon"> <property name="icon">
<iconset theme="QIcon::ThemeIcon::EditDelete"/> <iconset theme="QIcon::ThemeIcon::EditDelete"/>
</property> </property>
<property name="text"> <property name="text">
<string>Clear Playlist</string> <string>Clear Playlist</string>
</property> </property>
</action> </action>
<action name="actionSaveSong"> <action name="actionSaveSong">
<property name="icon"> <property name="icon">
<iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/> <iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/>
</property> </property>
<property name="text"> <property name="text">
<string>Save Song</string> <string>Save Song</string>
</property> </property>
</action> </action>
<action name="actionAppendPlaylist"> <action name="actionAppendPlaylist">
<property name="icon"> <property name="icon">
<iconset theme="QIcon::ThemeIcon::DocumentOpen"/> <iconset theme="QIcon::ThemeIcon::DocumentOpen"/>
</property> </property>
<property name="text"> <property name="text">
<string>Append Playlist</string> <string>Append Playlist</string>
</property> </property>
</action> </action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>ClickableSlider</class> <class>ClickableSlider</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>src/clickableslider.h</header> <header>src/clickableslider.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../res/resources.qrc"/> <include location="../res/resources.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View file

@ -3,75 +3,82 @@
#include <QMessageBox> #include <QMessageBox>
SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &lyrics, const QString &vocalLanguage) SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &lyrics, const QString &vocalLanguage)
: QDialog(parent), : QDialog(parent),
ui(new Ui::SongDialog) ui(new Ui::SongDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
// Set initial values if provided // Set initial values if provided
if (!caption.isEmpty()) { if (!caption.isEmpty())
ui->captionEdit->setPlainText(caption); {
} ui->captionEdit->setPlainText(caption);
if (!lyrics.isEmpty()) { }
ui->lyricsEdit->setPlainText(lyrics); if (!lyrics.isEmpty())
} {
ui->lyricsEdit->setPlainText(lyrics);
}
// Setup vocal language combo box // Setup vocal language combo box
ui->vocalLanguageCombo->addItem("--", ""); // Unset ui->vocalLanguageCombo->addItem("--", ""); // Unset
ui->vocalLanguageCombo->addItem("English (en)", "en"); ui->vocalLanguageCombo->addItem("English (en)", "en");
ui->vocalLanguageCombo->addItem("German (de)", "de"); ui->vocalLanguageCombo->addItem("German (de)", "de");
ui->vocalLanguageCombo->addItem("French (fr)", "fr"); ui->vocalLanguageCombo->addItem("French (fr)", "fr");
ui->vocalLanguageCombo->addItem("Spanish (es)", "es"); ui->vocalLanguageCombo->addItem("Spanish (es)", "es");
ui->vocalLanguageCombo->addItem("Japanese (ja)", "ja"); ui->vocalLanguageCombo->addItem("Japanese (ja)", "ja");
ui->vocalLanguageCombo->addItem("Chinese (zh)", "zh"); ui->vocalLanguageCombo->addItem("Chinese (zh)", "zh");
ui->vocalLanguageCombo->addItem("Italian (it)", "it"); ui->vocalLanguageCombo->addItem("Italian (it)", "it");
ui->vocalLanguageCombo->addItem("Portuguese (pt)", "pt"); ui->vocalLanguageCombo->addItem("Portuguese (pt)", "pt");
ui->vocalLanguageCombo->addItem("Russian (ru)", "ru"); ui->vocalLanguageCombo->addItem("Russian (ru)", "ru");
// Set current language if provided // Set current language if provided
if (!vocalLanguage.isEmpty()) { if (!vocalLanguage.isEmpty())
int index = ui->vocalLanguageCombo->findData(vocalLanguage); {
if (index >= 0) { int index = ui->vocalLanguageCombo->findData(vocalLanguage);
ui->vocalLanguageCombo->setCurrentIndex(index); if (index >= 0)
} {
} else { ui->vocalLanguageCombo->setCurrentIndex(index);
ui->vocalLanguageCombo->setCurrentIndex(0); // Default to unset }
} }
else
{
ui->vocalLanguageCombo->setCurrentIndex(0); // Default to unset
}
} }
SongDialog::~SongDialog() SongDialog::~SongDialog()
{ {
delete ui; delete ui;
} }
QString SongDialog::getCaption() const QString SongDialog::getCaption() const
{ {
return ui->captionEdit->toPlainText(); return ui->captionEdit->toPlainText();
} }
QString SongDialog::getLyrics() const QString SongDialog::getLyrics() const
{ {
return ui->lyricsEdit->toPlainText(); return ui->lyricsEdit->toPlainText();
} }
QString SongDialog::getVocalLanguage() const QString SongDialog::getVocalLanguage() const
{ {
return ui->vocalLanguageCombo->currentData().toString(); return ui->vocalLanguageCombo->currentData().toString();
} }
void SongDialog::on_okButton_clicked() void SongDialog::on_okButton_clicked()
{ {
// Validate that caption is not empty // Validate that caption is not empty
QString caption = getCaption(); QString caption = getCaption();
if (caption.trimmed().isEmpty()) { if (caption.trimmed().isEmpty())
QMessageBox::warning(this, "Invalid Input", "Caption cannot be empty."); {
return; QMessageBox::warning(this, "Invalid Input", "Caption cannot be empty.");
} return;
}
accept(); accept();
} }
void SongDialog::on_cancelButton_clicked() void SongDialog::on_cancelButton_clicked()
{ {
reject(); reject();
} }

View file

@ -4,28 +4,30 @@
#include <QDialog> #include <QDialog>
#include <QString> #include <QString>
namespace Ui { namespace Ui
{
class SongDialog; class SongDialog;
} }
class SongDialog : public QDialog class SongDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "", const QString &vocalLanguage = ""); explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "",
~SongDialog(); const QString &vocalLanguage = "");
~SongDialog();
QString getCaption() const; QString getCaption() const;
QString getLyrics() const; QString getLyrics() const;
QString getVocalLanguage() const; QString getVocalLanguage() const;
private slots: private slots:
void on_okButton_clicked(); void on_okButton_clicked();
void on_cancelButton_clicked(); void on_cancelButton_clicked();
private: private:
Ui::SongDialog *ui; Ui::SongDialog *ui;
}; };
#endif // SONGDIALOG_H #endif // SONGDIALOG_H

View file

@ -1,109 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>SongDialog</class> <class>SongDialog</class>
<widget class="QDialog" name="SongDialog"> <widget class="QDialog" name="SongDialog">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>500</width> <width>500</width>
<height>400</height> <height>400</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Song Details</string> <string>Song Details</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="captionLabel"> <widget class="QLabel" name="captionLabel">
<property name="text"> <property name="text">
<string>Caption:</string> <string>Caption:</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignTop</set> <set>Qt::AlignTop</set>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTextEdit" name="captionEdit"> <widget class="QTextEdit" name="captionEdit">
<property name="placeholderText"> <property name="placeholderText">
<string>Enter song caption (e.g., "Upbeat pop rock anthem with driving electric guitars")</string> <string>Enter song caption (e.g., "Upbeat pop rock anthem with driving electric guitars")</string>
</property> </property>
<property name="maximumHeight"> <property name="maximumHeight">
<number>80</number> <number>80</number>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="lyricsLabel"> <widget class="QLabel" name="lyricsLabel">
<property name="text"> <property name="text">
<string>Lyrics (optional):</string> <string>Lyrics (optional):</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignTop</set> <set>Qt::AlignTop</set>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTextEdit" name="lyricsEdit"> <widget class="QTextEdit" name="lyricsEdit">
<property name="placeholderText"> <property name="placeholderText">
<string>Enter lyrics or leave empty for instrumental music</string> <string>Enter lyrics or leave empty for instrumental music</string>
</property> </property>
<property name="minimumHeight"> <property name="minimumHeight">
<number>150</number> <number>150</number>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="vocalLanguageLabel"> <widget class="QLabel" name="vocalLanguageLabel">
<property name="text"> <property name="text">
<string>Vocal Language:</string> <string>Vocal Language:</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignTop</set> <set>Qt::AlignTop</set>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="vocalLanguageCombo"> <widget class="QComboBox" name="vocalLanguageCombo">
<property name="editable"> <property name="editable">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="buttonLayout"> <layout class="QHBoxLayout" name="buttonLayout">
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>40</width> <width>40</width>
<height>20</height> <height>20</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="okButton"> <widget class="QPushButton" name="okButton">
<property name="text"> <property name="text">
<string>OK</string> <string>OK</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="cancelButton"> <widget class="QPushButton" name="cancelButton">
<property name="text"> <property name="text">
<string>Cancel</string> <string>Cancel</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View file

@ -3,18 +3,20 @@
#include <QRandomGenerator> #include <QRandomGenerator>
#include <cstdint> #include <cstdint>
class SongItem { class SongItem
{
public: public:
QString caption; QString caption;
QString lyrics; QString lyrics;
uint64_t uniqueId; uint64_t uniqueId;
QString file; QString file;
QString vocalLanguage; QString vocalLanguage;
QString json; QString json;
inline SongItem(const QString &caption = "", const QString &lyrics = "") inline SongItem(const QString &caption = "", const QString &lyrics = "")
: caption(caption), lyrics(lyrics) { : caption(caption), lyrics(lyrics)
// Generate a unique ID using cryptographically secure random number {
uniqueId = QRandomGenerator::global()->generate64(); // Generate a unique ID using cryptographically secure random number
} uniqueId = QRandomGenerator::global()->generate64();
}
}; };

View file

@ -7,167 +7,179 @@
#include <QUuid> #include <QUuid>
SongListModel::SongListModel(QObject *parent) SongListModel::SongListModel(QObject *parent)
: QAbstractTableModel(parent), : QAbstractTableModel(parent),
m_playingIndex(-1) m_playingIndex(-1)
{ {
} }
int SongListModel::rowCount(const QModelIndex &parent) const int SongListModel::rowCount(const QModelIndex &parent) const
{ {
if (parent.isValid()) if (parent.isValid())
return 0; return 0;
return songList.size(); return songList.size();
} }
int SongListModel::columnCount(const QModelIndex &parent) const int SongListModel::columnCount(const QModelIndex &parent) const
{ {
// We have 3 columns: play indicator, song name, and vocal language (read-only) // We have 3 columns: play indicator, song name, and vocal language (read-only)
return 3; return 3;
} }
QVariant SongListModel::data(const QModelIndex &index, int role) const QVariant SongListModel::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid() || index.row() >= songList.size()) if (!index.isValid() || index.row() >= songList.size())
return QVariant(); return QVariant();
const SongItem &song = songList[index.row()]; const SongItem &song = songList[index.row()];
switch (role) { switch (role)
case Qt::DisplayRole: {
// Column 0: Play indicator column case Qt::DisplayRole:
if (index.column() == 0) { // Column 0: Play indicator column
return index.row() == m_playingIndex ? "" : ""; if (index.column() == 0)
} {
// Column 1: Song name return index.row() == m_playingIndex ? "" : "";
else if (index.column() == 1) { }
return song.caption; // Column 1: Song name
} else if (index.column() == 1)
// Column 2: Vocal language {
else if (index.column() == 2) { return song.caption;
return !song.vocalLanguage.isEmpty() ? song.vocalLanguage : "--"; }
} // Column 2: Vocal language
break; else if (index.column() == 2)
case Qt::FontRole: {
// Make play indicator bold and larger return !song.vocalLanguage.isEmpty() ? song.vocalLanguage : "--";
if (index.column() == 0 && index.row() == m_playingIndex) { }
QFont font = QApplication::font(); break;
font.setBold(true); case Qt::FontRole:
return font; // Make play indicator bold and larger
} if (index.column() == 0 && index.row() == m_playingIndex)
break; {
case Qt::TextAlignmentRole: QFont font = QApplication::font();
// Center align the play indicator font.setBold(true);
if (index.column() == 0) { return font;
return Qt::AlignCenter; }
} break;
break; case Qt::TextAlignmentRole:
case CaptionRole: // Center align the play indicator
return song.caption; if (index.column() == 0)
case LyricsRole: {
return song.lyrics; return Qt::AlignCenter;
case VocalLanguageRole: }
return song.vocalLanguage; break;
case IsPlayingRole: case CaptionRole:
return index.row() == m_playingIndex; return song.caption;
default: case LyricsRole:
return QVariant(); return song.lyrics;
} case VocalLanguageRole:
return song.vocalLanguage;
case IsPlayingRole:
return index.row() == m_playingIndex;
default:
return QVariant();
}
return QVariant(); return QVariant();
} }
bool SongListModel::setData(const QModelIndex &index, const QVariant &value, int role) bool SongListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{ {
if (!index.isValid() || index.row() >= songList.size()) if (!index.isValid() || index.row() >= songList.size())
return false; return false;
SongItem &song = songList[index.row()]; SongItem &song = songList[index.row()];
switch (role) { switch (role)
case CaptionRole: {
song.caption = value.toString(); case CaptionRole:
break; song.caption = value.toString();
case LyricsRole: break;
song.lyrics = value.toString(); case LyricsRole:
break; song.lyrics = value.toString();
case VocalLanguageRole: break;
song.vocalLanguage = value.toString(); case VocalLanguageRole:
break; song.vocalLanguage = value.toString();
default: break;
return false; default:
} return false;
}
emit dataChanged(index, index, {role}); emit dataChanged(index, index, {role});
return true; return true;
} }
Qt::ItemFlags SongListModel::flags(const QModelIndex &index) const Qt::ItemFlags SongListModel::flags(const QModelIndex &index) const
{ {
if (!index.isValid()) if (!index.isValid())
return Qt::NoItemFlags; return Qt::NoItemFlags;
// Remove ItemIsEditable to prevent inline editing and double-click issues // Remove ItemIsEditable to prevent inline editing and double-click issues
return Qt::ItemIsEnabled | Qt::ItemIsSelectable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
} }
void SongListModel::addSong(const SongItem &song) void SongListModel::addSong(const SongItem &song)
{ {
beginInsertRows(QModelIndex(), songList.size(), songList.size()); beginInsertRows(QModelIndex(), songList.size(), songList.size());
songList.append(song); songList.append(song);
endInsertRows(); endInsertRows();
} }
void SongListModel::removeSong(int index) void SongListModel::removeSong(int index)
{ {
if (index >= 0 && index < songList.size()) { if (index >= 0 && index < songList.size())
beginRemoveRows(QModelIndex(), index, index); {
songList.removeAt(index); beginRemoveRows(QModelIndex(), index, index);
endRemoveRows(); songList.removeAt(index);
} endRemoveRows();
}
} }
void SongListModel::clear() void SongListModel::clear()
{ {
beginRemoveRows(QModelIndex(), 0, songList.size()-1); beginRemoveRows(QModelIndex(), 0, songList.size()-1);
songList.clear(); songList.clear();
endRemoveRows(); endRemoveRows();
} }
bool SongListModel::empty() bool SongListModel::empty()
{ {
return songList.empty(); return songList.empty();
} }
SongItem SongListModel::getSong(int index) const SongItem SongListModel::getSong(int index) const
{ {
if (index >= 0 && index < songList.size()) { if (index >= 0 && index < songList.size())
return songList[index]; {
} return songList[index];
return SongItem(); }
return SongItem();
} }
QVariant SongListModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant SongListModel::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
// Hide headers since we don't need column titles {
return QVariant(); // Hide headers since we don't need column titles
} return QVariant();
return QAbstractTableModel::headerData(section, orientation, role); }
return QAbstractTableModel::headerData(section, orientation, role);
} }
void SongListModel::setPlayingIndex(int index) void SongListModel::setPlayingIndex(int index)
{ {
int oldPlayingIndex = m_playingIndex; int oldPlayingIndex = m_playingIndex;
m_playingIndex = index; m_playingIndex = index;
// Update both the old and new playing indices to trigger UI updates // Update both the old and new playing indices to trigger UI updates
if (oldPlayingIndex >= 0 && oldPlayingIndex < songList.size()) { if (oldPlayingIndex >= 0 && oldPlayingIndex < songList.size())
emit dataChanged(this->index(oldPlayingIndex, 0), this->index(oldPlayingIndex, 0)); {
} emit dataChanged(this->index(oldPlayingIndex, 0), this->index(oldPlayingIndex, 0));
}
if (index >= 0 && index < songList.size()) { if (index >= 0 && index < songList.size())
emit dataChanged(this->index(index, 0), this->index(index, 0)); {
} emit dataChanged(this->index(index, 0), this->index(index, 0));
}
} }
int SongListModel::songCount() int SongListModel::songCount()
@ -177,30 +189,34 @@ int SongListModel::songCount()
int SongListModel::findNextIndex(int currentIndex, bool shuffle) const int SongListModel::findNextIndex(int currentIndex, bool shuffle) const
{ {
if (songList.isEmpty()) if (songList.isEmpty())
return -1; return -1;
if (shuffle) { if (shuffle)
// Simple random selection for shuffle mode {
QRandomGenerator generator; // Simple random selection for shuffle mode
return generator.bounded(songList.size()); QRandomGenerator generator;
} return generator.bounded(songList.size());
}
// Sequential playback // Sequential playback
int nextIndex = currentIndex + 1; int nextIndex = currentIndex + 1;
if (nextIndex >= songList.size()) { if (nextIndex >= songList.size())
nextIndex = 0; // Loop back to beginning {
} nextIndex = 0; // Loop back to beginning
}
return nextIndex; return nextIndex;
} }
int SongListModel::findSongIndexById(uint64_t uniqueId) const int SongListModel::findSongIndexById(uint64_t uniqueId) const
{ {
for (int i = 0; i < songList.size(); ++i) { for (int i = 0; i < songList.size(); ++i)
if (songList[i].uniqueId == uniqueId) { {
return i; if (songList[i].uniqueId == uniqueId)
} {
} return i;
return -1; // Song not found }
}
return -1; // Song not found
} }

View file

@ -11,47 +11,51 @@
class SongListModel : public QAbstractTableModel class SongListModel : public QAbstractTableModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum Roles { enum Roles
CaptionRole = Qt::UserRole + 1, {
LyricsRole = Qt::UserRole + 2, CaptionRole = Qt::UserRole + 1,
VocalLanguageRole = Qt::UserRole + 3, LyricsRole = Qt::UserRole + 2,
IsPlayingRole = Qt::UserRole + 4 VocalLanguageRole = Qt::UserRole + 3,
}; IsPlayingRole = Qt::UserRole + 4
};
explicit SongListModel(QObject *parent = nullptr); explicit SongListModel(QObject *parent = nullptr);
// Basic functionality: // Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// Editable: // Editable:
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override; Qt::ItemFlags flags(const QModelIndex& index) const override;
// Add/remove songs // Add/remove songs
void addSong(const SongItem &song); void addSong(const SongItem &song);
void removeSong(int index); void removeSong(int index);
SongItem getSong(int index) const; SongItem getSong(int index) const;
int findNextIndex(int currentIndex, bool shuffle = false) const; int findNextIndex(int currentIndex, bool shuffle = false) const;
void clear(); void clear();
// Playing indicator // Playing indicator
void setPlayingIndex(int index); void setPlayingIndex(int index);
int playingIndex() const { return m_playingIndex; } int playingIndex() const
{
return m_playingIndex;
}
// Find song by unique ID // Find song by unique ID
int findSongIndexById(uint64_t uniqueId) const; int findSongIndexById(uint64_t uniqueId) const;
int songCount(); int songCount();
bool empty(); bool empty();
private: private:
QList<SongItem> songList; QList<SongItem> songList;
int m_playingIndex; int m_playingIndex;
}; };
#endif // SONGLISTMODEL_H #endif // SONGLISTMODEL_H

View file

@ -2,59 +2,65 @@
#include <QStyle> #include <QStyle>
ClickableSlider::ClickableSlider(QWidget *parent) ClickableSlider::ClickableSlider(QWidget *parent)
: QSlider(Qt::Orientation::Horizontal, parent) : QSlider(Qt::Orientation::Horizontal, parent)
{ {
} }
ClickableSlider::ClickableSlider(Qt::Orientation orientation, QWidget *parent) ClickableSlider::ClickableSlider(Qt::Orientation orientation, QWidget *parent)
: QSlider(orientation, parent) : QSlider(orientation, parent)
{ {
} }
void ClickableSlider::mousePressEvent(QMouseEvent *event) void ClickableSlider::mousePressEvent(QMouseEvent *event)
{ {
if (event->button() == Qt::LeftButton) { if (event->button() == Qt::LeftButton)
int val = pixelPosToRangeValue(event->pos()); {
int val = pixelPosToRangeValue(event->pos());
// Block signals temporarily to avoid infinite recursion // Block signals temporarily to avoid infinite recursion
blockSignals(true); blockSignals(true);
setValue(val); setValue(val);
blockSignals(false); blockSignals(false);
// Emit both valueChanged and sliderMoved signals for compatibility // Emit both valueChanged and sliderMoved signals for compatibility
emit valueChanged(val); emit valueChanged(val);
emit sliderMoved(val); emit sliderMoved(val);
} else { }
// Call base class implementation for other buttons else
QSlider::mousePressEvent(event); {
} // Call base class implementation for other buttons
QSlider::mousePressEvent(event);
}
} }
int ClickableSlider::pixelPosToRangeValue(const QPoint &pos) int ClickableSlider::pixelPosToRangeValue(const QPoint &pos)
{ {
QStyleOptionSlider opt; QStyleOptionSlider opt;
initStyleOption(&opt); initStyleOption(&opt);
QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this);
QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
int sliderLength; int sliderLength;
int sliderMin; int sliderMin;
int sliderMax; int sliderMax;
if (orientation() == Qt::Horizontal) { if (orientation() == Qt::Horizontal)
sliderLength = sr.width(); {
sliderMin = gr.x(); sliderLength = sr.width();
sliderMax = gr.right() - sliderLength + 1; sliderMin = gr.x();
} else { sliderMax = gr.right() - sliderLength + 1;
sliderLength = sr.height(); }
sliderMin = gr.y(); else
sliderMax = gr.bottom() - sliderLength + 1; {
} sliderLength = sr.height();
sliderMin = gr.y();
sliderMax = gr.bottom() - sliderLength + 1;
}
QPoint pr = pos - sr.center() + sr.topLeft(); QPoint pr = pos - sr.center() + sr.topLeft();
int p = orientation() == Qt::Horizontal ? pr.x() : pr.y(); int p = orientation() == Qt::Horizontal ? pr.x() : pr.y();
return QStyle::sliderValueFromPosition(minimum(), maximum(), p - sliderMin, return QStyle::sliderValueFromPosition(minimum(), maximum(), p - sliderMin,
sliderMax - sliderMin, opt.upsideDown); sliderMax - sliderMin, opt.upsideDown);
} }

View file

@ -7,16 +7,16 @@
class ClickableSlider : public QSlider class ClickableSlider : public QSlider
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ClickableSlider(QWidget *parent = nullptr); explicit ClickableSlider(QWidget *parent = nullptr);
explicit ClickableSlider(Qt::Orientation orientation, QWidget *parent = nullptr); explicit ClickableSlider(Qt::Orientation orientation, QWidget *parent = nullptr);
protected: protected:
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
private: private:
int pixelPosToRangeValue(const QPoint &pos); int pixelPosToRangeValue(const QPoint &pos);
}; };
#endif // CLICKABLESLIDER_H #endif // CLICKABLESLIDER_H

View file

@ -5,10 +5,10 @@
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QApplication app(argc, argv); QApplication app(argc, argv);
MainWindow window; MainWindow window;
window.show(); window.show();
return app.exec(); return app.exec();
} }