diff --git a/CMakeLists.txt b/CMakeLists.txt index c3dcea3..7a808f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,6 @@ add_executable(${PROJECT_NAME} src/clickableslider.cpp ${MusicGeneratorGUI_H} res/resources.qrc - src/elidedlabel.h src/elidedlabel.cpp ) # UI file @@ -61,7 +60,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE install(TARGETS ${PROJECT_NAME} DESTINATION bin) # Install .desktop file -install(FILES res/xyz.uvos.aceradio.desktop DESTINATION share/applications) +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) diff --git a/README.md b/README.md index 2333021..1daa6c7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Screenshot](res/scrot.png) -A C++ Qt graphical user interface for generating music using [acestep.cpp](https://github.com/ServeurpersoCom/acestep.cpp). +A C++ Qt graphical user interface for generating music using acestep.cpp. ## Requirements @@ -26,13 +26,13 @@ make -j$(nproc) ### Build the GUI: ```bash -git clone https://github.com/IMbackK/aceradio.git +git clone git@github.com:IMbackK/aceradio.git cd aceradio mkdir build && cd build cmake .. make -j$(nproc) ``` -## Setup Paths: +### Setup Paths: Go to settings->Ace Step->Model Paths and add the paths to the acestep.cpp binaries the models. diff --git a/res/xyz.uvos.aceradio.desktop b/aceradio.desktop similarity index 100% rename from res/xyz.uvos.aceradio.desktop rename to aceradio.desktop diff --git a/src/AceStepWorker.cpp b/src/AceStepWorker.cpp index e318e8f..3da8847 100644 --- a/src/AceStepWorker.cpp +++ b/src/AceStepWorker.cpp @@ -8,205 +8,269 @@ #include #include #include +#include -AceStep::AceStep(QObject* parent): QObject(parent) +AceStepWorker::AceStepWorker(QObject *parent) + : QObject(parent), + currentWorker(nullptr) { - connect(&qwenProcess, &QProcess::finished, this, &AceStep::qwenProcFinished); - connect(&ditVaeProcess, &QProcess::finished, this, &AceStep::ditProcFinished); } -bool AceStep::isGenerateing(SongItem* song) +AceStepWorker::~AceStepWorker() { - if(!busy && song) - *song = this->request.song; - return busy; + cancelGeneration(); } -void AceStep::cancleGenerateion() +void AceStepWorker::generateSong(const SongItem& song, const QString &jsonTemplate, + const QString &aceStepPath, const QString &qwen3ModelPath, + const QString &textEncoderModelPath, const QString &ditModelPath, + const QString &vaeModelPath) { - qwenProcess.blockSignals(true); - qwenProcess.terminate(); - qwenProcess.waitForFinished(); - qwenProcess.blockSignals(false); + // Cancel any ongoing generation + cancelGeneration(); - ditVaeProcess.blockSignals(true); - ditVaeProcess.terminate(); - ditVaeProcess.waitForFinished(); - ditVaeProcess.blockSignals(false); - - progressUpdate(100); - if(busy) - generationCancled(request.song); - - busy = false; + // Create worker and start it + currentWorker = new Worker(this, song, jsonTemplate, aceStepPath, qwen3ModelPath, + textEncoderModelPath, ditModelPath, vaeModelPath); + currentWorker->setAutoDelete(true); + QThreadPool::globalInstance()->start(currentWorker); } -bool AceStep::requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath, - QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath, - QString vaeModelPath) +void AceStepWorker::cancelGeneration() { - if(busy) - { - qWarning()<<"Droping song:"<generate(), aceStepPath, textEncoderModelPath, ditModelPath, vaeModelPath}; +bool AceStepWorker::songGenerateing(SongItem* song) +{ + workerMutex.lock(); + if(!currentWorker) + { + workerMutex.unlock(); + return false; + } + else + { + SongItem workerSong = currentWorker->getSong(); + workerMutex.unlock(); + if(song) + *song = workerSong; + return true; + } +} - QString qwen3Binary = aceStepPath + "/ace-qwen3"; - QFileInfo qwen3Info(qwen3Binary); - if (!qwen3Info.exists() || !qwen3Info.isExecutable()) - { - 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; - } - - request.requestFilePath = tempDir + "/request_" + QString::number(request.uid) + ".json"; +// 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(requestTemplate.toUtf8(), &parseError); + QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError); if (!templateDoc.isObject()) { - generationError("Invalid JSON template: " + QString(parseError.errorString())); - busy = false; - return false; + 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(request.requestFilePath); + QFile requestFileHandle(requestFile); if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text)) { - emit generationError("Failed to create request file: " + requestFileHandle.errorString()); - busy = false; - return false; + emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString()); + return; } + requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented)); requestFileHandle.close(); - QStringList qwen3Args; - qwen3Args << "--request" << request.requestFilePath; - qwen3Args << "--model" << qwen3ModelPath; + // Use provided paths for acestep.cpp binaries + QString qwen3Binary = this->aceStepPath + "/ace-qwen3"; + QString ditVaeBinary = this->aceStepPath + "/dit-vae"; - progressUpdate(30); + // Check if binaries exist + QFileInfo qwen3Info(qwen3Binary); + QFileInfo ditVaeInfo(ditVaeBinary); - qwenProcess.start(qwen3Binary, qwen3Args); - return true; -} - -void AceStep::qwenProcFinished(int code, QProcess::ExitStatus status) -{ - QFile::remove(request.requestFilePath); - if(code != 0) + if (!qwen3Info.exists() || !qwen3Info.isExecutable()) { - QString errorOutput = qwenProcess.readAllStandardError(); - generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput); - busy = false; + emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary); return; } - QString ditVaeBinary = request.aceStepPath + "/dit-vae"; - QFileInfo ditVaeInfo(ditVaeBinary); if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable()) { - generationError("dit-vae binary not found at: " + ditVaeBinary); - busy = false; + emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary); return; } - request.requestLlmFilePath = tempDir + "/request_" + QString::number(request.uid) + "0.json"; - if (!QFileInfo::exists(request.requestLlmFilePath)) + // Use provided model paths + QString qwen3Model = this->qwen3ModelPath; + QString textEncoderModel = this->textEncoderModelPath; + QString ditModel = this->ditModelPath; + QString vaeModel = this->vaeModelPath; + + if (!QFileInfo::exists(qwen3Model)) { - generationError("ace-qwen3 failed to create enhaced request file "+request.requestLlmFilePath); - busy = false; + 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(request.requestLlmFilePath); + QFile lmOutputFile(requestLmOutputFile); if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QJsonParseError parseError; - request.song.json = lmOutputFile.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(request.song.json.toUtf8(), &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()) - request.song.lyrics = obj["lyrics"].toString(); + { + song.lyrics = obj["lyrics"].toString(); + } } } - // Step 2: Run dit-vae to generate audio - QStringList ditVaeArgs; - ditVaeArgs << "--request"<progressUpdate(50); - progressUpdate(60); + // 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; + } -void AceStep::ditProcFinished(int code, QProcess::ExitStatus status) -{ - QFile::remove(request.requestLlmFilePath); - if (code != 0) + 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(); - generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput); - busy = false; + emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput); return; } + emit parent->progressUpdate(90); + // Find the generated WAV file - QString wavFile = tempDir+"/request_" + QString::number(request.uid) + "00.wav"; + QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav"; if (!QFileInfo::exists(wavFile)) { - generationError("No WAV file generated at "+wavFile); - busy = false; + emit parent->generationError("No WAV file generated at "+wavFile); return; } - busy = false; - progressUpdate(100); - request.song.file = wavFile; - songGenerated(request.song); + // 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(); } +const SongItem& AceStepWorker::Worker::getSong() +{ + return song; +} diff --git a/src/AceStepWorker.h b/src/AceStepWorker.h index 41c4676..fa7bbb8 100644 --- a/src/AceStepWorker.h +++ b/src/AceStepWorker.h @@ -2,55 +2,64 @@ #define ACESTEPWORKER_H #include +#include +#include #include -#include -#include +#include +#include #include "SongItem.h" -class AceStep : public QObject +class AceStepWorker : public QObject { Q_OBJECT - QProcess qwenProcess; - QProcess ditVaeProcess; +public: + explicit AceStepWorker(QObject *parent = nullptr); + ~AceStepWorker(); - bool busy = false; + 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); - struct Request +signals: + 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; + + const SongItem& getSong(); + + private: + AceStepWorker *parent; SongItem song; - uint64_t uid; + QString jsonTemplate; QString aceStepPath; + QString qwen3ModelPath; QString textEncoderModelPath; QString ditModelPath; QString vaeModelPath; - QString requestFilePath; - QString requestLlmFilePath; }; - 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); + QMutex workerMutex; + Worker *currentWorker; }; #endif // ACESTEPWORKER_H diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 62c8a63..0ce0b14 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -18,15 +18,13 @@ MainWindow::MainWindow(QWidget *parent) ui(new Ui::MainWindow), songModel(new SongListModel(this)), audioPlayer(new AudioPlayer(this)), - aceStep(new AceStep(this)), + aceStepWorker(new AceStepWorker(this)), playbackTimer(new QTimer(this)), isPlaying(false), isPaused(false), shuffleMode(false), isGeneratingNext(false) { - aceStep->moveToThread(&aceThread); - ui->setupUi(this); // Setup lyrics display @@ -59,10 +57,9 @@ 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(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(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); @@ -92,16 +89,14 @@ MainWindow::MainWindow(QWidget *parent) ui->songListView->setCurrentIndex(firstIndex); } - ui->nowPlayingLabel->setText("Now Playing:"); - currentSong = songModel->getSong(0); } MainWindow::~MainWindow() { - aceStep->cancleGenerateion(); - + // Auto-save playlist before closing autoSavePlaylist(); + saveSettings(); delete ui; } @@ -130,8 +125,6 @@ void MainWindow::loadSettings() { QSettings settings("MusicGenerator", "AceStepGUI"); - isFirstRun = settings.value("firstRun", true).toBool(); - // 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(); @@ -167,8 +160,6 @@ void MainWindow::saveSettings() settings.setValue("textEncoderModelPath", textEncoderModelPath); settings.setValue("ditModelPath", ditModelPath); settings.setValue("vaeModelPath", vaeModelPath); - - settings.setValue("firstRun", false); } QString MainWindow::formatTime(int milliseconds) @@ -276,10 +267,6 @@ void MainWindow::on_shuffleButton_clicked() { shuffleMode = ui->shuffleButton->isChecked(); updateControls(); - - flushGenerationQueue(); - if(isPlaying) - ensureSongsInQueue(); } void MainWindow::on_addSongButton_clicked() @@ -307,12 +294,16 @@ 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(); @@ -323,13 +314,14 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index) updateControls(); } + // Flush the generation queue when user selects a different song flushGenerationQueue(); - ui->nowPlayingLabel->setText("Now Playing: Waiting for generation..."); 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); @@ -340,12 +332,14 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index) 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); } @@ -401,6 +395,7 @@ void MainWindow::on_advancedSettingsButton_clicked() vaeModelPath = dialog.getVAEModelPath(); saveSettings(); + QMessageBox::information(this, "Settings Saved", "Advanced settings have been saved successfully."); } } @@ -431,16 +426,11 @@ void MainWindow::songGenerated(const SongItem& song) { generatedSongQueue.enqueue(song); } - ui->statusbar->showMessage("idle"); + ui->statusLabel->setText("idle"); ensureSongsInQueue(); } -void MainWindow::generationCanceld(const SongItem& song) -{ - ui->statusbar->showMessage("Geneartion cancled: " + song.caption); -} - void MainWindow::playNextSong() { if (!isPlaying) @@ -520,7 +510,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent) SongItem lastSong; SongItem workerSong; - if(aceStep->isGenerateing(&workerSong)) + if(aceStepWorker->songGenerateing(&workerSong)) lastSong = workerSong; else if(!generatedSongQueue.empty()) lastSong = generatedSongQueue.last(); @@ -540,8 +530,8 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent) isGeneratingNext = true; - ui->statusbar->showMessage("Generateing: "+nextSong.caption); - aceStep->requestGeneration(nextSong, jsonTemplate, + ui->statusLabel->setText("Generateing: "+nextSong.caption); + aceStepWorker->generateSong(nextSong, jsonTemplate, aceStepPath, qwen3ModelPath, textEncoderModelPath, ditModelPath, vaeModelPath); @@ -550,7 +540,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent) void MainWindow::flushGenerationQueue() { generatedSongQueue.clear(); - aceStep->cancleGenerateion(); + aceStepWorker->cancelGeneration(); isGeneratingNext = false; } @@ -807,10 +797,3 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList & return true; } - -void MainWindow::show() -{ - QMainWindow::show(); - if(isFirstRun) - QMessageBox::information(this, "Welcome", "Welcome to AceStepGUI! Please configure paths in Settings→Ace Step before generateing your first song."); -} diff --git a/src/MainWindow.h b/src/MainWindow.h index 54136f7..6a3bfd3 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -27,41 +27,10 @@ class MainWindow : public QMainWindow { Q_OBJECT - Ui::MainWindow *ui; - SongListModel *songModel; - AudioPlayer *audioPlayer; - QThread aceThread; - AceStep *aceStep; - QTimer *playbackTimer; - - QString formatTime(int milliseconds); - - SongItem currentSong; - bool isPlaying; - bool isPaused; - bool shuffleMode; - bool isGeneratingNext; - bool isFirstRun; - 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; - public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); -public slots: - void show(); - private slots: void on_playButton_clicked(); void on_pauseButton_clicked(); @@ -78,7 +47,6 @@ 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); @@ -89,6 +57,36 @@ private slots: void on_actionAppendPlaylist(); void on_actionSaveSong(); +private: + void startNextSongGeneration(); + +private: + 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 + static constexpr int generationTresh = 2; + QQueue generatedSongQueue; + private: void loadSettings(); void saveSettings(); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 828189b..d67fb70 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -1,361 +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 - - - - - - - - - - - 0 - 20 - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - - - - 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
-
- - ElidedLabel - QFrame -
src/elidedlabel.h
- 1 -
-
- - - - -
+ + 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/SongListModel.cpp b/src/SongListModel.cpp index caa856a..c658f64 100644 --- a/src/SongListModel.cpp +++ b/src/SongListModel.cpp @@ -193,7 +193,11 @@ int SongListModel::findNextIndex(int currentIndex, bool shuffle) const return -1; if (shuffle) - return QRandomGenerator::global()->bounded(songList.size()); + { + // Simple random selection for shuffle mode + QRandomGenerator generator; + return generator.bounded(songList.size()); + } // Sequential playback int nextIndex = currentIndex + 1; diff --git a/src/elidedlabel.cpp b/src/elidedlabel.cpp deleted file mode 100644 index 191a0fd..0000000 --- a/src/elidedlabel.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "elidedlabel.h" - -#include -#include - -ElidedLabel::ElidedLabel(const QString &text, QWidget *parent) - : QFrame(parent) - , elided(false) - , content(text) -{ - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); -} - -ElidedLabel::ElidedLabel(QWidget *parent) - : QFrame(parent) - , elided(false) - , content("") -{ - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); -} - -void ElidedLabel::setText(const QString &newText) -{ - content = newText; - update(); -} - -void ElidedLabel::paintEvent(QPaintEvent *event) -{ - QFrame::paintEvent(event); - - QPainter painter(this); - QFontMetrics fontMetrics = painter.fontMetrics(); - - bool didElide = false; - int lineSpacing = fontMetrics.lineSpacing(); - int y = 0; - - QTextLayout textLayout(content, painter.font()); - textLayout.beginLayout(); - forever { - QTextLine line = textLayout.createLine(); - - if (!line.isValid()) - break; - - line.setLineWidth(width()); - int nextLineY = y + lineSpacing; - - if (height() >= nextLineY + lineSpacing) { - line.draw(&painter, QPoint(0, y)); - y = nextLineY; - //! [2] - //! [3] - } else { - QString lastLine = content.mid(line.textStart()); - QString elidedLastLine = fontMetrics.elidedText(lastLine, Qt::ElideRight, width()); - painter.drawText(QPoint(0, y + fontMetrics.ascent()), elidedLastLine); - line = textLayout.createLine(); - didElide = line.isValid(); - break; - } - } - textLayout.endLayout(); - - if (didElide != elided) { - elided = didElide; - emit elisionChanged(didElide); - } -} diff --git a/src/elidedlabel.h b/src/elidedlabel.h deleted file mode 100644 index 76df007..0000000 --- a/src/elidedlabel.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef ELIDEDLABEL_H -#define ELIDEDLABEL_H - -#include - -class ElidedLabel : public QFrame -{ - Q_OBJECT - Q_PROPERTY(QString text READ text WRITE setText) - Q_PROPERTY(bool isElided READ isElided) - -public: - explicit ElidedLabel(const QString &text, QWidget *parent = 0); - explicit ElidedLabel(QWidget *parent = 0); - - void setText(const QString &text); - const QString & text() const { return content; } - bool isElided() const { return elided; } - -protected: - void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; - -signals: - void elisionChanged(bool elided); - -private: - bool elided; - QString content; -}; - - -#endif // ELIDEDLABEL_H diff --git a/src/main.cpp b/src/main.cpp index c51ac1f..c7bbeb7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ -#include -#include - #include "MainWindow.h" +#include +#include +#include int main(int argc, char *argv[]) {