mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-05-28 07:54:51 -04:00
164 lines
4.4 KiB
C++
164 lines
4.4 KiB
C++
#include "dusk/audio/DuskAudioSystem.h"
|
|
|
|
#include <SDL3/SDL_init.h>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <fstream>
|
|
#include <ios>
|
|
#include <span>
|
|
|
|
#include "JSystem/JAudio2/JASAiCtrl.h"
|
|
#include "JSystem/JAudio2/JASChannel.h"
|
|
#include "JSystem/JAudio2/JASCriticalSection.h"
|
|
#include "JSystem/JAudio2/JASDSPChannel.h"
|
|
#include "JSystem/JAudio2/JASHeapCtrl.h"
|
|
|
|
#include "DuskDsp.hpp"
|
|
#include "JSystem/JAudio2/JASAudioThread.h"
|
|
#include "JSystem/JAudio2/JASDriverIF.h"
|
|
|
|
// #define DUSK_DUMP_AUDIO
|
|
|
|
using namespace dusk::audio;
|
|
|
|
static OutputSubframe OutBuffer;
|
|
static std::array<f32, DSP_SUBFRAME_SIZE * OutputSubframe::NUM_CHANNELS> OutInterleaveBuffer;
|
|
|
|
static SDL_AudioStream* PlaybackStream;
|
|
|
|
/**
|
|
* SDL audiostream callback to trigger rendering of new audio data.
|
|
*/
|
|
static void SDLCALL GetNewAudio(
|
|
void*,
|
|
SDL_AudioStream*,
|
|
int needed,
|
|
int);
|
|
|
|
/**
|
|
* Render an entire new frame of audio and output it to SDL3.
|
|
* Note: "audio frames" are unrelated to video frames.
|
|
* @return Amount of audio samples rendered.
|
|
*/
|
|
static int RenderNewAudioFrame();
|
|
|
|
/**
|
|
* Render an audio subframe and output it to SDL3.
|
|
*/
|
|
static void RenderAudioSubframe();
|
|
|
|
static void InitSDL3Output() {
|
|
SDL_Init(SDL_INIT_AUDIO);
|
|
|
|
constexpr SDL_AudioSpec spec = {
|
|
SDL_AUDIO_F32,
|
|
2,
|
|
SampleRate,
|
|
};
|
|
PlaybackStream = SDL_OpenAudioDeviceStream(
|
|
SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
|
&spec,
|
|
&GetNewAudio,
|
|
nullptr);
|
|
}
|
|
|
|
void dusk::audio::Initialize() {
|
|
InitSDL3Output();
|
|
DspInit();
|
|
|
|
JASDsp::initBuffer();
|
|
JASDSPChannel::initAll();
|
|
|
|
JASPoolAllocObject_MultiThreaded<JASChannel>::newMemPool(0x48);
|
|
|
|
SDL_ResumeAudioStreamDevice(PlaybackStream);
|
|
}
|
|
|
|
void dusk::audio::SetMasterVolume(const f32 value) {
|
|
JASCriticalSection section;
|
|
|
|
MasterVolume = value;
|
|
}
|
|
|
|
void SDLCALL GetNewAudio(
|
|
void*,
|
|
SDL_AudioStream*,
|
|
int needed,
|
|
int) {
|
|
while (needed > 0) {
|
|
const int rendered = RenderNewAudioFrame();
|
|
needed -= rendered;
|
|
}
|
|
}
|
|
|
|
#if defined(DUSK_DUMP_AUDIO)
|
|
static std::ofstream outRaw("guh.raw", std::ios_base::out | std::ios_base::binary);
|
|
#endif
|
|
|
|
int RenderNewAudioFrame() {
|
|
JASCriticalSection section;
|
|
const u32 countSubframes = JASDriver::getSubFrames();
|
|
|
|
JASAudioThread::setDSPSyncCount(countSubframes);
|
|
|
|
for (u32 i = 0; i < countSubframes; i++) {
|
|
RenderAudioSubframe();
|
|
|
|
JASAudioThread::snIntCount -= 1;
|
|
}
|
|
|
|
#if defined(DUSK_DUMP_AUDIO)
|
|
outRaw.flush();
|
|
#endif
|
|
|
|
return static_cast<u16>(countSubframes) * DSP_SUBFRAME_SIZE;
|
|
}
|
|
|
|
static void InterleaveOutputData(const OutputSubframe& data, std::span<f32> target) {
|
|
assert(target.size() >= data.channels[0].size() * OutputSubframe::NUM_CHANNELS);
|
|
|
|
size_t outPos = 0;
|
|
for (size_t inPos = 0; inPos < data.channels[0].size(); inPos++) {
|
|
for (size_t channelIdx = 0; channelIdx < OutputSubframe::NUM_CHANNELS; channelIdx++) {
|
|
target[outPos++] = data.channels[channelIdx][inPos];
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderAudioSubframe() {
|
|
OutBuffer = {};
|
|
|
|
JASDriver::updateDSP();
|
|
DspRender(OutBuffer);
|
|
|
|
InterleaveOutputData(OutBuffer, OutInterleaveBuffer);
|
|
|
|
if (JASDriver::extMixCallback != nullptr && JASDriver::sMixMode == MIX_MODE_INTERLEAVE) {
|
|
static_assert(OutputSubframe::NUM_CHANNELS == 2); // This code only works with Stereo so far.
|
|
// NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe.
|
|
// That's probably more efficient, but I didn't wanna change the code to calculate the
|
|
// entire audio buffers at once.
|
|
// This is only used for the movie player, and it seems to work fine with the smaller calls.
|
|
const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE);
|
|
if (mixData) {
|
|
for (int i = 0; i < OutInterleaveBuffer.size(); i++) {
|
|
OutInterleaveBuffer[i] += static_cast<f32>(mixData[i]) / static_cast<f32>(0x7FFF);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(DUSK_DUMP_AUDIO)
|
|
outRaw.write((const char*)OutInterleaveBuffer.data(), sizeof(OutInterleaveBuffer));
|
|
#endif
|
|
|
|
SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer));
|
|
}
|
|
|
|
u32 dusk::audio::GetResetCount(int channelIdx) {
|
|
return ChannelAux[channelIdx].resetCount;
|
|
}
|
|
|
|
f32 dusk::audio::VolumeFromU16(u16 value) {
|
|
return static_cast<f32>(value) / static_cast<f32>(JASDriver::getChannelLevel_dsp());
|
|
}
|