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
|
||||
../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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue