From 58e834554216b92731308ed1f676d9be947aaae5 Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Fri, 6 Mar 2026 00:06:09 +0100 Subject: [PATCH] Reformat --- CMakeLists.txt | 10 + src/AceStepWorker.cpp | 415 +++++++------ src/AceStepWorker.h | 77 +-- src/AdvancedSettingsDialog.cpp | 79 +-- src/AdvancedSettingsDialog.h | 53 +- src/AdvancedSettingsDialog.ui | 404 ++++++------- src/AudioPlayer.cpp | 143 +++-- src/AudioPlayer.h | 50 +- src/MainWindow.cpp | 1043 +++++++++++++++++--------------- src/MainWindow.h | 129 ++-- src/MainWindow.ui | 716 +++++++++++----------- src/SongDialog.cpp | 99 +-- src/SongDialog.h | 26 +- src/SongDialog.ui | 216 +++---- src/SongItem.h | 26 +- src/SongListModel.cpp | 284 +++++---- src/SongListModel.h | 80 +-- src/clickableslider.cpp | 88 +-- src/clickableslider.h | 14 +- src/main.cpp | 12 +- 20 files changed, 2063 insertions(+), 1901 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c33265c..7a808f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 + +# 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) diff --git a/src/AceStepWorker.cpp b/src/AceStepWorker.cpp index 8f9038f..3da8847 100644 --- a/src/AceStepWorker.cpp +++ b/src/AceStepWorker.cpp @@ -11,44 +11,46 @@ #include AceStepWorker::AceStepWorker(QObject *parent) - : QObject(parent), - currentWorker(nullptr) + : QObject(parent), + currentWorker(nullptr) { } AceStepWorker::~AceStepWorker() { - cancelGeneration(); + cancelGeneration(); } void AceStepWorker::generateSong(const SongItem& song, const QString &jsonTemplate, - const QString &aceStepPath, const QString &qwen3ModelPath, - const QString &textEncoderModelPath, const QString &ditModelPath, - const QString &vaeModelPath) + const QString &aceStepPath, const QString &qwen3ModelPath, + const QString &textEncoderModelPath, const QString &ditModelPath, + const QString &vaeModelPath) { - // Cancel any ongoing generation - cancelGeneration(); - - // Create worker and start it - currentWorker = new Worker(this, song, jsonTemplate, aceStepPath, qwen3ModelPath, - textEncoderModelPath, ditModelPath, vaeModelPath); + // Cancel any ongoing generation + cancelGeneration(); + + // Create worker and start it + currentWorker = new Worker(this, song, jsonTemplate, aceStepPath, qwen3ModelPath, + textEncoderModelPath, ditModelPath, vaeModelPath); currentWorker->setAutoDelete(true); - QThreadPool::globalInstance()->start(currentWorker); + QThreadPool::globalInstance()->start(currentWorker); } void AceStepWorker::cancelGeneration() { - currentWorker = nullptr; + currentWorker = nullptr; } bool AceStepWorker::songGenerateing(SongItem* song) { workerMutex.lock(); - if(!currentWorker) { + if(!currentWorker) + { workerMutex.unlock(); return false; } - else { + else + { SongItem workerSong = currentWorker->getSong(); workerMutex.unlock(); if(song) @@ -60,186 +62,209 @@ bool AceStepWorker::songGenerateing(SongItem* song) // Worker implementation void AceStepWorker::Worker::run() { - uint64_t uid = QRandomGenerator::global()->generate(); - // Create temporary JSON file for the request - QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); - QString requestFile = tempDir + "/request_" + QString::number(uid) + ".json"; - - // Parse and modify the template - QJsonParseError parseError; - QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError); - if (!templateDoc.isObject()) { - emit parent->generationError("Invalid JSON template: " + QString(parseError.errorString())); - return; - } - - QJsonObject requestObj = templateDoc.object(); - requestObj["caption"] = song.caption; - - if (!song.lyrics.isEmpty()) { - requestObj["lyrics"] = song.lyrics; - } else { - // Remove lyrics field if empty to let the LLM generate them - requestObj.remove("lyrics"); - } - - // Apply vocal language override if set - if (!song.vocalLanguage.isEmpty()) { - requestObj["vocal_language"] = song.vocalLanguage; - } - - // Write the request file - QFile requestFileHandle(requestFile); - if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text)) { - emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString()); - return; - } - - requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented)); - requestFileHandle.close(); - - // Use provided paths for acestep.cpp binaries - QString qwen3Binary = this->aceStepPath + "/ace-qwen3"; - QString ditVaeBinary = this->aceStepPath + "/dit-vae"; - - // Check if binaries exist - QFileInfo qwen3Info(qwen3Binary); - QFileInfo ditVaeInfo(ditVaeBinary); - - if (!qwen3Info.exists() || !qwen3Info.isExecutable()) { - emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary); - return; - } - - if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable()) { - emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary); - return; - } - - // Use provided model paths - QString qwen3Model = this->qwen3ModelPath; - QString textEncoderModel = this->textEncoderModelPath; - QString ditModel = this->ditModelPath; - QString vaeModel = this->vaeModelPath; - - if (!QFileInfo::exists(qwen3Model)) { - emit parent->generationError("Qwen3 model not found: " + qwen3Model); - return; - } - - if (!QFileInfo::exists(textEncoderModel)) { - emit parent->generationError("Text encoder model not found: " + textEncoderModel); - return; - } - - if (!QFileInfo::exists(ditModel)) { - emit parent->generationError("DiT model not found: " + ditModel); - return; - } - - if (!QFileInfo::exists(vaeModel)) { - emit parent->generationError("VAE model not found: " + vaeModel); - return; - } - - // Step 1: Run ace-qwen3 to generate lyrics and audio codes - QProcess qwen3Process; - QStringList qwen3Args; - qwen3Args << "--request" << requestFile; - qwen3Args << "--model" << qwen3Model; - - emit parent->progressUpdate(20); - - qwen3Process.start(qwen3Binary, qwen3Args); - if (!qwen3Process.waitForStarted()) { - emit parent->generationError("Failed to start ace-qwen3: " + qwen3Process.errorString()); - return; - } - - if (!qwen3Process.waitForFinished(60000)) { // 60 second timeout - qwen3Process.terminate(); - qwen3Process.waitForFinished(5000); - emit parent->generationError("ace-qwen3 timed out after 60 seconds"); - return; - } - - int exitCode = qwen3Process.exitCode(); - if (exitCode != 0) { - QString errorOutput = qwen3Process.readAllStandardError(); - emit parent->generationError("ace-qwen3 exited with code " + QString::number(exitCode) + ": " + errorOutput); - return; - } + uint64_t uid = QRandomGenerator::global()->generate(); + // Create temporary JSON file for the request + QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + QString requestFile = tempDir + "/request_" + QString::number(uid) + ".json"; - QString requestLmOutputFile = tempDir + "/request_" + QString::number(uid) + "0.json"; - if (!QFileInfo::exists(requestLmOutputFile)) { - emit parent->generationError("ace-qwen3 failed to create enhaced request file "+requestLmOutputFile); - return; - } - - // Load lyrics from the enhanced request file - QFile lmOutputFile(requestLmOutputFile); - if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - QJsonParseError parseError; - song.json = lmOutputFile.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(song.json.toUtf8(), &parseError); - lmOutputFile.close(); - - if (doc.isObject() && !parseError.error) { - QJsonObject obj = doc.object(); - if (obj.contains("lyrics") && obj["lyrics"].isString()) { - song.lyrics = obj["lyrics"].toString(); - } - } - } - - emit parent->progressUpdate(50); - - // Step 2: Run dit-vae to generate audio - QProcess ditVaeProcess; - QStringList ditVaeArgs; - ditVaeArgs << "--request" << requestLmOutputFile; - ditVaeArgs << "--text-encoder" << textEncoderModel; - ditVaeArgs << "--dit" << ditModel; - ditVaeArgs << "--vae" << vaeModel; - - emit parent->progressUpdate(60); - - ditVaeProcess.start(ditVaeBinary, ditVaeArgs); - if (!ditVaeProcess.waitForStarted()) { - emit parent->generationError("Failed to start dit-vae: " + ditVaeProcess.errorString()); - return; - } - - if (!ditVaeProcess.waitForFinished(120000)) { // 2 minute timeout - ditVaeProcess.terminate(); - ditVaeProcess.waitForFinished(5000); - emit parent->generationError("dit-vae timed out after 2 minutes"); - return; - } - - exitCode = ditVaeProcess.exitCode(); - if (exitCode != 0) { - QString errorOutput = ditVaeProcess.readAllStandardError(); - emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput); - return; - } - - emit parent->progressUpdate(90); - - // Find the generated WAV file - QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav"; - if (!QFileInfo::exists(wavFile)) { - emit parent->generationError("No WAV file generated at "+wavFile); - return; - } - - // Clean up temporary files - QFile::remove(requestLmOutputFile); - QFile::remove(requestFile); - - emit parent->progressUpdate(100); - song.file = wavFile; - emit parent->songGenerated(song); + // Parse and modify the template + QJsonParseError parseError; + QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError); + if (!templateDoc.isObject()) + { + emit parent->generationError("Invalid JSON template: " + QString(parseError.errorString())); + return; + } + + QJsonObject requestObj = templateDoc.object(); + requestObj["caption"] = song.caption; + + if (!song.lyrics.isEmpty()) + { + requestObj["lyrics"] = song.lyrics; + } + else + { + // Remove lyrics field if empty to let the LLM generate them + requestObj.remove("lyrics"); + } + + // Apply vocal language override if set + if (!song.vocalLanguage.isEmpty()) + { + requestObj["vocal_language"] = song.vocalLanguage; + } + + // Write the request file + QFile requestFileHandle(requestFile); + if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text)) + { + emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString()); + return; + } + + requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented)); + requestFileHandle.close(); + + // Use provided paths for acestep.cpp binaries + QString qwen3Binary = this->aceStepPath + "/ace-qwen3"; + QString ditVaeBinary = this->aceStepPath + "/dit-vae"; + + // Check if binaries exist + QFileInfo qwen3Info(qwen3Binary); + QFileInfo ditVaeInfo(ditVaeBinary); + + if (!qwen3Info.exists() || !qwen3Info.isExecutable()) + { + emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary); + return; + } + + if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable()) + { + emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary); + return; + } + + // Use provided model paths + QString qwen3Model = this->qwen3ModelPath; + QString textEncoderModel = this->textEncoderModelPath; + QString ditModel = this->ditModelPath; + QString vaeModel = this->vaeModelPath; + + if (!QFileInfo::exists(qwen3Model)) + { + emit parent->generationError("Qwen3 model not found: " + qwen3Model); + return; + } + + if (!QFileInfo::exists(textEncoderModel)) + { + emit parent->generationError("Text encoder model not found: " + textEncoderModel); + return; + } + + if (!QFileInfo::exists(ditModel)) + { + emit parent->generationError("DiT model not found: " + ditModel); + return; + } + + if (!QFileInfo::exists(vaeModel)) + { + emit parent->generationError("VAE model not found: " + vaeModel); + return; + } + + // Step 1: Run ace-qwen3 to generate lyrics and audio codes + QProcess qwen3Process; + QStringList qwen3Args; + qwen3Args << "--request" << requestFile; + qwen3Args << "--model" << qwen3Model; + + emit parent->progressUpdate(20); + + qwen3Process.start(qwen3Binary, qwen3Args); + if (!qwen3Process.waitForStarted()) + { + emit parent->generationError("Failed to start ace-qwen3: " + qwen3Process.errorString()); + return; + } + + if (!qwen3Process.waitForFinished(60000)) // 60 second timeout + { + qwen3Process.terminate(); + qwen3Process.waitForFinished(5000); + emit parent->generationError("ace-qwen3 timed out after 60 seconds"); + return; + } + + int exitCode = qwen3Process.exitCode(); + if (exitCode != 0) + { + QString errorOutput = qwen3Process.readAllStandardError(); + emit parent->generationError("ace-qwen3 exited with code " + QString::number(exitCode) + ": " + errorOutput); + return; + } + + QString requestLmOutputFile = tempDir + "/request_" + QString::number(uid) + "0.json"; + if (!QFileInfo::exists(requestLmOutputFile)) + { + emit parent->generationError("ace-qwen3 failed to create enhaced request file "+requestLmOutputFile); + return; + } + + // Load lyrics from the enhanced request file + QFile lmOutputFile(requestLmOutputFile); + if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QJsonParseError parseError; + song.json = lmOutputFile.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(song.json.toUtf8(), &parseError); + lmOutputFile.close(); + + if (doc.isObject() && !parseError.error) + { + QJsonObject obj = doc.object(); + if (obj.contains("lyrics") && obj["lyrics"].isString()) + { + song.lyrics = obj["lyrics"].toString(); + } + } + } + + emit parent->progressUpdate(50); + + // Step 2: Run dit-vae to generate audio + QProcess ditVaeProcess; + QStringList ditVaeArgs; + ditVaeArgs << "--request" << requestLmOutputFile; + ditVaeArgs << "--text-encoder" << textEncoderModel; + ditVaeArgs << "--dit" << ditModel; + ditVaeArgs << "--vae" << vaeModel; + + emit parent->progressUpdate(60); + + ditVaeProcess.start(ditVaeBinary, ditVaeArgs); + if (!ditVaeProcess.waitForStarted()) + { + emit parent->generationError("Failed to start dit-vae: " + ditVaeProcess.errorString()); + return; + } + + if (!ditVaeProcess.waitForFinished(120000)) // 2 minute timeout + { + ditVaeProcess.terminate(); + ditVaeProcess.waitForFinished(5000); + emit parent->generationError("dit-vae timed out after 2 minutes"); + return; + } + + exitCode = ditVaeProcess.exitCode(); + if (exitCode != 0) + { + QString errorOutput = ditVaeProcess.readAllStandardError(); + emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput); + return; + } + + emit parent->progressUpdate(90); + + // Find the generated WAV file + QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav"; + if (!QFileInfo::exists(wavFile)) + { + emit parent->generationError("No WAV file generated at "+wavFile); + return; + } + + // Clean up temporary files + QFile::remove(requestLmOutputFile); + QFile::remove(requestFile); + + emit parent->progressUpdate(100); + song.file = wavFile; + emit parent->songGenerated(song); parent->workerMutex.lock(); parent->currentWorker = nullptr; parent->workerMutex.unlock(); diff --git a/src/AceStepWorker.h b/src/AceStepWorker.h index b407868..fa7bbb8 100644 --- a/src/AceStepWorker.h +++ b/src/AceStepWorker.h @@ -12,53 +12,54 @@ class AceStepWorker : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit AceStepWorker(QObject *parent = nullptr); - ~AceStepWorker(); - - void generateSong(const SongItem& song, const QString &jsonTemplate, - const QString &aceStepPath, const QString &qwen3ModelPath, - const QString &textEncoderModelPath, const QString &ditModelPath, - const QString &vaeModelPath); - void cancelGeneration(); + explicit AceStepWorker(QObject *parent = nullptr); + ~AceStepWorker(); + + void generateSong(const SongItem& song, const QString &jsonTemplate, + const QString &aceStepPath, const QString &qwen3ModelPath, + const QString &textEncoderModelPath, const QString &ditModelPath, + const QString &vaeModelPath); + void cancelGeneration(); bool songGenerateing(SongItem* song); - + signals: - void songGenerated(const SongItem& song); - void generationFinished(); - void generationError(const QString &error); + void songGenerated(const SongItem& song); + void generationFinished(); + void generationError(const QString &error); void progressUpdate(int percent); - + private: - class Worker : public QRunnable { - public: - Worker(AceStepWorker *parent, const SongItem& song, const QString &jsonTemplate, - const QString &aceStepPath, const QString &qwen3ModelPath, - const QString &textEncoderModelPath, const QString &ditModelPath, - const QString &vaeModelPath) - : parent(parent), song(song), jsonTemplate(jsonTemplate), - aceStepPath(aceStepPath), qwen3ModelPath(qwen3ModelPath), - textEncoderModelPath(textEncoderModelPath), ditModelPath(ditModelPath), - vaeModelPath(vaeModelPath) {} - - void run() override; + class Worker : public QRunnable + { + public: + Worker(AceStepWorker *parent, const SongItem& song, const QString &jsonTemplate, + const QString &aceStepPath, const QString &qwen3ModelPath, + const QString &textEncoderModelPath, const QString &ditModelPath, + const QString &vaeModelPath) + : parent(parent), song(song), jsonTemplate(jsonTemplate), + aceStepPath(aceStepPath), qwen3ModelPath(qwen3ModelPath), + textEncoderModelPath(textEncoderModelPath), ditModelPath(ditModelPath), + vaeModelPath(vaeModelPath) {} + + void run() override; const SongItem& getSong(); - - private: - AceStepWorker *parent; - SongItem song; - QString jsonTemplate; - QString aceStepPath; - QString qwen3ModelPath; - QString textEncoderModelPath; - QString ditModelPath; - QString vaeModelPath; - }; + + private: + AceStepWorker *parent; + SongItem song; + QString jsonTemplate; + QString aceStepPath; + QString qwen3ModelPath; + QString textEncoderModelPath; + QString ditModelPath; + QString vaeModelPath; + }; QMutex workerMutex; - Worker *currentWorker; + Worker *currentWorker; }; #endif // ACESTEPWORKER_H diff --git a/src/AdvancedSettingsDialog.cpp b/src/AdvancedSettingsDialog.cpp index ceb1cdb..163ba8a 100644 --- a/src/AdvancedSettingsDialog.cpp +++ b/src/AdvancedSettingsDialog.cpp @@ -6,113 +6,120 @@ #include AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent) - : QDialog(parent), - ui(new Ui::AdvancedSettingsDialog) + : QDialog(parent), + ui(new Ui::AdvancedSettingsDialog) { - ui->setupUi(this); + ui->setupUi(this); } AdvancedSettingsDialog::~AdvancedSettingsDialog() { - delete ui; + delete ui; } QString AdvancedSettingsDialog::getJsonTemplate() const { - return ui->jsonTemplateEdit->toPlainText(); + return ui->jsonTemplateEdit->toPlainText(); } QString AdvancedSettingsDialog::getAceStepPath() const { - return ui->aceStepPathEdit->text(); + return ui->aceStepPathEdit->text(); } QString AdvancedSettingsDialog::getQwen3ModelPath() const { - return ui->qwen3ModelEdit->text(); + return ui->qwen3ModelEdit->text(); } QString AdvancedSettingsDialog::getTextEncoderModelPath() const { - return ui->textEncoderEdit->text(); + return ui->textEncoderEdit->text(); } QString AdvancedSettingsDialog::getDiTModelPath() const { - return ui->ditModelEdit->text(); + return ui->ditModelEdit->text(); } QString AdvancedSettingsDialog::getVAEModelPath() const { - return ui->vaeModelEdit->text(); + return ui->vaeModelEdit->text(); } void AdvancedSettingsDialog::setJsonTemplate(const QString &templateStr) { - ui->jsonTemplateEdit->setPlainText(templateStr); + ui->jsonTemplateEdit->setPlainText(templateStr); } void AdvancedSettingsDialog::setAceStepPath(const QString &path) { - ui->aceStepPathEdit->setText(path); + ui->aceStepPathEdit->setText(path); } void AdvancedSettingsDialog::setQwen3ModelPath(const QString &path) { - ui->qwen3ModelEdit->setText(path); + ui->qwen3ModelEdit->setText(path); } void AdvancedSettingsDialog::setTextEncoderModelPath(const QString &path) { - ui->textEncoderEdit->setText(path); + ui->textEncoderEdit->setText(path); } void AdvancedSettingsDialog::setDiTModelPath(const QString &path) { - ui->ditModelEdit->setText(path); + ui->ditModelEdit->setText(path); } void AdvancedSettingsDialog::setVAEModelPath(const QString &path) { - ui->vaeModelEdit->setText(path); + ui->vaeModelEdit->setText(path); } void AdvancedSettingsDialog::on_aceStepBrowseButton_clicked() { - QString dir = QFileDialog::getExistingDirectory(this, "Select AceStep Build Directory", ui->aceStepPathEdit->text()); - if (!dir.isEmpty()) { - ui->aceStepPathEdit->setText(dir); - } + QString dir = QFileDialog::getExistingDirectory(this, "Select AceStep Build Directory", ui->aceStepPathEdit->text()); + if (!dir.isEmpty()) + { + ui->aceStepPathEdit->setText(dir); + } } void AdvancedSettingsDialog::on_qwen3BrowseButton_clicked() { - QString file = QFileDialog::getOpenFileName(this, "Select Qwen3 Model", ui->qwen3ModelEdit->text(), "GGUF Files (*.gguf)"); - if (!file.isEmpty()) { - ui->qwen3ModelEdit->setText(file); - } + QString file = QFileDialog::getOpenFileName(this, "Select Qwen3 Model", ui->qwen3ModelEdit->text(), + "GGUF Files (*.gguf)"); + if (!file.isEmpty()) + { + ui->qwen3ModelEdit->setText(file); + } } void AdvancedSettingsDialog::on_textEncoderBrowseButton_clicked() { - QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(), "GGUF Files (*.gguf)"); - if (!file.isEmpty()) { - ui->textEncoderEdit->setText(file); - } + QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(), + "GGUF Files (*.gguf)"); + if (!file.isEmpty()) + { + ui->textEncoderEdit->setText(file); + } } void AdvancedSettingsDialog::on_ditBrowseButton_clicked() { - QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)"); - if (!file.isEmpty()) { - ui->ditModelEdit->setText(file); - } + QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)"); + if (!file.isEmpty()) + { + ui->ditModelEdit->setText(file); + } } void AdvancedSettingsDialog::on_vaeBrowseButton_clicked() { - QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)"); - if (!file.isEmpty()) { - ui->vaeModelEdit->setText(file); - } + QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)"); + if (!file.isEmpty()) + { + ui->vaeModelEdit->setText(file); + } } \ No newline at end of file diff --git a/src/AdvancedSettingsDialog.h b/src/AdvancedSettingsDialog.h index be7b873..fb4aa69 100644 --- a/src/AdvancedSettingsDialog.h +++ b/src/AdvancedSettingsDialog.h @@ -4,43 +4,44 @@ #include #include -namespace Ui { +namespace Ui +{ class AdvancedSettingsDialog; } class AdvancedSettingsDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit AdvancedSettingsDialog(QWidget *parent = nullptr); - ~AdvancedSettingsDialog(); - - // Getters for settings - QString getJsonTemplate() const; - QString getAceStepPath() const; - QString getQwen3ModelPath() const; - QString getTextEncoderModelPath() const; - QString getDiTModelPath() const; - QString getVAEModelPath() const; - - // Setters for settings - void setJsonTemplate(const QString &templateStr); - void setAceStepPath(const QString &path); - void setQwen3ModelPath(const QString &path); - void setTextEncoderModelPath(const QString &path); - void setDiTModelPath(const QString &path); - void setVAEModelPath(const QString &path); + explicit AdvancedSettingsDialog(QWidget *parent = nullptr); + ~AdvancedSettingsDialog(); + + // Getters for settings + QString getJsonTemplate() const; + QString getAceStepPath() const; + QString getQwen3ModelPath() const; + QString getTextEncoderModelPath() const; + QString getDiTModelPath() const; + QString getVAEModelPath() const; + + // Setters for settings + void setJsonTemplate(const QString &templateStr); + void setAceStepPath(const QString &path); + void setQwen3ModelPath(const QString &path); + void setTextEncoderModelPath(const QString &path); + void setDiTModelPath(const QString &path); + void setVAEModelPath(const QString &path); private slots: - void on_aceStepBrowseButton_clicked(); - void on_qwen3BrowseButton_clicked(); - void on_textEncoderBrowseButton_clicked(); - void on_ditBrowseButton_clicked(); - void on_vaeBrowseButton_clicked(); + void on_aceStepBrowseButton_clicked(); + void on_qwen3BrowseButton_clicked(); + void on_textEncoderBrowseButton_clicked(); + void on_ditBrowseButton_clicked(); + void on_vaeBrowseButton_clicked(); private: - Ui::AdvancedSettingsDialog *ui; + Ui::AdvancedSettingsDialog *ui; }; #endif // ADVANCEDSETTINGSDIALOG_H \ No newline at end of file diff --git a/src/AdvancedSettingsDialog.ui b/src/AdvancedSettingsDialog.ui index 41d49a6..e9fadf1 100644 --- a/src/AdvancedSettingsDialog.ui +++ b/src/AdvancedSettingsDialog.ui @@ -1,203 +1,203 @@ - - AdvancedSettingsDialog - - - - 0 - 0 - 600 - 450 - - - - Advanced Settings - - - - - - 0 - - - - JSON Template - - - - - - JSON Template for AceStep generation: - - - true - - - - - - - - - - - Model Paths - - - - QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow - - - - - AceStep Path: - - - - - - - - - - - - Browse... - - - - - - - - - Qwen3 Model: - - - - - - - - - - - - Browse... - - - - - - - - - Text Encoder Model: - - - - - - - - - - - - Browse... - - - - - - - - - DiT Model: - - - - - - - - - - - - Browse... - - - - - - - - - VAE Model: - - - - - - - - - - - - Browse... - - - - - - - - - - - - - QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save - - - - - - - - - buttonBox - accepted() - AdvancedSettingsDialog - accept() - - - 248 - 254 - - - 157 - 254 - - - - - buttonBox - rejected() - AdvancedSettingsDialog - reject() - - - 316 - 260 - - - 286 - 260 - - - - - + + AdvancedSettingsDialog + + + + 0 + 0 + 600 + 450 + + + + Advanced Settings + + + + + + 0 + + + + JSON Template + + + + + + JSON Template for AceStep generation: + + + true + + + + + + + + + + + Model Paths + + + + QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow + + + + + AceStep Path: + + + + + + + + + + + + Browse... + + + + + + + + + Qwen3 Model: + + + + + + + + + + + + Browse... + + + + + + + + + Text Encoder Model: + + + + + + + + + + + + Browse... + + + + + + + + + DiT Model: + + + + + + + + + + + + Browse... + + + + + + + + + VAE Model: + + + + + + + + + + + + Browse... + + + + + + + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save + + + + + + + + + buttonBox + accepted() + AdvancedSettingsDialog + accept() + + + 248 + 254 + + + 157 + 254 + + + + + buttonBox + rejected() + AdvancedSettingsDialog + reject() + + + 316 + 260 + + + 286 + 260 + + + + + diff --git a/src/AudioPlayer.cpp b/src/AudioPlayer.cpp index a9b77ca..76694d4 100644 --- a/src/AudioPlayer.cpp +++ b/src/AudioPlayer.cpp @@ -2,113 +2,128 @@ #include AudioPlayer::AudioPlayer(QObject *parent) - : QObject(parent), - mediaPlayer(new QMediaPlayer(this)), - audioOutput(new QAudioOutput(this)), - positionTimer(new QTimer(this)) + : QObject(parent), + mediaPlayer(new QMediaPlayer(this)), + audioOutput(new QAudioOutput(this)), + positionTimer(new QTimer(this)) { - // Set up audio output with default device - mediaPlayer->setAudioOutput(audioOutput); - - connect(mediaPlayer, &QMediaPlayer::playbackStateChanged, - this, &AudioPlayer::handlePlaybackStateChanged); - connect(mediaPlayer, &QMediaPlayer::mediaStatusChanged, - this, &AudioPlayer::handleMediaStatusChanged); - - // Set up position timer for updating playback position - positionTimer->setInterval(500); // Update every 500ms - connect(positionTimer, &QTimer::timeout, [this]() { - if (isPlaying()) { - emit positionChanged(mediaPlayer->position()); - } - }); + // Set up audio output with default device + mediaPlayer->setAudioOutput(audioOutput); + + connect(mediaPlayer, &QMediaPlayer::playbackStateChanged, + this, &AudioPlayer::handlePlaybackStateChanged); + connect(mediaPlayer, &QMediaPlayer::mediaStatusChanged, + this, &AudioPlayer::handleMediaStatusChanged); + + // Set up position timer for updating playback position + positionTimer->setInterval(500); // Update every 500ms + connect(positionTimer, &QTimer::timeout, [this]() + { + if (isPlaying()) + { + emit positionChanged(mediaPlayer->position()); + } + }); } AudioPlayer::~AudioPlayer() { - stop(); + stop(); } void AudioPlayer::play(const QString &filePath) { - if (isPlaying()) { - stop(); - } - - mediaPlayer->setSource(QUrl::fromLocalFile(filePath)); - mediaPlayer->play(); - - // Start position timer - positionTimer->start(); + if (isPlaying()) + { + stop(); + } + + mediaPlayer->setSource(QUrl::fromLocalFile(filePath)); + mediaPlayer->play(); + + // Start position timer + positionTimer->start(); } void AudioPlayer::play() { - if (!isPlaying()) { - mediaPlayer->play(); - positionTimer->start(); - } + if (!isPlaying()) + { + mediaPlayer->play(); + positionTimer->start(); + } } void AudioPlayer::pause() { - if (isPlaying()) { - mediaPlayer->pause(); - positionTimer->stop(); - } + if (isPlaying()) + { + mediaPlayer->pause(); + positionTimer->stop(); + } } void AudioPlayer::setPosition(int position) { - mediaPlayer->setPosition(position); + mediaPlayer->setPosition(position); } void AudioPlayer::stop() { - mediaPlayer->stop(); - positionTimer->stop(); + mediaPlayer->stop(); + positionTimer->stop(); } bool AudioPlayer::isPlaying() const { - return mediaPlayer->playbackState() == QMediaPlayer::PlayingState; + return mediaPlayer->playbackState() == QMediaPlayer::PlayingState; } int AudioPlayer::duration() const { - return mediaPlayer->duration(); + return mediaPlayer->duration(); } int AudioPlayer::position() const { - return mediaPlayer->position(); + return mediaPlayer->position(); } void AudioPlayer::handlePlaybackStateChanged(QMediaPlayer::PlaybackState state) { - if (state == QMediaPlayer::PlayingState) { - emit playbackStarted(); - } else if (state == QMediaPlayer::StoppedState || - state == QMediaPlayer::PausedState) { - // Check if we reached the end - if (mediaPlayer->position() >= mediaPlayer->duration() - 100) { - emit playbackFinished(); - } - } + if (state == QMediaPlayer::PlayingState) + { + emit playbackStarted(); + } + else if (state == QMediaPlayer::StoppedState || + state == QMediaPlayer::PausedState) + { + // Check if we reached the end + if (mediaPlayer->position() >= mediaPlayer->duration() - 100) + { + emit playbackFinished(); + } + } } void AudioPlayer::handleMediaStatusChanged(QMediaPlayer::MediaStatus status) { - if (status == QMediaPlayer::EndOfMedia) { - emit playbackFinished(); - } else if (status == QMediaPlayer::LoadedMedia || - status == QMediaPlayer::BufferedMedia) { - // Media loaded successfully, emit duration - int duration = mediaPlayer->duration(); - if (duration > 0) { - emit durationChanged(duration); - } - } else if (status == QMediaPlayer::InvalidMedia) { - emit playbackError(mediaPlayer->errorString()); - } + if (status == QMediaPlayer::EndOfMedia) + { + emit playbackFinished(); + } + else if (status == QMediaPlayer::LoadedMedia || + status == QMediaPlayer::BufferedMedia) + { + // Media loaded successfully, emit duration + int duration = mediaPlayer->duration(); + if (duration > 0) + { + emit durationChanged(duration); + } + } + else if (status == QMediaPlayer::InvalidMedia) + { + emit playbackError(mediaPlayer->errorString()); + } } diff --git a/src/AudioPlayer.h b/src/AudioPlayer.h index 496670b..93f14b2 100644 --- a/src/AudioPlayer.h +++ b/src/AudioPlayer.h @@ -12,35 +12,35 @@ class AudioPlayer : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit AudioPlayer(QObject *parent = nullptr); - ~AudioPlayer(); - - void play(const QString &filePath); - void play(); - void stop(); - void pause(); - void setPosition(int position); - bool isPlaying() const; - int duration() const; - int position() const; - + explicit AudioPlayer(QObject *parent = nullptr); + ~AudioPlayer(); + + void play(const QString &filePath); + void play(); + void stop(); + void pause(); + void setPosition(int position); + bool isPlaying() const; + int duration() const; + int position() const; + signals: - void playbackStarted(); - void playbackFinished(); - void playbackError(const QString &error); - void positionChanged(int position); - void durationChanged(int duration); - + void playbackStarted(); + void playbackFinished(); + void playbackError(const QString &error); + void positionChanged(int position); + void durationChanged(int duration); + private slots: - void handlePlaybackStateChanged(QMediaPlayer::PlaybackState state); - void handleMediaStatusChanged(QMediaPlayer::MediaStatus status); - + void handlePlaybackStateChanged(QMediaPlayer::PlaybackState state); + void handleMediaStatusChanged(QMediaPlayer::MediaStatus status); + private: - QMediaPlayer *mediaPlayer; - QAudioOutput *audioOutput; - QTimer *positionTimer; + QMediaPlayer *mediaPlayer; + QAudioOutput *audioOutput; + QTimer *positionTimer; }; #endif // AUDIOPLAYER_H diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 4175c52..0ce0b14 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -14,57 +14,65 @@ #include MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), - ui(new Ui::MainWindow), - songModel(new SongListModel(this)), - audioPlayer(new AudioPlayer(this)), - aceStepWorker(new AceStepWorker(this)), - playbackTimer(new QTimer(this)), - isPlaying(false), - isPaused(false), - shuffleMode(false), - isGeneratingNext(false) + : QMainWindow(parent), + ui(new Ui::MainWindow), + songModel(new SongListModel(this)), + audioPlayer(new AudioPlayer(this)), + aceStepWorker(new AceStepWorker(this)), + playbackTimer(new QTimer(this)), + isPlaying(false), + isPaused(false), + shuffleMode(false), + isGeneratingNext(false) { - ui->setupUi(this); - - // Setup lyrics display - ui->lyricsTextEdit->setReadOnly(true); - - // Setup UI - setupUI(); - - // Load settings - loadSettings(); - - // Auto-load playlist from config location on startup - autoLoadPlaylist(); + ui->setupUi(this); - // Connect signals and slots - connect(ui->actionAdvancedSettings, &QAction::triggered, this, &MainWindow::on_advancedSettingsButton_clicked); - connect(ui->actionSavePlaylist, &QAction::triggered, this, &MainWindow::on_actionSavePlaylist); - connect(ui->actionLoadPlaylist, &QAction::triggered, this, &MainWindow::on_actionLoadPlaylist); - connect(ui->actionAppendPlaylist, &QAction::triggered, this, &MainWindow::on_actionAppendPlaylist); - connect(ui->actionSaveSong, &QAction::triggered, this, &MainWindow::on_actionSaveSong); - connect(ui->actionQuit, &QAction::triggered, this, [this](){close();}); - connect(ui->actionClearPlaylist, &QAction::triggered, this, [this](){songModel->clear();}); - connect(audioPlayer, &AudioPlayer::playbackFinished, this, &MainWindow::playNextSong); - connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted); + // Setup lyrics display + ui->lyricsTextEdit->setReadOnly(true); + + // Setup UI + setupUI(); + + // Load settings + loadSettings(); + + // Auto-load playlist from config location on startup + autoLoadPlaylist(); + + // Connect signals and slots + connect(ui->actionAdvancedSettings, &QAction::triggered, this, &MainWindow::on_advancedSettingsButton_clicked); + connect(ui->actionSavePlaylist, &QAction::triggered, this, &MainWindow::on_actionSavePlaylist); + connect(ui->actionLoadPlaylist, &QAction::triggered, this, &MainWindow::on_actionLoadPlaylist); + connect(ui->actionAppendPlaylist, &QAction::triggered, this, &MainWindow::on_actionAppendPlaylist); + connect(ui->actionSaveSong, &QAction::triggered, this, &MainWindow::on_actionSaveSong); + connect(ui->actionQuit, &QAction::triggered, this, [this]() + { + close(); + }); + connect(ui->actionClearPlaylist, &QAction::triggered, this, [this]() + { + songModel->clear(); + }); + connect(audioPlayer, &AudioPlayer::playbackFinished, this, &MainWindow::playNextSong); + connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted); connect(audioPlayer, &AudioPlayer::positionChanged, this, &MainWindow::updatePosition); connect(audioPlayer, &AudioPlayer::durationChanged, this, &MainWindow::updateDuration); - connect(aceStepWorker, &AceStepWorker::songGenerated, this, &MainWindow::songGenerated); - connect(aceStepWorker, &AceStepWorker::generationError, this, &MainWindow::generationError); - connect(aceStepWorker, &AceStepWorker::progressUpdate, ui->progressBar, &QProgressBar::setValue); - - // Connect double-click on song list for editing (works with QTableView too) - connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked); - - // Connect audio player error signal - connect(audioPlayer, &AudioPlayer::playbackError, [this](const QString &error) { - QMessageBox::warning(this, "Playback Error", "Failed to play audio: " + error); - }); + connect(aceStepWorker, &AceStepWorker::songGenerated, this, &MainWindow::songGenerated); + connect(aceStepWorker, &AceStepWorker::generationError, this, &MainWindow::generationError); + connect(aceStepWorker, &AceStepWorker::progressUpdate, ui->progressBar, &QProgressBar::setValue); + + // Connect double-click on song list for editing (works with QTableView too) + connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked); + + // Connect audio player error signal + connect(audioPlayer, &AudioPlayer::playbackError, [this](const QString &error) + { + QMessageBox::warning(this, "Playback Error", "Failed to play audio: " + error); + }); // Add some default songs - if(songModel->songCount() == 0) { + if(songModel->songCount() == 0) + { SongItem defaultSong1("Upbeat pop rock anthem with driving electric guitars", ""); SongItem defaultSong2("Chill electronic music with smooth synths and relaxing beats", ""); SongItem defaultSong3("Jazz fusion with saxophone solos and complex rhythms", ""); @@ -75,7 +83,8 @@ MainWindow::MainWindow(QWidget *parent) } // Select first item - if (songModel->rowCount() > 0) { + if (songModel->rowCount() > 0) + { QModelIndex firstIndex = songModel->index(0, 0); ui->songListView->setCurrentIndex(firstIndex); } @@ -85,128 +94,132 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { - // Auto-save playlist before closing - autoSavePlaylist(); - - saveSettings(); - delete ui; + // Auto-save playlist before closing + autoSavePlaylist(); + + saveSettings(); + delete ui; } void MainWindow::setupUI() { - // Setup song list view - ui->songListView->setModel(songModel); - - // Make sure the table view is read-only (no inline editing) - ui->songListView->setEditTriggers(QAbstractItemView::NoEditTriggers); - - // Hide headers for cleaner appearance - ui->songListView->horizontalHeader()->hide(); - ui->songListView->verticalHeader()->hide(); - - // Configure column sizes - ui->songListView->setColumnWidth(0, 40); // Fixed width for play indicator column - ui->songListView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Expand caption column - - // Enable row selection and disable column selection - ui->songListView->setSelectionBehavior(QAbstractItemView::SelectRows); + // Setup song list view + ui->songListView->setModel(songModel); + + // Make sure the table view is read-only (no inline editing) + ui->songListView->setEditTriggers(QAbstractItemView::NoEditTriggers); + + // Hide headers for cleaner appearance + ui->songListView->horizontalHeader()->hide(); + ui->songListView->verticalHeader()->hide(); + + // Configure column sizes + ui->songListView->setColumnWidth(0, 40); // Fixed width for play indicator column + ui->songListView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Expand caption column + + // Enable row selection and disable column selection + ui->songListView->setSelectionBehavior(QAbstractItemView::SelectRows); } void MainWindow::loadSettings() { - QSettings settings("MusicGenerator", "AceStepGUI"); - - // Load JSON template (default to simple configuration) - jsonTemplate = settings.value("jsonTemplate", "{\n\t\"inference_steps\": 8,\n\t\"shift\": 3.0,\n\t\"vocal_language\": \"en\"\n}").toString(); - - // Load shuffle mode - shuffleMode = settings.value("shuffleMode", false).toBool(); - ui->shuffleButton->setChecked(shuffleMode); - - // Load path settings with defaults based on application directory - QString appDir = QCoreApplication::applicationDirPath(); - aceStepPath = settings.value("aceStepPath", appDir + "/acestep.cpp").toString(); - qwen3ModelPath = settings.value("qwen3ModelPath", appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString(); - textEncoderModelPath = settings.value("textEncoderModelPath", appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-BF16.gguf").toString(); - ditModelPath = settings.value("ditModelPath", appDir + "/acestep.cpp/models/acestep-v15-turbo-Q8_0.gguf").toString(); - vaeModelPath = settings.value("vaeModelPath", appDir + "/acestep.cpp/models/vae-BF16.gguf").toString(); + QSettings settings("MusicGenerator", "AceStepGUI"); + + // Load JSON template (default to simple configuration) + jsonTemplate = settings.value("jsonTemplate", + "{\n\t\"inference_steps\": 8,\n\t\"shift\": 3.0,\n\t\"vocal_language\": \"en\"\n}").toString(); + + // Load shuffle mode + shuffleMode = settings.value("shuffleMode", false).toBool(); + ui->shuffleButton->setChecked(shuffleMode); + + // Load path settings with defaults based on application directory + QString appDir = QCoreApplication::applicationDirPath(); + aceStepPath = settings.value("aceStepPath", appDir + "/acestep.cpp").toString(); + qwen3ModelPath = settings.value("qwen3ModelPath", + appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString(); + textEncoderModelPath = settings.value("textEncoderModelPath", + appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-BF16.gguf").toString(); + ditModelPath = settings.value("ditModelPath", appDir + "/acestep.cpp/models/acestep-v15-turbo-Q8_0.gguf").toString(); + vaeModelPath = settings.value("vaeModelPath", appDir + "/acestep.cpp/models/vae-BF16.gguf").toString(); } void MainWindow::saveSettings() { - QSettings settings("MusicGenerator", "AceStepGUI"); - - // Save JSON template - settings.setValue("jsonTemplate", jsonTemplate); - - // Save shuffle mode - settings.setValue("shuffleMode", shuffleMode); - - // Save path settings - settings.setValue("aceStepPath", aceStepPath); - settings.setValue("qwen3ModelPath", qwen3ModelPath); - settings.setValue("textEncoderModelPath", textEncoderModelPath); - settings.setValue("ditModelPath", ditModelPath); - settings.setValue("vaeModelPath", vaeModelPath); + QSettings settings("MusicGenerator", "AceStepGUI"); + + // Save JSON template + settings.setValue("jsonTemplate", jsonTemplate); + + // Save shuffle mode + settings.setValue("shuffleMode", shuffleMode); + + // Save path settings + settings.setValue("aceStepPath", aceStepPath); + settings.setValue("qwen3ModelPath", qwen3ModelPath); + settings.setValue("textEncoderModelPath", textEncoderModelPath); + settings.setValue("ditModelPath", ditModelPath); + settings.setValue("vaeModelPath", vaeModelPath); } QString MainWindow::formatTime(int milliseconds) { - if (milliseconds < 0) - return "0:00"; - - int seconds = milliseconds / 1000; - int minutes = seconds / 60; - seconds = seconds % 60; - - return QString("%1:%2").arg(minutes).arg(seconds, 2, 10, QChar('0')); + if (milliseconds < 0) + return "0:00"; + + int seconds = milliseconds / 1000; + int minutes = seconds / 60; + seconds = seconds % 60; + + return QString("%1:%2").arg(minutes).arg(seconds, 2, 10, QChar('0')); } void MainWindow::updatePosition(int position) { if (position < 0) return; - - // Update slider and time labels - ui->positionSlider->setValue(position); - ui->elapsedTimeLabel->setText(formatTime(position)); + + // Update slider and time labels + ui->positionSlider->setValue(position); + ui->elapsedTimeLabel->setText(formatTime(position)); } void MainWindow::updateDuration(int duration) { if (duration <= 0) return; - - // Set slider range and update duration label - ui->positionSlider->setRange(0, duration); - ui->durationLabel->setText(formatTime(duration)); + + // Set slider range and update duration label + ui->positionSlider->setRange(0, duration); + ui->durationLabel->setText(formatTime(duration)); } void MainWindow::updateControls() { - bool hasSongs = songModel->rowCount() > 0; - - // Play button is enabled when not playing, or can be used to resume when paused - ui->playButton->setEnabled(hasSongs && (!isPlaying || isPaused)); - ui->pauseButton->setEnabled(isPlaying && !isPaused); - ui->skipButton->setEnabled(isPlaying); - ui->stopButton->setEnabled(isPlaying); - ui->addSongButton->setEnabled(true); - ui->removeSongButton->setEnabled(hasSongs && ui->songListView->currentIndex().isValid()); + bool hasSongs = songModel->rowCount() > 0; + + // Play button is enabled when not playing, or can be used to resume when paused + ui->playButton->setEnabled(hasSongs && (!isPlaying || isPaused)); + ui->pauseButton->setEnabled(isPlaying && !isPaused); + ui->skipButton->setEnabled(isPlaying); + ui->stopButton->setEnabled(isPlaying); + ui->addSongButton->setEnabled(true); + ui->removeSongButton->setEnabled(hasSongs && ui->songListView->currentIndex().isValid()); } void MainWindow::on_playButton_clicked() { - if (isPaused) { - // Resume playback - audioPlayer->play(); - isPaused = false; - updateControls(); - return; + if (isPaused) + { + // Resume playback + audioPlayer->play(); + isPaused = false; + updateControls(); + return; } if(songModel->empty()) - return; + return; isPlaying = true; ui->nowPlayingLabel->setText("Now Playing: Waiting for generation..."); @@ -217,162 +230,178 @@ void MainWindow::on_playButton_clicked() void MainWindow::on_pauseButton_clicked() { - if (isPlaying && !isPaused) { - // Pause playback - audioPlayer->pause(); - isPaused = true; - updateControls(); - } + if (isPlaying && !isPaused) + { + // Pause playback + audioPlayer->pause(); + isPaused = true; + updateControls(); + } } void MainWindow::on_skipButton_clicked() { - if (isPlaying) { - audioPlayer->stop(); - isPaused = false; + if (isPlaying) + { + audioPlayer->stop(); + isPaused = false; playNextSong(); - } + } } void MainWindow::on_stopButton_clicked() { - if (isPlaying) { - // Stop current playback completely - audioPlayer->stop(); + if (isPlaying) + { + // Stop current playback completely + audioPlayer->stop(); ui->nowPlayingLabel->setText("Now Playing:"); - isPlaying = false; - isPaused = false; - updateControls(); + isPlaying = false; + isPaused = false; + updateControls(); flushGenerationQueue(); - } + } } void MainWindow::on_shuffleButton_clicked() { - shuffleMode = ui->shuffleButton->isChecked(); - updateControls(); + shuffleMode = ui->shuffleButton->isChecked(); + updateControls(); } void MainWindow::on_addSongButton_clicked() { - SongDialog dialog(this); - - if (dialog.exec() == QDialog::Accepted) { - QString caption = dialog.getCaption(); - QString lyrics = dialog.getLyrics(); - QString vocalLanguage = dialog.getVocalLanguage(); - - SongItem newSong(caption, lyrics); - newSong.vocalLanguage = vocalLanguage; - songModel->addSong(newSong); - - // Select the new item - QModelIndex newIndex = songModel->index(songModel->rowCount() - 1, 0); - ui->songListView->setCurrentIndex(newIndex); - } + SongDialog dialog(this); + + if (dialog.exec() == QDialog::Accepted) + { + QString caption = dialog.getCaption(); + QString lyrics = dialog.getLyrics(); + QString vocalLanguage = dialog.getVocalLanguage(); + + SongItem newSong(caption, lyrics); + newSong.vocalLanguage = vocalLanguage; + songModel->addSong(newSong); + + // Select the new item + QModelIndex newIndex = songModel->index(songModel->rowCount() - 1, 0); + ui->songListView->setCurrentIndex(newIndex); + } } void MainWindow::on_songListView_doubleClicked(const QModelIndex &index) { - if (!index.isValid()) return; - - // Temporarily disconnect the signal to prevent multiple invocations - // This happens when the dialog closes and triggers another double-click event - disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked); - - int row = index.row(); - - // Different behavior based on which column was clicked - if (index.column() == 0) { - // Column 0 (play indicator): Stop current playback and play this song - if (isPlaying) { - audioPlayer->stop(); - } else { - isPlaying = true; - updateControls(); - } - - // Flush the generation queue when user selects a different song - flushGenerationQueue(); + if (!index.isValid()) + return; + + // Temporarily disconnect the signal to prevent multiple invocations + // This happens when the dialog closes and triggers another double-click event + disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked); + + int row = index.row(); + + // Different behavior based on which column was clicked + if (index.column() == 0) + { + // Column 0 (play indicator): Stop current playback and play this song + if (isPlaying) + { + audioPlayer->stop(); + } + else + { + isPlaying = true; + updateControls(); + } + + // Flush the generation queue when user selects a different song + flushGenerationQueue(); currentSong = songModel->getSong(row); ensureSongsInQueue(true); - } else if (index.column() == 1 || index.column() == 2) { - // Column 1 (caption): Edit the song - SongItem song = songModel->getSong(row); - - SongDialog dialog(this, song.caption, song.lyrics, song.vocalLanguage); - - if (dialog.exec() == QDialog::Accepted) { - QString caption = dialog.getCaption(); - QString lyrics = dialog.getLyrics(); - QString vocalLanguage = dialog.getVocalLanguage(); - - // Update the model - use column 1 for the song name - songModel->setData(songModel->index(row, 1), caption, SongListModel::CaptionRole); - songModel->setData(songModel->index(row, 1), lyrics, SongListModel::LyricsRole); - songModel->setData(songModel->index(row, 1), vocalLanguage, SongListModel::VocalLanguageRole); - } - } - - // Reconnect the signal after dialog is closed - connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked); + } + else if (index.column() == 1 || index.column() == 2) + { + // Column 1 (caption): Edit the song + SongItem song = songModel->getSong(row); + + SongDialog dialog(this, song.caption, song.lyrics, song.vocalLanguage); + + if (dialog.exec() == QDialog::Accepted) + { + QString caption = dialog.getCaption(); + QString lyrics = dialog.getLyrics(); + QString vocalLanguage = dialog.getVocalLanguage(); + + // Update the model - use column 1 for the song name + songModel->setData(songModel->index(row, 1), caption, SongListModel::CaptionRole); + songModel->setData(songModel->index(row, 1), lyrics, SongListModel::LyricsRole); + songModel->setData(songModel->index(row, 1), vocalLanguage, SongListModel::VocalLanguageRole); + } + } + + // Reconnect the signal after dialog is closed + connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked); } void MainWindow::on_removeSongButton_clicked() { - QModelIndex currentIndex = ui->songListView->currentIndex(); - if (!currentIndex.isValid()) return; - - // Get the row from the current selection (works with table view) - int row = currentIndex.row(); - - songModel->removeSong(row); + QModelIndex currentIndex = ui->songListView->currentIndex(); + if (!currentIndex.isValid()) + return; - // Select next item or previous if at end - int newRow = qMin(row, songModel->rowCount() - 1); - if (newRow >= 0) { - QModelIndex newIndex = songModel->index(newRow, 0); - ui->songListView->setCurrentIndex(newIndex); - } + // Get the row from the current selection (works with table view) + int row = currentIndex.row(); + + songModel->removeSong(row); + + // Select next item or previous if at end + int newRow = qMin(row, songModel->rowCount() - 1); + if (newRow >= 0) + { + QModelIndex newIndex = songModel->index(newRow, 0); + ui->songListView->setCurrentIndex(newIndex); + } } void MainWindow::on_advancedSettingsButton_clicked() { - AdvancedSettingsDialog dialog(this); - - // Set current values - dialog.setJsonTemplate(jsonTemplate); - dialog.setAceStepPath(aceStepPath); - dialog.setQwen3ModelPath(qwen3ModelPath); - dialog.setTextEncoderModelPath(textEncoderModelPath); - dialog.setDiTModelPath(ditModelPath); - dialog.setVAEModelPath(vaeModelPath); - - if (dialog.exec() == QDialog::Accepted) { - // Validate JSON template - QJsonParseError parseError; - QJsonDocument doc = QJsonDocument::fromJson(dialog.getJsonTemplate().toUtf8(), &parseError); - if (!doc.isObject()) { - QMessageBox::warning(this, "Invalid JSON", "Please enter valid JSON: " + QString(parseError.errorString())); - return; - } - - // Update settings - jsonTemplate = dialog.getJsonTemplate(); - aceStepPath = dialog.getAceStepPath(); - qwen3ModelPath = dialog.getQwen3ModelPath(); - textEncoderModelPath = dialog.getTextEncoderModelPath(); - ditModelPath = dialog.getDiTModelPath(); - vaeModelPath = dialog.getVAEModelPath(); - - saveSettings(); - QMessageBox::information(this, "Settings Saved", "Advanced settings have been saved successfully."); - } + AdvancedSettingsDialog dialog(this); + + // Set current values + dialog.setJsonTemplate(jsonTemplate); + dialog.setAceStepPath(aceStepPath); + dialog.setQwen3ModelPath(qwen3ModelPath); + dialog.setTextEncoderModelPath(textEncoderModelPath); + dialog.setDiTModelPath(ditModelPath); + dialog.setVAEModelPath(vaeModelPath); + + if (dialog.exec() == QDialog::Accepted) + { + // Validate JSON template + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(dialog.getJsonTemplate().toUtf8(), &parseError); + if (!doc.isObject()) + { + QMessageBox::warning(this, "Invalid JSON", "Please enter valid JSON: " + QString(parseError.errorString())); + return; + } + + // Update settings + jsonTemplate = dialog.getJsonTemplate(); + aceStepPath = dialog.getAceStepPath(); + qwen3ModelPath = dialog.getQwen3ModelPath(); + textEncoderModelPath = dialog.getTextEncoderModelPath(); + ditModelPath = dialog.getDiTModelPath(); + vaeModelPath = dialog.getVAEModelPath(); + + saveSettings(); + QMessageBox::information(this, "Settings Saved", "Advanced settings have been saved successfully."); + } } void MainWindow::playbackStarted() { - ensureSongsInQueue(); + ensureSongsInQueue(); } void MainWindow::playSong(const SongItem& song) @@ -381,18 +410,20 @@ void MainWindow::playSong(const SongItem& song) audioPlayer->play(song.file); songModel->setPlayingIndex(songModel->findSongIndexById(song.uniqueId)); ui->nowPlayingLabel->setText("Now Playing: " + song.caption); - ui->lyricsTextEdit->setPlainText(song.lyrics); - ui->jsonTextEdit->setPlainText(song.json); + ui->lyricsTextEdit->setPlainText(song.lyrics); + ui->jsonTextEdit->setPlainText(song.json); } void MainWindow::songGenerated(const SongItem& song) { - isGeneratingNext = false; + isGeneratingNext = false; - if (!isPaused && isPlaying && !audioPlayer->isPlaying()) { + if (!isPaused && isPlaying && !audioPlayer->isPlaying()) + { playSong(song); } - else { + else + { generatedSongQueue.enqueue(song); } ui->statusLabel->setText("idle"); @@ -405,72 +436,77 @@ void MainWindow::playNextSong() if (!isPlaying) return; - // Check if we have a pre-generated next song in the queue - if (!generatedSongQueue.isEmpty()) { + // Check if we have a pre-generated next song in the queue + if (!generatedSongQueue.isEmpty()) + { SongItem generatedSong = generatedSongQueue.dequeue(); playSong(generatedSong); - } else { + } + else + { ui->nowPlayingLabel->setText("Now Playing: Waiting for generation..."); } - - // Ensure we have songs in the queue for smooth playback - ensureSongsInQueue(); + + // Ensure we have songs in the queue for smooth playback + ensureSongsInQueue(); } void MainWindow::generationError(const QString &error) { - // Reset the generation flag on error - isGeneratingNext = false; - - // Show detailed error in a dialog with QTextEdit - QDialog dialog(this); - dialog.setWindowTitle("Generation Error"); - dialog.resize(600, 400); - - QVBoxLayout *layout = new QVBoxLayout(&dialog); - - QLabel *errorLabel = new QLabel("Error occurred during music generation:"); - errorLabel->setStyleSheet("font-weight: bold; color: darkred;"); - layout->addWidget(errorLabel); - - QTextEdit *errorTextEdit = new QTextEdit(); - errorTextEdit->setReadOnly(true); - errorTextEdit->setPlainText(error); - errorTextEdit->setLineWrapMode(QTextEdit::NoWrap); - errorTextEdit->setFontFamily("Monospace"); - layout->addWidget(errorTextEdit); - - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); - layout->addWidget(buttonBox); - - connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); - - dialog.exec(); - - isPlaying = false; - isPaused = false; - updateControls(); + // Reset the generation flag on error + isGeneratingNext = false; + + // Show detailed error in a dialog with QTextEdit + QDialog dialog(this); + dialog.setWindowTitle("Generation Error"); + dialog.resize(600, 400); + + QVBoxLayout *layout = new QVBoxLayout(&dialog); + + QLabel *errorLabel = new QLabel("Error occurred during music generation:"); + errorLabel->setStyleSheet("font-weight: bold; color: darkred;"); + layout->addWidget(errorLabel); + + QTextEdit *errorTextEdit = new QTextEdit(); + errorTextEdit->setReadOnly(true); + errorTextEdit->setPlainText(error); + errorTextEdit->setLineWrapMode(QTextEdit::NoWrap); + errorTextEdit->setFontFamily("Monospace"); + layout->addWidget(errorTextEdit); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + + dialog.exec(); + + isPlaying = false; + isPaused = false; + updateControls(); } void MainWindow::updatePlaybackStatus(bool playing) { - isPlaying = playing; - updateControls(); + isPlaying = playing; + updateControls(); } void MainWindow::on_positionSlider_sliderMoved(int position) { - if (isPlaying && audioPlayer->isPlaying()) { - audioPlayer->setPosition(position); - } + if (isPlaying && audioPlayer->isPlaying()) + { + audioPlayer->setPosition(position); + } } void MainWindow::ensureSongsInQueue(bool enqeueCurrent) { - // Only generate more songs if we're playing and not already at capacity - if (!isPlaying || isGeneratingNext || generatedSongQueue.size() >= generationTresh) { - return; - } + // Only generate more songs if we're playing and not already at capacity + if (!isPlaying || isGeneratingNext || generatedSongQueue.size() >= generationTresh) + { + return; + } SongItem lastSong; SongItem workerSong; @@ -482,255 +518,282 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent) lastSong = currentSong; SongItem nextSong; - if(enqeueCurrent) { + if(enqeueCurrent) + { nextSong = lastSong; } - else { + else + { int nextIndex = songModel->findNextIndex(songModel->findSongIndexById(lastSong.uniqueId), shuffleMode); nextSong = songModel->getSong(nextIndex); } - isGeneratingNext = true; + isGeneratingNext = true; ui->statusLabel->setText("Generateing: "+nextSong.caption); - aceStepWorker->generateSong(nextSong, jsonTemplate, - aceStepPath, qwen3ModelPath, - textEncoderModelPath, ditModelPath, - vaeModelPath); + aceStepWorker->generateSong(nextSong, jsonTemplate, + aceStepPath, qwen3ModelPath, + textEncoderModelPath, ditModelPath, + vaeModelPath); } void MainWindow::flushGenerationQueue() { - generatedSongQueue.clear(); + generatedSongQueue.clear(); aceStepWorker->cancelGeneration(); - isGeneratingNext = false; + isGeneratingNext = false; } // Playlist save/load methods void MainWindow::on_actionSavePlaylist() { - QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist", - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/playlist.json", - "JSON Files (*.json);;All Files (*)"); - - if (!filePath.isEmpty()) { - savePlaylist(filePath); - } + QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist", + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/playlist.json", + "JSON Files (*.json);;All Files (*)"); + + if (!filePath.isEmpty()) + { + savePlaylist(filePath); + } } void MainWindow::on_actionLoadPlaylist() { - QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist", - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "JSON Files (*.json);;All Files (*)"); - if (!filePath.isEmpty()) { - songModel->clear(); - flushGenerationQueue(); - loadPlaylist(filePath); - } + QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist", + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "JSON Files (*.json);;All Files (*)"); + if (!filePath.isEmpty()) + { + songModel->clear(); + flushGenerationQueue(); + loadPlaylist(filePath); + } } void MainWindow::on_actionAppendPlaylist() { - QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist", - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "JSON Files (*.json);;All Files (*)"); - if (!filePath.isEmpty()) { - loadPlaylist(filePath); - } + QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist", + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "JSON Files (*.json);;All Files (*)"); + if (!filePath.isEmpty()) + { + loadPlaylist(filePath); + } } void MainWindow::on_actionSaveSong() { - QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist", - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/song.json", - "JSON Files (*.json);;All Files (*)"); - if (!filePath.isEmpty()) { - QJsonArray songsArray; - QJsonParseError parseError; - QJsonDocument songDoc = QJsonDocument::fromJson(currentSong.json.toUtf8(), &parseError); - if(parseError.error) - return; - songsArray.append(songDoc.object()); + QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist", + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/song.json", + "JSON Files (*.json);;All Files (*)"); + if (!filePath.isEmpty()) + { + QJsonArray songsArray; + QJsonParseError parseError; + QJsonDocument songDoc = QJsonDocument::fromJson(currentSong.json.toUtf8(), &parseError); + if(parseError.error) + return; + songsArray.append(songDoc.object()); - QJsonObject rootObj; - rootObj["songs"] = songsArray; - rootObj["version"] = "1.0"; + QJsonObject rootObj; + rootObj["songs"] = songsArray; + rootObj["version"] = "1.0"; - QJsonDocument doc(rootObj); - QByteArray jsonData = doc.toJson(); + QJsonDocument doc(rootObj); + QByteArray jsonData = doc.toJson(); - QFile file(filePath); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) - return; - QFile::copy(currentSong.file, filePath + ".wav"); - file.write(jsonData); - file.close(); - } + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + return; + QFile::copy(currentSong.file, filePath + ".wav"); + file.write(jsonData); + file.close(); + } } void MainWindow::savePlaylist(const QString &filePath) { - // Get current songs from the model - QList songs; - for (int i = 0; i < songModel->rowCount(); ++i) { - songs.append(songModel->getSong(i)); - } - - savePlaylistToJson(filePath, songs); + // Get current songs from the model + QList songs; + for (int i = 0; i < songModel->rowCount(); ++i) + { + songs.append(songModel->getSong(i)); + } + + savePlaylistToJson(filePath, songs); } void MainWindow::loadPlaylist(const QString& filePath) { - QList songs; - if (loadPlaylistFromJson(filePath, songs)) { - // Add loaded songs - for (const SongItem &song : songs) { - songModel->addSong(song); - } - } + QList songs; + if (loadPlaylistFromJson(filePath, songs)) + { + // Add loaded songs + for (const SongItem &song : songs) + { + songModel->addSong(song); + } + } } void MainWindow::autoSavePlaylist() { - QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); - QString appConfigPath = configPath + "/MusicGenerator/AceStepGUI"; - - // Create directory if it doesn't exist - QDir().mkpath(appConfigPath); - - QString filePath = appConfigPath + "/playlist.json"; - - // Get current songs from the model - QList songs; - for (int i = 0; i < songModel->rowCount(); ++i) { - songs.append(songModel->getSong(i)); - } - - savePlaylistToJson(filePath, songs); + QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + QString appConfigPath = configPath + "/MusicGenerator/AceStepGUI"; + + // Create directory if it doesn't exist + QDir().mkpath(appConfigPath); + + QString filePath = appConfigPath + "/playlist.json"; + + // Get current songs from the model + QList songs; + for (int i = 0; i < songModel->rowCount(); ++i) + { + songs.append(songModel->getSong(i)); + } + + savePlaylistToJson(filePath, songs); } void MainWindow::autoLoadPlaylist() { - QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); - QString appConfigPath = configPath + "/MusicGenerator/AceStepGUI"; - QString filePath = appConfigPath + "/playlist.json"; - - // Check if the auto-save file exists - if (QFile::exists(filePath)) { - QList songs; - if (loadPlaylistFromJson(filePath, songs)) { - songModel->clear(); - for (const SongItem &song : songs) - songModel->addSong(song); - } - } + QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + QString appConfigPath = configPath + "/MusicGenerator/AceStepGUI"; + QString filePath = appConfigPath + "/playlist.json"; + + // Check if the auto-save file exists + if (QFile::exists(filePath)) + { + QList songs; + if (loadPlaylistFromJson(filePath, songs)) + { + songModel->clear(); + for (const SongItem &song : songs) + songModel->addSong(song); + } + } } bool MainWindow::savePlaylistToJson(const QString &filePath, const QList &songs) { - QJsonArray songsArray; - - for (const SongItem &song : songs) { - QJsonObject songObj; - songObj["caption"] = song.caption; - songObj["lyrics"] = song.lyrics; - songObj["vocalLanguage"] = song.vocalLanguage; - songObj["uniqueId"] = static_cast(song.uniqueId); // Store as qint64 for JSON compatibility - songsArray.append(songObj); - } - - QJsonObject rootObj; - rootObj["songs"] = songsArray; - rootObj["version"] = "1.0"; - - QJsonDocument doc(rootObj); - QByteArray jsonData = doc.toJson(); - - QFile file(filePath); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - qWarning() << "Could not open file for writing:" << filePath; - return false; - } - - file.write(jsonData); - file.close(); - - return true; + QJsonArray songsArray; + + for (const SongItem &song : songs) + { + QJsonObject songObj; + songObj["caption"] = song.caption; + songObj["lyrics"] = song.lyrics; + songObj["vocalLanguage"] = song.vocalLanguage; + songObj["uniqueId"] = static_cast(song.uniqueId); // Store as qint64 for JSON compatibility + songsArray.append(songObj); + } + + QJsonObject rootObj; + rootObj["songs"] = songsArray; + rootObj["version"] = "1.0"; + + QJsonDocument doc(rootObj); + QByteArray jsonData = doc.toJson(); + + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + qWarning() << "Could not open file for writing:" << filePath; + return false; + } + + file.write(jsonData); + file.close(); + + return true; } bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList &songs) { - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning() << "Could not open file for reading:" << filePath; - return false; - } + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + qWarning() << "Could not open file for reading:" << filePath; + return false; + } - qDebug()<<"Loading from"<(songObj["uniqueId"].toInteger()); - } else { - // Generate new ID for old playlists without uniqueId - song.uniqueId = QRandomGenerator::global()->generate64(); - } - - songs.append(song); - } - - return true; + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError); + + if (parseError.error != QJsonParseError::NoError) + { + qWarning() << "JSON parse error:" << parseError.errorString(); + return false; + } + + if (!doc.isObject()) + { + qWarning() << "JSON root is not an object"; + return false; + } + + QJsonObject rootObj = doc.object(); + + // Check for version compatibility + if (rootObj.contains("version") && rootObj["version"].toString() != "1.0") + { + qWarning() << "Unsupported playlist version:" << rootObj["version"].toString(); + return false; + } + + if (!rootObj.contains("songs") || !rootObj["songs"].isArray()) + { + qWarning() << "Invalid playlist format: missing songs array"; + return false; + } + + QJsonArray songsArray = rootObj["songs"].toArray(); + + qDebug()<<"Loading"<(songObj["uniqueId"].toInteger()); + } + else + { + // Generate new ID for old playlists without uniqueId + song.uniqueId = QRandomGenerator::global()->generate64(); + } + + songs.append(song); + } + + return true; } diff --git a/src/MainWindow.h b/src/MainWindow.h index 5c6ece0..6a3bfd3 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -17,90 +17,93 @@ #include "AceStepWorker.h" QT_BEGIN_NAMESPACE -namespace Ui { class MainWindow; } +namespace Ui +{ +class MainWindow; +} QT_END_NAMESPACE class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - MainWindow(QWidget *parent = nullptr); - ~MainWindow(); + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); private slots: - void on_playButton_clicked(); - void on_pauseButton_clicked(); - void on_skipButton_clicked(); - void on_stopButton_clicked(); - void on_shuffleButton_clicked(); - void on_positionSlider_sliderMoved(int position); - void updatePosition(int position); - void updateDuration(int duration); - void on_addSongButton_clicked(); - void on_removeSongButton_clicked(); - void on_advancedSettingsButton_clicked(); - - void on_songListView_doubleClicked(const QModelIndex &index); - + void on_playButton_clicked(); + void on_pauseButton_clicked(); + void on_skipButton_clicked(); + void on_stopButton_clicked(); + void on_shuffleButton_clicked(); + void on_positionSlider_sliderMoved(int position); + void updatePosition(int position); + void updateDuration(int duration); + void on_addSongButton_clicked(); + void on_removeSongButton_clicked(); + void on_advancedSettingsButton_clicked(); + + void on_songListView_doubleClicked(const QModelIndex &index); + void songGenerated(const SongItem& song); - void playNextSong(); - void playbackStarted(); + void playNextSong(); + void playbackStarted(); void updatePlaybackStatus(bool playing); - void generationError(const QString &error); - - void on_actionSavePlaylist(); - void on_actionLoadPlaylist(); - void on_actionAppendPlaylist(); - void on_actionSaveSong(); - + void generationError(const QString &error); + + void on_actionSavePlaylist(); + void on_actionLoadPlaylist(); + void on_actionAppendPlaylist(); + void on_actionSaveSong(); + private: - void startNextSongGeneration(); - + void startNextSongGeneration(); + private: - Ui::MainWindow *ui; - SongListModel *songModel; - AudioPlayer *audioPlayer; - AceStepWorker *aceStepWorker; - QTimer *playbackTimer; - - QString formatTime(int milliseconds); + Ui::MainWindow *ui; + SongListModel *songModel; + AudioPlayer *audioPlayer; + AceStepWorker *aceStepWorker; + QTimer *playbackTimer; + + QString formatTime(int milliseconds); SongItem currentSong; - bool isPlaying; - bool isPaused; - bool shuffleMode; - bool isGeneratingNext; - QString jsonTemplate; - - // Path settings - QString aceStepPath; - QString qwen3ModelPath; - QString textEncoderModelPath; - QString ditModelPath; - QString vaeModelPath; - - // Queue for generated songs + bool isPlaying; + bool isPaused; + bool shuffleMode; + bool isGeneratingNext; + QString jsonTemplate; + + // Path settings + QString aceStepPath; + QString qwen3ModelPath; + QString textEncoderModelPath; + QString ditModelPath; + QString vaeModelPath; + + // Queue for generated songs static constexpr int generationTresh = 2; QQueue generatedSongQueue; - + private: - void loadSettings(); - void saveSettings(); - void loadPlaylist(const QString &filePath); - void savePlaylist(const QString &filePath); - void autoSavePlaylist(); - void autoLoadPlaylist(); + void loadSettings(); + void saveSettings(); + void loadPlaylist(const QString &filePath); + void savePlaylist(const QString &filePath); + void autoSavePlaylist(); + void autoLoadPlaylist(); void playSong(const SongItem& song); - - bool savePlaylistToJson(const QString &filePath, const QList &songs); - bool loadPlaylistFromJson(const QString &filePath, QList &songs); - - void setupUI(); + + bool savePlaylistToJson(const QString &filePath, const QList &songs); + bool loadPlaylistFromJson(const QString &filePath, QList &songs); + + void setupUI(); void updateControls(); void ensureSongsInQueue(bool enqeueCurrent = false); - void flushGenerationQueue(); + void flushGenerationQueue(); }; #endif // MAINWINDOW_H diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 4767b57..d67fb70 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -1,359 +1,359 @@ - - MainWindow - - - - 0 - 0 - 800 - 600 - - - - Aceradio - - - - :/icons/xyz.uvos.aceradio.png:/icons/xyz.uvos.aceradio.png - - - - - - - 0 - - - - Songs - - - - - - - 0 - 200 - - - - QAbstractItemView::EditTrigger::NoEditTriggers - - - QAbstractItemView::SelectionBehavior::SelectRows - - - - - - - - Info - - - - - - true - - - - - - - - Lyrics - - - - - - - Monospace - - - - true - - - - - - - - - - - - - Add Song - - - - - - - Remove Song - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::LayoutDirection::LeftToRight - - - Now Playing: - - - Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter - - - - - - - - - 0:00 - - - - - - - false - - - false - - - - - - - 0:00 - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - Play - - - - - - - - - - Pause - - - - - - - - - - Skip - - - - - - - - - - Stop - - - - - - - - - - Shuffle - - - - - - true - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - 0 - 0 - 800 - 32 - - - - - File - - - - - - - - - - - Settings - - - - - - - - - - - - - Save Playlist - - - Ctrl+S - - - - - - - - Load Playlist... - - - Ctrl+O - - - - - Ace Step - - - - - - - - Quit - - - Ctrl+Q - - - - - - - - Clear Playlist - - - - - - - - Save Song - - - - - - - - Append Playlist - - - - - - ClickableSlider - QWidget -
src/clickableslider.h
-
-
- - - - -
+ + MainWindow + + + + 0 + 0 + 800 + 600 + + + + Aceradio + + + + :/icons/xyz.uvos.aceradio.png:/icons/xyz.uvos.aceradio.png + + + + + + + 0 + + + + Songs + + + + + + + 0 + 200 + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + QAbstractItemView::SelectionBehavior::SelectRows + + + + + + + + Info + + + + + + true + + + + + + + + Lyrics + + + + + + + Monospace + + + + true + + + + + + + + + + + + + Add Song + + + + + + + Remove Song + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::LayoutDirection::LeftToRight + + + Now Playing: + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + + + + 0:00 + + + + + + + false + + + false + + + + + + + 0:00 + + + + + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + Play + + + + + + + + + + Pause + + + + + + + + + + Skip + + + + + + + + + + Stop + + + + + + + + + + Shuffle + + + + + + true + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + 0 + 0 + 800 + 32 + + + + + File + + + + + + + + + + + Settings + + + + + + + + + + + + + Save Playlist + + + Ctrl+S + + + + + + + + Load Playlist... + + + Ctrl+O + + + + + Ace Step + + + + + + + + Quit + + + Ctrl+Q + + + + + + + + Clear Playlist + + + + + + + + Save Song + + + + + + + + Append Playlist + + + + + + ClickableSlider + QWidget +
src/clickableslider.h
+
+
+ + + + +
diff --git a/src/SongDialog.cpp b/src/SongDialog.cpp index a7a3d9e..baa9ddc 100644 --- a/src/SongDialog.cpp +++ b/src/SongDialog.cpp @@ -3,75 +3,82 @@ #include SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &lyrics, const QString &vocalLanguage) - : QDialog(parent), - ui(new Ui::SongDialog) + : QDialog(parent), + ui(new Ui::SongDialog) { - ui->setupUi(this); - - // Set initial values if provided - if (!caption.isEmpty()) { - ui->captionEdit->setPlainText(caption); - } - if (!lyrics.isEmpty()) { - ui->lyricsEdit->setPlainText(lyrics); - } - - // Setup vocal language combo box - ui->vocalLanguageCombo->addItem("--", ""); // Unset - ui->vocalLanguageCombo->addItem("English (en)", "en"); - ui->vocalLanguageCombo->addItem("German (de)", "de"); - ui->vocalLanguageCombo->addItem("French (fr)", "fr"); - ui->vocalLanguageCombo->addItem("Spanish (es)", "es"); - ui->vocalLanguageCombo->addItem("Japanese (ja)", "ja"); - ui->vocalLanguageCombo->addItem("Chinese (zh)", "zh"); - ui->vocalLanguageCombo->addItem("Italian (it)", "it"); - ui->vocalLanguageCombo->addItem("Portuguese (pt)", "pt"); - ui->vocalLanguageCombo->addItem("Russian (ru)", "ru"); - - // Set current language if provided - if (!vocalLanguage.isEmpty()) { - int index = ui->vocalLanguageCombo->findData(vocalLanguage); - if (index >= 0) { - ui->vocalLanguageCombo->setCurrentIndex(index); - } - } else { - ui->vocalLanguageCombo->setCurrentIndex(0); // Default to unset - } + ui->setupUi(this); + + // Set initial values if provided + if (!caption.isEmpty()) + { + ui->captionEdit->setPlainText(caption); + } + if (!lyrics.isEmpty()) + { + ui->lyricsEdit->setPlainText(lyrics); + } + + // Setup vocal language combo box + ui->vocalLanguageCombo->addItem("--", ""); // Unset + ui->vocalLanguageCombo->addItem("English (en)", "en"); + ui->vocalLanguageCombo->addItem("German (de)", "de"); + ui->vocalLanguageCombo->addItem("French (fr)", "fr"); + ui->vocalLanguageCombo->addItem("Spanish (es)", "es"); + ui->vocalLanguageCombo->addItem("Japanese (ja)", "ja"); + ui->vocalLanguageCombo->addItem("Chinese (zh)", "zh"); + ui->vocalLanguageCombo->addItem("Italian (it)", "it"); + ui->vocalLanguageCombo->addItem("Portuguese (pt)", "pt"); + ui->vocalLanguageCombo->addItem("Russian (ru)", "ru"); + + // Set current language if provided + if (!vocalLanguage.isEmpty()) + { + int index = ui->vocalLanguageCombo->findData(vocalLanguage); + if (index >= 0) + { + ui->vocalLanguageCombo->setCurrentIndex(index); + } + } + else + { + ui->vocalLanguageCombo->setCurrentIndex(0); // Default to unset + } } SongDialog::~SongDialog() { - delete ui; + delete ui; } QString SongDialog::getCaption() const { - return ui->captionEdit->toPlainText(); + return ui->captionEdit->toPlainText(); } QString SongDialog::getLyrics() const { - return ui->lyricsEdit->toPlainText(); + return ui->lyricsEdit->toPlainText(); } QString SongDialog::getVocalLanguage() const { - return ui->vocalLanguageCombo->currentData().toString(); + return ui->vocalLanguageCombo->currentData().toString(); } void SongDialog::on_okButton_clicked() { - // Validate that caption is not empty - QString caption = getCaption(); - if (caption.trimmed().isEmpty()) { - QMessageBox::warning(this, "Invalid Input", "Caption cannot be empty."); - return; - } - - accept(); + // Validate that caption is not empty + QString caption = getCaption(); + if (caption.trimmed().isEmpty()) + { + QMessageBox::warning(this, "Invalid Input", "Caption cannot be empty."); + return; + } + + accept(); } void SongDialog::on_cancelButton_clicked() { - reject(); + reject(); } \ No newline at end of file diff --git a/src/SongDialog.h b/src/SongDialog.h index c59f865..c8137f5 100644 --- a/src/SongDialog.h +++ b/src/SongDialog.h @@ -4,28 +4,30 @@ #include #include -namespace Ui { +namespace Ui +{ class SongDialog; } class SongDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "", const QString &vocalLanguage = ""); - ~SongDialog(); - - QString getCaption() const; - QString getLyrics() const; - QString getVocalLanguage() const; - + explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "", + const QString &vocalLanguage = ""); + ~SongDialog(); + + QString getCaption() const; + QString getLyrics() const; + QString getVocalLanguage() const; + private slots: - void on_okButton_clicked(); - void on_cancelButton_clicked(); + void on_okButton_clicked(); + void on_cancelButton_clicked(); private: - Ui::SongDialog *ui; + Ui::SongDialog *ui; }; #endif // SONGDIALOG_H \ No newline at end of file diff --git a/src/SongDialog.ui b/src/SongDialog.ui index 7fbba67..efedda8 100644 --- a/src/SongDialog.ui +++ b/src/SongDialog.ui @@ -1,109 +1,109 @@ - - SongDialog - - - - 0 - 0 - 500 - 400 - - - - Song Details - - - - - - Caption: - - - Qt::AlignTop - - - - - - - Enter song caption (e.g., "Upbeat pop rock anthem with driving electric guitars") - - - 80 - - - - - - - Lyrics (optional): - - - Qt::AlignTop - - - - - - - Enter lyrics or leave empty for instrumental music - - - 150 - - - - - - - Vocal Language: - - - Qt::AlignTop - - - - - - - true - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - OK - - - - - - - Cancel - - - - - - - - - - \ No newline at end of file + + SongDialog + + + + 0 + 0 + 500 + 400 + + + + Song Details + + + + + + Caption: + + + Qt::AlignTop + + + + + + + Enter song caption (e.g., "Upbeat pop rock anthem with driving electric guitars") + + + 80 + + + + + + + Lyrics (optional): + + + Qt::AlignTop + + + + + + + Enter lyrics or leave empty for instrumental music + + + 150 + + + + + + + Vocal Language: + + + Qt::AlignTop + + + + + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + + + + Cancel + + + + + + + + + + \ No newline at end of file diff --git a/src/SongItem.h b/src/SongItem.h index 46ba137..8a28891 100644 --- a/src/SongItem.h +++ b/src/SongItem.h @@ -3,18 +3,20 @@ #include #include -class SongItem { +class SongItem +{ public: - QString caption; - QString lyrics; - uint64_t uniqueId; - QString file; - QString vocalLanguage; - QString json; + QString caption; + QString lyrics; + uint64_t uniqueId; + QString file; + QString vocalLanguage; + QString json; - inline SongItem(const QString &caption = "", const QString &lyrics = "") - : caption(caption), lyrics(lyrics) { - // Generate a unique ID using cryptographically secure random number - uniqueId = QRandomGenerator::global()->generate64(); - } + inline SongItem(const QString &caption = "", const QString &lyrics = "") + : caption(caption), lyrics(lyrics) + { + // Generate a unique ID using cryptographically secure random number + uniqueId = QRandomGenerator::global()->generate64(); + } }; diff --git a/src/SongListModel.cpp b/src/SongListModel.cpp index 8966df2..c658f64 100644 --- a/src/SongListModel.cpp +++ b/src/SongListModel.cpp @@ -7,167 +7,179 @@ #include SongListModel::SongListModel(QObject *parent) - : QAbstractTableModel(parent), - m_playingIndex(-1) + : QAbstractTableModel(parent), + m_playingIndex(-1) { } int SongListModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) - return 0; - return songList.size(); + if (parent.isValid()) + return 0; + return songList.size(); } int SongListModel::columnCount(const QModelIndex &parent) const { - // We have 3 columns: play indicator, song name, and vocal language (read-only) - return 3; + // We have 3 columns: play indicator, song name, and vocal language (read-only) + return 3; } QVariant SongListModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= songList.size()) - return QVariant(); - - const SongItem &song = songList[index.row()]; - - switch (role) { - case Qt::DisplayRole: - // Column 0: Play indicator column - if (index.column() == 0) { - return index.row() == m_playingIndex ? "▶" : ""; - } - // Column 1: Song name - else if (index.column() == 1) { - return song.caption; - } - // Column 2: Vocal language - else if (index.column() == 2) { - return !song.vocalLanguage.isEmpty() ? song.vocalLanguage : "--"; - } - break; - case Qt::FontRole: - // Make play indicator bold and larger - if (index.column() == 0 && index.row() == m_playingIndex) { - QFont font = QApplication::font(); - font.setBold(true); - return font; - } - break; - case Qt::TextAlignmentRole: - // Center align the play indicator - if (index.column() == 0) { - return Qt::AlignCenter; - } - break; - case CaptionRole: - return song.caption; - case LyricsRole: - return song.lyrics; - case VocalLanguageRole: - return song.vocalLanguage; - case IsPlayingRole: - return index.row() == m_playingIndex; - default: - return QVariant(); - } - - return QVariant(); + if (!index.isValid() || index.row() >= songList.size()) + return QVariant(); + + const SongItem &song = songList[index.row()]; + + switch (role) + { + case Qt::DisplayRole: + // Column 0: Play indicator column + if (index.column() == 0) + { + return index.row() == m_playingIndex ? "▶" : ""; + } + // Column 1: Song name + else if (index.column() == 1) + { + return song.caption; + } + // Column 2: Vocal language + else if (index.column() == 2) + { + return !song.vocalLanguage.isEmpty() ? song.vocalLanguage : "--"; + } + break; + case Qt::FontRole: + // Make play indicator bold and larger + if (index.column() == 0 && index.row() == m_playingIndex) + { + QFont font = QApplication::font(); + font.setBold(true); + return font; + } + break; + case Qt::TextAlignmentRole: + // Center align the play indicator + if (index.column() == 0) + { + return Qt::AlignCenter; + } + break; + case CaptionRole: + return song.caption; + case LyricsRole: + return song.lyrics; + case VocalLanguageRole: + return song.vocalLanguage; + case IsPlayingRole: + return index.row() == m_playingIndex; + default: + return QVariant(); + } + + return QVariant(); } bool SongListModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() >= songList.size()) - return false; - - SongItem &song = songList[index.row()]; - - switch (role) { - case CaptionRole: - song.caption = value.toString(); - break; - case LyricsRole: - song.lyrics = value.toString(); - break; - case VocalLanguageRole: - song.vocalLanguage = value.toString(); - break; - default: - return false; - } - - emit dataChanged(index, index, {role}); - return true; + if (!index.isValid() || index.row() >= songList.size()) + return false; + + SongItem &song = songList[index.row()]; + + switch (role) + { + case CaptionRole: + song.caption = value.toString(); + break; + case LyricsRole: + song.lyrics = value.toString(); + break; + case VocalLanguageRole: + song.vocalLanguage = value.toString(); + break; + default: + return false; + } + + emit dataChanged(index, index, {role}); + return true; } Qt::ItemFlags SongListModel::flags(const QModelIndex &index) const { - if (!index.isValid()) - return Qt::NoItemFlags; - - // Remove ItemIsEditable to prevent inline editing and double-click issues - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + if (!index.isValid()) + return Qt::NoItemFlags; + + // Remove ItemIsEditable to prevent inline editing and double-click issues + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } void SongListModel::addSong(const SongItem &song) { - beginInsertRows(QModelIndex(), songList.size(), songList.size()); - songList.append(song); - endInsertRows(); + beginInsertRows(QModelIndex(), songList.size(), songList.size()); + songList.append(song); + endInsertRows(); } void SongListModel::removeSong(int index) { - if (index >= 0 && index < songList.size()) { - beginRemoveRows(QModelIndex(), index, index); - songList.removeAt(index); - endRemoveRows(); - } + if (index >= 0 && index < songList.size()) + { + beginRemoveRows(QModelIndex(), index, index); + songList.removeAt(index); + endRemoveRows(); + } } void SongListModel::clear() { - beginRemoveRows(QModelIndex(), 0, songList.size()-1); - songList.clear(); - endRemoveRows(); + beginRemoveRows(QModelIndex(), 0, songList.size()-1); + songList.clear(); + endRemoveRows(); } bool SongListModel::empty() { - return songList.empty(); + return songList.empty(); } SongItem SongListModel::getSong(int index) const { - if (index >= 0 && index < songList.size()) { - return songList[index]; - } - return SongItem(); + if (index >= 0 && index < songList.size()) + { + return songList[index]; + } + return SongItem(); } QVariant SongListModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { - // Hide headers since we don't need column titles - return QVariant(); - } - return QAbstractTableModel::headerData(section, orientation, role); + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + // Hide headers since we don't need column titles + return QVariant(); + } + return QAbstractTableModel::headerData(section, orientation, role); } void SongListModel::setPlayingIndex(int index) { - int oldPlayingIndex = m_playingIndex; - m_playingIndex = index; - - // Update both the old and new playing indices to trigger UI updates - if (oldPlayingIndex >= 0 && oldPlayingIndex < songList.size()) { - emit dataChanged(this->index(oldPlayingIndex, 0), this->index(oldPlayingIndex, 0)); - } - - if (index >= 0 && index < songList.size()) { - emit dataChanged(this->index(index, 0), this->index(index, 0)); - } + int oldPlayingIndex = m_playingIndex; + m_playingIndex = index; + + // Update both the old and new playing indices to trigger UI updates + if (oldPlayingIndex >= 0 && oldPlayingIndex < songList.size()) + { + emit dataChanged(this->index(oldPlayingIndex, 0), this->index(oldPlayingIndex, 0)); + } + + if (index >= 0 && index < songList.size()) + { + emit dataChanged(this->index(index, 0), this->index(index, 0)); + } } int SongListModel::songCount() @@ -177,30 +189,34 @@ int SongListModel::songCount() int SongListModel::findNextIndex(int currentIndex, bool shuffle) const { - if (songList.isEmpty()) - return -1; - - if (shuffle) { - // Simple random selection for shuffle mode - QRandomGenerator generator; - return generator.bounded(songList.size()); - } - - // Sequential playback - int nextIndex = currentIndex + 1; - if (nextIndex >= songList.size()) { - nextIndex = 0; // Loop back to beginning - } - - return nextIndex; + if (songList.isEmpty()) + return -1; + + if (shuffle) + { + // Simple random selection for shuffle mode + QRandomGenerator generator; + return generator.bounded(songList.size()); + } + + // Sequential playback + int nextIndex = currentIndex + 1; + if (nextIndex >= songList.size()) + { + nextIndex = 0; // Loop back to beginning + } + + return nextIndex; } int SongListModel::findSongIndexById(uint64_t uniqueId) const { - for (int i = 0; i < songList.size(); ++i) { - if (songList[i].uniqueId == uniqueId) { - return i; - } - } - return -1; // Song not found + for (int i = 0; i < songList.size(); ++i) + { + if (songList[i].uniqueId == uniqueId) + { + return i; + } + } + return -1; // Song not found } diff --git a/src/SongListModel.h b/src/SongListModel.h index 46c6a5b..4a3275d 100644 --- a/src/SongListModel.h +++ b/src/SongListModel.h @@ -11,47 +11,51 @@ class SongListModel : public QAbstractTableModel { - Q_OBJECT + Q_OBJECT public: - enum Roles { - CaptionRole = Qt::UserRole + 1, - LyricsRole = Qt::UserRole + 2, - VocalLanguageRole = Qt::UserRole + 3, - IsPlayingRole = Qt::UserRole + 4 - }; - - explicit SongListModel(QObject *parent = nullptr); - - // Basic functionality: - int rowCount(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 headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - // Editable: - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::ItemFlags flags(const QModelIndex& index) const override; - - // Add/remove songs - void addSong(const SongItem &song); - void removeSong(int index); - SongItem getSong(int index) const; - int findNextIndex(int currentIndex, bool shuffle = false) const; - void clear(); - - // Playing indicator - void setPlayingIndex(int index); - int playingIndex() const { return m_playingIndex; } - - // Find song by unique ID - int findSongIndexById(uint64_t uniqueId) const; - int songCount(); - bool empty(); - + enum Roles + { + CaptionRole = Qt::UserRole + 1, + LyricsRole = Qt::UserRole + 2, + VocalLanguageRole = Qt::UserRole + 3, + IsPlayingRole = Qt::UserRole + 4 + }; + + explicit SongListModel(QObject *parent = nullptr); + + // Basic functionality: + int rowCount(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 headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + // Add/remove songs + void addSong(const SongItem &song); + void removeSong(int index); + SongItem getSong(int index) const; + int findNextIndex(int currentIndex, bool shuffle = false) const; + void clear(); + + // Playing indicator + void setPlayingIndex(int index); + int playingIndex() const + { + return m_playingIndex; + } + + // Find song by unique ID + int findSongIndexById(uint64_t uniqueId) const; + int songCount(); + bool empty(); + private: - QList songList; - int m_playingIndex; + QList songList; + int m_playingIndex; }; #endif // SONGLISTMODEL_H diff --git a/src/clickableslider.cpp b/src/clickableslider.cpp index a6262ee..3006b2e 100644 --- a/src/clickableslider.cpp +++ b/src/clickableslider.cpp @@ -2,59 +2,65 @@ #include ClickableSlider::ClickableSlider(QWidget *parent) - : QSlider(Qt::Orientation::Horizontal, parent) + : QSlider(Qt::Orientation::Horizontal, parent) { } ClickableSlider::ClickableSlider(Qt::Orientation orientation, QWidget *parent) - : QSlider(orientation, parent) + : QSlider(orientation, parent) { } void ClickableSlider::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - int val = pixelPosToRangeValue(event->pos()); - - // Block signals temporarily to avoid infinite recursion - blockSignals(true); - setValue(val); - blockSignals(false); - - // Emit both valueChanged and sliderMoved signals for compatibility - emit valueChanged(val); - emit sliderMoved(val); - } else { - // Call base class implementation for other buttons - QSlider::mousePressEvent(event); - } + if (event->button() == Qt::LeftButton) + { + int val = pixelPosToRangeValue(event->pos()); + + // Block signals temporarily to avoid infinite recursion + blockSignals(true); + setValue(val); + blockSignals(false); + + // Emit both valueChanged and sliderMoved signals for compatibility + emit valueChanged(val); + emit sliderMoved(val); + } + else + { + // Call base class implementation for other buttons + QSlider::mousePressEvent(event); + } } int ClickableSlider::pixelPosToRangeValue(const QPoint &pos) { - QStyleOptionSlider opt; - initStyleOption(&opt); - - QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); - QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); + QStyleOptionSlider opt; + initStyleOption(&opt); - int sliderLength; - int sliderMin; - int sliderMax; - - if (orientation() == Qt::Horizontal) { - sliderLength = sr.width(); - sliderMin = gr.x(); - sliderMax = gr.right() - sliderLength + 1; - } else { - sliderLength = sr.height(); - sliderMin = gr.y(); - sliderMax = gr.bottom() - sliderLength + 1; - } - - QPoint pr = pos - sr.center() + sr.topLeft(); - int p = orientation() == Qt::Horizontal ? pr.x() : pr.y(); - - return QStyle::sliderValueFromPosition(minimum(), maximum(), p - sliderMin, - sliderMax - sliderMin, opt.upsideDown); + QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); + QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); + + int sliderLength; + int sliderMin; + int sliderMax; + + if (orientation() == Qt::Horizontal) + { + sliderLength = sr.width(); + sliderMin = gr.x(); + sliderMax = gr.right() - sliderLength + 1; + } + else + { + sliderLength = sr.height(); + sliderMin = gr.y(); + sliderMax = gr.bottom() - sliderLength + 1; + } + + QPoint pr = pos - sr.center() + sr.topLeft(); + int p = orientation() == Qt::Horizontal ? pr.x() : pr.y(); + + return QStyle::sliderValueFromPosition(minimum(), maximum(), p - sliderMin, + sliderMax - sliderMin, opt.upsideDown); } diff --git a/src/clickableslider.h b/src/clickableslider.h index 14a152d..4b4e669 100644 --- a/src/clickableslider.h +++ b/src/clickableslider.h @@ -7,16 +7,16 @@ class ClickableSlider : public QSlider { - Q_OBJECT + Q_OBJECT public: - explicit ClickableSlider(QWidget *parent = nullptr); - explicit ClickableSlider(Qt::Orientation orientation, QWidget *parent = nullptr); - + explicit ClickableSlider(QWidget *parent = nullptr); + explicit ClickableSlider(Qt::Orientation orientation, QWidget *parent = nullptr); + protected: - void mousePressEvent(QMouseEvent *event) override; - + void mousePressEvent(QMouseEvent *event) override; + private: - int pixelPosToRangeValue(const QPoint &pos); + int pixelPosToRangeValue(const QPoint &pos); }; #endif // CLICKABLESLIDER_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 205189f..c7bbeb7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,10 +5,10 @@ int main(int argc, char *argv[]) { - QApplication app(argc, argv); - - MainWindow window; - window.show(); - - return app.exec(); + QApplication app(argc, argv); + + MainWindow window; + window.show(); + + return app.exec(); }