mirror of https://github.com/mgba-emu/mgba.git
Audio rendering in Qt
This commit is contained in:
parent
8a82144ceb
commit
baeaf8729f
|
@ -0,0 +1,33 @@
|
||||||
|
#include "AudioDevice.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "gba.h"
|
||||||
|
#include "gba-audio.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace QGBA;
|
||||||
|
|
||||||
|
AudioDevice::AudioDevice(GBAAudio* audio, QObject* parent)
|
||||||
|
: QIODevice(parent)
|
||||||
|
, m_audio(audio)
|
||||||
|
{
|
||||||
|
setOpenMode(ReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDevice::setFormat(const QAudioFormat& format) {
|
||||||
|
// TODO: merge where the fudge rate exists
|
||||||
|
float fudgeRate = 16853760.0f / GBA_ARM7TDMI_FREQUENCY;
|
||||||
|
m_ratio = format.sampleRate() / (float) (m_audio->sampleRate * fudgeRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AudioDevice::readData(char* data, qint64 maxSize) {
|
||||||
|
if (maxSize > 0xFFFFFFFF) {
|
||||||
|
maxSize = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GBAAudioResampleNN(m_audio, m_ratio, &m_drift, reinterpret_cast<GBAStereoSample*>(data), maxSize / sizeof(GBAStereoSample)) * sizeof(GBAStereoSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AudioDevice::writeData(const char*, qint64) {
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef QGBA_AUDIO_DEVICE
|
||||||
|
#define QGBA_AUDIO_DEVICE
|
||||||
|
|
||||||
|
#include <QAudioFormat>
|
||||||
|
#include <QIODevice>
|
||||||
|
|
||||||
|
struct GBAAudio;
|
||||||
|
|
||||||
|
namespace QGBA {
|
||||||
|
|
||||||
|
class AudioDevice : public QIODevice {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
AudioDevice(GBAAudio* audio, QObject* parent = 0);
|
||||||
|
|
||||||
|
void setFormat(const QAudioFormat& format);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual qint64 readData(char* data, qint64 maxSize);
|
||||||
|
virtual qint64 writeData(const char* data, qint64 maxSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GBAAudio* m_audio;
|
||||||
|
float m_drift;
|
||||||
|
float m_ratio;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -14,11 +14,11 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
find_package(Qt5Widgets REQUIRED)
|
find_package(Qt5Widgets REQUIRED)
|
||||||
|
|
||||||
set(UI_FILES Window.ui)
|
set(UI_FILES Window.ui)
|
||||||
set(SOURCE_FILES Display.cpp GameController.cpp Window.cpp)
|
set(SOURCE_FILES AudioDevice.cpp Display.cpp GameController.cpp Window.cpp)
|
||||||
|
|
||||||
qt5_wrap_ui(UI_HEADERS ${UI_FILES})
|
qt5_wrap_ui(UI_HEADERS ${UI_FILES})
|
||||||
|
|
||||||
add_executable(QGBAc WIN32 MACOSX_BUNDLE ${UI_FILES} ${UI_HEADERS} main.cpp ${SOURCE_FILES})
|
add_executable(QGBAc WIN32 MACOSX_BUNDLE ${UI_FILES} ${UI_HEADERS} main.cpp ${SOURCE_FILES})
|
||||||
|
|
||||||
qt5_use_modules(QGBAc Widgets OpenGL)
|
qt5_use_modules(QGBAc Widgets Multimedia OpenGL)
|
||||||
target_link_libraries(QGBAc ${OPENGL_LIBRARY} ${BINARY_NAME})
|
target_link_libraries(QGBAc ${OPENGL_LIBRARY} ${BINARY_NAME})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "GameController.h"
|
#include "GameController.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
#include "gba.h"
|
||||||
#include "renderers/video-software.h"
|
#include "renderers/video-software.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ using namespace QGBA;
|
||||||
GameController::GameController(QObject* parent)
|
GameController::GameController(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_drawContext(256, 256, QImage::Format_RGB32)
|
, m_drawContext(256, 256, QImage::Format_RGB32)
|
||||||
|
, m_audioContext(0)
|
||||||
{
|
{
|
||||||
m_renderer = new GBAVideoSoftwareRenderer;
|
m_renderer = new GBAVideoSoftwareRenderer;
|
||||||
GBAVideoSoftwareRendererCreate(m_renderer);
|
GBAVideoSoftwareRendererCreate(m_renderer);
|
||||||
|
@ -19,11 +21,14 @@ GameController::GameController(QObject* parent)
|
||||||
m_threadContext.renderer = &m_renderer->d;
|
m_threadContext.renderer = &m_renderer->d;
|
||||||
m_threadContext.frameskip = 0;
|
m_threadContext.frameskip = 0;
|
||||||
m_threadContext.sync.videoFrameWait = 0;
|
m_threadContext.sync.videoFrameWait = 0;
|
||||||
m_threadContext.sync.audioWait = 0;
|
m_threadContext.sync.audioWait = 1;
|
||||||
m_threadContext.startCallback = 0;
|
m_threadContext.startCallback = [] (GBAThread* context) {
|
||||||
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
|
controller->setupAudio(&context->gba->audio);
|
||||||
|
};
|
||||||
m_threadContext.cleanCallback = 0;
|
m_threadContext.cleanCallback = 0;
|
||||||
m_threadContext.frameCallback = [] (GBAThread* context) {
|
m_threadContext.frameCallback = [] (GBAThread* context) {
|
||||||
GameController* controller = (GameController*) context->userData;
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
controller->frameAvailable(controller->m_drawContext);
|
controller->frameAvailable(controller->m_drawContext);
|
||||||
};
|
};
|
||||||
m_threadContext.userData = this;
|
m_threadContext.userData = this;
|
||||||
|
@ -48,3 +53,12 @@ bool GameController::loadGame(const QString& path) {
|
||||||
GBAThreadStart(&m_threadContext);
|
GBAThreadStart(&m_threadContext);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameController::setupAudio(GBAAudio* audio) {
|
||||||
|
if (m_audioContext) {
|
||||||
|
delete m_audioContext;
|
||||||
|
}
|
||||||
|
m_audioContext = new AudioDevice(audio);
|
||||||
|
|
||||||
|
emit audioDeviceAvailable(m_audioContext);
|
||||||
|
}
|
||||||
|
|
|
@ -6,10 +6,13 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include "AudioDevice.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "gba-thread.h"
|
#include "gba-thread.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GBAAudio;
|
||||||
struct GBAVideoSoftwareRenderer;
|
struct GBAVideoSoftwareRenderer;
|
||||||
|
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
@ -23,12 +26,16 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void frameAvailable(const QImage&);
|
void frameAvailable(const QImage&);
|
||||||
|
void audioDeviceAvailable(AudioDevice*);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool loadGame(const QString& path);
|
bool loadGame(const QString& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setupAudio(GBAAudio* audio);
|
||||||
|
|
||||||
QImage m_drawContext;
|
QImage m_drawContext;
|
||||||
|
AudioDevice* m_audioContext;
|
||||||
GBAThread m_threadContext;
|
GBAThread m_threadContext;
|
||||||
GBAVideoSoftwareRenderer* m_renderer;
|
GBAVideoSoftwareRenderer* m_renderer;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ Window::Window(QWidget* parent) : QMainWindow(parent) {
|
||||||
m_display = new Display(this);
|
m_display = new Display(this);
|
||||||
setCentralWidget(m_display);
|
setCentralWidget(m_display);
|
||||||
connect(m_controller, SIGNAL(frameAvailable(const QImage&)), m_display, SLOT(draw(const QImage&)));
|
connect(m_controller, SIGNAL(frameAvailable(const QImage&)), m_display, SLOT(draw(const QImage&)));
|
||||||
|
connect(m_controller, SIGNAL(audioDeviceAvailable(AudioDevice*)), this, SLOT(setupAudio(AudioDevice*)));
|
||||||
|
|
||||||
connect(actionOpen, SIGNAL(triggered()), this, SLOT(selectROM()));
|
connect(actionOpen, SIGNAL(triggered()), this, SLOT(selectROM()));
|
||||||
}
|
}
|
||||||
|
@ -21,3 +22,20 @@ void Window::selectROM() {
|
||||||
m_controller->loadGame(filename);
|
m_controller->loadGame(filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::setupAudio(AudioDevice* device) {
|
||||||
|
if (!m_audio) {
|
||||||
|
QAudioFormat format;
|
||||||
|
format.setSampleRate(44100);
|
||||||
|
format.setChannelCount(2);
|
||||||
|
format.setSampleSize(16);
|
||||||
|
format.setCodec("audio/pcm");
|
||||||
|
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||||
|
format.setSampleType(QAudioFormat::SignedInt);
|
||||||
|
|
||||||
|
m_audio = new QAudioOutput(format, this);
|
||||||
|
m_audio->setBufferSize(1024);
|
||||||
|
}
|
||||||
|
device->setFormat(m_audio->format());
|
||||||
|
m_audio->start(device);
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef QGBA_WINDOW
|
#ifndef QGBA_WINDOW
|
||||||
#define QGBA_WINDOW
|
#define QGBA_WINDOW
|
||||||
|
|
||||||
|
#include <QAudioOutput>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
|
||||||
#include "GameController.h"
|
#include "GameController.h"
|
||||||
|
@ -19,7 +20,11 @@ public:
|
||||||
public slots:
|
public slots:
|
||||||
void selectROM();
|
void selectROM();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void setupAudio(AudioDevice*);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QAudioOutput* m_audio;
|
||||||
GameController* m_controller;
|
GameController* m_controller;
|
||||||
Display* m_display;
|
Display* m_display;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue