Reformat
This commit is contained in:
parent
10769eef09
commit
58e8345542
20 changed files with 2063 additions and 1901 deletions
|
|
@ -55,3 +55,13 @@ target_include_directories(${PROJECT_NAME} PRIVATE
|
|||
)
|
||||
|
||||
# Note: acestep.cpp binaries (ace-qwen3, dit-vae) and models should be provided at runtime
|
||||
|
||||
# Install targets
|
||||
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
||||
|
||||
# Install .desktop file
|
||||
install(FILES aceradio.desktop DESTINATION share/applications)
|
||||
|
||||
# Install icon files
|
||||
install(FILES res/xyz.uvos.aceradio.png DESTINATION share/icons/hicolor/256x256/apps RENAME xyz.uvos.aceradio.png)
|
||||
install(FILES res/xyz.uvos.aceradio.svg DESTINATION share/icons/hicolor/scalable/apps RENAME xyz.uvos.aceradio.svg)
|
||||
|
|
|
|||
|
|
@ -44,11 +44,13 @@ void AceStepWorker::cancelGeneration()
|
|||
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)
|
||||
|
|
@ -68,7 +70,8 @@ void AceStepWorker::Worker::run()
|
|||
// Parse and modify the template
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError);
|
||||
if (!templateDoc.isObject()) {
|
||||
if (!templateDoc.isObject())
|
||||
{
|
||||
emit parent->generationError("Invalid JSON template: " + QString(parseError.errorString()));
|
||||
return;
|
||||
}
|
||||
|
|
@ -76,21 +79,26 @@ void AceStepWorker::Worker::run()
|
|||
QJsonObject requestObj = templateDoc.object();
|
||||
requestObj["caption"] = song.caption;
|
||||
|
||||
if (!song.lyrics.isEmpty()) {
|
||||
if (!song.lyrics.isEmpty())
|
||||
{
|
||||
requestObj["lyrics"] = song.lyrics;
|
||||
} else {
|
||||
}
|
||||
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()) {
|
||||
if (!song.vocalLanguage.isEmpty())
|
||||
{
|
||||
requestObj["vocal_language"] = song.vocalLanguage;
|
||||
}
|
||||
|
||||
// Write the request file
|
||||
QFile requestFileHandle(requestFile);
|
||||
if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString());
|
||||
return;
|
||||
}
|
||||
|
|
@ -106,12 +114,14 @@ void AceStepWorker::Worker::run()
|
|||
QFileInfo qwen3Info(qwen3Binary);
|
||||
QFileInfo ditVaeInfo(ditVaeBinary);
|
||||
|
||||
if (!qwen3Info.exists() || !qwen3Info.isExecutable()) {
|
||||
if (!qwen3Info.exists() || !qwen3Info.isExecutable())
|
||||
{
|
||||
emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable()) {
|
||||
if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable())
|
||||
{
|
||||
emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary);
|
||||
return;
|
||||
}
|
||||
|
|
@ -122,22 +132,26 @@ void AceStepWorker::Worker::run()
|
|||
QString ditModel = this->ditModelPath;
|
||||
QString vaeModel = this->vaeModelPath;
|
||||
|
||||
if (!QFileInfo::exists(qwen3Model)) {
|
||||
if (!QFileInfo::exists(qwen3Model))
|
||||
{
|
||||
emit parent->generationError("Qwen3 model not found: " + qwen3Model);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(textEncoderModel)) {
|
||||
if (!QFileInfo::exists(textEncoderModel))
|
||||
{
|
||||
emit parent->generationError("Text encoder model not found: " + textEncoderModel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(ditModel)) {
|
||||
if (!QFileInfo::exists(ditModel))
|
||||
{
|
||||
emit parent->generationError("DiT model not found: " + ditModel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(vaeModel)) {
|
||||
if (!QFileInfo::exists(vaeModel))
|
||||
{
|
||||
emit parent->generationError("VAE model not found: " + vaeModel);
|
||||
return;
|
||||
}
|
||||
|
|
@ -151,12 +165,14 @@ void AceStepWorker::Worker::run()
|
|||
emit parent->progressUpdate(20);
|
||||
|
||||
qwen3Process.start(qwen3Binary, qwen3Args);
|
||||
if (!qwen3Process.waitForStarted()) {
|
||||
if (!qwen3Process.waitForStarted())
|
||||
{
|
||||
emit parent->generationError("Failed to start ace-qwen3: " + qwen3Process.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!qwen3Process.waitForFinished(60000)) { // 60 second timeout
|
||||
if (!qwen3Process.waitForFinished(60000)) // 60 second timeout
|
||||
{
|
||||
qwen3Process.terminate();
|
||||
qwen3Process.waitForFinished(5000);
|
||||
emit parent->generationError("ace-qwen3 timed out after 60 seconds");
|
||||
|
|
@ -164,29 +180,34 @@ void AceStepWorker::Worker::run()
|
|||
}
|
||||
|
||||
int exitCode = qwen3Process.exitCode();
|
||||
if (exitCode != 0) {
|
||||
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)) {
|
||||
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)) {
|
||||
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) {
|
||||
if (doc.isObject() && !parseError.error)
|
||||
{
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.contains("lyrics") && obj["lyrics"].isString()) {
|
||||
if (obj.contains("lyrics") && obj["lyrics"].isString())
|
||||
{
|
||||
song.lyrics = obj["lyrics"].toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -205,12 +226,14 @@ void AceStepWorker::Worker::run()
|
|||
emit parent->progressUpdate(60);
|
||||
|
||||
ditVaeProcess.start(ditVaeBinary, ditVaeArgs);
|
||||
if (!ditVaeProcess.waitForStarted()) {
|
||||
if (!ditVaeProcess.waitForStarted())
|
||||
{
|
||||
emit parent->generationError("Failed to start dit-vae: " + ditVaeProcess.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ditVaeProcess.waitForFinished(120000)) { // 2 minute timeout
|
||||
if (!ditVaeProcess.waitForFinished(120000)) // 2 minute timeout
|
||||
{
|
||||
ditVaeProcess.terminate();
|
||||
ditVaeProcess.waitForFinished(5000);
|
||||
emit parent->generationError("dit-vae timed out after 2 minutes");
|
||||
|
|
@ -218,7 +241,8 @@ void AceStepWorker::Worker::run()
|
|||
}
|
||||
|
||||
exitCode = ditVaeProcess.exitCode();
|
||||
if (exitCode != 0) {
|
||||
if (exitCode != 0)
|
||||
{
|
||||
QString errorOutput = ditVaeProcess.readAllStandardError();
|
||||
emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput);
|
||||
return;
|
||||
|
|
@ -228,7 +252,8 @@ void AceStepWorker::Worker::run()
|
|||
|
||||
// Find the generated WAV file
|
||||
QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav";
|
||||
if (!QFileInfo::exists(wavFile)) {
|
||||
if (!QFileInfo::exists(wavFile))
|
||||
{
|
||||
emit parent->generationError("No WAV file generated at "+wavFile);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ signals:
|
|||
void progressUpdate(int percent);
|
||||
|
||||
private:
|
||||
class Worker : public QRunnable {
|
||||
class Worker : public QRunnable
|
||||
{
|
||||
public:
|
||||
Worker(AceStepWorker *parent, const SongItem& song, const QString &jsonTemplate,
|
||||
const QString &aceStepPath, const QString &qwen3ModelPath,
|
||||
|
|
|
|||
|
|
@ -80,23 +80,28 @@ void AdvancedSettingsDialog::setVAEModelPath(const QString &path)
|
|||
void AdvancedSettingsDialog::on_aceStepBrowseButton_clicked()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this, "Select AceStep Build Directory", ui->aceStepPathEdit->text());
|
||||
if (!dir.isEmpty()) {
|
||||
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()) {
|
||||
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()) {
|
||||
QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(),
|
||||
"GGUF Files (*.gguf)");
|
||||
if (!file.isEmpty())
|
||||
{
|
||||
ui->textEncoderEdit->setText(file);
|
||||
}
|
||||
}
|
||||
|
|
@ -104,7 +109,8 @@ void AdvancedSettingsDialog::on_textEncoderBrowseButton_clicked()
|
|||
void AdvancedSettingsDialog::on_ditBrowseButton_clicked()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)");
|
||||
if (!file.isEmpty()) {
|
||||
if (!file.isEmpty())
|
||||
{
|
||||
ui->ditModelEdit->setText(file);
|
||||
}
|
||||
}
|
||||
|
|
@ -112,7 +118,8 @@ void AdvancedSettingsDialog::on_ditBrowseButton_clicked()
|
|||
void AdvancedSettingsDialog::on_vaeBrowseButton_clicked()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)");
|
||||
if (!file.isEmpty()) {
|
||||
if (!file.isEmpty())
|
||||
{
|
||||
ui->vaeModelEdit->setText(file);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,8 @@
|
|||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
namespace Ui {
|
||||
namespace Ui
|
||||
{
|
||||
class AdvancedSettingsDialog;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ AudioPlayer::AudioPlayer(QObject *parent)
|
|||
|
||||
// Set up position timer for updating playback position
|
||||
positionTimer->setInterval(500); // Update every 500ms
|
||||
connect(positionTimer, &QTimer::timeout, [this]() {
|
||||
if (isPlaying()) {
|
||||
connect(positionTimer, &QTimer::timeout, [this]()
|
||||
{
|
||||
if (isPlaying())
|
||||
{
|
||||
emit positionChanged(mediaPlayer->position());
|
||||
}
|
||||
});
|
||||
|
|
@ -31,7 +33,8 @@ AudioPlayer::~AudioPlayer()
|
|||
|
||||
void AudioPlayer::play(const QString &filePath)
|
||||
{
|
||||
if (isPlaying()) {
|
||||
if (isPlaying())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +47,8 @@ void AudioPlayer::play(const QString &filePath)
|
|||
|
||||
void AudioPlayer::play()
|
||||
{
|
||||
if (!isPlaying()) {
|
||||
if (!isPlaying())
|
||||
{
|
||||
mediaPlayer->play();
|
||||
positionTimer->start();
|
||||
}
|
||||
|
|
@ -52,7 +56,8 @@ void AudioPlayer::play()
|
|||
|
||||
void AudioPlayer::pause()
|
||||
{
|
||||
if (isPlaying()) {
|
||||
if (isPlaying())
|
||||
{
|
||||
mediaPlayer->pause();
|
||||
positionTimer->stop();
|
||||
}
|
||||
|
|
@ -86,12 +91,16 @@ int AudioPlayer::position() const
|
|||
|
||||
void AudioPlayer::handlePlaybackStateChanged(QMediaPlayer::PlaybackState state)
|
||||
{
|
||||
if (state == QMediaPlayer::PlayingState) {
|
||||
if (state == QMediaPlayer::PlayingState)
|
||||
{
|
||||
emit playbackStarted();
|
||||
} else if (state == QMediaPlayer::StoppedState ||
|
||||
state == QMediaPlayer::PausedState) {
|
||||
}
|
||||
else if (state == QMediaPlayer::StoppedState ||
|
||||
state == QMediaPlayer::PausedState)
|
||||
{
|
||||
// Check if we reached the end
|
||||
if (mediaPlayer->position() >= mediaPlayer->duration() - 100) {
|
||||
if (mediaPlayer->position() >= mediaPlayer->duration() - 100)
|
||||
{
|
||||
emit playbackFinished();
|
||||
}
|
||||
}
|
||||
|
|
@ -99,16 +108,22 @@ void AudioPlayer::handlePlaybackStateChanged(QMediaPlayer::PlaybackState state)
|
|||
|
||||
void AudioPlayer::handleMediaStatusChanged(QMediaPlayer::MediaStatus status)
|
||||
{
|
||||
if (status == QMediaPlayer::EndOfMedia) {
|
||||
if (status == QMediaPlayer::EndOfMedia)
|
||||
{
|
||||
emit playbackFinished();
|
||||
} else if (status == QMediaPlayer::LoadedMedia ||
|
||||
status == QMediaPlayer::BufferedMedia) {
|
||||
}
|
||||
else if (status == QMediaPlayer::LoadedMedia ||
|
||||
status == QMediaPlayer::BufferedMedia)
|
||||
{
|
||||
// Media loaded successfully, emit duration
|
||||
int duration = mediaPlayer->duration();
|
||||
if (duration > 0) {
|
||||
if (duration > 0)
|
||||
{
|
||||
emit durationChanged(duration);
|
||||
}
|
||||
} else if (status == QMediaPlayer::InvalidMedia) {
|
||||
}
|
||||
else if (status == QMediaPlayer::InvalidMedia)
|
||||
{
|
||||
emit playbackError(mediaPlayer->errorString());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,8 +45,14 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
connect(ui->actionLoadPlaylist, &QAction::triggered, this, &MainWindow::on_actionLoadPlaylist);
|
||||
connect(ui->actionAppendPlaylist, &QAction::triggered, this, &MainWindow::on_actionAppendPlaylist);
|
||||
connect(ui->actionSaveSong, &QAction::triggered, this, &MainWindow::on_actionSaveSong);
|
||||
connect(ui->actionQuit, &QAction::triggered, this, [this](){close();});
|
||||
connect(ui->actionClearPlaylist, &QAction::triggered, this, [this](){songModel->clear();});
|
||||
connect(ui->actionQuit, &QAction::triggered, this, [this]()
|
||||
{
|
||||
close();
|
||||
});
|
||||
connect(ui->actionClearPlaylist, &QAction::triggered, this, [this]()
|
||||
{
|
||||
songModel->clear();
|
||||
});
|
||||
connect(audioPlayer, &AudioPlayer::playbackFinished, this, &MainWindow::playNextSong);
|
||||
connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted);
|
||||
connect(audioPlayer, &AudioPlayer::positionChanged, this, &MainWindow::updatePosition);
|
||||
|
|
@ -59,12 +65,14 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
||||
|
||||
// Connect audio player error signal
|
||||
connect(audioPlayer, &AudioPlayer::playbackError, [this](const QString &error) {
|
||||
connect(audioPlayer, &AudioPlayer::playbackError, [this](const QString &error)
|
||||
{
|
||||
QMessageBox::warning(this, "Playback Error", "Failed to play audio: " + error);
|
||||
});
|
||||
|
||||
// Add some default songs
|
||||
if(songModel->songCount() == 0) {
|
||||
if(songModel->songCount() == 0)
|
||||
{
|
||||
SongItem defaultSong1("Upbeat pop rock anthem with driving electric guitars", "");
|
||||
SongItem defaultSong2("Chill electronic music with smooth synths and relaxing beats", "");
|
||||
SongItem defaultSong3("Jazz fusion with saxophone solos and complex rhythms", "");
|
||||
|
|
@ -75,7 +83,8 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
}
|
||||
|
||||
// Select first item
|
||||
if (songModel->rowCount() > 0) {
|
||||
if (songModel->rowCount() > 0)
|
||||
{
|
||||
QModelIndex firstIndex = songModel->index(0, 0);
|
||||
ui->songListView->setCurrentIndex(firstIndex);
|
||||
}
|
||||
|
|
@ -117,7 +126,8 @@ void MainWindow::loadSettings()
|
|||
QSettings settings("MusicGenerator", "AceStepGUI");
|
||||
|
||||
// Load JSON template (default to simple configuration)
|
||||
jsonTemplate = settings.value("jsonTemplate", "{\n\t\"inference_steps\": 8,\n\t\"shift\": 3.0,\n\t\"vocal_language\": \"en\"\n}").toString();
|
||||
jsonTemplate = settings.value("jsonTemplate",
|
||||
"{\n\t\"inference_steps\": 8,\n\t\"shift\": 3.0,\n\t\"vocal_language\": \"en\"\n}").toString();
|
||||
|
||||
// Load shuffle mode
|
||||
shuffleMode = settings.value("shuffleMode", false).toBool();
|
||||
|
|
@ -126,8 +136,10 @@ void MainWindow::loadSettings()
|
|||
// Load path settings with defaults based on application directory
|
||||
QString appDir = QCoreApplication::applicationDirPath();
|
||||
aceStepPath = settings.value("aceStepPath", appDir + "/acestep.cpp").toString();
|
||||
qwen3ModelPath = settings.value("qwen3ModelPath", appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString();
|
||||
textEncoderModelPath = settings.value("textEncoderModelPath", appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-BF16.gguf").toString();
|
||||
qwen3ModelPath = settings.value("qwen3ModelPath",
|
||||
appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString();
|
||||
textEncoderModelPath = settings.value("textEncoderModelPath",
|
||||
appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-BF16.gguf").toString();
|
||||
ditModelPath = settings.value("ditModelPath", appDir + "/acestep.cpp/models/acestep-v15-turbo-Q8_0.gguf").toString();
|
||||
vaeModelPath = settings.value("vaeModelPath", appDir + "/acestep.cpp/models/vae-BF16.gguf").toString();
|
||||
}
|
||||
|
|
@ -197,7 +209,8 @@ void MainWindow::updateControls()
|
|||
|
||||
void MainWindow::on_playButton_clicked()
|
||||
{
|
||||
if (isPaused) {
|
||||
if (isPaused)
|
||||
{
|
||||
// Resume playback
|
||||
audioPlayer->play();
|
||||
isPaused = false;
|
||||
|
|
@ -217,7 +230,8 @@ void MainWindow::on_playButton_clicked()
|
|||
|
||||
void MainWindow::on_pauseButton_clicked()
|
||||
{
|
||||
if (isPlaying && !isPaused) {
|
||||
if (isPlaying && !isPaused)
|
||||
{
|
||||
// Pause playback
|
||||
audioPlayer->pause();
|
||||
isPaused = true;
|
||||
|
|
@ -227,7 +241,8 @@ void MainWindow::on_pauseButton_clicked()
|
|||
|
||||
void MainWindow::on_skipButton_clicked()
|
||||
{
|
||||
if (isPlaying) {
|
||||
if (isPlaying)
|
||||
{
|
||||
audioPlayer->stop();
|
||||
isPaused = false;
|
||||
playNextSong();
|
||||
|
|
@ -236,7 +251,8 @@ void MainWindow::on_skipButton_clicked()
|
|||
|
||||
void MainWindow::on_stopButton_clicked()
|
||||
{
|
||||
if (isPlaying) {
|
||||
if (isPlaying)
|
||||
{
|
||||
// Stop current playback completely
|
||||
audioPlayer->stop();
|
||||
ui->nowPlayingLabel->setText("Now Playing:");
|
||||
|
|
@ -257,7 +273,8 @@ void MainWindow::on_addSongButton_clicked()
|
|||
{
|
||||
SongDialog dialog(this);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
QString caption = dialog.getCaption();
|
||||
QString lyrics = dialog.getLyrics();
|
||||
QString vocalLanguage = dialog.getVocalLanguage();
|
||||
|
|
@ -274,7 +291,8 @@ void MainWindow::on_addSongButton_clicked()
|
|||
|
||||
void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
||||
{
|
||||
if (!index.isValid()) return;
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
||||
// Temporarily disconnect the signal to prevent multiple invocations
|
||||
// This happens when the dialog closes and triggers another double-click event
|
||||
|
|
@ -283,11 +301,15 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
|||
int row = index.row();
|
||||
|
||||
// Different behavior based on which column was clicked
|
||||
if (index.column() == 0) {
|
||||
if (index.column() == 0)
|
||||
{
|
||||
// Column 0 (play indicator): Stop current playback and play this song
|
||||
if (isPlaying) {
|
||||
if (isPlaying)
|
||||
{
|
||||
audioPlayer->stop();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
isPlaying = true;
|
||||
updateControls();
|
||||
}
|
||||
|
|
@ -296,13 +318,16 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
|||
flushGenerationQueue();
|
||||
currentSong = songModel->getSong(row);
|
||||
ensureSongsInQueue(true);
|
||||
} else if (index.column() == 1 || index.column() == 2) {
|
||||
}
|
||||
else if (index.column() == 1 || index.column() == 2)
|
||||
{
|
||||
// Column 1 (caption): Edit the song
|
||||
SongItem song = songModel->getSong(row);
|
||||
|
||||
SongDialog dialog(this, song.caption, song.lyrics, song.vocalLanguage);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
QString caption = dialog.getCaption();
|
||||
QString lyrics = dialog.getLyrics();
|
||||
QString vocalLanguage = dialog.getVocalLanguage();
|
||||
|
|
@ -321,7 +346,8 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
|||
void MainWindow::on_removeSongButton_clicked()
|
||||
{
|
||||
QModelIndex currentIndex = ui->songListView->currentIndex();
|
||||
if (!currentIndex.isValid()) return;
|
||||
if (!currentIndex.isValid())
|
||||
return;
|
||||
|
||||
// Get the row from the current selection (works with table view)
|
||||
int row = currentIndex.row();
|
||||
|
|
@ -330,7 +356,8 @@ void MainWindow::on_removeSongButton_clicked()
|
|||
|
||||
// Select next item or previous if at end
|
||||
int newRow = qMin(row, songModel->rowCount() - 1);
|
||||
if (newRow >= 0) {
|
||||
if (newRow >= 0)
|
||||
{
|
||||
QModelIndex newIndex = songModel->index(newRow, 0);
|
||||
ui->songListView->setCurrentIndex(newIndex);
|
||||
}
|
||||
|
|
@ -348,11 +375,13 @@ void MainWindow::on_advancedSettingsButton_clicked()
|
|||
dialog.setDiTModelPath(ditModelPath);
|
||||
dialog.setVAEModelPath(vaeModelPath);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
// Validate JSON template
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(dialog.getJsonTemplate().toUtf8(), &parseError);
|
||||
if (!doc.isObject()) {
|
||||
if (!doc.isObject())
|
||||
{
|
||||
QMessageBox::warning(this, "Invalid JSON", "Please enter valid JSON: " + QString(parseError.errorString()));
|
||||
return;
|
||||
}
|
||||
|
|
@ -389,10 +418,12 @@ void MainWindow::songGenerated(const SongItem& song)
|
|||
{
|
||||
isGeneratingNext = false;
|
||||
|
||||
if (!isPaused && isPlaying && !audioPlayer->isPlaying()) {
|
||||
if (!isPaused && isPlaying && !audioPlayer->isPlaying())
|
||||
{
|
||||
playSong(song);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
generatedSongQueue.enqueue(song);
|
||||
}
|
||||
ui->statusLabel->setText("idle");
|
||||
|
|
@ -406,10 +437,13 @@ void MainWindow::playNextSong()
|
|||
return;
|
||||
|
||||
// Check if we have a pre-generated next song in the queue
|
||||
if (!generatedSongQueue.isEmpty()) {
|
||||
if (!generatedSongQueue.isEmpty())
|
||||
{
|
||||
SongItem generatedSong = generatedSongQueue.dequeue();
|
||||
playSong(generatedSong);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->nowPlayingLabel->setText("Now Playing: Waiting for generation...");
|
||||
}
|
||||
|
||||
|
|
@ -460,7 +494,8 @@ void MainWindow::updatePlaybackStatus(bool playing)
|
|||
|
||||
void MainWindow::on_positionSlider_sliderMoved(int position)
|
||||
{
|
||||
if (isPlaying && audioPlayer->isPlaying()) {
|
||||
if (isPlaying && audioPlayer->isPlaying())
|
||||
{
|
||||
audioPlayer->setPosition(position);
|
||||
}
|
||||
}
|
||||
|
|
@ -468,7 +503,8 @@ void MainWindow::on_positionSlider_sliderMoved(int position)
|
|||
void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
|
||||
{
|
||||
// Only generate more songs if we're playing and not already at capacity
|
||||
if (!isPlaying || isGeneratingNext || generatedSongQueue.size() >= generationTresh) {
|
||||
if (!isPlaying || isGeneratingNext || generatedSongQueue.size() >= generationTresh)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -482,10 +518,12 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
|
|||
lastSong = currentSong;
|
||||
|
||||
SongItem nextSong;
|
||||
if(enqeueCurrent) {
|
||||
if(enqeueCurrent)
|
||||
{
|
||||
nextSong = lastSong;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
int nextIndex = songModel->findNextIndex(songModel->findSongIndexById(lastSong.uniqueId), shuffleMode);
|
||||
nextSong = songModel->getSong(nextIndex);
|
||||
}
|
||||
|
|
@ -513,7 +551,8 @@ void MainWindow::on_actionSavePlaylist()
|
|||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/playlist.json",
|
||||
"JSON Files (*.json);;All Files (*)");
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
if (!filePath.isEmpty())
|
||||
{
|
||||
savePlaylist(filePath);
|
||||
}
|
||||
}
|
||||
|
|
@ -523,7 +562,8 @@ void MainWindow::on_actionLoadPlaylist()
|
|||
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
|
||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
||||
"JSON Files (*.json);;All Files (*)");
|
||||
if (!filePath.isEmpty()) {
|
||||
if (!filePath.isEmpty())
|
||||
{
|
||||
songModel->clear();
|
||||
flushGenerationQueue();
|
||||
loadPlaylist(filePath);
|
||||
|
|
@ -535,7 +575,8 @@ void MainWindow::on_actionAppendPlaylist()
|
|||
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
|
||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
||||
"JSON Files (*.json);;All Files (*)");
|
||||
if (!filePath.isEmpty()) {
|
||||
if (!filePath.isEmpty())
|
||||
{
|
||||
loadPlaylist(filePath);
|
||||
}
|
||||
}
|
||||
|
|
@ -545,7 +586,8 @@ void MainWindow::on_actionSaveSong()
|
|||
QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist",
|
||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/song.json",
|
||||
"JSON Files (*.json);;All Files (*)");
|
||||
if (!filePath.isEmpty()) {
|
||||
if (!filePath.isEmpty())
|
||||
{
|
||||
QJsonArray songsArray;
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument songDoc = QJsonDocument::fromJson(currentSong.json.toUtf8(), &parseError);
|
||||
|
|
@ -573,7 +615,8 @@ void MainWindow::savePlaylist(const QString &filePath)
|
|||
{
|
||||
// Get current songs from the model
|
||||
QList<SongItem> songs;
|
||||
for (int i = 0; i < songModel->rowCount(); ++i) {
|
||||
for (int i = 0; i < songModel->rowCount(); ++i)
|
||||
{
|
||||
songs.append(songModel->getSong(i));
|
||||
}
|
||||
|
||||
|
|
@ -583,9 +626,11 @@ void MainWindow::savePlaylist(const QString &filePath)
|
|||
void MainWindow::loadPlaylist(const QString& filePath)
|
||||
{
|
||||
QList<SongItem> songs;
|
||||
if (loadPlaylistFromJson(filePath, songs)) {
|
||||
if (loadPlaylistFromJson(filePath, songs))
|
||||
{
|
||||
// Add loaded songs
|
||||
for (const SongItem &song : songs) {
|
||||
for (const SongItem &song : songs)
|
||||
{
|
||||
songModel->addSong(song);
|
||||
}
|
||||
}
|
||||
|
|
@ -603,7 +648,8 @@ void MainWindow::autoSavePlaylist()
|
|||
|
||||
// Get current songs from the model
|
||||
QList<SongItem> songs;
|
||||
for (int i = 0; i < songModel->rowCount(); ++i) {
|
||||
for (int i = 0; i < songModel->rowCount(); ++i)
|
||||
{
|
||||
songs.append(songModel->getSong(i));
|
||||
}
|
||||
|
||||
|
|
@ -617,9 +663,11 @@ void MainWindow::autoLoadPlaylist()
|
|||
QString filePath = appConfigPath + "/playlist.json";
|
||||
|
||||
// Check if the auto-save file exists
|
||||
if (QFile::exists(filePath)) {
|
||||
if (QFile::exists(filePath))
|
||||
{
|
||||
QList<SongItem> songs;
|
||||
if (loadPlaylistFromJson(filePath, songs)) {
|
||||
if (loadPlaylistFromJson(filePath, songs))
|
||||
{
|
||||
songModel->clear();
|
||||
for (const SongItem &song : songs)
|
||||
songModel->addSong(song);
|
||||
|
|
@ -631,7 +679,8 @@ bool MainWindow::savePlaylistToJson(const QString &filePath, const QList<SongIte
|
|||
{
|
||||
QJsonArray songsArray;
|
||||
|
||||
for (const SongItem &song : songs) {
|
||||
for (const SongItem &song : songs)
|
||||
{
|
||||
QJsonObject songObj;
|
||||
songObj["caption"] = song.caption;
|
||||
songObj["lyrics"] = song.lyrics;
|
||||
|
|
@ -648,7 +697,8 @@ bool MainWindow::savePlaylistToJson(const QString &filePath, const QList<SongIte
|
|||
QByteArray jsonData = doc.toJson();
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
qWarning() << "Could not open file for writing:" << filePath;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -662,7 +712,8 @@ bool MainWindow::savePlaylistToJson(const QString &filePath, const QList<SongIte
|
|||
bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList<SongItem> &songs)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
qWarning() << "Could not open file for reading:" << filePath;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -675,12 +726,14 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList<SongItem> &
|
|||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
qWarning() << "JSON parse error:" << parseError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!doc.isObject()) {
|
||||
if (!doc.isObject())
|
||||
{
|
||||
qWarning() << "JSON root is not an object";
|
||||
return false;
|
||||
}
|
||||
|
|
@ -688,12 +741,14 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList<SongItem> &
|
|||
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")
|
||||
{
|
||||
qWarning() << "Unsupported playlist version:" << rootObj["version"].toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rootObj.contains("songs") || !rootObj["songs"].isArray()) {
|
||||
if (!rootObj.contains("songs") || !rootObj["songs"].isArray())
|
||||
{
|
||||
qWarning() << "Invalid playlist format: missing songs array";
|
||||
return false;
|
||||
}
|
||||
|
|
@ -702,29 +757,37 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList<SongItem> &
|
|||
|
||||
qDebug()<<"Loading"<<songsArray.size()<<"songs";
|
||||
|
||||
for (const QJsonValue &value : songsArray) {
|
||||
if (!value.isObject()) continue;
|
||||
for (const QJsonValue &value : songsArray)
|
||||
{
|
||||
if (!value.isObject())
|
||||
continue;
|
||||
|
||||
QJsonObject songObj = value.toObject();
|
||||
SongItem song;
|
||||
|
||||
if (songObj.contains("caption")) {
|
||||
if (songObj.contains("caption"))
|
||||
{
|
||||
song.caption = songObj["caption"].toString();
|
||||
}
|
||||
|
||||
if (songObj.contains("lyrics")) {
|
||||
if (songObj.contains("lyrics"))
|
||||
{
|
||||
song.lyrics = songObj["lyrics"].toString();
|
||||
}
|
||||
|
||||
// Load vocalLanguage if present
|
||||
if (songObj.contains("vocalLanguage")) {
|
||||
if (songObj.contains("vocalLanguage"))
|
||||
{
|
||||
song.vocalLanguage = songObj["vocalLanguage"].toString();
|
||||
}
|
||||
|
||||
// Load uniqueId if present (for backward compatibility)
|
||||
if (songObj.contains("uniqueId")) {
|
||||
if (songObj.contains("uniqueId"))
|
||||
{
|
||||
song.uniqueId = static_cast<uint64_t>(songObj["uniqueId"].toInteger());
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generate new ID for old playlists without uniqueId
|
||||
song.uniqueId = QRandomGenerator::global()->generate64();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@
|
|||
#include "AceStepWorker.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui { class MainWindow; }
|
||||
namespace Ui
|
||||
{
|
||||
class MainWindow;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &l
|
|||
ui->setupUi(this);
|
||||
|
||||
// Set initial values if provided
|
||||
if (!caption.isEmpty()) {
|
||||
if (!caption.isEmpty())
|
||||
{
|
||||
ui->captionEdit->setPlainText(caption);
|
||||
}
|
||||
if (!lyrics.isEmpty()) {
|
||||
if (!lyrics.isEmpty())
|
||||
{
|
||||
ui->lyricsEdit->setPlainText(lyrics);
|
||||
}
|
||||
|
||||
|
|
@ -29,12 +31,16 @@ SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &l
|
|||
ui->vocalLanguageCombo->addItem("Russian (ru)", "ru");
|
||||
|
||||
// Set current language if provided
|
||||
if (!vocalLanguage.isEmpty()) {
|
||||
if (!vocalLanguage.isEmpty())
|
||||
{
|
||||
int index = ui->vocalLanguageCombo->findData(vocalLanguage);
|
||||
if (index >= 0) {
|
||||
if (index >= 0)
|
||||
{
|
||||
ui->vocalLanguageCombo->setCurrentIndex(index);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->vocalLanguageCombo->setCurrentIndex(0); // Default to unset
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +69,8 @@ void SongDialog::on_okButton_clicked()
|
|||
{
|
||||
// Validate that caption is not empty
|
||||
QString caption = getCaption();
|
||||
if (caption.trimmed().isEmpty()) {
|
||||
if (caption.trimmed().isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, "Invalid Input", "Caption cannot be empty.");
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
namespace Ui {
|
||||
namespace Ui
|
||||
{
|
||||
class SongDialog;
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +14,8 @@ class SongDialog : public QDialog
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "", const QString &vocalLanguage = "");
|
||||
explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "",
|
||||
const QString &vocalLanguage = "");
|
||||
~SongDialog();
|
||||
|
||||
QString getCaption() const;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
#include <QRandomGenerator>
|
||||
#include <cstdint>
|
||||
|
||||
class SongItem {
|
||||
class SongItem
|
||||
{
|
||||
public:
|
||||
QString caption;
|
||||
QString lyrics;
|
||||
|
|
@ -13,7 +14,8 @@ public:
|
|||
QString json;
|
||||
|
||||
inline SongItem(const QString &caption = "", const QString &lyrics = "")
|
||||
: caption(caption), lyrics(lyrics) {
|
||||
: caption(caption), lyrics(lyrics)
|
||||
{
|
||||
// Generate a unique ID using cryptographically secure random number
|
||||
uniqueId = QRandomGenerator::global()->generate64();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,24 +32,29 @@ QVariant SongListModel::data(const QModelIndex &index, int role) const
|
|||
|
||||
const SongItem &song = songList[index.row()];
|
||||
|
||||
switch (role) {
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
// Column 0: Play indicator column
|
||||
if (index.column() == 0) {
|
||||
if (index.column() == 0)
|
||||
{
|
||||
return index.row() == m_playingIndex ? "▶" : "";
|
||||
}
|
||||
// Column 1: Song name
|
||||
else if (index.column() == 1) {
|
||||
else if (index.column() == 1)
|
||||
{
|
||||
return song.caption;
|
||||
}
|
||||
// Column 2: Vocal language
|
||||
else if (index.column() == 2) {
|
||||
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) {
|
||||
if (index.column() == 0 && index.row() == m_playingIndex)
|
||||
{
|
||||
QFont font = QApplication::font();
|
||||
font.setBold(true);
|
||||
return font;
|
||||
|
|
@ -57,7 +62,8 @@ QVariant SongListModel::data(const QModelIndex &index, int role) const
|
|||
break;
|
||||
case Qt::TextAlignmentRole:
|
||||
// Center align the play indicator
|
||||
if (index.column() == 0) {
|
||||
if (index.column() == 0)
|
||||
{
|
||||
return Qt::AlignCenter;
|
||||
}
|
||||
break;
|
||||
|
|
@ -83,7 +89,8 @@ bool SongListModel::setData(const QModelIndex &index, const QVariant &value, int
|
|||
|
||||
SongItem &song = songList[index.row()];
|
||||
|
||||
switch (role) {
|
||||
switch (role)
|
||||
{
|
||||
case CaptionRole:
|
||||
song.caption = value.toString();
|
||||
break;
|
||||
|
|
@ -119,7 +126,8 @@ void SongListModel::addSong(const SongItem &song)
|
|||
|
||||
void SongListModel::removeSong(int index)
|
||||
{
|
||||
if (index >= 0 && index < songList.size()) {
|
||||
if (index >= 0 && index < songList.size())
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
songList.removeAt(index);
|
||||
endRemoveRows();
|
||||
|
|
@ -140,7 +148,8 @@ bool SongListModel::empty()
|
|||
|
||||
SongItem SongListModel::getSong(int index) const
|
||||
{
|
||||
if (index >= 0 && index < songList.size()) {
|
||||
if (index >= 0 && index < songList.size())
|
||||
{
|
||||
return songList[index];
|
||||
}
|
||||
return SongItem();
|
||||
|
|
@ -148,7 +157,8 @@ SongItem SongListModel::getSong(int index) const
|
|||
|
||||
QVariant SongListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
|
||||
if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
|
||||
{
|
||||
// Hide headers since we don't need column titles
|
||||
return QVariant();
|
||||
}
|
||||
|
|
@ -161,11 +171,13 @@ void SongListModel::setPlayingIndex(int index)
|
|||
m_playingIndex = index;
|
||||
|
||||
// Update both the old and new playing indices to trigger UI updates
|
||||
if (oldPlayingIndex >= 0 && oldPlayingIndex < songList.size()) {
|
||||
if (oldPlayingIndex >= 0 && oldPlayingIndex < songList.size())
|
||||
{
|
||||
emit dataChanged(this->index(oldPlayingIndex, 0), this->index(oldPlayingIndex, 0));
|
||||
}
|
||||
|
||||
if (index >= 0 && index < songList.size()) {
|
||||
if (index >= 0 && index < songList.size())
|
||||
{
|
||||
emit dataChanged(this->index(index, 0), this->index(index, 0));
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +192,8 @@ int SongListModel::findNextIndex(int currentIndex, bool shuffle) const
|
|||
if (songList.isEmpty())
|
||||
return -1;
|
||||
|
||||
if (shuffle) {
|
||||
if (shuffle)
|
||||
{
|
||||
// Simple random selection for shuffle mode
|
||||
QRandomGenerator generator;
|
||||
return generator.bounded(songList.size());
|
||||
|
|
@ -188,7 +201,8 @@ int SongListModel::findNextIndex(int currentIndex, bool shuffle) const
|
|||
|
||||
// Sequential playback
|
||||
int nextIndex = currentIndex + 1;
|
||||
if (nextIndex >= songList.size()) {
|
||||
if (nextIndex >= songList.size())
|
||||
{
|
||||
nextIndex = 0; // Loop back to beginning
|
||||
}
|
||||
|
||||
|
|
@ -197,8 +211,10 @@ int SongListModel::findNextIndex(int currentIndex, bool shuffle) const
|
|||
|
||||
int SongListModel::findSongIndexById(uint64_t uniqueId) const
|
||||
{
|
||||
for (int i = 0; i < songList.size(); ++i) {
|
||||
if (songList[i].uniqueId == uniqueId) {
|
||||
for (int i = 0; i < songList.size(); ++i)
|
||||
{
|
||||
if (songList[i].uniqueId == uniqueId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ class SongListModel : public QAbstractTableModel
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
enum Roles
|
||||
{
|
||||
CaptionRole = Qt::UserRole + 1,
|
||||
LyricsRole = Qt::UserRole + 2,
|
||||
VocalLanguageRole = Qt::UserRole + 3,
|
||||
|
|
@ -42,7 +43,10 @@ public:
|
|||
|
||||
// Playing indicator
|
||||
void setPlayingIndex(int index);
|
||||
int playingIndex() const { return m_playingIndex; }
|
||||
int playingIndex() const
|
||||
{
|
||||
return m_playingIndex;
|
||||
}
|
||||
|
||||
// Find song by unique ID
|
||||
int findSongIndexById(uint64_t uniqueId) const;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ ClickableSlider::ClickableSlider(Qt::Orientation orientation, QWidget *parent)
|
|||
|
||||
void ClickableSlider::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
int val = pixelPosToRangeValue(event->pos());
|
||||
|
||||
// Block signals temporarily to avoid infinite recursion
|
||||
|
|
@ -24,7 +25,9 @@ void ClickableSlider::mousePressEvent(QMouseEvent *event)
|
|||
// Emit both valueChanged and sliderMoved signals for compatibility
|
||||
emit valueChanged(val);
|
||||
emit sliderMoved(val);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Call base class implementation for other buttons
|
||||
QSlider::mousePressEvent(event);
|
||||
}
|
||||
|
|
@ -42,11 +45,14 @@ int ClickableSlider::pixelPosToRangeValue(const QPoint &pos)
|
|||
int sliderMin;
|
||||
int sliderMax;
|
||||
|
||||
if (orientation() == Qt::Horizontal) {
|
||||
if (orientation() == Qt::Horizontal)
|
||||
{
|
||||
sliderLength = sr.width();
|
||||
sliderMin = gr.x();
|
||||
sliderMax = gr.right() - sliderLength + 1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
sliderLength = sr.height();
|
||||
sliderMin = gr.y();
|
||||
sliderMax = gr.bottom() - sliderLength + 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue