Replaced Qt audio by miniaudio

This commit is contained in:
Anthony Rabine 2023-05-23 09:31:10 +02:00
parent f495e5d944
commit c2d8ccdde9
9 changed files with 93580 additions and 30 deletions

View file

@ -0,0 +1,84 @@
/*
Demonstrates how to load a sound file and play it back using the low-level API.
The low-level API uses a callback to deliver audio between the application and miniaudio for playback or recording. When
in playback mode, as in this example, the application sends raw audio data to miniaudio which is then played back through
the default playback device as defined by the operating system.
This example uses the `ma_decoder` API to load a sound and play it back. The decoder is entirely decoupled from the
device and can be used independently of it. This example only plays back a single sound file, but it's possible to play
back multiple files by simple loading multiple decoders and mixing them (do not create multiple devices to do this). See
the simple_mixing example for how best to do this.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#include <stdio.h>
ma_event g_stopEvent; /* <-- Signaled by the audio thread, waited on by the main thread. */
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
ma_decoder* pDecoder = (ma_decoder*)pDevice->pUserData;
if (pDecoder == NULL) {
return;
}
ma_uint64 framesRead;
ma_result result = ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, &framesRead);
if (framesRead < frameCount) {
// Reached the end.
ma_event_signal(&g_stopEvent);
}
(void)pInput;
}
int miniaudio_play(const char* filename)
{
ma_result result;
ma_decoder decoder;
ma_device_config deviceConfig;
ma_device device;
result = ma_decoder_init_file(filename, NULL, &decoder);
if (result != MA_SUCCESS) {
printf("Could not load file: %s\n", filename);
return -2;
}
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.format = decoder.outputFormat;
deviceConfig.playback.channels = decoder.outputChannels;
deviceConfig.sampleRate = decoder.outputSampleRate;
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = &decoder;
if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
printf("Failed to open playback device.\n");
ma_decoder_uninit(&decoder);
return -3;
}
ma_event_init(&g_stopEvent);
if (ma_device_start(&device) != MA_SUCCESS) {
printf("Failed to start playback device.\n");
ma_device_uninit(&device);
ma_decoder_uninit(&decoder);
return -4;
}
printf("Wait untile end...\n");
// getchar();
ma_event_wait(&g_stopEvent);
printf("End!\n");
ma_device_uninit(&device);
ma_decoder_uninit(&decoder);
return 0;
}

93401
software/library/miniaudio.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -73,9 +73,11 @@ set(PROJECT_SOURCES
src/new-project.ui
../software/chip32/chip32_assembler.cpp
../software/chip32/chip32_vm.c
../software/library/miniaudio.c
)
include_directories(../software/chip32)
include_directories(../software/library)
add_subdirectory(QHexView)
option(BUILD_SHARED_LIBS "" OFF)

@ -1 +1 @@
Subproject commit 5554a14d72a39ac173bd75cbcdb7527cb9157a0f
Subproject commit bc72cd22267f752f6552b4f6e6c733649e54c4c2

@ -1 +1 @@
Subproject commit ba75bd93e1b1c5d5bc387e49f35737697d180b30
Subproject commit e1c9ee891129e24c5703d5044509faabd8725923

View file

@ -14,7 +14,7 @@ ResourcesDock::ResourcesDock(StoryProject &project, ResourceModel &model)
m_proxyModel.setSourceModel(&m_resourcesModel);
connect(m_uiOstResources.addImageButton, &QPushButton::clicked, [=](bool checked) {
connect(m_uiOstResources.addImageButton, &QPushButton::clicked, [&](bool checked) {
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
".",
@ -34,7 +34,7 @@ ResourcesDock::ResourcesDock(StoryProject &project, ResourceModel &model)
}
});
connect(m_uiOstResources.addSoundButton, &QPushButton::clicked, [=](bool checked) {
connect(m_uiOstResources.addSoundButton, &QPushButton::clicked, [&](bool checked) {
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
".",
@ -54,7 +54,7 @@ ResourcesDock::ResourcesDock(StoryProject &project, ResourceModel &model)
}
});
connect(m_uiOstResources.deleteButton, &QPushButton::clicked, [=](bool checked) {
connect(m_uiOstResources.deleteButton, &QPushButton::clicked, [&](bool checked) {
QItemSelectionModel *selectionModel = m_uiOstResources.resourcesView->selectionModel();
// Récupération des lignes sélectionnées
QModelIndexList selectedRows = selectionModel->selectedRows();

View file

@ -6,25 +6,51 @@
#include <iterator>
#include <QDir>
extern "C" int miniaudio_play(const char* filename);
StoryGraphModel::StoryGraphModel(StoryProject &project)
: m_project(project)
{
m_player = new QMediaPlayer;
m_audioOutput = new QAudioOutput;
m_player->setAudioOutput(m_audioOutput);
connect(m_player, &QMediaPlayer::playbackStateChanged, this, [&](QMediaPlayer::PlaybackState newState) {
if (newState == QMediaPlayer::PlaybackState::StoppedState) {
m_player->stop();
emit sigAudioStopped();
}
});
m_audioThread = std::thread( std::bind(&StoryGraphModel::AudioThread, this) );
// connect(m_player, &QMediaPlayer::playbackStateChanged, this, [&](QMediaPlayer::PlaybackState newState) {
// if (newState == QMediaPlayer::PlaybackState::StoppedState) {
// m_player->stop();
// emit sigAudioStopped();
// }
// });
}
StoryGraphModel::~StoryGraphModel()
{
//
// Quit audio thread
m_audioQueue.push({"quit", ""});
if (m_audioThread.joinable())
{
m_audioThread.join();
}
}
void StoryGraphModel::AudioThread()
{
for (;;)
{
auto cmd = m_audioQueue.front();
if (cmd.order == "play") {
miniaudio_play(cmd.filename.c_str());
QMetaObject::invokeMethod(this, "sigAudioStopped", Qt::QueuedConnection);
m_audioQueue.pop();
} else {
return;
}
}
}
void StoryGraphModel::PlaySound(const QString &fileName)
{
m_audioQueue.push({"play", fileName.toStdString()});
}
std::unordered_set<NodeId> StoryGraphModel::allNodeIds() const
{
@ -415,13 +441,6 @@ nlohmann::json StoryGraphModel::SaveNode(NodeId const nodeId) const
return nodeJson;
}
void StoryGraphModel::PlaySound(const QString &fileName)
{
m_player->setSource(QUrl::fromLocalFile(fileName));
m_audioOutput->setVolume(50);
m_player->play();
}
void StoryGraphModel::LoadNode(const nlohmann::json &nodeJson)
{
@ -502,7 +521,7 @@ std::string StoryGraphModel::Build()
{
std::stringstream chip32;
FindFirstNode();
NodeId firstNode = FindFirstNode();
chip32 << "\tjump .entry\r\n";
@ -516,7 +535,7 @@ std::string StoryGraphModel::Build()
}
}
chip32 << ".entry:\r\n";
nlohmann::json nodesJsonArray;
for (auto const nodeId : allNodeIds())
@ -526,6 +545,11 @@ std::string StoryGraphModel::Build()
return "";
auto &model = it->second;
if (model->getNodeId() == firstNode)
{
chip32 << ".entry:\r\n";
}
chip32 << model->Build() << "\n";
}

View file

@ -16,6 +16,10 @@
#include <QAudioOutput>
#include <QMediaPlayer>
#include <thread>
#include <queue>
#include <condition_variable>
using QtNodes::ConnectionStyle;
using QtNodes::NodeRole;
using QtNodes::StyleCollection;
@ -37,6 +41,40 @@ class PortAddRemoveWidget;
#include "story_node_base.h"
#include "story_project.h"
template <typename T>
class ThreadSafeQueue {
std::mutex mutex;
std::condition_variable cond_var;
std::queue<T> queue;
public:
void push(T&& item) {
{
std::lock_guard lock(mutex);
queue.push(item);
}
cond_var.notify_one();
}
T& front() {
std::unique_lock lock(mutex);
cond_var.wait(lock, [&]{ return !queue.empty(); });
return queue.front();
}
void pop() {
std::lock_guard lock(mutex);
queue.pop();
}
};
struct AudioCommand {
std::string order;
std::string filename;
};
/**
* The class implements a bare minimum required to demonstrate a model-based
* graph.
@ -179,6 +217,7 @@ private:
/// A convenience variable needed for generating unique node ids.
unsigned int _nextNodeId{0};
QMediaPlayer *m_player{nullptr};
QAudioOutput *m_audioOutput{nullptr};
std::thread m_audioThread;
ThreadSafeQueue<AudioCommand> m_audioQueue;
void AudioThread();
};

View file

@ -20,15 +20,15 @@ VmDock::VmDock(Chip32::Assembler &assembler)
m_uiVM.regsTable->setItem(i, 1, regValueItem);
}
connect(m_uiVM.generateButton, &QPushButton::clicked, [=](bool checked) {
connect(m_uiVM.generateButton, &QPushButton::clicked, [&](bool checked) {
emit sigCompile();
});
connect(m_uiVM.playButton, &QPushButton::clicked, [=](bool checked) {
connect(m_uiVM.playButton, &QPushButton::clicked, [&](bool checked) {
emit sigStepInstruction();
});
connect(m_uiVM.buildButton, &QPushButton::clicked, [=](bool checked) {
connect(m_uiVM.buildButton, &QPushButton::clicked, [&](bool checked) {
emit sigBuild();
});
}