diff --git a/src/AceStepWorker.cpp b/src/AceStepWorker.cpp index 3da8847..e318e8f 100644 --- a/src/AceStepWorker.cpp +++ b/src/AceStepWorker.cpp @@ -8,269 +8,205 @@ #include #include #include -#include -AceStepWorker::AceStepWorker(QObject *parent) - : QObject(parent), - currentWorker(nullptr) +AceStep::AceStep(QObject* parent): QObject(parent) { + connect(&qwenProcess, &QProcess::finished, this, &AceStep::qwenProcFinished); + connect(&ditVaeProcess, &QProcess::finished, this, &AceStep::ditProcFinished); } -AceStepWorker::~AceStepWorker() +bool AceStep::isGenerateing(SongItem* song) { - cancelGeneration(); + if(!busy && song) + *song = this->request.song; + return busy; } -void AceStepWorker::generateSong(const SongItem& song, const QString &jsonTemplate, - const QString &aceStepPath, const QString &qwen3ModelPath, - const QString &textEncoderModelPath, const QString &ditModelPath, - const QString &vaeModelPath) +void AceStep::cancleGenerateion() { - // Cancel any ongoing generation - cancelGeneration(); + qwenProcess.blockSignals(true); + qwenProcess.terminate(); + qwenProcess.waitForFinished(); + qwenProcess.blockSignals(false); - // Create worker and start it - currentWorker = new Worker(this, song, jsonTemplate, aceStepPath, qwen3ModelPath, - textEncoderModelPath, ditModelPath, vaeModelPath); - currentWorker->setAutoDelete(true); - QThreadPool::globalInstance()->start(currentWorker); + ditVaeProcess.blockSignals(true); + ditVaeProcess.terminate(); + ditVaeProcess.waitForFinished(); + ditVaeProcess.blockSignals(false); + + progressUpdate(100); + if(busy) + generationCancled(request.song); + + busy = false; } -void AceStepWorker::cancelGeneration() +bool AceStep::requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath, + QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath, + QString vaeModelPath) { - currentWorker = nullptr; -} - -bool AceStepWorker::songGenerateing(SongItem* song) -{ - workerMutex.lock(); - if(!currentWorker) + if(busy) { - workerMutex.unlock(); + qWarning()<<"Droping song:"<generate(), aceStepPath, textEncoderModelPath, ditModelPath, vaeModelPath}; + + QString qwen3Binary = aceStepPath + "/ace-qwen3"; + QFileInfo qwen3Info(qwen3Binary); + if (!qwen3Info.exists() || !qwen3Info.isExecutable()) { - SongItem workerSong = currentWorker->getSong(); - workerMutex.unlock(); - if(song) - *song = workerSong; - return true; + generationError("ace-qwen3 binary not found at: " + qwen3Binary); + busy = false; + return false; + } + if (!QFileInfo::exists(qwen3ModelPath)) + { + generationError("Qwen3 model not found: " + qwen3ModelPath); + busy = false; + return false; + } + if (!QFileInfo::exists(textEncoderModelPath)) + { + generationError("Text encoder model not found: " + textEncoderModelPath); + busy = false; + return false; + } + if (!QFileInfo::exists(ditModelPath)) + { + generationError("DiT model not found: " + ditModelPath); + busy = false; + return false; + } + if (!QFileInfo::exists(vaeModelPath)) + { + generationError("VAE model not found: " + vaeModelPath); + busy = false; + return false; } -} -// 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"; + request.requestFilePath = tempDir + "/request_" + QString::number(request.uid) + ".json"; - // Parse and modify the template QJsonParseError parseError; - QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError); + QJsonDocument templateDoc = QJsonDocument::fromJson(requestTemplate.toUtf8(), &parseError); if (!templateDoc.isObject()) { - emit parent->generationError("Invalid JSON template: " + QString(parseError.errorString())); - return; + generationError("Invalid JSON template: " + QString(parseError.errorString())); + busy = false; + return false; } 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); + QFile requestFileHandle(request.requestFilePath); if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text)) { - emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString()); - return; + emit generationError("Failed to create request file: " + requestFileHandle.errorString()); + busy = false; + return false; } - 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"; + QStringList qwen3Args; + qwen3Args << "--request" << request.requestFilePath; + qwen3Args << "--model" << qwen3ModelPath; - // Check if binaries exist - QFileInfo qwen3Info(qwen3Binary); - QFileInfo ditVaeInfo(ditVaeBinary); + progressUpdate(30); - if (!qwen3Info.exists() || !qwen3Info.isExecutable()) + qwenProcess.start(qwen3Binary, qwen3Args); + return true; +} + +void AceStep::qwenProcFinished(int code, QProcess::ExitStatus status) +{ + QFile::remove(request.requestFilePath); + if(code != 0) { - emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary); + QString errorOutput = qwenProcess.readAllStandardError(); + generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput); + busy = false; return; } + QString ditVaeBinary = request.aceStepPath + "/dit-vae"; + QFileInfo ditVaeInfo(ditVaeBinary); if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable()) { - emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary); + generationError("dit-vae binary not found at: " + ditVaeBinary); + busy = false; 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)) + request.requestLlmFilePath = tempDir + "/request_" + QString::number(request.uid) + "0.json"; + if (!QFileInfo::exists(request.requestLlmFilePath)) { - 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); + generationError("ace-qwen3 failed to create enhaced request file "+request.requestLlmFilePath); + busy = false; return; } // Load lyrics from the enhanced request file - QFile lmOutputFile(requestLmOutputFile); + QFile lmOutputFile(request.requestLlmFilePath); if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QJsonParseError parseError; - song.json = lmOutputFile.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(song.json.toUtf8(), &parseError); + request.song.json = lmOutputFile.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(request.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(); - } + request.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; + ditVaeArgs << "--request"<progressUpdate(60); + 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) +void AceStep::ditProcFinished(int code, QProcess::ExitStatus status) +{ + QFile::remove(request.requestLlmFilePath); + if (code != 0) { QString errorOutput = ditVaeProcess.readAllStandardError(); - emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput); + generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput); + busy = false; return; } - emit parent->progressUpdate(90); - // Find the generated WAV file - QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav"; + QString wavFile = tempDir+"/request_" + QString::number(request.uid) + "00.wav"; if (!QFileInfo::exists(wavFile)) { - emit parent->generationError("No WAV file generated at "+wavFile); + generationError("No WAV file generated at "+wavFile); + busy = false; return; } + busy = false; - // 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(); + progressUpdate(100); + request.song.file = wavFile; + songGenerated(request.song); } -const SongItem& AceStepWorker::Worker::getSong() -{ - return song; -} diff --git a/src/AceStepWorker.h b/src/AceStepWorker.h index fa7bbb8..41c4676 100644 --- a/src/AceStepWorker.h +++ b/src/AceStepWorker.h @@ -2,64 +2,55 @@ #define ACESTEPWORKER_H #include -#include -#include #include -#include -#include +#include +#include #include "SongItem.h" -class AceStepWorker : public QObject +class AceStep : public QObject { Q_OBJECT -public: - explicit AceStepWorker(QObject *parent = nullptr); - ~AceStepWorker(); + QProcess qwenProcess; + QProcess ditVaeProcess; - 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); + bool busy = false; -signals: - void songGenerated(const SongItem& song); - void generationFinished(); - void generationError(const QString &error); - void progressUpdate(int percent); - -private: - class Worker : public QRunnable + struct Request { - 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; + uint64_t uid; QString aceStepPath; - QString qwen3ModelPath; QString textEncoderModelPath; QString ditModelPath; QString vaeModelPath; + QString requestFilePath; + QString requestLlmFilePath; }; - QMutex workerMutex; - Worker *currentWorker; + Request request; + + const QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + +signals: + void songGenerated(SongItem song); + void generationCancled(SongItem song); + void generationError(QString error); + void progressUpdate(int progress); + +public slots: + bool requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath, + QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath, + QString vaeModelPath); + +public: + AceStep(QObject* parent = nullptr); + bool isGenerateing(SongItem* song = nullptr); + void cancleGenerateion(); + +private slots: + void qwenProcFinished(int code, QProcess::ExitStatus status); + void ditProcFinished(int code, QProcess::ExitStatus status); }; #endif // ACESTEPWORKER_H diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 9480bcf..683d771 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -18,13 +18,15 @@ MainWindow::MainWindow(QWidget *parent) ui(new Ui::MainWindow), songModel(new SongListModel(this)), audioPlayer(new AudioPlayer(this)), - aceStepWorker(new AceStepWorker(this)), + aceStep(new AceStep(this)), playbackTimer(new QTimer(this)), isPlaying(false), isPaused(false), shuffleMode(false), isGeneratingNext(false) { + aceStep->moveToThread(&aceThread); + ui->setupUi(this); // Setup lyrics display @@ -57,9 +59,10 @@ MainWindow::MainWindow(QWidget *parent) 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(aceStep, &AceStep::songGenerated, this, &MainWindow::songGenerated); + connect(aceStep, &AceStep::generationCancled, this, &MainWindow::generationCanceld); + connect(aceStep, &AceStep::generationError, this, &MainWindow::generationError); + connect(aceStep, &AceStep::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); @@ -269,6 +272,10 @@ void MainWindow::on_shuffleButton_clicked() { shuffleMode = ui->shuffleButton->isChecked(); updateControls(); + + flushGenerationQueue(); + if(isPlaying) + ensureSongsInQueue(); } void MainWindow::on_addSongButton_clicked() @@ -426,6 +433,11 @@ void MainWindow::songGenerated(const SongItem& song) ensureSongsInQueue(); } +void MainWindow::generationCanceld(const SongItem& song) +{ + ui->statusbar->showMessage("Geneartion cancled: " + song.caption); +} + void MainWindow::playNextSong() { if (!isPlaying) @@ -505,7 +517,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent) SongItem lastSong; SongItem workerSong; - if(aceStepWorker->songGenerateing(&workerSong)) + if(aceStep->isGenerateing(&workerSong)) lastSong = workerSong; else if(!generatedSongQueue.empty()) lastSong = generatedSongQueue.last(); @@ -526,7 +538,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent) isGeneratingNext = true; ui->statusbar->showMessage("Generateing: "+nextSong.caption); - aceStepWorker->generateSong(nextSong, jsonTemplate, + aceStep->requestGeneration(nextSong, jsonTemplate, aceStepPath, qwen3ModelPath, textEncoderModelPath, ditModelPath, vaeModelPath); @@ -535,7 +547,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent) void MainWindow::flushGenerationQueue() { generatedSongQueue.clear(); - aceStepWorker->cancelGeneration(); + aceStep->cancleGenerateion(); isGeneratingNext = false; } diff --git a/src/MainWindow.h b/src/MainWindow.h index 6a3bfd3..de7860d 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -47,6 +47,7 @@ private slots: void on_songListView_doubleClicked(const QModelIndex &index); void songGenerated(const SongItem& song); + void generationCanceld(const SongItem& song); void playNextSong(); void playbackStarted(); void updatePlaybackStatus(bool playing); @@ -64,7 +65,8 @@ private: Ui::MainWindow *ui; SongListModel *songModel; AudioPlayer *audioPlayer; - AceStepWorker *aceStepWorker; + QThread aceThread; + AceStep *aceStep; QTimer *playbackTimer; QString formatTime(int milliseconds); diff --git a/src/main.cpp b/src/main.cpp index c7bbeb7..c51ac1f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ -#include "MainWindow.h" #include -#include -#include +#include + +#include "MainWindow.h" int main(int argc, char *argv[]) {