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

View file

@ -11,44 +11,46 @@
#include <cstdint>
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();

View file

@ -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

View file

@ -6,113 +6,120 @@
#include <QJsonParseError>
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);
}
}

View file

@ -4,43 +4,44 @@
#include <QDialog>
#include <QString>
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

View file

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

View file

@ -2,113 +2,128 @@
#include <QDebug>
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());
}
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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<SongItem> 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<SongItem> &songs);
bool loadPlaylistFromJson(const QString &filePath, QList<SongItem> &songs);
void setupUI();
bool savePlaylistToJson(const QString &filePath, const QList<SongItem> &songs);
bool loadPlaylistFromJson(const QString &filePath, QList<SongItem> &songs);
void setupUI();
void updateControls();
void ensureSongsInQueue(bool enqeueCurrent = false);
void flushGenerationQueue();
void flushGenerationQueue();
};
#endif // MAINWINDOW_H

View file

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

View file

@ -3,75 +3,82 @@
#include <QMessageBox>
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();
}

View file

@ -4,28 +4,30 @@
#include <QDialog>
#include <QString>
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

View file

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

View file

@ -3,18 +3,20 @@
#include <QRandomGenerator>
#include <cstdint>
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();
}
};

View file

@ -7,167 +7,179 @@
#include <QUuid>
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
}

View file

@ -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<SongItem> songList;
int m_playingIndex;
QList<SongItem> songList;
int m_playingIndex;
};
#endif // SONGLISTMODEL_H

View file

@ -2,59 +2,65 @@
#include <QStyle>
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);
}

View file

@ -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

View file

@ -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();
}