Keep audio in memory

This commit is contained in:
Carl Philipp Klemm 2026-04-15 12:35:44 +02:00
parent de7207f07e
commit 14dec9f335
6 changed files with 78 additions and 36 deletions

View file

@ -191,18 +191,8 @@ void AceStepWorker::runGeneration()
return; return;
} }
// Save audio to file // Store audio in memory as WAV
QString wavFile = m_tempDir + "/request_" + QString::number(m_uid) + ".wav"; auto audioData = std::make_shared<QByteArray>();
// 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;
}
// Simple WAV header + stereo float data // Simple WAV header + stereo float data
int numChannels = 2; int numChannels = 2;
@ -212,27 +202,27 @@ void AceStepWorker::runGeneration()
int dataSize = outputAudio.n_samples * numChannels * (bitsPerSample / 8); int dataSize = outputAudio.n_samples * numChannels * (bitsPerSample / 8);
// RIFF header // RIFF header
outFile.write("RIFF"); audioData->append("RIFF");
outFile.write(reinterpret_cast<const char*>(&dataSize), 4); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&dataSize), 4));
outFile.write("WAVE"); audioData->append("WAVE");
// fmt chunk // fmt chunk
outFile.write("fmt "); audioData->append("fmt ");
int fmtSize = 16; int fmtSize = 16;
outFile.write(reinterpret_cast<const char*>(&fmtSize), 4); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&fmtSize), 4));
short audioFormat = 1; // PCM short audioFormat = 1; // PCM
outFile.write(reinterpret_cast<const char*>(&audioFormat), 2); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&audioFormat), 2));
short numCh = numChannels; short numCh = numChannels;
outFile.write(reinterpret_cast<const char*>(&numCh), 2); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&numCh), 2));
int sampleRate = outputAudio.sample_rate; int sampleRate = outputAudio.sample_rate;
outFile.write(reinterpret_cast<const char*>(&sampleRate), 4); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&sampleRate), 4));
outFile.write(reinterpret_cast<const char*>(&byteRate), 4); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&byteRate), 4));
outFile.write(reinterpret_cast<const char*>(&blockAlign), 2); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&blockAlign), 2));
outFile.write(reinterpret_cast<const char*>(&bitsPerSample), 2); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&bitsPerSample), 2));
// data chunk // data chunk
outFile.write("data"); audioData->append("data");
outFile.write(reinterpret_cast<const char*>(&dataSize), 4); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&dataSize), 4));
// Convert float samples to 16-bit and write // Convert float samples to 16-bit and write
QVector<short> interleaved(outputAudio.n_samples * numChannels); QVector<short> interleaved(outputAudio.n_samples * numChannels);
@ -246,15 +236,14 @@ void AceStepWorker::runGeneration()
interleaved[i * 2] = static_cast<short>(left * 32767.0f); interleaved[i * 2] = static_cast<short>(left * 32767.0f);
interleaved[i * 2 + 1] = static_cast<short>(right * 32767.0f); interleaved[i * 2 + 1] = static_cast<short>(right * 32767.0f);
} }
outFile.write(reinterpret_cast<const char*>(interleaved.data()), dataSize); audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(interleaved.data()), dataSize));
outFile.close();
// Free audio buffer // Free audio buffer
ace_audio_free(&outputAudio); ace_audio_free(&outputAudio);
// Store the JSON with all generated fields // Store the JSON with all generated fields
m_currentSong.json = QString::fromStdString(request_to_json(&lmOutput, true)); m_currentSong.json = QString::fromStdString(request_to_json(&lmOutput, true));
m_currentSong.file = wavFile; m_currentSong.audioData = audioData;
// Extract BPM if available // Extract BPM if available
if (lmOutput.bpm > 0) if (lmOutput.bpm > 0)

View file

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

View file

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

View file

@ -409,7 +409,14 @@ void MainWindow::playbackStarted()
void MainWindow::playSong(const SongItem& song) void MainWindow::playSong(const SongItem& song)
{ {
currentSong = song; currentSong = song;
if (song.audioData)
{
audioPlayer->play(song.audioData);
}
else if (!song.file.isEmpty())
{
audioPlayer->play(song.file); 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);
@ -610,7 +617,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;
// 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"); QFile::copy(currentSong.file, filePath + ".wav");
}
file.write(jsonData); file.write(jsonData);
file.close(); file.close();
} }

View file

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

View file

@ -274,13 +274,12 @@ TEST(generateSong)
loop.exec(); loop.exec();
ASSERT_TRUE(generationCompleted); ASSERT_TRUE(generationCompleted);
ASSERT_TRUE(!resultSong.file.isEmpty()); ASSERT_TRUE(resultSong.audioData != nullptr);
ASSERT_TRUE(QFileInfo::exists(resultSong.file)); ASSERT_TRUE(!resultSong.audioData->isEmpty());
// Check file is not empty // Check audio data is not empty
QFileInfo fileInfo(resultSong.file); std::cout << " Audio data size: " << resultSong.audioData->size() << " bytes" << std::endl;
std::cout << " File size: " << fileInfo.size() << " bytes" << std::endl; ASSERT_TRUE(resultSong.audioData->size() > 1000); // Should be at least 1KB for valid audio
ASSERT_TRUE(fileInfo.size() > 1000); // Should be at least 1KB for valid audio
} }
// Test 11: Test cancellation // Test 11: Test cancellation