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;
}
// 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<QByteArray>();
// 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<const char*>(&dataSize), 4);
outFile.write("WAVE");
audioData->append("RIFF");
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&dataSize), 4));
audioData->append("WAVE");
// fmt chunk
outFile.write("fmt ");
audioData->append("fmt ");
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
outFile.write(reinterpret_cast<const char*>(&audioFormat), 2);
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&audioFormat), 2));
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;
outFile.write(reinterpret_cast<const char*>(&sampleRate), 4);
outFile.write(reinterpret_cast<const char*>(&byteRate), 4);
outFile.write(reinterpret_cast<const char*>(&blockAlign), 2);
outFile.write(reinterpret_cast<const char*>(&bitsPerSample), 2);
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
outFile.write("data");
outFile.write(reinterpret_cast<const char*>(&dataSize), 4);
audioData->append("data");
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(&dataSize), 4));
// Convert float samples to 16-bit and write
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 + 1] = static_cast<short>(right * 32767.0f);
}
outFile.write(reinterpret_cast<const char*>(interleaved.data()), dataSize);
outFile.close();
audioData->append(QByteArray::fromRawData(reinterpret_cast<const char*>(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)

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "AudioPlayer.h"
#include <QBuffer>
#include <QDebug>
AudioPlayer::AudioPlayer(QObject *parent)
@ -48,6 +49,33 @@ void AudioPlayer::play(const QString &filePath)
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()
{
if (!isPlaying())

View file

@ -14,6 +14,7 @@
#include <QMediaDevices>
#include <QAudioDevice>
#include <QTimer>
#include <memory>
class AudioPlayer : public QObject
{
@ -23,6 +24,7 @@ public:
~AudioPlayer();
void play(const QString &filePath);
void play(std::shared_ptr<QByteArray> audioData);
void play();
void stop();
void pause();

View file

@ -409,7 +409,14 @@ void MainWindow::playbackStarted()
void MainWindow::playSong(const SongItem& song)
{
currentSong = song;
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;
// 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();
}

View file

@ -8,6 +8,7 @@
#include <QRandomGenerator>
#include <cstdint>
#include <QJsonObject>
#include <memory>
class SongItem
{
@ -22,6 +23,7 @@ public:
uint64_t uniqueId;
QString file;
QString json;
std::shared_ptr<QByteArray> audioData;
SongItem(const QString &caption = "", const QString &lyrics = "");
SongItem(const QJsonObject& json);

View file

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