Browse Source

Implemented audio subsystem.

* Plip natively uses 32-bit stereo floats. The frontend is responsible for
      converting this to the system's native format.
    * Somewhat buggy (wrt conversion) SDL audio implementation added.
    * Test code included, free of charge!
master
Ian Burgmyer 4 years ago
parent
commit
28601e45c4
  1. 1
      CMakeLists.txt
  2. 27
      libplip/Audio/PlipAudio.h
  3. 7
      libplip/Plip.cpp
  4. 5
      libplip/Plip.h
  5. 87
      plip-sdl/SDL/SdlAudio.cpp
  6. 33
      plip-sdl/SDL/SdlAudio.h
  7. 33
      plip-sdl/main.cpp

1
CMakeLists.txt

@ -56,6 +56,7 @@ add_dependencies(${lib_name} GENERATE_LIB_VERSION_HEADER)
add_executable(${gui_name}
plip-sdl/main.cpp
plip-sdl/Config.cpp
plip-sdl/SDL/SdlAudio.cpp
plip-sdl/SDL/SdlEvent.cpp
plip-sdl/SDL/SdlWindow.cpp
)

27
libplip/Audio/PlipAudio.h

@ -0,0 +1,27 @@
/* PlipAudio.h
*
* Provides a toolkit-agnostic audio interface.
*/
#pragma once
#include <vector>
namespace Plip {
class PlipAudio {
public:
virtual void DequeueAll() = 0;
virtual void Enqueue(std::vector<float> buffer) = 0;
virtual uintmax_t GetQueueSize() = 0;
virtual bool IsActive() final { return m_active; }
static const int BitRate = 32;
static const int Channels = 2;
static const int SampleRate = 48000;
protected:
PlipAudio() = default;
bool m_active = false;
};
}

7
libplip/Plip.cpp

@ -9,10 +9,15 @@
#include "Core/PlipChip8.h"
namespace Plip {
Plip::Plip(PlipVideo *video) {
Plip::Plip(PlipVideo *video, PlipAudio *audio) {
m_audio = audio;
m_video = video;
}
PlipAudio* Plip::GetAudio() {
return m_audio;
}
PlipCore* Plip::GetCore() {
return m_core;
}

5
libplip/Plip.h

@ -9,17 +9,19 @@
#include "PlipCore.h"
#include "PlipError.h"
#include "Audio/PlipAudio.h"
#include "Input/PlipInput.h"
#include "Video/PlipVideo.h"
namespace Plip {
class Plip final {
public:
explicit Plip(PlipVideo *video);
explicit Plip(PlipVideo *video, PlipAudio *audio);
static std::string GetVersion();
static std::vector<PlipCoreDescription> GetSupportedCores();
PlipAudio* GetAudio();
PlipCore* GetCore();
PlipInput* GetInput();
PlipVideo* GetVideo();
@ -27,6 +29,7 @@ namespace Plip {
void Run(long ns);
private:
PlipAudio *m_audio;
PlipCore *m_core = nullptr;
PlipInput *m_input = new PlipInput();
PlipVideo *m_video;

87
plip-sdl/SDL/SdlAudio.cpp

@ -0,0 +1,87 @@
/* SdlAudio.cpp
*
* An SDL2 audio implementation.
*/
#include <iostream>
#include "SdlAudio.h"
namespace PlipSdl {
SdlAudio::SdlAudio() {
SDL_InitSubSystem(SDL_INIT_AUDIO);
// Open audio device.
SDL_AudioSpec want {};
want.freq = 8000; // Plip::PlipAudio::SampleRate;
want.format = AUDIO_S8;
want.channels = 1; // Plip::PlipAudio::Channels;
want.samples = SampleLength;
want.callback = nullptr;
m_device = SDL_OpenAudioDevice(nullptr, 0, &want, &m_spec, SDL_AUDIO_ALLOW_ANY_CHANGE);
if(m_device < 0) {
std::cerr << "Unable to open audio: " << SDL_GetError() << std::endl;
return;
}
// Set up conversion structure.
SDL_BuildAudioCVT(&m_cvt,
AUDIO_F32, 2, 48000,
m_spec.format, m_spec.channels, m_spec.freq);
if(m_cvt.needed > 0) {
// Conversion required.
m_playFunc = &SdlAudio::ConvertQueue;
} else if(m_cvt.needed < 0) {
// An error has occurred.
std::cerr << "Unable to set up audio converter: " << SDL_GetError() << std::endl;
SDL_CloseAudio();
return;
} else {
// No conversion required.
m_playFunc = &SdlAudio::DirectQueue;
}
m_active = true;
SDL_PauseAudioDevice(m_device, 0);
}
SdlAudio::~SdlAudio() {
if(m_active) SDL_CloseAudioDevice(m_device);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
void SdlAudio::ConvertQueue(void *data, int size) {
m_cvt.len = size * 4; // 32-bit float == 4 bytes
m_cvt.buf = (Uint8*)SDL_malloc(m_cvt.len * m_cvt.len_mult);
SDL_memcpy(m_cvt.buf, data, m_cvt.len);
SDL_ConvertAudio(&m_cvt);
// TODO: This currently leaves gaps in the audio if m_cvt.buf is not a
// multiple of SampleRate. Should probably be fixed at some point. :)
SDL_QueueAudio(m_device, m_cvt.buf, m_cvt.len_cvt);
SDL_free(m_cvt.buf);
}
void SdlAudio::DequeueAll() {
SDL_ClearQueuedAudio(m_device);
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "readability-make-member-function-const"
void SdlAudio::DirectQueue(void *data, int size) {
SDL_QueueAudio(m_device, data, size * 4);
}
#pragma clang diagnostic pop
void SdlAudio::Enqueue(std::vector<float> buffer) {
if(!IsActive()) return;
(this->*m_playFunc)(buffer.data(), buffer.size());
}
uintmax_t SdlAudio::GetQueueSize() {
return SDL_GetQueuedAudioSize(m_device);
}
}

33
plip-sdl/SDL/SdlAudio.h

@ -0,0 +1,33 @@
/* SdlAudio.h
*
* An SDL2 audio implementation.
*/
#pragma once
#include <SDL.h>
#include "Audio/PlipAudio.h"
namespace PlipSdl {
class SdlAudio : public Plip::PlipAudio {
public:
SdlAudio();
~SdlAudio();
void DequeueAll() override;
void Enqueue(std::vector<float> buffer) override;
uintmax_t GetQueueSize() override;
static const int SampleLength = 4096;
private:
void ConvertQueue(void *data, int size);
void DirectQueue(void *data, int size);
SDL_AudioCVT m_cvt {};
SDL_AudioDeviceID m_device = 0;
SDL_AudioSpec m_spec {};
void (SdlAudio::*m_playFunc)(void*, int);
};
}

33
plip-sdl/main.cpp

@ -11,6 +11,7 @@
#include "Plip.h"
#include "Config.h"
#include "SDL/SdlAudio.h"
#include "SDL/SdlEvent.h"
#include "SDL/SdlWindow.h"
@ -30,7 +31,31 @@ std::vector<std::vector<std::string>> intParamMapping = {
{ "fps" , "video", "targetFps" }
};
#include <cmath>
double angle = 0.0;
std::vector<float> waveGen() {
using pa = Plip::PlipAudio;
auto vol = 0.5;
auto len = PlipSdl::SdlAudio::SampleLength * 8;
auto hz = 440;
auto smp = pa::SampleRate;
auto chan = pa::Channels;
std::vector<float> res;
auto cycles = (double)hz / (double)smp;
auto delta = cycles * 2.0 * M_PI;
for(auto i = 0; i < len; i++) {
for(auto c = 0; c < chan; c++)
res.push_back(vol * std::sin(angle));
angle += delta;
}
return res;
}
void gameLoop(Plip::Plip *plip, PlipSdl::Config *config, PlipSdl::SdlEvent *event, PlipSdl::Timer *timer) {
auto audio = plip->GetAudio();
auto video = plip->GetVideo();
auto targetFps = config->GetValue<int>("video", "targetFps");
@ -48,6 +73,9 @@ void gameLoop(Plip::Plip *plip, PlipSdl::Config *config, PlipSdl::SdlEvent *even
// TODO: Fix this so that it will skip frames where appropriate.
plip->Run(frameTime);
if(audio->GetQueueSize() < PlipSdl::SdlAudio::SampleLength / 2)
audio->Enqueue(waveGen());
auto time = timer->StopwatchStop();
auto delay = frameTime - time;
while(delay < 0)
@ -55,6 +83,8 @@ void gameLoop(Plip::Plip *plip, PlipSdl::Config *config, PlipSdl::SdlEvent *even
timer->Nanosleep(delay);
}
audio->DequeueAll();
}
cxxopts::ParseResult parseCmdLine(int argc, char **argv) {
@ -179,7 +209,8 @@ int main(int argc, char **argv) {
auto videoScale = config->GetValue<int>("video", "scale");
auto wnd = new PlipSdl::SdlWindow(videoScale, version);
auto plip = new Plip::Plip(wnd);
auto audio = new PlipSdl::SdlAudio();
auto plip = new Plip::Plip(wnd, audio);
#ifdef UNIX
auto timer = new PlipSdl::TimerPosix();

Loading…
Cancel
Save