From 4cacaa04e4e43bbdfded1752a1059f9331ebe241 Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Sat, 21 Mar 2026 23:09:39 +0100 Subject: [PATCH] Refactor json handling for SongItems and add new fields. --- CMakeLists.txt | 2 + src/AceStepWorker.cpp | 9 +--- src/MainWindow.cpp | 49 +++---------------- src/SongDialog.cpp | 111 +++++++++++++++++++++++++++++------------- src/SongDialog.h | 11 ++--- src/SongDialog.ui | 67 ++++++++++++++++++++----- src/SongItem.cpp | 43 ++++++++++++++++ src/SongItem.h | 19 +++++--- src/SongListModel.cpp | 20 ++++++++ src/SongListModel.h | 1 + 10 files changed, 224 insertions(+), 108 deletions(-) create mode 100644 src/SongItem.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a542e75..191c922 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,8 @@ add_executable(${PROJECT_NAME} ${MusicGeneratorGUI_H} res/resources.qrc src/elidedlabel.h src/elidedlabel.cpp + src/SongItem.cpp + ) # UI file diff --git a/src/AceStepWorker.cpp b/src/AceStepWorker.cpp index 837247b..50d940c 100644 --- a/src/AceStepWorker.cpp +++ b/src/AceStepWorker.cpp @@ -102,14 +102,7 @@ bool AceStep::requestGeneration(SongItem song, QString requestTemplate, QString } QJsonObject requestObj = templateDoc.object(); - requestObj["caption"] = song.caption; - - if (!song.lyrics.isEmpty()) - requestObj["lyrics"] = song.lyrics; - if (!song.vocalLanguage.isEmpty()) - requestObj["vocal_language"] = song.vocalLanguage; - - requestObj["use_cot_caption"] = song.cotCaption; + song.store(requestObj); // Write the request file QFile requestFileHandle(request.requestFilePath); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index d2dd51d..d09e473 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -291,12 +291,7 @@ void MainWindow::on_addSongButton_clicked() if (dialog.exec() == QDialog::Accepted) { - QString caption = dialog.getCaption(); - QString lyrics = dialog.getLyrics(); - QString vocalLanguage = dialog.getVocalLanguage(); - - SongItem newSong(caption, lyrics); - newSong.vocalLanguage = vocalLanguage; + SongItem newSong = dialog.getSong(); songModel->addSong(newSong); // Select the new item @@ -335,18 +330,10 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index) { SongItem song = songModel->getSong(row); - SongDialog dialog(this, song.caption, song.lyrics, song.vocalLanguage); + SongDialog dialog(this, song); if (dialog.exec() == QDialog::Accepted) - { - QString caption = dialog.getCaption(); - QString lyrics = dialog.getLyrics(); - QString vocalLanguage = dialog.getVocalLanguage(); - - 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); - } + songModel->updateSong(songModel->index(row, 1), dialog.getSong()); } connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked); @@ -695,17 +682,13 @@ bool MainWindow::savePlaylistToJson(const QString &filePath, const QList(song.uniqueId); - songObj["use_cot_caption"] = song.cotCaption; + song.store(songObj); songsArray.append(songObj); } QJsonObject rootObj; rootObj["songs"] = songsArray; - rootObj["version"] = "1.0"; + rootObj["version"] = "1.1"; QJsonDocument doc(rootObj); QByteArray jsonData = doc.toJson(); @@ -754,8 +737,7 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList & QJsonObject rootObj = doc.object(); - // Check for version compatibility - if (rootObj.contains("version") && rootObj["version"].toString() != "1.0") + if (rootObj.contains("version") && rootObj["version"].toString() != "1.0" && rootObj["version"].toString() != "1.1") { qWarning() << "Unsupported playlist version:" << rootObj["version"].toString(); return false; @@ -777,24 +759,7 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList & continue; QJsonObject songObj = value.toObject(); - SongItem song; - - if (songObj.contains("caption")) - song.caption = songObj["caption"].toString(); - - if (songObj.contains("lyrics")) - song.lyrics = songObj["lyrics"].toString(); - - if (songObj.contains("vocalLanguage")) - song.vocalLanguage = songObj["vocalLanguage"].toString(); - - if (songObj.contains("uniqueId")) - song.uniqueId = static_cast(songObj["uniqueId"].toInteger()); - else - song.uniqueId = QRandomGenerator::global()->generate64(); - - song.cotCaption = songObj["use_cot_caption"].toBool(true); - + SongItem song(songObj); songs.append(song); } diff --git a/src/SongDialog.cpp b/src/SongDialog.cpp index 6f5db6b..58f4ec6 100644 --- a/src/SongDialog.cpp +++ b/src/SongDialog.cpp @@ -5,19 +5,19 @@ #include "ui_SongDialog.h" #include -SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &lyrics, const QString &vocalLanguage, bool cotEnabled) +SongDialog::SongDialog(QWidget *parent, const SongItem &song) : QDialog(parent), - ui(new Ui::SongDialog) + song(song), + ui(new Ui::SongDialog) { ui->setupUi(this); - ui->captionEdit->setPlainText(caption); - ui->lyricsEdit->setPlainText(lyrics); - - ui->checkBoxEnhanceCaption->setChecked(cotEnabled); + ui->captionEdit->setPlainText(song.caption); + ui->lyricsEdit->setPlainText(song.lyrics); + ui->checkBoxEnhanceCaption->setChecked(song.cotCaption); // Setup vocal language combo box - ui->vocalLanguageCombo->addItem("--", ""); // Unset + ui->vocalLanguageCombo->addItem("--", ""); ui->vocalLanguageCombo->addItem("English (en)", "en"); ui->vocalLanguageCombo->addItem("Chinese (zh)", "zh"); ui->vocalLanguageCombo->addItem("Japanese (ja)", "ja"); @@ -70,19 +70,69 @@ SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &l ui->vocalLanguageCombo->addItem("Cantonese (yue)", "yue"); ui->vocalLanguageCombo->addItem("Unknown", "unknown"); - // Set current language if provided - if (!vocalLanguage.isEmpty()) + ui->keyScaleCombo->addItem("--"); + ui->keyScaleCombo->addItem("C major"); + ui->keyScaleCombo->addItem("C# major"); + ui->keyScaleCombo->addItem("D major"); + ui->keyScaleCombo->addItem("D# major"); + ui->keyScaleCombo->addItem("E major"); + ui->keyScaleCombo->addItem("F major"); + ui->keyScaleCombo->addItem("F# major"); + ui->keyScaleCombo->addItem("G major"); + ui->keyScaleCombo->addItem("G# major"); + ui->keyScaleCombo->addItem("A major"); + ui->keyScaleCombo->addItem("A# major"); + ui->keyScaleCombo->addItem("B major"); + ui->keyScaleCombo->addItem("C minor"); + ui->keyScaleCombo->addItem("C# minor"); + ui->keyScaleCombo->addItem("D minor"); + ui->keyScaleCombo->addItem("D# minor"); + ui->keyScaleCombo->addItem("E minor"); + ui->keyScaleCombo->addItem("F minor"); + ui->keyScaleCombo->addItem("F# minor"); + ui->keyScaleCombo->addItem("G minor"); + ui->keyScaleCombo->addItem("G# minor"); + ui->keyScaleCombo->addItem("A minor"); + ui->keyScaleCombo->addItem("A# minor"); + ui->keyScaleCombo->addItem("B minor"); + + if (!song.vocalLanguage.isEmpty()) { - int index = ui->vocalLanguageCombo->findData(vocalLanguage); + int index = ui->vocalLanguageCombo->findData(song.vocalLanguage); if (index >= 0) { ui->vocalLanguageCombo->setCurrentIndex(index); } + else + { + ui->vocalLanguageCombo->addItem(song.vocalLanguage); + ui->vocalLanguageCombo->setCurrentIndex(ui->vocalLanguageCombo->count()-1); + } } else { - ui->vocalLanguageCombo->setCurrentIndex(0); // Default to unset + ui->vocalLanguageCombo->setCurrentIndex(0); } + + if (!song.key.isEmpty()) + { + int index = ui->keyScaleCombo->findText(song.key); + if (index >= 0) + { + ui->keyScaleCombo->setCurrentIndex(index); + } + else + { + ui->keyScaleCombo->addItem(song.key); + ui->keyScaleCombo->setCurrentIndex(ui->keyScaleCombo->count()-1); + } + } + else + { + ui->keyScaleCombo->setCurrentIndex(0); + } + + ui->bpmSpinBox->setValue(song.bpm); } SongDialog::~SongDialog() @@ -90,30 +140,10 @@ SongDialog::~SongDialog() delete ui; } -QString SongDialog::getCaption() const -{ - return ui->captionEdit->toPlainText(); -} - -QString SongDialog::getLyrics() const -{ - return ui->lyricsEdit->toPlainText(); -} - -QString SongDialog::getVocalLanguage() const -{ - return ui->vocalLanguageCombo->currentData().toString(); -} - -bool SongDialog::getCotEnabled() const -{ - return ui->checkBoxEnhanceCaption->isChecked(); -} - void SongDialog::on_okButton_clicked() { // Validate that caption is not empty - QString caption = getCaption(); + QString caption = ui->captionEdit->toPlainText(); if (caption.trimmed().isEmpty()) { QMessageBox::warning(this, "Invalid Input", "Caption cannot be empty."); @@ -126,4 +156,19 @@ void SongDialog::on_okButton_clicked() void SongDialog::on_cancelButton_clicked() { reject(); -} \ No newline at end of file +} + +const SongItem& SongDialog::getSong() +{ + song.caption = ui->captionEdit->toPlainText(); + song.lyrics = ui->lyricsEdit->toPlainText(); + song.vocalLanguage = ui->vocalLanguageCombo->currentData().toString(); + song.cotCaption = ui->checkBoxEnhanceCaption->isChecked(); + if(ui->keyScaleCombo->currentIndex() > 0) + song.key = ui->keyScaleCombo->currentText(); + else + song.key = ""; + song.bpm = ui->bpmSpinBox->value(); + return song; +} + diff --git a/src/SongDialog.h b/src/SongDialog.h index 7230814..1ab08de 100644 --- a/src/SongDialog.h +++ b/src/SongDialog.h @@ -9,6 +9,8 @@ #include #include +#include "SongItem.h" + namespace Ui { class SongDialog; @@ -17,16 +19,13 @@ class SongDialog; class SongDialog : public QDialog { Q_OBJECT + SongItem song; public: - explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "", - const QString &vocalLanguage = "", bool cotEnabled = true); + explicit SongDialog(QWidget *parent = nullptr, const SongItem& song = SongItem()); ~SongDialog(); - QString getCaption() const; - QString getLyrics() const; - QString getVocalLanguage() const; - bool getCotEnabled() const; + const SongItem& getSong(); private slots: void on_okButton_clicked(); diff --git a/src/SongDialog.ui b/src/SongDialog.ui index 8c0710a..56e0842 100644 --- a/src/SongDialog.ui +++ b/src/SongDialog.ui @@ -58,21 +58,66 @@ - - - Vocal Language: + + + 5 - - Qt::AlignmentFlag::AlignTop - - + + + + Keyscale + + + + + + + true + + + + + + + Vocal Language: + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + + true + + + + + + + Bpm (0 for llm chooses) + + + + + + + 9999 + + + + - - - true + + + 5 - + + 0 + + diff --git a/src/SongItem.cpp b/src/SongItem.cpp new file mode 100644 index 0000000..68eef23 --- /dev/null +++ b/src/SongItem.cpp @@ -0,0 +1,43 @@ +#include "SongItem.h" + +SongItem::SongItem(const QString &caption, const QString &lyrics) + : caption(caption), lyrics(lyrics), cotCaption(true) +{ + uniqueId = QRandomGenerator::global()->generate64(); +} + +SongItem::SongItem(const QJsonObject& json) +{ + load(json); +} + +void SongItem::store(QJsonObject &json) const +{ + if(!caption.isEmpty()) + json["caption"] = caption; + if(!lyrics.isEmpty()) + json["lyrics"] = lyrics; + json["unique_id"] = QString::number(uniqueId); + json["use_cot_caption"] = cotCaption; + if(!vocalLanguage.isEmpty()) + json["vocal_language"] = vocalLanguage; + if(!key.isEmpty()) + json["keyscale"] = key; + if(bpm != 0) + json["bpm"] = static_cast(bpm); +} + +void SongItem::load(const QJsonObject &json) +{ + caption = json["caption"].toString(); + lyrics = json["lyrics"].toString(); + if(json.contains("unique_id")) + uniqueId = json["unique_id"].toString().toULongLong(); + else + uniqueId = QRandomGenerator::global()->generate64(); + cotCaption = json["use_cot_caption"].toBool(true); + vocalLanguage = json["vocal_language"].toString(); + key = json["keyscale"].toString(); + bpm = json["bpm"].toInt(0); +} + diff --git a/src/SongItem.h b/src/SongItem.h index 7084e0f..a8e993c 100644 --- a/src/SongItem.h +++ b/src/SongItem.h @@ -7,22 +7,25 @@ #include #include #include +#include class SongItem { public: QString caption; QString lyrics; - uint64_t uniqueId; - QString file; + unsigned int bpm; + QString key; QString vocalLanguage; bool cotCaption; + + uint64_t uniqueId; + QString file; QString json; - inline SongItem(const QString &caption = "", const QString &lyrics = "") - : caption(caption), lyrics(lyrics), cotCaption(true) - { - // Generate a unique ID using a cryptographically secure random number - uniqueId = QRandomGenerator::global()->generate64(); - } + SongItem(const QString &caption = "", const QString &lyrics = ""); + SongItem(const QJsonObject& json); + + void store(QJsonObject& json) const; + void load(const QJsonObject& json); }; diff --git a/src/SongListModel.cpp b/src/SongListModel.cpp index a77747a..0b40f05 100644 --- a/src/SongListModel.cpp +++ b/src/SongListModel.cpp @@ -111,6 +111,26 @@ bool SongListModel::setData(const QModelIndex &index, const QVariant &value, int return true; } +void SongListModel::updateSong(const QModelIndex &index, const SongItem& song) +{ + const SongItem &oldSong = songList[index.row()]; + + if(song.caption != oldSong.caption) + { + emit dataChanged(index, index, {CaptionRole}); + } + if(song.lyrics != oldSong.lyrics) + { + emit dataChanged(index, index, {LyricsRole}); + } + if(song.vocalLanguage != oldSong.vocalLanguage) + { + emit dataChanged(index, index, {VocalLanguageRole}); + } + + songList[index.row()] = song; +} + Qt::ItemFlags SongListModel::flags(const QModelIndex &index) const { if (!index.isValid()) diff --git a/src/SongListModel.h b/src/SongListModel.h index c13ff4c..bf1e22e 100644 --- a/src/SongListModel.h +++ b/src/SongListModel.h @@ -37,6 +37,7 @@ public: // Editable: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + void updateSong(const QModelIndex &index, const SongItem& song); Qt::ItemFlags flags(const QModelIndex& index) const override; // Add/remove songs