Compare commits
12 commits
6be80f1d5c
...
c7f5c97e7b
| Author | SHA1 | Date | |
|---|---|---|---|
| c7f5c97e7b | |||
| f85bbb326e | |||
| cc72f360b9 | |||
| e3fb4761b0 | |||
| 55be24b36f | |||
| be21c1f2bd | |||
| ef17335614 | |||
| 39f6429199 | |||
| 275c1a1852 | |||
| 216e59c105 | |||
| 14dec9f335 | |||
| de7207f07e |
16 changed files with 1386 additions and 481 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "third_party/acestep.cpp"]
|
||||||
|
path = third_party/acestep.cpp
|
||||||
|
url = https://github.com/ServeurpersoCom/acestep.cpp.git
|
||||||
|
|
@ -10,7 +10,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
# Find Qt packages
|
# Find Qt packages
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets Multimedia REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Widgets Multimedia REQUIRED)
|
||||||
|
|
||||||
# Note: acestep.cpp binaries and models should be provided at runtime
|
# Add acestep.cpp subdirectory
|
||||||
|
add_subdirectory(third_party/acestep.cpp)
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
@ -47,21 +48,22 @@ add_executable(${PROJECT_NAME}
|
||||||
# UI file
|
# UI file
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
# Link libraries (only Qt libraries - acestep.cpp is external)
|
# Link libraries (Qt + acestep.cpp)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
Qt6::Gui
|
Qt6::Gui
|
||||||
Qt6::Widgets
|
Qt6::Widgets
|
||||||
Qt6::Multimedia
|
Qt6::Multimedia
|
||||||
|
acestep-core
|
||||||
|
ggml
|
||||||
)
|
)
|
||||||
|
|
||||||
# Include directories (only our source directory - acestep.cpp is external)
|
# Include directories
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/third_party/acestep.cpp/src
|
||||||
)
|
)
|
||||||
|
|
||||||
# Note: acestep.cpp binaries (ace-qwen3, dit-vae) and models should be provided at runtime
|
|
||||||
|
|
||||||
# Install targets
|
# Install targets
|
||||||
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
||||||
|
|
||||||
|
|
@ -71,3 +73,24 @@ install(FILES res/xyz.uvos.aceradio.desktop DESTINATION share/applications)
|
||||||
# Install icon files
|
# 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.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)
|
install(FILES res/xyz.uvos.aceradio.svg DESTINATION share/icons/hicolor/scalable/apps RENAME xyz.uvos.aceradio.svg)
|
||||||
|
|
||||||
|
# Test executable
|
||||||
|
add_executable(test_acestep_worker
|
||||||
|
tests/test_acestep_worker.cpp
|
||||||
|
src/AceStepWorker.cpp
|
||||||
|
src/AceStepWorker.h
|
||||||
|
src/SongItem.cpp
|
||||||
|
src/SongItem.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(test_acestep_worker PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Widgets
|
||||||
|
acestep-core
|
||||||
|
ggml
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(test_acestep_worker PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/third_party/acestep.cpp/src
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,207 +5,393 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QProcess>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QRandomGenerator>
|
#include <QRandomGenerator>
|
||||||
|
|
||||||
AceStep::AceStep(QObject* parent): QObject(parent)
|
// acestep.cpp headers
|
||||||
|
#include "pipeline-lm.h"
|
||||||
|
#include "request.h"
|
||||||
|
|
||||||
|
AceStepWorker::AceStepWorker(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
connect(&qwenProcess, &QProcess::finished, this, &AceStep::qwenProcFinished);
|
|
||||||
connect(&ditVaeProcess, &QProcess::finished, this, &AceStep::ditProcFinished);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AceStep::isGenerating(SongItem* song)
|
AceStepWorker::~AceStepWorker()
|
||||||
{
|
{
|
||||||
if(!busy && song)
|
cancelGeneration();
|
||||||
*song = this->request.song;
|
unloadModels();
|
||||||
return busy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AceStep::cancelGeneration()
|
void AceStepWorker::setModelPaths(QString lmPath, QString textEncoderPath, QString ditPath, QString vaePath)
|
||||||
{
|
{
|
||||||
qwenProcess.blockSignals(true);
|
// Check if paths actually changed
|
||||||
qwenProcess.terminate();
|
bool pathsChanged = (m_lmModelPath != lmPath || m_textEncoderPath != textEncoderPath ||
|
||||||
qwenProcess.waitForFinished();
|
m_ditPath != ditPath || m_vaePath != vaePath);
|
||||||
qwenProcess.blockSignals(false);
|
|
||||||
|
|
||||||
ditVaeProcess.blockSignals(true);
|
m_lmModelPath = lmPath;
|
||||||
ditVaeProcess.terminate();
|
m_textEncoderPath = textEncoderPath;
|
||||||
ditVaeProcess.waitForFinished();
|
m_ditPath = ditPath;
|
||||||
ditVaeProcess.blockSignals(false);
|
m_vaePath = vaePath;
|
||||||
|
|
||||||
progressUpdate(100);
|
// Cache as byte arrays to avoid dangling pointers
|
||||||
if(busy)
|
m_lmModelPathBytes = lmPath.toUtf8();
|
||||||
generationCanceled(request.song);
|
m_textEncoderPathBytes = textEncoderPath.toUtf8();
|
||||||
|
m_ditPathBytes = ditPath.toUtf8();
|
||||||
|
m_vaePathBytes = vaePath.toUtf8();
|
||||||
|
|
||||||
busy = false;
|
// If paths changed and models are loaded, unload them so they'll be reloaded with new paths
|
||||||
|
if (pathsChanged && m_modelsLoaded.load())
|
||||||
|
{
|
||||||
|
unloadModels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AceStep::requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath,
|
void AceStepWorker::setLowVramMode(bool enabled)
|
||||||
QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath,
|
|
||||||
QString vaeModelPath)
|
|
||||||
{
|
{
|
||||||
if(busy)
|
m_lowVramMode = enabled;
|
||||||
{
|
}
|
||||||
qWarning()<<"Dropping song:"<<song.caption;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
busy = true;
|
|
||||||
|
|
||||||
request = {song, QRandomGenerator::global()->generate(), aceStepPath, textEncoderModelPath, ditModelPath, vaeModelPath};
|
void AceStepWorker::setFlashAttention(bool enabled)
|
||||||
|
{
|
||||||
|
m_flashAttention = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
QString qwen3Binary = aceStepPath + "/ace-lm" + EXE_EXT;
|
bool AceStepWorker::isGenerating(SongItem* song)
|
||||||
QFileInfo qwen3Info(qwen3Binary);
|
{
|
||||||
if (!qwen3Info.exists() || !qwen3Info.isExecutable())
|
if (!m_busy.load() && song)
|
||||||
|
*song = m_currentSong;
|
||||||
|
return m_busy.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AceStepWorker::cancelGeneration()
|
||||||
|
{
|
||||||
|
m_cancelRequested.store(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AceStepWorker::requestGeneration(SongItem song, QString requestTemplate)
|
||||||
|
{
|
||||||
|
if (m_busy.load())
|
||||||
{
|
{
|
||||||
generationError("ace-lm binary not found at: " + qwen3Binary);
|
qWarning() << "Dropping song:" << song.caption;
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(qwen3ModelPath))
|
|
||||||
{
|
|
||||||
generationError("Qwen3 model not found: " + qwen3ModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(textEncoderModelPath))
|
|
||||||
{
|
|
||||||
generationError("Text encoder model not found: " + textEncoderModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(ditModelPath))
|
|
||||||
{
|
|
||||||
generationError("DiT model not found: " + ditModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(vaeModelPath))
|
|
||||||
{
|
|
||||||
generationError("VAE model not found: " + vaeModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.requestFilePath = tempDir + "/request_" + QString::number(request.uid) + ".json";
|
m_busy.store(true);
|
||||||
|
m_cancelRequested.store(false);
|
||||||
|
m_currentSong = song;
|
||||||
|
m_requestTemplate = requestTemplate;
|
||||||
|
m_uid = QRandomGenerator::global()->generate();
|
||||||
|
|
||||||
QJsonParseError parseError;
|
// Validate model paths
|
||||||
QJsonDocument templateDoc = QJsonDocument::fromJson(requestTemplate.toUtf8(), &parseError);
|
if (m_lmModelPath.isEmpty() || m_textEncoderPath.isEmpty() ||
|
||||||
if (!templateDoc.isObject())
|
m_ditPath.isEmpty() || m_vaePath.isEmpty())
|
||||||
{
|
{
|
||||||
generationError("Invalid JSON template: " + QString(parseError.errorString()));
|
emit generationError("Model paths not set. Call setModelPaths() first.");
|
||||||
busy = false;
|
m_busy.store(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject requestObj = templateDoc.object();
|
// Validate model files exist
|
||||||
song.store(requestObj);
|
if (!QFileInfo::exists(m_lmModelPath))
|
||||||
|
|
||||||
// Write the request file
|
|
||||||
QFile requestFileHandle(request.requestFilePath);
|
|
||||||
if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text))
|
|
||||||
{
|
{
|
||||||
emit generationError("Failed to create request file: " + requestFileHandle.errorString());
|
emit generationError("LM model not found: " + m_lmModelPath);
|
||||||
busy = false;
|
m_busy.store(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!QFileInfo::exists(m_textEncoderPath))
|
||||||
|
{
|
||||||
|
emit generationError("Text encoder model not found: " + m_textEncoderPath);
|
||||||
|
m_busy.store(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!QFileInfo::exists(m_ditPath))
|
||||||
|
{
|
||||||
|
emit generationError("DiT model not found: " + m_ditPath);
|
||||||
|
m_busy.store(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!QFileInfo::exists(m_vaePath))
|
||||||
|
{
|
||||||
|
emit generationError("VAE model not found: " + m_vaePath);
|
||||||
|
m_busy.store(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented));
|
|
||||||
requestFileHandle.close();
|
|
||||||
|
|
||||||
QStringList qwen3Args;
|
// Run generation in the worker thread
|
||||||
qwen3Args << "--request" << request.requestFilePath;
|
QMetaObject::invokeMethod(this, &AceStepWorker::runGeneration, Qt::QueuedConnection);
|
||||||
qwen3Args << "--lm" << qwen3ModelPath;
|
|
||||||
|
|
||||||
progressUpdate(30);
|
|
||||||
|
|
||||||
qwenProcess.start(qwen3Binary, qwen3Args);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AceStep::qwenProcFinished(int code, QProcess::ExitStatus status)
|
bool AceStepWorker::checkCancel(void* data)
|
||||||
{
|
{
|
||||||
QFile::remove(request.requestFilePath);
|
AceStepWorker* worker = static_cast<AceStepWorker*>(data);
|
||||||
if(code != 0)
|
return worker->m_cancelRequested.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> AceStepWorker::convertToWav(const AceAudio& audio)
|
||||||
|
{
|
||||||
|
auto audioData = std::make_shared<QByteArray>();
|
||||||
|
|
||||||
|
// Simple WAV header + stereo float data
|
||||||
|
int numChannels = 2;
|
||||||
|
int bitsPerSample = 16;
|
||||||
|
int byteRate = audio.sample_rate * numChannels * (bitsPerSample / 8);
|
||||||
|
int blockAlign = numChannels * (bitsPerSample / 8);
|
||||||
|
int dataSize = audio.n_samples * numChannels * (bitsPerSample / 8);
|
||||||
|
|
||||||
|
// RIFF header
|
||||||
|
audioData->append("RIFF");
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&dataSize), 4));
|
||||||
|
audioData->append("WAVE");
|
||||||
|
|
||||||
|
// fmt chunk
|
||||||
|
audioData->append("fmt ");
|
||||||
|
int fmtSize = 16;
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&fmtSize), 4));
|
||||||
|
short audioFormat = 1; // PCM
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&audioFormat), 2));
|
||||||
|
short numCh = numChannels;
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&numCh), 2));
|
||||||
|
int sampleRate = audio.sample_rate;
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&sampleRate), 4));
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&byteRate), 4));
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&blockAlign), 2));
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&bitsPerSample), 2));
|
||||||
|
|
||||||
|
// data chunk
|
||||||
|
audioData->append("data");
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&dataSize), 4));
|
||||||
|
|
||||||
|
// Convert float samples to 16-bit and write
|
||||||
|
QVector<short> interleaved(audio.n_samples * numChannels);
|
||||||
|
for (int i = 0; i < audio.n_samples; i++)
|
||||||
{
|
{
|
||||||
QString errorOutput = qwenProcess.readAllStandardError();
|
float left = audio.samples[i];
|
||||||
generationError("ace-lm exited with code " + QString::number(code) + ": " + errorOutput);
|
float right = audio.samples[i + audio.n_samples];
|
||||||
busy = false;
|
// Clamp and convert to 16-bit
|
||||||
|
left = std::max(-1.0f, std::min(1.0f, left));
|
||||||
|
right = std::max(-1.0f, std::min(1.0f, right));
|
||||||
|
interleaved[i * 2] = static_cast<short>(left * 32767.0f);
|
||||||
|
interleaved[i * 2 + 1] = static_cast<short>(right * 32767.0f);
|
||||||
|
}
|
||||||
|
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(interleaved.data()), dataSize));
|
||||||
|
return audioData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AceStepWorker::runGeneration()
|
||||||
|
{
|
||||||
|
// Convert SongItem to AceRequest
|
||||||
|
AceRequest req = songToRequest(m_currentSong, m_requestTemplate);
|
||||||
|
AceRequest lmOutput;
|
||||||
|
request_init(&lmOutput);
|
||||||
|
|
||||||
|
emit progressUpdate(10);
|
||||||
|
|
||||||
|
if (!loadLm())
|
||||||
|
{
|
||||||
|
m_busy.store(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ditVaeBinary = request.aceStepPath + "/ace-synth" + EXE_EXT;
|
emit progressUpdate(30);
|
||||||
QFileInfo ditVaeInfo(ditVaeBinary);
|
|
||||||
if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable())
|
int lmResult = ace_lm_generate(m_lmContext, &req, 1, &lmOutput,
|
||||||
|
nullptr, nullptr,
|
||||||
|
checkCancel, this,
|
||||||
|
LM_MODE_GENERATE);
|
||||||
|
|
||||||
|
if (m_cancelRequested.load())
|
||||||
{
|
{
|
||||||
generationError("ace-synth binary not found at: " + ditVaeBinary);
|
if(m_lowVramMode)
|
||||||
busy = false;
|
unloadModels();
|
||||||
|
emit generationCanceled(m_currentSong);
|
||||||
|
m_busy.store(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.requestLlmFilePath = tempDir + "/request_" + QString::number(request.uid) + "0.json";
|
if (lmResult != 0)
|
||||||
if (!QFileInfo::exists(request.requestLlmFilePath))
|
|
||||||
{
|
{
|
||||||
generationError("ace-lm failed to create enhanced request file "+request.requestLlmFilePath);
|
if(m_lowVramMode)
|
||||||
busy = false;
|
unloadModels();
|
||||||
|
emit generationError("LM generation failed or was canceled");
|
||||||
|
m_busy.store(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load lyrics from the enhanced request file
|
m_currentSong.lyrics = QString::fromStdString(lmOutput.lyrics);
|
||||||
QFile lmOutputFile(request.requestLlmFilePath);
|
|
||||||
if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
if(m_lowVramMode)
|
||||||
|
unloadLm();
|
||||||
|
|
||||||
|
emit progressUpdate(50);
|
||||||
|
|
||||||
|
if (!loadSynth())
|
||||||
|
{
|
||||||
|
m_busy.store(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit progressUpdate(60);
|
||||||
|
|
||||||
|
AceAudio outputAudio;
|
||||||
|
outputAudio.samples = nullptr;
|
||||||
|
outputAudio.n_samples = 0;
|
||||||
|
outputAudio.sample_rate = 48000;
|
||||||
|
|
||||||
|
int synthResult = ace_synth_generate(m_synthContext, &lmOutput,
|
||||||
|
nullptr, 0, // no source audio
|
||||||
|
nullptr, 0, // no reference audio
|
||||||
|
1, &outputAudio,
|
||||||
|
checkCancel, this);
|
||||||
|
|
||||||
|
if(m_lowVramMode)
|
||||||
|
unloadSynth();
|
||||||
|
|
||||||
|
if (m_cancelRequested.load())
|
||||||
|
{
|
||||||
|
emit generationCanceled(m_currentSong);
|
||||||
|
m_busy.store(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (synthResult != 0)
|
||||||
|
{
|
||||||
|
emit generationError("Synthesis failed or was canceled");
|
||||||
|
m_busy.store(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> audioData = convertToWav(outputAudio);
|
||||||
|
ace_audio_free(&outputAudio);
|
||||||
|
|
||||||
|
m_currentSong.json = QString::fromStdString(request_to_json(&lmOutput, true));
|
||||||
|
m_currentSong.audioData = audioData;
|
||||||
|
|
||||||
|
if (lmOutput.bpm > 0)
|
||||||
|
m_currentSong.bpm = lmOutput.bpm;
|
||||||
|
|
||||||
|
if (!lmOutput.keyscale.empty())
|
||||||
|
m_currentSong.key = QString::fromStdString(lmOutput.keyscale);
|
||||||
|
|
||||||
|
emit progressUpdate(100);
|
||||||
|
emit songGenerated(m_currentSong);
|
||||||
|
|
||||||
|
m_busy.store(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AceStepWorker::loadModels()
|
||||||
|
{
|
||||||
|
bool ret = loadSynth();
|
||||||
|
if(!ret)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ret = loadLm();
|
||||||
|
if(!ret)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AceStepWorker::unloadModels()
|
||||||
|
{
|
||||||
|
if (m_synthContext)
|
||||||
|
{
|
||||||
|
ace_synth_free(m_synthContext);
|
||||||
|
m_synthContext = nullptr;
|
||||||
|
}
|
||||||
|
if (m_lmContext)
|
||||||
|
{
|
||||||
|
ace_lm_free(m_lmContext);
|
||||||
|
m_lmContext = nullptr;
|
||||||
|
}
|
||||||
|
m_modelsLoaded.store(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AceStepWorker::loadLm()
|
||||||
|
{
|
||||||
|
if (m_lmContext)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
AceLmParams lmParams;
|
||||||
|
ace_lm_default_params(&lmParams);
|
||||||
|
lmParams.model_path = m_lmModelPathBytes.constData();
|
||||||
|
lmParams.use_fsm = true;
|
||||||
|
lmParams.use_fa = m_flashAttention;
|
||||||
|
|
||||||
|
m_lmContext = ace_lm_load(&lmParams);
|
||||||
|
if (!m_lmContext)
|
||||||
|
{
|
||||||
|
emit generationError("Failed to load LM model: " + m_lmModelPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AceStepWorker::unloadLm()
|
||||||
|
{
|
||||||
|
if (m_lmContext)
|
||||||
|
{
|
||||||
|
ace_lm_free(m_lmContext);
|
||||||
|
m_lmContext = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AceStepWorker::loadSynth()
|
||||||
|
{
|
||||||
|
if (m_synthContext)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
AceSynthParams synthParams;
|
||||||
|
ace_synth_default_params(&synthParams);
|
||||||
|
synthParams.text_encoder_path = m_textEncoderPathBytes.constData();
|
||||||
|
synthParams.dit_path = m_ditPathBytes.constData();
|
||||||
|
synthParams.vae_path = m_vaePathBytes.constData();
|
||||||
|
synthParams.use_fa = m_flashAttention;
|
||||||
|
|
||||||
|
m_synthContext = ace_synth_load(&synthParams);
|
||||||
|
if (!m_synthContext)
|
||||||
|
{
|
||||||
|
emit generationError("Failed to load synthesis models");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AceStepWorker::unloadSynth()
|
||||||
|
{
|
||||||
|
if (m_synthContext)
|
||||||
|
{
|
||||||
|
ace_synth_free(m_synthContext);
|
||||||
|
m_synthContext = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AceRequest AceStepWorker::songToRequest(const SongItem& song, const QString& templateJson)
|
||||||
|
{
|
||||||
|
AceRequest req;
|
||||||
|
request_init(&req);
|
||||||
|
|
||||||
|
// Parse template first to get all default values
|
||||||
|
QJsonObject requestJson;
|
||||||
|
if (!templateJson.isEmpty())
|
||||||
{
|
{
|
||||||
QJsonParseError parseError;
|
QJsonParseError parseError;
|
||||||
request.song.json = lmOutputFile.readAll();
|
QJsonDocument templateDoc = QJsonDocument::fromJson(templateJson.toUtf8(), &parseError);
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(request.song.json.toUtf8(), &parseError);
|
if (templateDoc.isObject())
|
||||||
lmOutputFile.close();
|
requestJson = templateDoc.object();
|
||||||
|
else
|
||||||
if (doc.isObject() && !parseError.error)
|
qWarning() << "Failed to parse request template:" << parseError.errorString();
|
||||||
{
|
|
||||||
QJsonObject obj = doc.object();
|
|
||||||
if (obj.contains("lyrics") && obj["lyrics"].isString())
|
|
||||||
request.song.lyrics = obj["lyrics"].toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Run ace-synth to generate audio
|
song.store(requestJson);
|
||||||
QStringList ditVaeArgs;
|
QJsonDocument requestJsonDoc(requestJson);
|
||||||
ditVaeArgs << "--request"<<request.requestLlmFilePath;
|
QString requestJsonString = QString::fromUtf8(requestJsonDoc.toJson(QJsonDocument::Compact));
|
||||||
ditVaeArgs << "--embedding"<<request.textEncoderModelPath;
|
if (!request_parse_json(&req, requestJsonString.toUtf8()))
|
||||||
ditVaeArgs << "--dit"<<request.ditModelPath;
|
qWarning() << "Failed to parse merged request JSON";
|
||||||
ditVaeArgs << "--vae"<<request.vaeModelPath;
|
|
||||||
ditVaeArgs << "--wav";
|
|
||||||
|
|
||||||
progressUpdate(60);
|
if (req.seed < 0)
|
||||||
|
req.seed = static_cast<int64_t>(QRandomGenerator::global()->generate());
|
||||||
|
|
||||||
ditVaeProcess.start(ditVaeBinary, ditVaeArgs);
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AceStep::ditProcFinished(int code, QProcess::ExitStatus status)
|
|
||||||
{
|
|
||||||
QFile::remove(request.requestLlmFilePath);
|
|
||||||
if (code != 0)
|
|
||||||
{
|
|
||||||
QString errorOutput = ditVaeProcess.readAllStandardError();
|
|
||||||
generationError("ace-synth exited with code " + QString::number(code) + ": " + errorOutput);
|
|
||||||
busy = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the generated WAV file
|
|
||||||
QString wavFile = tempDir+"/request_" + QString::number(request.uid) + "00.wav";
|
|
||||||
if (!QFileInfo::exists(wavFile))
|
|
||||||
{
|
|
||||||
generationError("No WAV file generated at "+wavFile);
|
|
||||||
busy = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
busy = false;
|
|
||||||
|
|
||||||
progressUpdate(100);
|
|
||||||
request.song.file = wavFile;
|
|
||||||
songGenerated(request.song);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,40 +8,41 @@
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QProcess>
|
#include <QThread>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
#include "SongItem.h"
|
#include "SongItem.h"
|
||||||
|
#include "pipeline-synth.h"
|
||||||
|
#include "request.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
struct AceLm;
|
||||||
inline const QString EXE_EXT = ".exe";
|
struct AceSynth;
|
||||||
#else
|
|
||||||
inline const QString EXE_EXT = "";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class AceStep : public QObject
|
class AceStepWorker : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QProcess qwenProcess;
|
|
||||||
QProcess ditVaeProcess;
|
|
||||||
|
|
||||||
bool busy = false;
|
public:
|
||||||
|
explicit AceStepWorker(QObject* parent = nullptr);
|
||||||
|
~AceStepWorker();
|
||||||
|
|
||||||
struct Request
|
bool isGenerating(SongItem* song = nullptr);
|
||||||
{
|
void cancelGeneration();
|
||||||
SongItem song;
|
|
||||||
uint64_t uid;
|
|
||||||
QString aceStepPath;
|
|
||||||
QString textEncoderModelPath;
|
|
||||||
QString ditModelPath;
|
|
||||||
QString vaeModelPath;
|
|
||||||
QString requestFilePath;
|
|
||||||
QString requestLlmFilePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
Request request;
|
// Model paths - set these before first generation
|
||||||
|
void setModelPaths(QString lmPath, QString textEncoderPath, QString ditPath, QString vaePath);
|
||||||
|
|
||||||
const QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
// Low VRAM mode: unload models between phases to save VRAM
|
||||||
|
void setLowVramMode(bool enabled);
|
||||||
|
bool isLowVramMode() const { return m_lowVramMode; }
|
||||||
|
|
||||||
|
// Flash attention mode
|
||||||
|
void setFlashAttention(bool enabled);
|
||||||
|
bool isFlashAttention() const { return m_flashAttention; }
|
||||||
|
|
||||||
|
// Request a new song generation
|
||||||
|
bool requestGeneration(SongItem song, QString requestTemplate);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void songGenerated(SongItem song);
|
void songGenerated(SongItem song);
|
||||||
|
|
@ -49,19 +50,49 @@ signals:
|
||||||
void generationError(QString error);
|
void generationError(QString error);
|
||||||
void progressUpdate(int progress);
|
void progressUpdate(int progress);
|
||||||
|
|
||||||
public slots:
|
|
||||||
bool requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath,
|
|
||||||
QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath,
|
|
||||||
QString vaeModelPath);
|
|
||||||
|
|
||||||
public:
|
|
||||||
AceStep(QObject* parent = nullptr);
|
|
||||||
bool isGenerating(SongItem* song = nullptr);
|
|
||||||
void cancelGeneration();
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void qwenProcFinished(int code, QProcess::ExitStatus status);
|
void runGeneration();
|
||||||
void ditProcFinished(int code, QProcess::ExitStatus status);
|
|
||||||
|
private:
|
||||||
|
static bool checkCancel(void* data);
|
||||||
|
|
||||||
|
bool loadModels();
|
||||||
|
void unloadModels();
|
||||||
|
bool loadLm();
|
||||||
|
void unloadLm();
|
||||||
|
bool loadSynth();
|
||||||
|
void unloadSynth();
|
||||||
|
|
||||||
|
AceRequest songToRequest(const SongItem& song, const QString& templateJson);
|
||||||
|
|
||||||
|
static std::shared_ptr<QByteArray> convertToWav(const AceAudio& audio);
|
||||||
|
|
||||||
|
// Generation state
|
||||||
|
std::atomic<bool> m_busy{false};
|
||||||
|
std::atomic<bool> m_cancelRequested{false};
|
||||||
|
std::atomic<bool> m_modelsLoaded{false};
|
||||||
|
bool m_lowVramMode = false;
|
||||||
|
bool m_flashAttention = true;
|
||||||
|
|
||||||
|
// Current request data
|
||||||
|
SongItem m_currentSong;
|
||||||
|
QString m_requestTemplate;
|
||||||
|
uint64_t m_uid;
|
||||||
|
|
||||||
|
// Model paths
|
||||||
|
QString m_lmModelPath;
|
||||||
|
QString m_textEncoderPath;
|
||||||
|
QString m_ditPath;
|
||||||
|
QString m_vaePath;
|
||||||
|
|
||||||
|
// Loaded models (accessed from worker thread only)
|
||||||
|
AceLm* m_lmContext = nullptr;
|
||||||
|
AceSynth* m_synthContext = nullptr;
|
||||||
|
|
||||||
|
QByteArray m_lmModelPathBytes;
|
||||||
|
QByteArray m_textEncoderPathBytes;
|
||||||
|
QByteArray m_ditPathBytes;
|
||||||
|
QByteArray m_vaePathBytes;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ACESTEPWORKER_H
|
#endif // ACESTEPWORKER_H
|
||||||
|
|
@ -13,6 +13,12 @@ AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent)
|
||||||
ui(new Ui::AdvancedSettingsDialog)
|
ui(new Ui::AdvancedSettingsDialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
// Connect signals and slots explicitly
|
||||||
|
connect(ui->qwen3BrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onQwen3BrowseButtonClicked);
|
||||||
|
connect(ui->textEncoderBrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onTextEncoderBrowseButtonClicked);
|
||||||
|
connect(ui->ditBrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onDiTBrowseButtonClicked);
|
||||||
|
connect(ui->vaeBrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onVAEBrowseButtonClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdvancedSettingsDialog::~AdvancedSettingsDialog()
|
AdvancedSettingsDialog::~AdvancedSettingsDialog()
|
||||||
|
|
@ -25,11 +31,6 @@ QString AdvancedSettingsDialog::getJsonTemplate() const
|
||||||
return ui->jsonTemplateEdit->toPlainText();
|
return ui->jsonTemplateEdit->toPlainText();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AdvancedSettingsDialog::getAceStepPath() const
|
|
||||||
{
|
|
||||||
return ui->aceStepPathEdit->text();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AdvancedSettingsDialog::getQwen3ModelPath() const
|
QString AdvancedSettingsDialog::getQwen3ModelPath() const
|
||||||
{
|
{
|
||||||
return ui->qwen3ModelEdit->text();
|
return ui->qwen3ModelEdit->text();
|
||||||
|
|
@ -50,16 +51,21 @@ QString AdvancedSettingsDialog::getVAEModelPath() const
|
||||||
return ui->vaeModelEdit->text();
|
return ui->vaeModelEdit->text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AdvancedSettingsDialog::getLowVramMode() const
|
||||||
|
{
|
||||||
|
return ui->lowVramCheckBox->isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdvancedSettingsDialog::getFlashAttention() const
|
||||||
|
{
|
||||||
|
return ui->flashAttentionCheckBox->isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
void AdvancedSettingsDialog::setJsonTemplate(const QString &templateStr)
|
void AdvancedSettingsDialog::setJsonTemplate(const QString &templateStr)
|
||||||
{
|
{
|
||||||
ui->jsonTemplateEdit->setPlainText(templateStr);
|
ui->jsonTemplateEdit->setPlainText(templateStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvancedSettingsDialog::setAceStepPath(const QString &path)
|
|
||||||
{
|
|
||||||
ui->aceStepPathEdit->setText(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AdvancedSettingsDialog::setQwen3ModelPath(const QString &path)
|
void AdvancedSettingsDialog::setQwen3ModelPath(const QString &path)
|
||||||
{
|
{
|
||||||
ui->qwen3ModelEdit->setText(path);
|
ui->qwen3ModelEdit->setText(path);
|
||||||
|
|
@ -80,16 +86,17 @@ void AdvancedSettingsDialog::setVAEModelPath(const QString &path)
|
||||||
ui->vaeModelEdit->setText(path);
|
ui->vaeModelEdit->setText(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvancedSettingsDialog::on_aceStepBrowseButton_clicked()
|
void AdvancedSettingsDialog::setLowVramMode(bool enabled)
|
||||||
{
|
{
|
||||||
QString dir = QFileDialog::getExistingDirectory(this, "Select AceStep Build Directory", ui->aceStepPathEdit->text());
|
ui->lowVramCheckBox->setChecked(enabled);
|
||||||
if (!dir.isEmpty())
|
|
||||||
{
|
|
||||||
ui->aceStepPathEdit->setText(dir);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvancedSettingsDialog::on_qwen3BrowseButton_clicked()
|
void AdvancedSettingsDialog::setFlashAttention(bool enabled)
|
||||||
|
{
|
||||||
|
ui->flashAttentionCheckBox->setChecked(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdvancedSettingsDialog::onQwen3BrowseButtonClicked()
|
||||||
{
|
{
|
||||||
QString file = QFileDialog::getOpenFileName(this, "Select Qwen3 Model", ui->qwen3ModelEdit->text(),
|
QString file = QFileDialog::getOpenFileName(this, "Select Qwen3 Model", ui->qwen3ModelEdit->text(),
|
||||||
"GGUF Files (*.gguf)");
|
"GGUF Files (*.gguf)");
|
||||||
|
|
@ -99,7 +106,7 @@ void AdvancedSettingsDialog::on_qwen3BrowseButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvancedSettingsDialog::on_textEncoderBrowseButton_clicked()
|
void AdvancedSettingsDialog::onTextEncoderBrowseButtonClicked()
|
||||||
{
|
{
|
||||||
QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(),
|
QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(),
|
||||||
"GGUF Files (*.gguf)");
|
"GGUF Files (*.gguf)");
|
||||||
|
|
@ -109,7 +116,7 @@ void AdvancedSettingsDialog::on_textEncoderBrowseButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvancedSettingsDialog::on_ditBrowseButton_clicked()
|
void AdvancedSettingsDialog::onDiTBrowseButtonClicked()
|
||||||
{
|
{
|
||||||
QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)");
|
QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)");
|
||||||
if (!file.isEmpty())
|
if (!file.isEmpty())
|
||||||
|
|
@ -118,7 +125,7 @@ void AdvancedSettingsDialog::on_ditBrowseButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvancedSettingsDialog::on_vaeBrowseButton_clicked()
|
void AdvancedSettingsDialog::onVAEBrowseButtonClicked()
|
||||||
{
|
{
|
||||||
QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)");
|
QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)");
|
||||||
if (!file.isEmpty())
|
if (!file.isEmpty())
|
||||||
|
|
|
||||||
|
|
@ -24,26 +24,27 @@ public:
|
||||||
|
|
||||||
// Getters for settings
|
// Getters for settings
|
||||||
QString getJsonTemplate() const;
|
QString getJsonTemplate() const;
|
||||||
QString getAceStepPath() const;
|
|
||||||
QString getQwen3ModelPath() const;
|
QString getQwen3ModelPath() const;
|
||||||
QString getTextEncoderModelPath() const;
|
QString getTextEncoderModelPath() const;
|
||||||
QString getDiTModelPath() const;
|
QString getDiTModelPath() const;
|
||||||
QString getVAEModelPath() const;
|
QString getVAEModelPath() const;
|
||||||
|
bool getLowVramMode() const;
|
||||||
|
bool getFlashAttention() const;
|
||||||
|
|
||||||
// Setters for settings
|
// Setters for settings
|
||||||
void setJsonTemplate(const QString &templateStr);
|
void setJsonTemplate(const QString &templateStr);
|
||||||
void setAceStepPath(const QString &path);
|
|
||||||
void setQwen3ModelPath(const QString &path);
|
void setQwen3ModelPath(const QString &path);
|
||||||
void setTextEncoderModelPath(const QString &path);
|
void setTextEncoderModelPath(const QString &path);
|
||||||
void setDiTModelPath(const QString &path);
|
void setDiTModelPath(const QString &path);
|
||||||
void setVAEModelPath(const QString &path);
|
void setVAEModelPath(const QString &path);
|
||||||
|
void setLowVramMode(bool enabled);
|
||||||
|
void setFlashAttention(bool enabled);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_aceStepBrowseButton_clicked();
|
void onQwen3BrowseButtonClicked();
|
||||||
void on_qwen3BrowseButton_clicked();
|
void onTextEncoderBrowseButtonClicked();
|
||||||
void on_textEncoderBrowseButton_clicked();
|
void onDiTBrowseButtonClicked();
|
||||||
void on_ditBrowseButton_clicked();
|
void onVAEBrowseButtonClicked();
|
||||||
void on_vaeBrowseButton_clicked();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::AdvancedSettingsDialog *ui;
|
Ui::AdvancedSettingsDialog *ui;
|
||||||
|
|
|
||||||
|
|
@ -1,203 +1,239 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>AdvancedSettingsDialog</class>
|
<class>AdvancedSettingsDialog</class>
|
||||||
<widget class="QDialog" name="AdvancedSettingsDialog">
|
<widget class="QDialog" name="AdvancedSettingsDialog">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>600</width>
|
<width>600</width>
|
||||||
<height>450</height>
|
<height>450</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Advanced Settings</string>
|
<string>Advanced Settings</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="jsonTab">
|
<widget class="QWidget" name="performanceTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>JSON Template</string>
|
<string>Performance</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="jsonLayout">
|
<layout class="QVBoxLayout" name="performanceLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="jsonLabel">
|
<widget class="QCheckBox" name="lowVramCheckBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>JSON Template for AceStep generation:</string>
|
<string>Low VRAM Mode</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
</widget>
|
||||||
<bool>true</bool>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QLabel" name="lowVramLabel">
|
||||||
</item>
|
<property name="text">
|
||||||
<item>
|
<string>Unload models between generation phases to save VRAM. Slower but uses less memory.</string>
|
||||||
<widget class="QTextEdit" name="jsonTemplateEdit"/>
|
</property>
|
||||||
</item>
|
<property name="wordWrap">
|
||||||
</layout>
|
<bool>true</bool>
|
||||||
</widget>
|
</property>
|
||||||
<widget class="QWidget" name="pathsTab">
|
</widget>
|
||||||
<attribute name="title">
|
</item>
|
||||||
<string>Model Paths</string>
|
<item>
|
||||||
</attribute>
|
<widget class="QCheckBox" name="flashAttentionCheckBox">
|
||||||
<layout class="QFormLayout" name="pathsLayout">
|
<property name="text">
|
||||||
<property name="fieldGrowthPolicy">
|
<string>Flash Attention</string>
|
||||||
<enum>QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow</enum>
|
</property>
|
||||||
</property>
|
<property name="checked">
|
||||||
<item row="0" column="0">
|
<bool>true</bool>
|
||||||
<widget class="QLabel" name="aceStepLabel">
|
</property>
|
||||||
<property name="text">
|
</widget>
|
||||||
<string>AceStep Path:</string>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QLabel" name="flashAttentionLabel">
|
||||||
</item>
|
<property name="text">
|
||||||
<item row="0" column="1">
|
<string>Use flash attention for faster generation. Disable if experiencing poor output quality on vulkan.</string>
|
||||||
<layout class="QHBoxLayout" name="aceStepLayout">
|
</property>
|
||||||
<item>
|
<property name="wordWrap">
|
||||||
<widget class="QLineEdit" name="aceStepPathEdit"/>
|
<bool>true</bool>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
</widget>
|
||||||
<widget class="QPushButton" name="aceStepBrowseButton">
|
</item>
|
||||||
<property name="text">
|
<item>
|
||||||
<string>Browse...</string>
|
<spacer name="verticalSpacer">
|
||||||
</property>
|
<property name="orientation">
|
||||||
</widget>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</item>
|
</property>
|
||||||
</layout>
|
<property name="sizeHint" stdset="0">
|
||||||
</item>
|
<size>
|
||||||
<item row="1" column="0">
|
<width>20</width>
|
||||||
<widget class="QLabel" name="qwen3Label">
|
<height>40</height>
|
||||||
<property name="text">
|
</size>
|
||||||
<string>Qwen3 Model:</string>
|
</property>
|
||||||
</property>
|
</spacer>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
</layout>
|
||||||
<item row="1" column="1">
|
</widget>
|
||||||
<layout class="QHBoxLayout" name="qwen3Layout">
|
<widget class="QWidget" name="jsonTab">
|
||||||
<item>
|
<attribute name="title">
|
||||||
<widget class="QLineEdit" name="qwen3ModelEdit"/>
|
<string>JSON Template</string>
|
||||||
</item>
|
</attribute>
|
||||||
<item>
|
<layout class="QVBoxLayout" name="jsonLayout">
|
||||||
<widget class="QPushButton" name="qwen3BrowseButton">
|
<item>
|
||||||
<property name="text">
|
<widget class="QLabel" name="jsonLabel">
|
||||||
<string>Browse...</string>
|
<property name="text">
|
||||||
</property>
|
<string>JSON Template for AceStep generation:</string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
<property name="wordWrap">
|
||||||
</layout>
|
<bool>true</bool>
|
||||||
</item>
|
</property>
|
||||||
<item row="2" column="0">
|
</widget>
|
||||||
<widget class="QLabel" name="textEncoderLabel">
|
</item>
|
||||||
<property name="text">
|
<item>
|
||||||
<string>Text Encoder Model:</string>
|
<widget class="QTextEdit" name="jsonTemplateEdit"/>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
</layout>
|
||||||
</item>
|
</widget>
|
||||||
<item row="2" column="1">
|
<widget class="QWidget" name="pathsTab">
|
||||||
<layout class="QHBoxLayout" name="textEncoderLayout">
|
<attribute name="title">
|
||||||
<item>
|
<string>Model Paths</string>
|
||||||
<widget class="QLineEdit" name="textEncoderEdit"/>
|
</attribute>
|
||||||
</item>
|
<layout class="QFormLayout" name="pathsLayout">
|
||||||
<item>
|
<property name="fieldGrowthPolicy">
|
||||||
<widget class="QPushButton" name="textEncoderBrowseButton">
|
<enum>QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow</enum>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Browse...</string>
|
<item row="0" column="0">
|
||||||
</property>
|
<widget class="QLabel" name="qwen3Label">
|
||||||
</widget>
|
<property name="text">
|
||||||
</item>
|
<string>Qwen3 Model:</string>
|
||||||
</layout>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item row="3" column="0">
|
</item>
|
||||||
<widget class="QLabel" name="ditLabel">
|
<item row="0" column="1">
|
||||||
<property name="text">
|
<layout class="QHBoxLayout" name="qwen3Layout">
|
||||||
<string>DiT Model:</string>
|
<item>
|
||||||
</property>
|
<widget class="QLineEdit" name="qwen3ModelEdit"/>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
<item row="3" column="1">
|
<widget class="QPushButton" name="qwen3BrowseButton">
|
||||||
<layout class="QHBoxLayout" name="ditLayout">
|
<property name="text">
|
||||||
<item>
|
<string>Browse...</string>
|
||||||
<widget class="QLineEdit" name="ditModelEdit"/>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<widget class="QPushButton" name="ditBrowseButton">
|
</layout>
|
||||||
<property name="text">
|
</item>
|
||||||
<string>Browse...</string>
|
<item row="1" column="0">
|
||||||
</property>
|
<widget class="QLabel" name="textEncoderLabel">
|
||||||
</widget>
|
<property name="text">
|
||||||
</item>
|
<string>Text Encoder Model:</string>
|
||||||
</layout>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item row="4" column="0">
|
</item>
|
||||||
<widget class="QLabel" name="vaeLabel">
|
<item row="1" column="1">
|
||||||
<property name="text">
|
<layout class="QHBoxLayout" name="textEncoderLayout">
|
||||||
<string>VAE Model:</string>
|
<item>
|
||||||
</property>
|
<widget class="QLineEdit" name="textEncoderEdit"/>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
<item row="4" column="1">
|
<widget class="QPushButton" name="textEncoderBrowseButton">
|
||||||
<layout class="QHBoxLayout" name="vaeLayout">
|
<property name="text">
|
||||||
<item>
|
<string>Browse...</string>
|
||||||
<widget class="QLineEdit" name="vaeModelEdit"/>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<widget class="QPushButton" name="vaeBrowseButton">
|
</layout>
|
||||||
<property name="text">
|
</item>
|
||||||
<string>Browse...</string>
|
<item row="2" column="0">
|
||||||
</property>
|
<widget class="QLabel" name="ditLabel">
|
||||||
</widget>
|
<property name="text">
|
||||||
</item>
|
<string>DiT Model:</string>
|
||||||
</layout>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
</layout>
|
</item>
|
||||||
</widget>
|
<item row="2" column="1">
|
||||||
</widget>
|
<layout class="QHBoxLayout" name="ditLayout">
|
||||||
</item>
|
<item>
|
||||||
<item>
|
<widget class="QLineEdit" name="ditModelEdit"/>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
</item>
|
||||||
<property name="standardButtons">
|
<item>
|
||||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
|
<widget class="QPushButton" name="ditBrowseButton">
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string>Browse...</string>
|
||||||
</item>
|
</property>
|
||||||
</layout>
|
</widget>
|
||||||
</widget>
|
</item>
|
||||||
<resources/>
|
</layout>
|
||||||
<connections>
|
</item>
|
||||||
<connection>
|
<item row="3" column="0">
|
||||||
<sender>buttonBox</sender>
|
<widget class="QLabel" name="vaeLabel">
|
||||||
<signal>accepted()</signal>
|
<property name="text">
|
||||||
<receiver>AdvancedSettingsDialog</receiver>
|
<string>VAE Model:</string>
|
||||||
<slot>accept()</slot>
|
</property>
|
||||||
<hints>
|
</widget>
|
||||||
<hint type="sourcelabel">
|
</item>
|
||||||
<x>248</x>
|
<item row="3" column="1">
|
||||||
<y>254</y>
|
<layout class="QHBoxLayout" name="vaeLayout">
|
||||||
</hint>
|
<item>
|
||||||
<hint type="destinationlabel">
|
<widget class="QLineEdit" name="vaeModelEdit"/>
|
||||||
<x>157</x>
|
</item>
|
||||||
<y>254</y>
|
<item>
|
||||||
</hint>
|
<widget class="QPushButton" name="vaeBrowseButton">
|
||||||
</hints>
|
<property name="text">
|
||||||
</connection>
|
<string>Browse...</string>
|
||||||
<connection>
|
</property>
|
||||||
<sender>buttonBox</sender>
|
</widget>
|
||||||
<signal>rejected()</signal>
|
</item>
|
||||||
<receiver>AdvancedSettingsDialog</receiver>
|
</layout>
|
||||||
<slot>reject()</slot>
|
</item>
|
||||||
<hints>
|
</layout>
|
||||||
<hint type="sourcelabel">
|
</widget>
|
||||||
<x>316</x>
|
</widget>
|
||||||
<y>260</y>
|
</item>
|
||||||
</hint>
|
<item>
|
||||||
<hint type="destinationlabel">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<x>286</x>
|
<property name="standardButtons">
|
||||||
<y>260</y>
|
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
|
||||||
</hint>
|
</property>
|
||||||
</hints>
|
</widget>
|
||||||
</connection>
|
</item>
|
||||||
</connections>
|
</layout>
|
||||||
</ui>
|
</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>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "AudioPlayer.h"
|
#include "AudioPlayer.h"
|
||||||
|
#include <QBuffer>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
AudioPlayer::AudioPlayer(QObject *parent)
|
AudioPlayer::AudioPlayer(QObject *parent)
|
||||||
|
|
@ -48,6 +49,33 @@ void AudioPlayer::play(const QString &filePath)
|
||||||
positionTimer->start();
|
positionTimer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioPlayer::play(std::shared_ptr<QByteArray> audioData)
|
||||||
|
{
|
||||||
|
if (isPlaying())
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!audioData || audioData->isEmpty())
|
||||||
|
{
|
||||||
|
emit playbackError("No audio data available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a buffer with the audio data
|
||||||
|
QBuffer *buffer = new QBuffer();
|
||||||
|
buffer->setData(*audioData);
|
||||||
|
buffer->open(QIODevice::ReadOnly);
|
||||||
|
buffer->setParent(this);
|
||||||
|
|
||||||
|
// Use QMediaPlayer::setSourceDevice for in-memory playback
|
||||||
|
mediaPlayer->setSourceDevice(buffer, QUrl("memory://audio.wav"));
|
||||||
|
mediaPlayer->play();
|
||||||
|
|
||||||
|
// Start position timer
|
||||||
|
positionTimer->start();
|
||||||
|
}
|
||||||
|
|
||||||
void AudioPlayer::play()
|
void AudioPlayer::play()
|
||||||
{
|
{
|
||||||
if (!isPlaying())
|
if (!isPlaying())
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QMediaDevices>
|
#include <QMediaDevices>
|
||||||
#include <QAudioDevice>
|
#include <QAudioDevice>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class AudioPlayer : public QObject
|
class AudioPlayer : public QObject
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +24,7 @@ public:
|
||||||
~AudioPlayer();
|
~AudioPlayer();
|
||||||
|
|
||||||
void play(const QString &filePath);
|
void play(const QString &filePath);
|
||||||
|
void play(std::shared_ptr<QByteArray> audioData);
|
||||||
void play();
|
void play();
|
||||||
void stop();
|
void stop();
|
||||||
void pause();
|
void pause();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
ui(new Ui::MainWindow),
|
ui(new Ui::MainWindow),
|
||||||
songModel(new SongListModel(this)),
|
songModel(new SongListModel(this)),
|
||||||
audioPlayer(new AudioPlayer(this)),
|
audioPlayer(new AudioPlayer(this)),
|
||||||
aceStep(new AceStep(this)),
|
aceStep(new AceStepWorker),
|
||||||
playbackTimer(new QTimer(this)),
|
playbackTimer(new QTimer(this)),
|
||||||
isPlaying(false),
|
isPlaying(false),
|
||||||
isPaused(false),
|
isPaused(false),
|
||||||
|
|
@ -29,6 +29,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
isGeneratingNext(false)
|
isGeneratingNext(false)
|
||||||
{
|
{
|
||||||
aceStep->moveToThread(&aceThread);
|
aceStep->moveToThread(&aceThread);
|
||||||
|
aceThread.setObjectName("AceStep Woker Thread");
|
||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
|
@ -41,15 +42,25 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
// Load settings
|
// Load settings
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
// Set model paths for acestep.cpp
|
||||||
|
aceStep->setModelPaths(qwen3ModelPath, textEncoderModelPath, ditModelPath, vaeModelPath);
|
||||||
|
|
||||||
// Auto-load playlist from config location on startup
|
// Auto-load playlist from config location on startup
|
||||||
autoLoadPlaylist();
|
autoLoadPlaylist();
|
||||||
|
|
||||||
// Connect signals and slots
|
// Connect signals and slots
|
||||||
connect(ui->actionAdvancedSettings, &QAction::triggered, this, &MainWindow::on_advancedSettingsButton_clicked);
|
connect(ui->playButton, &QPushButton::clicked, this, &MainWindow::onPlayButtonClicked);
|
||||||
connect(ui->actionSavePlaylist, &QAction::triggered, this, &MainWindow::on_actionSavePlaylist);
|
connect(ui->pauseButton, &QPushButton::clicked, this, &MainWindow::onPauseButtonClicked);
|
||||||
connect(ui->actionLoadPlaylist, &QAction::triggered, this, &MainWindow::on_actionLoadPlaylist);
|
connect(ui->skipButton, &QPushButton::clicked, this, &MainWindow::onSkipButtonClicked);
|
||||||
connect(ui->actionAppendPlaylist, &QAction::triggered, this, &MainWindow::on_actionAppendPlaylist);
|
connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::onStopButtonClicked);
|
||||||
connect(ui->actionSaveSong, &QAction::triggered, this, &MainWindow::on_actionSaveSong);
|
connect(ui->shuffleButton, &QPushButton::clicked, this, &MainWindow::onShuffleButtonClicked);
|
||||||
|
connect(ui->addSongButton, &QPushButton::clicked, this, &MainWindow::onAddSongButtonClicked);
|
||||||
|
connect(ui->removeSongButton, &QPushButton::clicked, this, &MainWindow::onRemoveSongButtonClicked);
|
||||||
|
connect(ui->actionAdvancedSettings, &QAction::triggered, this, &MainWindow::onAdvancedSettingsButtonClicked);
|
||||||
|
connect(ui->actionSavePlaylist, &QAction::triggered, this, &MainWindow::onActionSavePlaylist);
|
||||||
|
connect(ui->actionLoadPlaylist, &QAction::triggered, this, &MainWindow::onActionLoadPlaylist);
|
||||||
|
connect(ui->actionAppendPlaylist, &QAction::triggered, this, &MainWindow::onActionAppendPlaylist);
|
||||||
|
connect(ui->actionSaveSong, &QAction::triggered, this, &MainWindow::onActionSaveSong);
|
||||||
connect(ui->actionQuit, &QAction::triggered, this, [this]()
|
connect(ui->actionQuit, &QAction::triggered, this, [this]()
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
|
|
@ -62,16 +73,16 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted);
|
connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted);
|
||||||
connect(audioPlayer, &AudioPlayer::positionChanged, this, &MainWindow::updatePosition);
|
connect(audioPlayer, &AudioPlayer::positionChanged, this, &MainWindow::updatePosition);
|
||||||
connect(audioPlayer, &AudioPlayer::durationChanged, this, &MainWindow::updateDuration);
|
connect(audioPlayer, &AudioPlayer::durationChanged, this, &MainWindow::updateDuration);
|
||||||
connect(aceStep, &AceStep::songGenerated, this, &MainWindow::songGenerated);
|
connect(aceStep, &AceStepWorker::songGenerated, this, &MainWindow::songGenerated);
|
||||||
connect(aceStep, &AceStep::generationCanceled, this, &MainWindow::generationCanceld);
|
connect(aceStep, &AceStepWorker::generationCanceled, this, &MainWindow::generationCanceld);
|
||||||
connect(aceStep, &AceStep::generationError, this, &MainWindow::generationError);
|
connect(aceStep, &AceStepWorker::generationError, this, &MainWindow::generationError);
|
||||||
connect(aceStep, &AceStep::progressUpdate, ui->progressBar, &QProgressBar::setValue);
|
connect(aceStep, &AceStepWorker::progressUpdate, ui->progressBar, &QProgressBar::setValue);
|
||||||
|
|
||||||
// Connect double-click on song list for editing (works with QTableView too)
|
// Connect double-click on song list for editing (works with QTableView too)
|
||||||
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::onSongListViewDoubleClicked);
|
||||||
|
|
||||||
// Connect audio player error signal
|
// Connect audio player error signal
|
||||||
connect(audioPlayer, &AudioPlayer::playbackError, [this](const QString &error)
|
connect(audioPlayer, &AudioPlayer::playbackError, this, [this](const QString &error)
|
||||||
{
|
{
|
||||||
QMessageBox::warning(this, "Playback Error", "Failed to play audio: " + error);
|
QMessageBox::warning(this, "Playback Error", "Failed to play audio: " + error);
|
||||||
});
|
});
|
||||||
|
|
@ -98,11 +109,17 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
ui->nowPlayingLabel->setText("Now Playing:");
|
ui->nowPlayingLabel->setText("Now Playing:");
|
||||||
|
|
||||||
currentSong = songModel->getSong(0);
|
currentSong = songModel->getSong(0);
|
||||||
|
|
||||||
|
// Start the worker thread and enter its event loop
|
||||||
|
QObject::connect(&aceThread, &QThread::started, [this]() {qDebug() << "Worker thread started";});
|
||||||
|
aceThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
{
|
{
|
||||||
aceStep->cancelGeneration();
|
aceStep->cancelGeneration();
|
||||||
|
aceThread.quit();
|
||||||
|
aceThread.wait();
|
||||||
|
|
||||||
autoSavePlaylist();
|
autoSavePlaylist();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
|
@ -145,13 +162,20 @@ void MainWindow::loadSettings()
|
||||||
|
|
||||||
// Load path settings with defaults based on application directory
|
// Load path settings with defaults based on application directory
|
||||||
QString appDir = QCoreApplication::applicationDirPath();
|
QString appDir = QCoreApplication::applicationDirPath();
|
||||||
aceStepPath = settings.value("aceStepPath", appDir + "/acestep.cpp").toString();
|
|
||||||
qwen3ModelPath = settings.value("qwen3ModelPath",
|
qwen3ModelPath = settings.value("qwen3ModelPath",
|
||||||
appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString();
|
appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString();
|
||||||
textEncoderModelPath = settings.value("textEncoderModelPath",
|
textEncoderModelPath = settings.value("textEncoderModelPath",
|
||||||
appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-BF16.gguf").toString();
|
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();
|
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();
|
vaeModelPath = settings.value("vaeModelPath", appDir + "/acestep.cpp/models/vae-BF16.gguf").toString();
|
||||||
|
|
||||||
|
// Load low VRAM mode
|
||||||
|
bool lowVram = settings.value("lowVramMode", false).toBool();
|
||||||
|
aceStep->setLowVramMode(lowVram);
|
||||||
|
|
||||||
|
// Load flash attention setting
|
||||||
|
bool flashAttention = settings.value("flashAttention", false).toBool();
|
||||||
|
aceStep->setFlashAttention(flashAttention);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::saveSettings()
|
void MainWindow::saveSettings()
|
||||||
|
|
@ -165,12 +189,17 @@ void MainWindow::saveSettings()
|
||||||
settings.setValue("shuffleMode", shuffleMode);
|
settings.setValue("shuffleMode", shuffleMode);
|
||||||
|
|
||||||
// Save path settings
|
// Save path settings
|
||||||
settings.setValue("aceStepPath", aceStepPath);
|
|
||||||
settings.setValue("qwen3ModelPath", qwen3ModelPath);
|
settings.setValue("qwen3ModelPath", qwen3ModelPath);
|
||||||
settings.setValue("textEncoderModelPath", textEncoderModelPath);
|
settings.setValue("textEncoderModelPath", textEncoderModelPath);
|
||||||
settings.setValue("ditModelPath", ditModelPath);
|
settings.setValue("ditModelPath", ditModelPath);
|
||||||
settings.setValue("vaeModelPath", vaeModelPath);
|
settings.setValue("vaeModelPath", vaeModelPath);
|
||||||
|
|
||||||
|
// Save low VRAM mode
|
||||||
|
settings.setValue("lowVramMode", aceStep->isLowVramMode());
|
||||||
|
|
||||||
|
// Save flash attention setting
|
||||||
|
settings.setValue("flashAttention", aceStep->isFlashAttention());
|
||||||
|
|
||||||
settings.setValue("firstRun", false);
|
settings.setValue("firstRun", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,7 +248,7 @@ void MainWindow::updateControls()
|
||||||
ui->removeSongButton->setEnabled(hasSongs && ui->songListView->currentIndex().isValid());
|
ui->removeSongButton->setEnabled(hasSongs && ui->songListView->currentIndex().isValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_playButton_clicked()
|
void MainWindow::onPlayButtonClicked()
|
||||||
{
|
{
|
||||||
if (isPaused)
|
if (isPaused)
|
||||||
{
|
{
|
||||||
|
|
@ -240,7 +269,7 @@ void MainWindow::on_playButton_clicked()
|
||||||
updateControls();
|
updateControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_pauseButton_clicked()
|
void MainWindow::onPauseButtonClicked()
|
||||||
{
|
{
|
||||||
if (isPlaying && !isPaused && audioPlayer->isPlaying())
|
if (isPlaying && !isPaused && audioPlayer->isPlaying())
|
||||||
{
|
{
|
||||||
|
|
@ -251,7 +280,7 @@ void MainWindow::on_pauseButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_skipButton_clicked()
|
void MainWindow::onSkipButtonClicked()
|
||||||
{
|
{
|
||||||
if (isPlaying)
|
if (isPlaying)
|
||||||
{
|
{
|
||||||
|
|
@ -261,7 +290,7 @@ void MainWindow::on_skipButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_stopButton_clicked()
|
void MainWindow::onStopButtonClicked()
|
||||||
{
|
{
|
||||||
if (isPlaying)
|
if (isPlaying)
|
||||||
{
|
{
|
||||||
|
|
@ -275,7 +304,7 @@ void MainWindow::on_stopButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_shuffleButton_clicked()
|
void MainWindow::onShuffleButtonClicked()
|
||||||
{
|
{
|
||||||
shuffleMode = ui->shuffleButton->isChecked();
|
shuffleMode = ui->shuffleButton->isChecked();
|
||||||
updateControls();
|
updateControls();
|
||||||
|
|
@ -285,7 +314,7 @@ void MainWindow::on_shuffleButton_clicked()
|
||||||
ensureSongsInQueue();
|
ensureSongsInQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_addSongButton_clicked()
|
void MainWindow::onAddSongButtonClicked()
|
||||||
{
|
{
|
||||||
SongDialog dialog(this);
|
SongDialog dialog(this);
|
||||||
|
|
||||||
|
|
@ -300,12 +329,12 @@ void MainWindow::on_addSongButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
void MainWindow::onSongListViewDoubleClicked(const QModelIndex &index)
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::onSongListViewDoubleClicked);
|
||||||
|
|
||||||
int row = index.row();
|
int row = index.row();
|
||||||
|
|
||||||
|
|
@ -337,10 +366,10 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
||||||
songModel->updateSong(songModel->index(row, 1), dialog.getSong());
|
songModel->updateSong(songModel->index(row, 1), dialog.getSong());
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::onSongListViewDoubleClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_removeSongButton_clicked()
|
void MainWindow::onRemoveSongButtonClicked()
|
||||||
{
|
{
|
||||||
QModelIndex currentIndex = ui->songListView->currentIndex();
|
QModelIndex currentIndex = ui->songListView->currentIndex();
|
||||||
if (!currentIndex.isValid())
|
if (!currentIndex.isValid())
|
||||||
|
|
@ -360,17 +389,18 @@ void MainWindow::on_removeSongButton_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_advancedSettingsButton_clicked()
|
void MainWindow::onAdvancedSettingsButtonClicked()
|
||||||
{
|
{
|
||||||
AdvancedSettingsDialog dialog(this);
|
AdvancedSettingsDialog dialog(this);
|
||||||
|
|
||||||
// Set current values
|
// Set current values
|
||||||
dialog.setJsonTemplate(jsonTemplate);
|
dialog.setJsonTemplate(jsonTemplate);
|
||||||
dialog.setAceStepPath(aceStepPath);
|
|
||||||
dialog.setQwen3ModelPath(qwen3ModelPath);
|
dialog.setQwen3ModelPath(qwen3ModelPath);
|
||||||
dialog.setTextEncoderModelPath(textEncoderModelPath);
|
dialog.setTextEncoderModelPath(textEncoderModelPath);
|
||||||
dialog.setDiTModelPath(ditModelPath);
|
dialog.setDiTModelPath(ditModelPath);
|
||||||
dialog.setVAEModelPath(vaeModelPath);
|
dialog.setVAEModelPath(vaeModelPath);
|
||||||
|
dialog.setLowVramMode(aceStep->isLowVramMode());
|
||||||
|
dialog.setFlashAttention(aceStep->isFlashAttention());
|
||||||
|
|
||||||
if (dialog.exec() == QDialog::Accepted)
|
if (dialog.exec() == QDialog::Accepted)
|
||||||
{
|
{
|
||||||
|
|
@ -385,12 +415,20 @@ void MainWindow::on_advancedSettingsButton_clicked()
|
||||||
|
|
||||||
// Update settings
|
// Update settings
|
||||||
jsonTemplate = dialog.getJsonTemplate();
|
jsonTemplate = dialog.getJsonTemplate();
|
||||||
aceStepPath = dialog.getAceStepPath();
|
|
||||||
qwen3ModelPath = dialog.getQwen3ModelPath();
|
qwen3ModelPath = dialog.getQwen3ModelPath();
|
||||||
textEncoderModelPath = dialog.getTextEncoderModelPath();
|
textEncoderModelPath = dialog.getTextEncoderModelPath();
|
||||||
ditModelPath = dialog.getDiTModelPath();
|
ditModelPath = dialog.getDiTModelPath();
|
||||||
vaeModelPath = dialog.getVAEModelPath();
|
vaeModelPath = dialog.getVAEModelPath();
|
||||||
|
|
||||||
|
// Update model paths for acestep.cpp
|
||||||
|
aceStep->setModelPaths(qwen3ModelPath, textEncoderModelPath, ditModelPath, vaeModelPath);
|
||||||
|
|
||||||
|
// Update low VRAM mode
|
||||||
|
aceStep->setLowVramMode(dialog.getLowVramMode());
|
||||||
|
|
||||||
|
// Update flash attention setting
|
||||||
|
aceStep->setFlashAttention(dialog.getFlashAttention());
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +441,14 @@ void MainWindow::playbackStarted()
|
||||||
void MainWindow::playSong(const SongItem& song)
|
void MainWindow::playSong(const SongItem& song)
|
||||||
{
|
{
|
||||||
currentSong = song;
|
currentSong = song;
|
||||||
audioPlayer->play(song.file);
|
if (song.audioData)
|
||||||
|
{
|
||||||
|
audioPlayer->play(song.audioData);
|
||||||
|
}
|
||||||
|
else if (!song.file.isEmpty())
|
||||||
|
{
|
||||||
|
audioPlayer->play(song.file);
|
||||||
|
}
|
||||||
songModel->setPlayingIndex(songModel->findSongIndexById(song.uniqueId));
|
songModel->setPlayingIndex(songModel->findSongIndexById(song.uniqueId));
|
||||||
ui->nowPlayingLabel->setText("Now Playing: " + song.caption);
|
ui->nowPlayingLabel->setText("Now Playing: " + song.caption);
|
||||||
ui->lyricsTextEdit->setPlainText(song.lyrics);
|
ui->lyricsTextEdit->setPlainText(song.lyrics);
|
||||||
|
|
@ -494,7 +539,7 @@ void MainWindow::updatePlaybackStatus(bool playing)
|
||||||
updateControls();
|
updateControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_positionSlider_sliderMoved(int position)
|
void MainWindow::onPositionSliderSliderMoved(int position)
|
||||||
{
|
{
|
||||||
if (isPlaying && audioPlayer->isPlaying())
|
if (isPlaying && audioPlayer->isPlaying())
|
||||||
{
|
{
|
||||||
|
|
@ -533,10 +578,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
|
||||||
isGeneratingNext = true;
|
isGeneratingNext = true;
|
||||||
|
|
||||||
ui->statusbar->showMessage("Generateing: "+nextSong.caption);
|
ui->statusbar->showMessage("Generateing: "+nextSong.caption);
|
||||||
aceStep->requestGeneration(nextSong, jsonTemplate,
|
QMetaObject::invokeMethod(aceStep, &AceStepWorker::requestGeneration, Qt::QueuedConnection, nextSong, jsonTemplate);
|
||||||
aceStepPath, qwen3ModelPath,
|
|
||||||
textEncoderModelPath, ditModelPath,
|
|
||||||
vaeModelPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::flushGenerationQueue()
|
void MainWindow::flushGenerationQueue()
|
||||||
|
|
@ -547,7 +589,7 @@ void MainWindow::flushGenerationQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playlist save/load methods
|
// Playlist save/load methods
|
||||||
void MainWindow::on_actionSavePlaylist()
|
void MainWindow::onActionSavePlaylist()
|
||||||
{
|
{
|
||||||
QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist",
|
QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist",
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/playlist.json",
|
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/playlist.json",
|
||||||
|
|
@ -559,7 +601,7 @@ void MainWindow::on_actionSavePlaylist()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionLoadPlaylist()
|
void MainWindow::onActionLoadPlaylist()
|
||||||
{
|
{
|
||||||
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
|
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
||||||
|
|
@ -572,7 +614,7 @@ void MainWindow::on_actionLoadPlaylist()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionAppendPlaylist()
|
void MainWindow::onActionAppendPlaylist()
|
||||||
{
|
{
|
||||||
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
|
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
||||||
|
|
@ -583,7 +625,7 @@ void MainWindow::on_actionAppendPlaylist()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionSaveSong()
|
void MainWindow::onActionSaveSong()
|
||||||
{
|
{
|
||||||
QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist",
|
QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist",
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/song.json",
|
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/song.json",
|
||||||
|
|
@ -607,7 +649,22 @@ void MainWindow::on_actionSaveSong()
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
return;
|
return;
|
||||||
QFile::copy(currentSong.file, filePath + ".wav");
|
|
||||||
|
// Save audio from memory if available, otherwise fall back to file
|
||||||
|
if (currentSong.audioData)
|
||||||
|
{
|
||||||
|
QFile wavFile(filePath + ".wav");
|
||||||
|
if (wavFile.open(QIODevice::WriteOnly))
|
||||||
|
{
|
||||||
|
wavFile.write(*currentSong.audioData);
|
||||||
|
wavFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!currentSong.file.isEmpty())
|
||||||
|
{
|
||||||
|
QFile::copy(currentSong.file, filePath + ".wav");
|
||||||
|
}
|
||||||
|
|
||||||
file.write(jsonData);
|
file.write(jsonData);
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class MainWindow : public QMainWindow
|
||||||
SongListModel *songModel;
|
SongListModel *songModel;
|
||||||
AudioPlayer *audioPlayer;
|
AudioPlayer *audioPlayer;
|
||||||
QThread aceThread;
|
QThread aceThread;
|
||||||
AceStep *aceStep;
|
AceStepWorker *aceStep;
|
||||||
QTimer *playbackTimer;
|
QTimer *playbackTimer;
|
||||||
|
|
||||||
QString formatTime(int milliseconds);
|
QString formatTime(int milliseconds);
|
||||||
|
|
@ -50,7 +50,6 @@ class MainWindow : public QMainWindow
|
||||||
QString jsonTemplate;
|
QString jsonTemplate;
|
||||||
|
|
||||||
// Path settings
|
// Path settings
|
||||||
QString aceStepPath;
|
|
||||||
QString qwen3ModelPath;
|
QString qwen3ModelPath;
|
||||||
QString textEncoderModelPath;
|
QString textEncoderModelPath;
|
||||||
QString ditModelPath;
|
QString ditModelPath;
|
||||||
|
|
@ -68,19 +67,19 @@ public slots:
|
||||||
void show();
|
void show();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_playButton_clicked();
|
void onPlayButtonClicked();
|
||||||
void on_pauseButton_clicked();
|
void onPauseButtonClicked();
|
||||||
void on_skipButton_clicked();
|
void onSkipButtonClicked();
|
||||||
void on_stopButton_clicked();
|
void onStopButtonClicked();
|
||||||
void on_shuffleButton_clicked();
|
void onShuffleButtonClicked();
|
||||||
void on_positionSlider_sliderMoved(int position);
|
void onPositionSliderSliderMoved(int position);
|
||||||
void updatePosition(int position);
|
void updatePosition(int position);
|
||||||
void updateDuration(int duration);
|
void updateDuration(int duration);
|
||||||
void on_addSongButton_clicked();
|
void onAddSongButtonClicked();
|
||||||
void on_removeSongButton_clicked();
|
void onRemoveSongButtonClicked();
|
||||||
void on_advancedSettingsButton_clicked();
|
void onAdvancedSettingsButtonClicked();
|
||||||
|
|
||||||
void on_songListView_doubleClicked(const QModelIndex &index);
|
void onSongListViewDoubleClicked(const QModelIndex &index);
|
||||||
|
|
||||||
void songGenerated(const SongItem& song);
|
void songGenerated(const SongItem& song);
|
||||||
void generationCanceld(const SongItem& song);
|
void generationCanceld(const SongItem& song);
|
||||||
|
|
@ -89,10 +88,10 @@ private slots:
|
||||||
void updatePlaybackStatus(bool playing);
|
void updatePlaybackStatus(bool playing);
|
||||||
void generationError(const QString &error);
|
void generationError(const QString &error);
|
||||||
|
|
||||||
void on_actionSavePlaylist();
|
void onActionSavePlaylist();
|
||||||
void on_actionLoadPlaylist();
|
void onActionLoadPlaylist();
|
||||||
void on_actionAppendPlaylist();
|
void onActionAppendPlaylist();
|
||||||
void on_actionSaveSong();
|
void onActionSaveSong();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ SongDialog::SongDialog(QWidget *parent, const SongItem &song)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
// Connect signals and slots explicitly
|
||||||
|
connect(ui->okButton, &QPushButton::clicked, this, &SongDialog::onOkButtonClicked);
|
||||||
|
connect(ui->cancelButton, &QPushButton::clicked, this, &SongDialog::onCancelButtonClicked);
|
||||||
|
|
||||||
ui->captionEdit->setPlainText(song.caption);
|
ui->captionEdit->setPlainText(song.caption);
|
||||||
ui->lyricsEdit->setPlainText(song.lyrics);
|
ui->lyricsEdit->setPlainText(song.lyrics);
|
||||||
ui->checkBoxEnhanceCaption->setChecked(song.cotCaption);
|
ui->checkBoxEnhanceCaption->setChecked(song.cotCaption);
|
||||||
|
|
@ -140,7 +144,7 @@ SongDialog::~SongDialog()
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongDialog::on_okButton_clicked()
|
void SongDialog::onOkButtonClicked()
|
||||||
{
|
{
|
||||||
// Validate that caption is not empty
|
// Validate that caption is not empty
|
||||||
QString caption = ui->captionEdit->toPlainText();
|
QString caption = ui->captionEdit->toPlainText();
|
||||||
|
|
@ -153,7 +157,7 @@ void SongDialog::on_okButton_clicked()
|
||||||
accept();
|
accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongDialog::on_cancelButton_clicked()
|
void SongDialog::onCancelButtonClicked()
|
||||||
{
|
{
|
||||||
reject();
|
reject();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ public:
|
||||||
const SongItem& getSong();
|
const SongItem& getSong();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_okButton_clicked();
|
void onOkButtonClicked();
|
||||||
void on_cancelButton_clicked();
|
void onCancelButtonClicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::SongDialog *ui;
|
Ui::SongDialog *ui;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QRandomGenerator>
|
#include <QRandomGenerator>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class SongItem
|
class SongItem
|
||||||
{
|
{
|
||||||
|
|
@ -22,6 +23,7 @@ public:
|
||||||
uint64_t uniqueId;
|
uint64_t uniqueId;
|
||||||
QString file;
|
QString file;
|
||||||
QString json;
|
QString json;
|
||||||
|
std::shared_ptr<QByteArray> audioData;
|
||||||
|
|
||||||
SongItem(const QString &caption = "", const QString &lyrics = "");
|
SongItem(const QString &caption = "", const QString &lyrics = "");
|
||||||
SongItem(const QJsonObject& json);
|
SongItem(const QJsonObject& json);
|
||||||
|
|
|
||||||
525
tests/test_acestep_worker.cpp
Normal file
525
tests/test_acestep_worker.cpp
Normal file
|
|
@ -0,0 +1,525 @@
|
||||||
|
// Test for AceStepWorker
|
||||||
|
// Compile with: cmake .. && make test_acestep_worker && ./test_acestep_worker
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "../src/AceStepWorker.h"
|
||||||
|
|
||||||
|
// Test result tracking
|
||||||
|
static int testsPassed = 0;
|
||||||
|
static int testsFailed = 0;
|
||||||
|
static int testsSkipped = 0;
|
||||||
|
|
||||||
|
#define TEST(name) void test_##name()
|
||||||
|
#define RUN_TEST(name) do { \
|
||||||
|
std::cout << "Running " << #name << "... "; \
|
||||||
|
test_##name(); \
|
||||||
|
if (test_skipped) { \
|
||||||
|
std::cout << "SKIPPED" << std::endl; \
|
||||||
|
testsSkipped++; \
|
||||||
|
test_skipped = false; \
|
||||||
|
} else if (test_failed) { \
|
||||||
|
std::cout << "FAILED" << std::endl; \
|
||||||
|
testsFailed++; \
|
||||||
|
test_failed = false; \
|
||||||
|
} else { \
|
||||||
|
std::cout << "PASSED" << std::endl; \
|
||||||
|
testsPassed++; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
static bool test_failed = false;
|
||||||
|
static bool test_skipped = false;
|
||||||
|
|
||||||
|
#define ASSERT_TRUE(cond) do { \
|
||||||
|
if (!(cond)) { \
|
||||||
|
std::cout << "FAILED: " << #cond << " at line " << __LINE__ << std::endl; \
|
||||||
|
test_failed = true; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define ASSERT_FALSE(cond) ASSERT_TRUE(!(cond))
|
||||||
|
|
||||||
|
#define SKIP_IF(cond) do { \
|
||||||
|
if (cond) { \
|
||||||
|
std::cout << "(skipping: " << #cond << ") "; \
|
||||||
|
test_skipped = true; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
// Helper to get model paths from settings like main app
|
||||||
|
struct ModelPaths {
|
||||||
|
QString lmPath;
|
||||||
|
QString textEncoderPath;
|
||||||
|
QString ditPath;
|
||||||
|
QString vaePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
static ModelPaths getModelPathsFromSettings()
|
||||||
|
{
|
||||||
|
ModelPaths paths;
|
||||||
|
QSettings settings("MusicGenerator", "AceStepGUI");
|
||||||
|
|
||||||
|
QString appDir = QCoreApplication::applicationDirPath();
|
||||||
|
paths.lmPath = settings.value("qwen3ModelPath",
|
||||||
|
appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString();
|
||||||
|
paths.textEncoderPath = settings.value("textEncoderModelPath",
|
||||||
|
appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-Q8_0.gguf").toString();
|
||||||
|
paths.ditPath = settings.value("ditModelPath",
|
||||||
|
appDir + "/acestep.cpp/models/acestep-v15-turbo-Q8_0.gguf").toString();
|
||||||
|
paths.vaePath = settings.value("vaeModelPath",
|
||||||
|
appDir + "/acestep.cpp/models/vae-BF16.gguf").toString();
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool modelsExist(const ModelPaths& paths)
|
||||||
|
{
|
||||||
|
return QFileInfo::exists(paths.lmPath) &&
|
||||||
|
QFileInfo::exists(paths.textEncoderPath) &&
|
||||||
|
QFileInfo::exists(paths.ditPath) &&
|
||||||
|
QFileInfo::exists(paths.vaePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Check that isGenerating returns false initially
|
||||||
|
TEST(initialState)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
ASSERT_TRUE(!worker.isGenerating());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Check that requestGeneration returns false when no model paths set
|
||||||
|
TEST(noModelPaths)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
SongItem song("test caption", "");
|
||||||
|
|
||||||
|
bool result = worker.requestGeneration(song, "{}");
|
||||||
|
ASSERT_FALSE(result);
|
||||||
|
ASSERT_TRUE(!worker.isGenerating());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Check that setModelPaths stores paths correctly
|
||||||
|
TEST(setModelPaths)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
|
||||||
|
ASSERT_TRUE(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Check async behavior - requestGeneration returns immediately
|
||||||
|
TEST(asyncReturnsImmediately)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
|
||||||
|
|
||||||
|
SongItem song("test caption", "");
|
||||||
|
|
||||||
|
// If this blocks, the test will hang
|
||||||
|
bool result = worker.requestGeneration(song, "{}");
|
||||||
|
|
||||||
|
// Should return false due to invalid paths, but immediately
|
||||||
|
ASSERT_FALSE(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Check that cancelGeneration sets the cancel flag
|
||||||
|
TEST(cancellationFlag)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
|
||||||
|
worker.cancelGeneration();
|
||||||
|
ASSERT_TRUE(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: Check that signals are defined correctly
|
||||||
|
TEST(signalsExist)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
|
||||||
|
// Verify signals exist by connecting to them (compile-time check)
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated, [](const SongItem&) {});
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationCanceled, [](const SongItem&) {});
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError, [](const QString&) {});
|
||||||
|
QObject::connect(&worker, &AceStepWorker::progressUpdate, [](int) {});
|
||||||
|
|
||||||
|
ASSERT_TRUE(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Check SongItem to AceRequest conversion (internal)
|
||||||
|
TEST(requestConversion)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
|
||||||
|
SongItem song("Upbeat pop rock", "[Verse 1]");
|
||||||
|
song.cotCaption = true;
|
||||||
|
|
||||||
|
QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
|
||||||
|
|
||||||
|
worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
|
||||||
|
bool result = worker.requestGeneration(song, templateJson);
|
||||||
|
|
||||||
|
// Should fail due to invalid paths, but shouldn't crash
|
||||||
|
ASSERT_FALSE(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: Read model paths from settings
|
||||||
|
TEST(readSettings)
|
||||||
|
{
|
||||||
|
ModelPaths paths = getModelPathsFromSettings();
|
||||||
|
|
||||||
|
std::cout << "\n Model paths from settings:" << std::endl;
|
||||||
|
std::cout << " LM: " << paths.lmPath.toStdString() << std::endl;
|
||||||
|
std::cout << " Text Encoder: " << paths.textEncoderPath.toStdString() << std::endl;
|
||||||
|
std::cout << " DiT: " << paths.ditPath.toStdString() << std::endl;
|
||||||
|
std::cout << " VAE: " << paths.vaePath.toStdString() << std::endl;
|
||||||
|
|
||||||
|
ASSERT_TRUE(!paths.lmPath.isEmpty());
|
||||||
|
ASSERT_TRUE(!paths.textEncoderPath.isEmpty());
|
||||||
|
ASSERT_TRUE(!paths.ditPath.isEmpty());
|
||||||
|
ASSERT_TRUE(!paths.vaePath.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 9: Check if model files exist
|
||||||
|
TEST(checkModelFiles)
|
||||||
|
{
|
||||||
|
ModelPaths paths = getModelPathsFromSettings();
|
||||||
|
|
||||||
|
bool lmExists = QFileInfo::exists(paths.lmPath);
|
||||||
|
bool encoderExists = QFileInfo::exists(paths.textEncoderPath);
|
||||||
|
bool ditExists = QFileInfo::exists(paths.ditPath);
|
||||||
|
bool vaeExists = QFileInfo::exists(paths.vaePath);
|
||||||
|
|
||||||
|
std::cout << "\n Model file status:" << std::endl;
|
||||||
|
std::cout << " LM: " << (lmExists ? "EXISTS" : "MISSING") << std::endl;
|
||||||
|
std::cout << " Text Encoder: " << (encoderExists ? "EXISTS" : "MISSING") << std::endl;
|
||||||
|
std::cout << " DiT: " << (ditExists ? "EXISTS" : "MISSING") << std::endl;
|
||||||
|
std::cout << " VAE: " << (vaeExists ? "EXISTS" : "MISSING") << std::endl;
|
||||||
|
|
||||||
|
ASSERT_TRUE(lmExists);
|
||||||
|
ASSERT_TRUE(encoderExists);
|
||||||
|
ASSERT_TRUE(ditExists);
|
||||||
|
ASSERT_TRUE(vaeExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 10: Actually generate a song (requires valid model paths)
|
||||||
|
TEST(generateSong)
|
||||||
|
{
|
||||||
|
ModelPaths paths = getModelPathsFromSettings();
|
||||||
|
|
||||||
|
// Skip if models don't exist
|
||||||
|
SKIP_IF(!modelsExist(paths));
|
||||||
|
|
||||||
|
AceStepWorker worker;
|
||||||
|
worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
|
||||||
|
|
||||||
|
SongItem song("Upbeat pop rock with driving guitars", "");
|
||||||
|
|
||||||
|
QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
|
||||||
|
|
||||||
|
// Track if we get progress updates
|
||||||
|
bool gotProgress = false;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::progressUpdate, [&gotProgress](int p) {
|
||||||
|
std::cout << "\n Progress: " << p << "%" << std::endl;
|
||||||
|
gotProgress = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track generation result
|
||||||
|
bool generationCompleted = false;
|
||||||
|
SongItem resultSong;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated,
|
||||||
|
[&generationCompleted, &resultSong](const SongItem& song) {
|
||||||
|
std::cout << "\n Song generated successfully!" << std::endl;
|
||||||
|
std::cout << " Caption: " << song.caption.toStdString() << std::endl;
|
||||||
|
std::cout << " Lyrics: " << song.lyrics.left(100).toStdString() << "..." << std::endl;
|
||||||
|
std::cout << " File: " << song.file.toStdString() << std::endl;
|
||||||
|
resultSong = song;
|
||||||
|
generationCompleted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
QString errorMsg;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError,
|
||||||
|
[&errorMsg](const QString& err) {
|
||||||
|
std::cout << "\n Error: " << err.toStdString() << std::endl;
|
||||||
|
errorMsg = err;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << "\n Starting generation..." << std::endl;
|
||||||
|
|
||||||
|
// Request generation
|
||||||
|
bool result = worker.requestGeneration(song, templateJson);
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
|
||||||
|
// Use QEventLoop with timer for proper event processing
|
||||||
|
QEventLoop loop;
|
||||||
|
QTimer timeoutTimer;
|
||||||
|
|
||||||
|
timeoutTimer.setSingleShot(true);
|
||||||
|
timeoutTimer.start(300000); // 5 minute timeout
|
||||||
|
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||||
|
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
ASSERT_TRUE(generationCompleted);
|
||||||
|
ASSERT_TRUE(resultSong.audioData != nullptr);
|
||||||
|
ASSERT_TRUE(!resultSong.audioData->isEmpty());
|
||||||
|
|
||||||
|
// Check audio data is not empty
|
||||||
|
std::cout << " Audio data size: " << resultSong.audioData->size() << " bytes" << std::endl;
|
||||||
|
ASSERT_TRUE(resultSong.audioData->size() > 1000); // Should be at least 1KB for valid audio
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 11: Test cancellation
|
||||||
|
TEST(cancellation)
|
||||||
|
{
|
||||||
|
ModelPaths paths = getModelPathsFromSettings();
|
||||||
|
|
||||||
|
// Skip if models don't exist
|
||||||
|
SKIP_IF(!modelsExist(paths));
|
||||||
|
|
||||||
|
AceStepWorker worker;
|
||||||
|
worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
|
||||||
|
|
||||||
|
SongItem song("A very long ambient piece", "");
|
||||||
|
|
||||||
|
QString templateJson = R"({"inference_steps": 50, "shift": 3.0, "vocal_language": "en"})";
|
||||||
|
|
||||||
|
bool cancelReceived = false;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationCanceled,
|
||||||
|
[&cancelReceived](const SongItem&) {
|
||||||
|
std::cout << "\n Generation was canceled!" << std::endl;
|
||||||
|
cancelReceived = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << "\n Starting generation and will cancel after 2 seconds..." << std::endl;
|
||||||
|
|
||||||
|
// Start generation
|
||||||
|
bool result = worker.requestGeneration(song, templateJson);
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
|
||||||
|
// Wait 2 seconds then cancel
|
||||||
|
QThread::sleep(2);
|
||||||
|
worker.cancelGeneration();
|
||||||
|
|
||||||
|
// Wait a bit for cancellation to be processed
|
||||||
|
QThread::sleep(1);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
// Note: cancellation may or may not complete depending on where in the process
|
||||||
|
// the cancel was requested. The important thing is it doesn't crash.
|
||||||
|
std::cout << " Cancel requested, no crash detected" << std::endl;
|
||||||
|
ASSERT_TRUE(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 12: Test low VRAM mode generation
|
||||||
|
TEST(generateSongLowVram)
|
||||||
|
{
|
||||||
|
ModelPaths paths = getModelPathsFromSettings();
|
||||||
|
|
||||||
|
// Skip if models don't exist
|
||||||
|
SKIP_IF(!modelsExist(paths));
|
||||||
|
|
||||||
|
AceStepWorker worker;
|
||||||
|
worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
|
||||||
|
worker.setLowVramMode(true);
|
||||||
|
|
||||||
|
ASSERT_TRUE(worker.isLowVramMode());
|
||||||
|
|
||||||
|
SongItem song("Chill electronic music", "");
|
||||||
|
|
||||||
|
QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
|
||||||
|
|
||||||
|
// Track generation result
|
||||||
|
bool generationCompleted = false;
|
||||||
|
SongItem resultSong;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated,
|
||||||
|
[&generationCompleted, &resultSong](const SongItem& song) {
|
||||||
|
std::cout << "\n Low VRAM mode: Song generated successfully!" << std::endl;
|
||||||
|
std::cout << " Caption: " << song.caption.toStdString() << std::endl;
|
||||||
|
if (song.audioData) {
|
||||||
|
std::cout << " Audio data size: " << song.audioData->size() << " bytes" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << " Audio data size: null" << std::endl;
|
||||||
|
}
|
||||||
|
resultSong = song;
|
||||||
|
generationCompleted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
QString errorMsg;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError,
|
||||||
|
[&errorMsg](const QString& err) {
|
||||||
|
std::cout << "\n Error: " << err.toStdString() << std::endl;
|
||||||
|
errorMsg = err;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << "\n Starting low VRAM mode generation..." << std::endl;
|
||||||
|
|
||||||
|
// Request generation
|
||||||
|
bool result = worker.requestGeneration(song, templateJson);
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
|
||||||
|
// Use QEventLoop with timer for proper event processing
|
||||||
|
QEventLoop loop;
|
||||||
|
QTimer timeoutTimer;
|
||||||
|
|
||||||
|
timeoutTimer.setSingleShot(true);
|
||||||
|
timeoutTimer.start(300000); // 5 minute timeout
|
||||||
|
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||||
|
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
ASSERT_TRUE(generationCompleted);
|
||||||
|
ASSERT_TRUE(resultSong.audioData != nullptr);
|
||||||
|
ASSERT_TRUE(!resultSong.audioData->isEmpty());
|
||||||
|
|
||||||
|
std::cout << " Audio data size: " << resultSong.audioData->size() << " bytes" << std::endl;
|
||||||
|
ASSERT_TRUE(resultSong.audioData->size() > 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 13: Test normal mode keeps models loaded between generations
|
||||||
|
TEST(normalModeKeepsModelsLoaded)
|
||||||
|
{
|
||||||
|
ModelPaths paths = getModelPathsFromSettings();
|
||||||
|
|
||||||
|
// Skip if models don't exist
|
||||||
|
SKIP_IF(!modelsExist(paths));
|
||||||
|
|
||||||
|
AceStepWorker worker;
|
||||||
|
worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
|
||||||
|
// Normal mode is default (lowVramMode = false)
|
||||||
|
|
||||||
|
ASSERT_FALSE(worker.isLowVramMode());
|
||||||
|
|
||||||
|
QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
|
||||||
|
|
||||||
|
// Generate first song
|
||||||
|
bool firstGenerationCompleted = false;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated,
|
||||||
|
[&firstGenerationCompleted](const SongItem&) {
|
||||||
|
firstGenerationCompleted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError,
|
||||||
|
[](const QString& err) {
|
||||||
|
std::cout << "\n Error: " << err.toStdString() << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << "\n Generating first song (normal mode)..." << std::endl;
|
||||||
|
|
||||||
|
SongItem song1("First song", "");
|
||||||
|
bool result = worker.requestGeneration(song1, templateJson);
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
|
||||||
|
QEventLoop loop;
|
||||||
|
QTimer timeoutTimer;
|
||||||
|
timeoutTimer.setSingleShot(true);
|
||||||
|
timeoutTimer.start(300000);
|
||||||
|
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||||
|
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
ASSERT_TRUE(firstGenerationCompleted);
|
||||||
|
std::cout << " First generation completed, models should still be loaded" << std::endl;
|
||||||
|
|
||||||
|
// Generate second song - in normal mode this should be faster since models are already loaded
|
||||||
|
bool secondGenerationCompleted = false;
|
||||||
|
SongItem secondResult;
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated,
|
||||||
|
[&secondGenerationCompleted, &secondResult](const SongItem& song) {
|
||||||
|
secondGenerationCompleted = true;
|
||||||
|
secondResult = song;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << " Generating second song (should use cached models)..." << std::endl;
|
||||||
|
|
||||||
|
SongItem song2("Second song", "");
|
||||||
|
result = worker.requestGeneration(song2, templateJson);
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
|
||||||
|
QEventLoop loop2;
|
||||||
|
QTimer timeoutTimer2;
|
||||||
|
timeoutTimer2.setSingleShot(true);
|
||||||
|
timeoutTimer2.start(300000);
|
||||||
|
|
||||||
|
QObject::connect(&worker, &AceStepWorker::songGenerated, &loop2, &QEventLoop::quit);
|
||||||
|
QObject::connect(&worker, &AceStepWorker::generationError, &loop2, &QEventLoop::quit);
|
||||||
|
QObject::connect(&timeoutTimer2, &QTimer::timeout, &loop2, &QEventLoop::quit);
|
||||||
|
|
||||||
|
loop2.exec();
|
||||||
|
|
||||||
|
ASSERT_TRUE(secondGenerationCompleted);
|
||||||
|
ASSERT_TRUE(secondResult.audioData != nullptr);
|
||||||
|
ASSERT_TRUE(!secondResult.audioData->isEmpty());
|
||||||
|
|
||||||
|
std::cout << " Second generation completed successfully" << std::endl;
|
||||||
|
std::cout << " Audio data size: " << secondResult.audioData->size() << " bytes" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 14: Test setLowVramMode toggle
|
||||||
|
TEST(lowVramModeToggle)
|
||||||
|
{
|
||||||
|
AceStepWorker worker;
|
||||||
|
|
||||||
|
// Default should be false (normal mode)
|
||||||
|
ASSERT_FALSE(worker.isLowVramMode());
|
||||||
|
|
||||||
|
// Enable low VRAM mode
|
||||||
|
worker.setLowVramMode(true);
|
||||||
|
ASSERT_TRUE(worker.isLowVramMode());
|
||||||
|
|
||||||
|
// Disable low VRAM mode
|
||||||
|
worker.setLowVramMode(false);
|
||||||
|
ASSERT_FALSE(worker.isLowVramMode());
|
||||||
|
|
||||||
|
// Toggle again
|
||||||
|
worker.setLowVramMode(true);
|
||||||
|
ASSERT_TRUE(worker.isLowVramMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
std::cout << "=== AceStepWorker Tests ===" << std::endl;
|
||||||
|
|
||||||
|
RUN_TEST(initialState);
|
||||||
|
RUN_TEST(noModelPaths);
|
||||||
|
RUN_TEST(setModelPaths);
|
||||||
|
RUN_TEST(asyncReturnsImmediately);
|
||||||
|
RUN_TEST(cancellationFlag);
|
||||||
|
RUN_TEST(signalsExist);
|
||||||
|
RUN_TEST(requestConversion);
|
||||||
|
RUN_TEST(readSettings);
|
||||||
|
RUN_TEST(checkModelFiles);
|
||||||
|
RUN_TEST(generateSong);
|
||||||
|
RUN_TEST(cancellation);
|
||||||
|
RUN_TEST(generateSongLowVram);
|
||||||
|
RUN_TEST(normalModeKeepsModelsLoaded);
|
||||||
|
RUN_TEST(lowVramModeToggle);
|
||||||
|
|
||||||
|
std::cout << "\n=== Results ===" << std::endl;
|
||||||
|
std::cout << "Passed: " << testsPassed << std::endl;
|
||||||
|
std::cout << "Skipped: " << testsSkipped << std::endl;
|
||||||
|
std::cout << "Failed: " << testsFailed << std::endl;
|
||||||
|
|
||||||
|
return testsFailed > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
1
third_party/acestep.cpp
vendored
Submodule
1
third_party/acestep.cpp
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit d28398db0ffdb77e8ae071ff31bde8c559e7085a
|
||||||
Loading…
Add table
Add a link
Reference in a new issue