mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
Replaced Qt audio by miniaudio
This commit is contained in:
parent
f495e5d944
commit
c2d8ccdde9
9 changed files with 93580 additions and 30 deletions
84
software/library/miniaudio.c
Normal file
84
software/library/miniaudio.c
Normal 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
93401
software/library/miniaudio.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -73,9 +73,11 @@ set(PROJECT_SOURCES
|
||||||
src/new-project.ui
|
src/new-project.ui
|
||||||
../software/chip32/chip32_assembler.cpp
|
../software/chip32/chip32_assembler.cpp
|
||||||
../software/chip32/chip32_vm.c
|
../software/chip32/chip32_vm.c
|
||||||
|
../software/library/miniaudio.c
|
||||||
)
|
)
|
||||||
|
|
||||||
include_directories(../software/chip32)
|
include_directories(../software/chip32)
|
||||||
|
include_directories(../software/library)
|
||||||
|
|
||||||
add_subdirectory(QHexView)
|
add_subdirectory(QHexView)
|
||||||
option(BUILD_SHARED_LIBS "" OFF)
|
option(BUILD_SHARED_LIBS "" OFF)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 5554a14d72a39ac173bd75cbcdb7527cb9157a0f
|
Subproject commit bc72cd22267f752f6552b4f6e6c733649e54c4c2
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit ba75bd93e1b1c5d5bc387e49f35737697d180b30
|
Subproject commit e1c9ee891129e24c5703d5044509faabd8725923
|
||||||
|
|
@ -14,7 +14,7 @@ ResourcesDock::ResourcesDock(StoryProject &project, ResourceModel &model)
|
||||||
|
|
||||||
m_proxyModel.setSourceModel(&m_resourcesModel);
|
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"),
|
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"),
|
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();
|
QItemSelectionModel *selectionModel = m_uiOstResources.resourcesView->selectionModel();
|
||||||
// Récupération des lignes sélectionnées
|
// Récupération des lignes sélectionnées
|
||||||
QModelIndexList selectedRows = selectionModel->selectedRows();
|
QModelIndexList selectedRows = selectionModel->selectedRows();
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,52 @@
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
|
||||||
|
extern "C" int miniaudio_play(const char* filename);
|
||||||
|
|
||||||
StoryGraphModel::StoryGraphModel(StoryProject &project)
|
StoryGraphModel::StoryGraphModel(StoryProject &project)
|
||||||
: m_project(project)
|
: m_project(project)
|
||||||
{
|
{
|
||||||
m_player = new QMediaPlayer;
|
m_audioThread = std::thread( std::bind(&StoryGraphModel::AudioThread, this) );
|
||||||
m_audioOutput = new QAudioOutput;
|
// connect(m_player, &QMediaPlayer::playbackStateChanged, this, [&](QMediaPlayer::PlaybackState newState) {
|
||||||
m_player->setAudioOutput(m_audioOutput);
|
// if (newState == QMediaPlayer::PlaybackState::StoppedState) {
|
||||||
|
// m_player->stop();
|
||||||
connect(m_player, &QMediaPlayer::playbackStateChanged, this, [&](QMediaPlayer::PlaybackState newState) {
|
// emit sigAudioStopped();
|
||||||
if (newState == QMediaPlayer::PlaybackState::StoppedState) {
|
// }
|
||||||
m_player->stop();
|
// });
|
||||||
emit sigAudioStopped();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StoryGraphModel::~StoryGraphModel()
|
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
|
std::unordered_set<NodeId> StoryGraphModel::allNodeIds() const
|
||||||
{
|
{
|
||||||
return _nodeIds;
|
return _nodeIds;
|
||||||
|
|
@ -415,13 +441,6 @@ nlohmann::json StoryGraphModel::SaveNode(NodeId const nodeId) const
|
||||||
return nodeJson;
|
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)
|
void StoryGraphModel::LoadNode(const nlohmann::json &nodeJson)
|
||||||
{
|
{
|
||||||
|
|
@ -502,7 +521,7 @@ std::string StoryGraphModel::Build()
|
||||||
{
|
{
|
||||||
std::stringstream chip32;
|
std::stringstream chip32;
|
||||||
|
|
||||||
FindFirstNode();
|
NodeId firstNode = FindFirstNode();
|
||||||
|
|
||||||
chip32 << "\tjump .entry\r\n";
|
chip32 << "\tjump .entry\r\n";
|
||||||
|
|
||||||
|
|
@ -516,7 +535,7 @@ std::string StoryGraphModel::Build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chip32 << ".entry:\r\n";
|
|
||||||
|
|
||||||
nlohmann::json nodesJsonArray;
|
nlohmann::json nodesJsonArray;
|
||||||
for (auto const nodeId : allNodeIds())
|
for (auto const nodeId : allNodeIds())
|
||||||
|
|
@ -526,6 +545,11 @@ std::string StoryGraphModel::Build()
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
auto &model = it->second;
|
auto &model = it->second;
|
||||||
|
if (model->getNodeId() == firstNode)
|
||||||
|
{
|
||||||
|
chip32 << ".entry:\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
chip32 << model->Build() << "\n";
|
chip32 << model->Build() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@
|
||||||
#include <QAudioOutput>
|
#include <QAudioOutput>
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <queue>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
using QtNodes::ConnectionStyle;
|
using QtNodes::ConnectionStyle;
|
||||||
using QtNodes::NodeRole;
|
using QtNodes::NodeRole;
|
||||||
using QtNodes::StyleCollection;
|
using QtNodes::StyleCollection;
|
||||||
|
|
@ -37,6 +41,40 @@ class PortAddRemoveWidget;
|
||||||
#include "story_node_base.h"
|
#include "story_node_base.h"
|
||||||
#include "story_project.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
|
* The class implements a bare minimum required to demonstrate a model-based
|
||||||
* graph.
|
* graph.
|
||||||
|
|
@ -179,6 +217,7 @@ private:
|
||||||
/// A convenience variable needed for generating unique node ids.
|
/// A convenience variable needed for generating unique node ids.
|
||||||
unsigned int _nextNodeId{0};
|
unsigned int _nextNodeId{0};
|
||||||
|
|
||||||
QMediaPlayer *m_player{nullptr};
|
std::thread m_audioThread;
|
||||||
QAudioOutput *m_audioOutput{nullptr};
|
ThreadSafeQueue<AudioCommand> m_audioQueue;
|
||||||
|
void AudioThread();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,15 @@ VmDock::VmDock(Chip32::Assembler &assembler)
|
||||||
m_uiVM.regsTable->setItem(i, 1, regValueItem);
|
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();
|
emit sigCompile();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_uiVM.playButton, &QPushButton::clicked, [=](bool checked) {
|
connect(m_uiVM.playButton, &QPushButton::clicked, [&](bool checked) {
|
||||||
emit sigStepInstruction();
|
emit sigStepInstruction();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_uiVM.buildButton, &QPushButton::clicked, [=](bool checked) {
|
connect(m_uiVM.buildButton, &QPushButton::clicked, [&](bool checked) {
|
||||||
emit sigBuild();
|
emit sigBuild();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue