diff --git a/src/AceStepWorker.cpp b/src/AceStepWorker.cpp index 2e7bb85..c9c9e25 100644 --- a/src/AceStepWorker.cpp +++ b/src/AceStepWorker.cpp @@ -191,18 +191,8 @@ void AceStepWorker::runGeneration() return; } - // Save audio to file - QString wavFile = m_tempDir + "/request_" + QString::number(m_uid) + ".wav"; - - // Write WAV file - QFile outFile(wavFile); - if (!outFile.open(QIODevice::WriteOnly)) - { - emit generationError("Failed to create output file: " + outFile.errorString()); - ace_audio_free(&outputAudio); - m_busy.store(false); - return; - } + // Store audio in memory as WAV + auto audioData = std::make_shared(); // Simple WAV header + stereo float data int numChannels = 2; @@ -212,27 +202,27 @@ void AceStepWorker::runGeneration() int dataSize = outputAudio.n_samples * numChannels * (bitsPerSample / 8); // RIFF header - outFile.write("RIFF"); - outFile.write(reinterpret_cast(&dataSize), 4); - outFile.write("WAVE"); + audioData->append("RIFF"); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&dataSize), 4)); + audioData->append("WAVE"); // fmt chunk - outFile.write("fmt "); + audioData->append("fmt "); int fmtSize = 16; - outFile.write(reinterpret_cast(&fmtSize), 4); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&fmtSize), 4)); short audioFormat = 1; // PCM - outFile.write(reinterpret_cast(&audioFormat), 2); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&audioFormat), 2)); short numCh = numChannels; - outFile.write(reinterpret_cast(&numCh), 2); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&numCh), 2)); int sampleRate = outputAudio.sample_rate; - outFile.write(reinterpret_cast(&sampleRate), 4); - outFile.write(reinterpret_cast(&byteRate), 4); - outFile.write(reinterpret_cast(&blockAlign), 2); - outFile.write(reinterpret_cast(&bitsPerSample), 2); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&sampleRate), 4)); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&byteRate), 4)); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&blockAlign), 2)); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&bitsPerSample), 2)); // data chunk - outFile.write("data"); - outFile.write(reinterpret_cast(&dataSize), 4); + audioData->append("data"); + audioData->append(QByteArray::fromRawData(reinterpret_cast(&dataSize), 4)); // Convert float samples to 16-bit and write QVector interleaved(outputAudio.n_samples * numChannels); @@ -246,15 +236,14 @@ void AceStepWorker::runGeneration() interleaved[i * 2] = static_cast(left * 32767.0f); interleaved[i * 2 + 1] = static_cast(right * 32767.0f); } - outFile.write(reinterpret_cast(interleaved.data()), dataSize); - outFile.close(); + audioData->append(QByteArray::fromRawData(reinterpret_cast(interleaved.data()), dataSize)); // Free audio buffer ace_audio_free(&outputAudio); // Store the JSON with all generated fields m_currentSong.json = QString::fromStdString(request_to_json(&lmOutput, true)); - m_currentSong.file = wavFile; + m_currentSong.audioData = audioData; // Extract BPM if available if (lmOutput.bpm > 0) diff --git a/src/AudioPlayer.cpp b/src/AudioPlayer.cpp index 2f039bb..bb53670 100644 --- a/src/AudioPlayer.cpp +++ b/src/AudioPlayer.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "AudioPlayer.h" +#include #include AudioPlayer::AudioPlayer(QObject *parent) @@ -48,6 +49,33 @@ void AudioPlayer::play(const QString &filePath) positionTimer->start(); } +void AudioPlayer::play(std::shared_ptr 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() { if (!isPlaying()) diff --git a/src/AudioPlayer.h b/src/AudioPlayer.h index 6eb4b25..e1d0b47 100644 --- a/src/AudioPlayer.h +++ b/src/AudioPlayer.h @@ -14,6 +14,7 @@ #include #include #include +#include class AudioPlayer : public QObject { @@ -23,6 +24,7 @@ public: ~AudioPlayer(); void play(const QString &filePath); + void play(std::shared_ptr audioData); void play(); void stop(); void pause(); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ba2c79e..a8b64f7 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -409,7 +409,14 @@ void MainWindow::playbackStarted() void MainWindow::playSong(const SongItem& 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)); ui->nowPlayingLabel->setText("Now Playing: " + song.caption); ui->lyricsTextEdit->setPlainText(song.lyrics); @@ -610,7 +617,22 @@ void MainWindow::on_actionSaveSong() QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) 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.close(); } diff --git a/src/SongItem.h b/src/SongItem.h index a8e993c..0a80926 100644 --- a/src/SongItem.h +++ b/src/SongItem.h @@ -8,6 +8,7 @@ #include #include #include +#include class SongItem { @@ -22,6 +23,7 @@ public: uint64_t uniqueId; QString file; QString json; + std::shared_ptr audioData; SongItem(const QString &caption = "", const QString &lyrics = ""); SongItem(const QJsonObject& json); diff --git a/tests/test_acestep_worker.cpp b/tests/test_acestep_worker.cpp index e360d70..2d32416 100644 --- a/tests/test_acestep_worker.cpp +++ b/tests/test_acestep_worker.cpp @@ -274,13 +274,12 @@ TEST(generateSong) loop.exec(); ASSERT_TRUE(generationCompleted); - ASSERT_TRUE(!resultSong.file.isEmpty()); - ASSERT_TRUE(QFileInfo::exists(resultSong.file)); + ASSERT_TRUE(resultSong.audioData != nullptr); + ASSERT_TRUE(!resultSong.audioData->isEmpty()); - // Check file is not empty - QFileInfo fileInfo(resultSong.file); - std::cout << " File size: " << fileInfo.size() << " bytes" << std::endl; - ASSERT_TRUE(fileInfo.size() > 1000); // Should be at least 1KB for valid audio + // 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