Keep audio in memory
This commit is contained in:
parent
de7207f07e
commit
14dec9f335
6 changed files with 78 additions and 36 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue