mirror of
https://github.com/citra-emu/citra.git
synced 2025-07-03 18:00:07 +00:00
186 lines
5.1 KiB
C++
186 lines
5.1 KiB
C++
|
|
#include "AL/al.h"
|
|
#include "AL/alc.h"
|
|
#include "AL/alext.h"
|
|
|
|
#include "common/logging/log.h"
|
|
|
|
#include "core/audio/stream.h"
|
|
|
|
#include <array>
|
|
#include <queue>
|
|
|
|
namespace Audio {
|
|
static const int BASE_SAMPLE_RATE = 22050;
|
|
|
|
struct Buffer {
|
|
u16 id;
|
|
ALuint buffer;
|
|
bool is_looping;
|
|
|
|
bool operator < (const Buffer& other) const {
|
|
if ((other.id - id) > 1000) return true;
|
|
if ((id - other.id) > 1000) return false;
|
|
return id > other.id;
|
|
}
|
|
};
|
|
|
|
struct OutputChannel {
|
|
ALuint source;
|
|
int mono_or_stereo;
|
|
Format format;
|
|
std::priority_queue<Buffer> queue;
|
|
std::queue<Buffer> playing;
|
|
u16 last_bufid;
|
|
};
|
|
|
|
OutputChannel chans[24];
|
|
|
|
int InitAL(void)
|
|
{
|
|
ALCdevice *device;
|
|
ALCcontext *ctx;
|
|
|
|
/* Open and initialize a device with default settings */
|
|
device = alcOpenDevice(NULL);
|
|
if (!device)
|
|
{
|
|
LOG_CRITICAL(Audio, "Could not open a device!");
|
|
return 1;
|
|
}
|
|
|
|
ctx = alcCreateContext(device, NULL);
|
|
if (ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE)
|
|
{
|
|
if (ctx != NULL)
|
|
alcDestroyContext(ctx);
|
|
alcCloseDevice(device);
|
|
LOG_CRITICAL(Audio, "Could not set a context!");
|
|
return 1;
|
|
}
|
|
|
|
LOG_INFO(Audio, "Opened \"%s\"", alcGetString(device, ALC_DEVICE_SPECIFIER));
|
|
return 0;
|
|
}
|
|
|
|
ALuint source, buffer;
|
|
ALCint dev_rate;
|
|
|
|
void Init() {
|
|
InitAL();
|
|
|
|
{
|
|
ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
|
|
alcGetIntegerv(device, ALC_FREQUENCY, 1, &dev_rate);
|
|
if (alcGetError(device) != ALC_NO_ERROR) LOG_CRITICAL(Audio, "Failed to get device sample rate");
|
|
LOG_INFO(Audio, "Device Frequency: %i", dev_rate);
|
|
}
|
|
|
|
for (int i = 0; i < 24; i++) {
|
|
alGenSources(1, &chans[i].source);
|
|
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to setup sound source");
|
|
}
|
|
}
|
|
|
|
void Shutdown() {}
|
|
|
|
void UpdateFormat(int chanid, int mono_or_stereo, Format format) {
|
|
chans[chanid].mono_or_stereo = mono_or_stereo;
|
|
chans[chanid].format = format;
|
|
|
|
LOG_WARNING(Audio, "(STUB)");
|
|
}
|
|
|
|
void EnqueueBuffer(int chanid, u16 buffer_id,
|
|
void* data, int sample_count,
|
|
bool has_adpcm, u16 adpcm_ps, s16 adpcm_yn[2],
|
|
bool is_looping) {
|
|
|
|
if (chans[chanid].format != FORMAT_PCM16) {
|
|
LOG_ERROR(Audio, "Unimplemented format");
|
|
return;
|
|
}
|
|
|
|
// TODO: ADPCM processing should happen here
|
|
|
|
ALuint b;
|
|
alGenBuffers(1, &b);
|
|
alBufferData(b, AL_FORMAT_MONO16, data, sample_count*2, BASE_SAMPLE_RATE);
|
|
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to init buffer");
|
|
|
|
chans[chanid].queue.emplace( Buffer { buffer_id, b, is_looping });
|
|
}
|
|
|
|
void Tick(int chanid) {
|
|
auto& c = chans[chanid];
|
|
|
|
if (!c.queue.empty()) {
|
|
while (!c.queue.empty()) {
|
|
alSourceQueueBuffers(c.source, 1, &c.queue.top().buffer);
|
|
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to enqueue buffer");
|
|
c.playing.emplace(c.queue.top());
|
|
LOG_INFO(Audio, "Enqueued buffer id %i", c.queue.top().id);
|
|
c.queue.pop();
|
|
}
|
|
|
|
ALint state;
|
|
alGetSourcei(c.source, AL_SOURCE_STATE, &state);
|
|
if (state != AL_PLAYING) {
|
|
alSourcePlay(c.source);
|
|
}
|
|
}
|
|
|
|
if (!c.playing.empty()) {
|
|
c.last_bufid = c.playing.front().id;
|
|
}
|
|
|
|
ALint processed;
|
|
alGetSourcei(c.source, AL_BUFFERS_PROCESSED, &processed);
|
|
while (processed > 0) {
|
|
ALuint buf;
|
|
alSourceUnqueueBuffers(c.source, 1, &buf);
|
|
processed--;
|
|
|
|
LOG_INFO(Audio, "Finished buffer id %i", c.playing.front().id);
|
|
|
|
while (!c.playing.empty() && c.playing.front().buffer != buf) {
|
|
c.playing.pop();
|
|
LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Desynced queue.)");
|
|
}
|
|
|
|
if (!c.playing.empty()) {
|
|
c.last_bufid = c.playing.front().id;
|
|
c.playing.pop();
|
|
} else {
|
|
LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Empty queue.)");
|
|
}
|
|
|
|
alDeleteBuffers(1, &buf);
|
|
}
|
|
|
|
if (!c.playing.empty()) {
|
|
c.last_bufid = c.playing.front().id;
|
|
}
|
|
}
|
|
|
|
std::tuple<bool, u16, u32> GetStatus(int chanid) {
|
|
auto& c = chans[chanid];
|
|
|
|
bool isplaying = false;
|
|
u16 bufid = 0;
|
|
u32 pos = 0;
|
|
|
|
ALint state, samples;
|
|
alGetSourcei(c.source, AL_SOURCE_STATE, &state);
|
|
alGetSourcei(c.source, AL_SAMPLE_OFFSET, &samples);
|
|
|
|
if (state == AL_PLAYING) isplaying = true;
|
|
|
|
bufid = c.last_bufid;
|
|
|
|
pos = samples;
|
|
|
|
return std::make_tuple(isplaying, bufid, pos);
|
|
}
|
|
|
|
}; |