mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-27 06:31:42 -04:00
@@ -552,7 +552,11 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3<f32>& pos, JAISoundID
|
||||
}
|
||||
|
||||
void Z2Audience::deleteAudible(JAIAudible* audible) {
|
||||
#if TARGET_PC
|
||||
JKR_DELETE(static_cast<Z2Audible*>(audible));
|
||||
#else
|
||||
JKR_DELETE(audible);
|
||||
#endif
|
||||
}
|
||||
|
||||
Z2Audible::~Z2Audible() {}
|
||||
|
||||
@@ -31,15 +31,15 @@ Z2AudioMgr::Z2AudioMgr() : mSoundStarter(true) {
|
||||
void Z2AudioMgr::init(JKRSolidHeap* heap, u32 memSize, void* baaData, JKRArchive* seqArc) {
|
||||
JAU_JASInitializer JASInitializer;
|
||||
JASInitializer.audioMemSize_ = memSize;
|
||||
JASInitializer.field_0x1c = 140;
|
||||
JASInitializer.mJasTrackPoolSize = 140;
|
||||
JASInitializer.dspLevel_ = 1.3f;
|
||||
JASInitializer.waveArcDir_ = "Audiores/Waves/";
|
||||
JASInitializer.initJASystem(heap);
|
||||
|
||||
JAU_JAIInitializer JAIInitializer;
|
||||
JAIInitializer.field_0x0 = 78;
|
||||
JAIInitializer.field_0x4 = 4;
|
||||
JAIInitializer.field_0xc = 48;
|
||||
JAIInitializer.mJaiSePoolSize = 78;
|
||||
JAIInitializer.mJaiSeqPoolSize = 4;
|
||||
JAIInitializer.mJaiSoundChildPoolSize = 48;
|
||||
JAIInitializer.initJAInterface();
|
||||
|
||||
JAISeMgr* seMgr = mSoundMgr.getSeMgr();
|
||||
@@ -107,19 +107,6 @@ void Z2AudioMgr::init(JKRSolidHeap* heap, u32 memSize, void* baaData, JKRArchive
|
||||
JASPoolAllocObject<Z2SoundHandlePool>::newMemPool(0x4e);
|
||||
OS_REPORT("[Z2AudioMgr::init]before Create Section: %d\n", heap->getFreeSize());
|
||||
|
||||
#if TARGET_PC
|
||||
// Fix a race condition with OS threading where JAUNewSectionHeap will use all the remaining
|
||||
// space in the JASDram heap before JASAudioThread has finished initializing.
|
||||
|
||||
OSLockMutex(&JASAudioThread::sThreadInitCompleteMutex);
|
||||
while (!JASAudioThread::sThreadInitComplete) {
|
||||
OSWaitCond(
|
||||
&JASAudioThread::sThreadInitCompleteCond,
|
||||
&JASAudioThread::sThreadInitCompleteMutex);
|
||||
}
|
||||
OSUnlockMutex(&JASAudioThread::sThreadInitCompleteMutex);
|
||||
#endif
|
||||
|
||||
JAUSectionHeap* sectionHeap = JAUNewSectionHeap(true);
|
||||
sectionHeap->setSeqDataArchive(seqArc);
|
||||
size_t resMaxSize = JASResArcLoader::getResMaxSize(seqArc);
|
||||
@@ -158,7 +145,7 @@ void Z2AudioMgr::init(JKRSolidHeap* heap, u32 memSize, void* baaData, JKRArchive
|
||||
}
|
||||
|
||||
void Z2AudioMgr::setOutputMode(u32 mode) {
|
||||
if (mode <= 2) {
|
||||
if (mode <= JAS_OUTPUT_SURROUND) {
|
||||
JAISetOutputMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,11 +211,6 @@ Z2SoundHandlePool* Z2Creature::startCreatureSoundLevel(JAISoundID soundID, u32 m
|
||||
}
|
||||
|
||||
Z2SoundHandlePool* Z2Creature::startCreatureVoice(JAISoundID soundID, s8 reverb) {
|
||||
#if TARGET_PC
|
||||
// stubbed
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
switch (soundID) {
|
||||
case Z2SE_MDN_V_FLY_OUT:
|
||||
case Z2SE_MDN_V_MGN_TAME:
|
||||
|
||||
@@ -2018,10 +2018,6 @@ bool Z2SceneMgr::loadSceneWave(u32 wave, u32 bank) {
|
||||
#endif
|
||||
|
||||
bool Z2SceneMgr::loadSeWave(u32 wave) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* //lw stub lw
|
||||
JAUSectionHeap* sectionHeap = JASGlobalInstance<JAUSectionHeap>::getInstance();
|
||||
JUT_ASSERT(3030, sectionHeap);
|
||||
|
||||
@@ -2036,13 +2032,8 @@ bool Z2SceneMgr::loadSeWave(u32 wave) {
|
||||
JUT_WARN_DEVICE(3038, 1, "Z2SceneMgr::cannot load SE wave:%d\n", wave);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
bool Z2SceneMgr::loadBgmWave(u32 wave) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* //lw stub lw
|
||||
|
||||
JAUSectionHeap* sectionHeap = JASGlobalInstance<JAUSectionHeap>::getInstance();
|
||||
JUT_ASSERT(3047, sectionHeap);
|
||||
|
||||
@@ -2056,4 +2047,4 @@ bool Z2SceneMgr::loadBgmWave(u32 wave) {
|
||||
|
||||
JUT_WARN_DEVICE(3055, 1, "Z2SceneMgr::cannot load BGM wave:%d\n", wave);
|
||||
return false;
|
||||
} */
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ Z2SoundHandlePool* Z2SoundHandles::getHandleSoundID(JAISoundID soundID) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Z2SoundHandlePool* Z2SoundHandles::getHandleUserData(u32 userData) {
|
||||
Z2SoundHandlePool* Z2SoundHandles::getHandleUserData(uintptr_t userData) {
|
||||
JSULink<Z2SoundHandlePool>* i;
|
||||
for (i = getFirst(); i != NULL; i = i->getNext()) {
|
||||
Z2SoundHandlePool* handle = i->getObject();
|
||||
|
||||
@@ -502,7 +502,7 @@ void Z2SoundObjAnime::startSoundInner(const JGeometry::TVec3<f32>& pos, f32 para
|
||||
JUT_ASSERT(747, curSoundIndex_ < animation_->getNumSounds());
|
||||
|
||||
const JAUSoundAnimationSound* animationSound = animation_->getSound(curSoundIndex_);
|
||||
u32 user_data = (uintptr_t)animationSound;
|
||||
uintptr_t user_data = (uintptr_t)animationSound;
|
||||
if (reverse_) {
|
||||
curSoundIndex_--;
|
||||
} else {
|
||||
|
||||
@@ -1467,7 +1467,7 @@ static f32 l_ladderAnmBaseTransY = 102.00054168701172f;
|
||||
|
||||
static dCcD_SrcCyl l_cylSrc = {
|
||||
{
|
||||
{0, {{AT_TYPE_WOLF_ATTACK, 3, 0x1A}, {0xD8FFFDFF, 5}, 0x73}},
|
||||
{0, {{(u32)AT_TYPE_WOLF_ATTACK, 3, 0x1A}, {0xD8FFFDFF, 5}, 0x73}},
|
||||
{dCcD_SE_WOLF_BITE, 3, 1, 0, {1}},
|
||||
{dCcD_SE_NONE, 6, 0, 0, {0}},
|
||||
{0},
|
||||
|
||||
@@ -3268,7 +3268,7 @@ static void daMP_MixAudio(s16* destination, s16*, u32 sample) {
|
||||
if (r_mix > 32767)
|
||||
r_mix = 32767;
|
||||
|
||||
if (JASDriver::getOutputMode() == 0) {
|
||||
if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) {
|
||||
l_mix = r_mix = ((r_mix >> 1) + (l_mix >> 1));
|
||||
r_mix = (s16)r_mix;
|
||||
l_mix = (s16)l_mix;
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
#include <cstring>
|
||||
|
||||
#include "JSystem/JAudio2/JASDriverIF.h"
|
||||
|
||||
typedef void (dMenu_Option_c::*initFunc)();
|
||||
static initFunc init[] = {
|
||||
&dMenu_Option_c::atten_init,
|
||||
@@ -89,7 +91,7 @@ dMenu_Option_c::dMenu_Option_c(JKRArchive* i_archive, STControl* i_stick) {
|
||||
|
||||
dMenu_Option_c::~dMenu_Option_c() {}
|
||||
|
||||
static const u32 dMo_soundMode[3] = {0, 1, 2};
|
||||
static const u32 dMo_soundMode[3] = {JAS_OUTPUT_MONO, JAS_OUTPUT_STEREO, JAS_OUTPUT_SURROUND};
|
||||
|
||||
void dMenu_Option_c::_create() {
|
||||
static const u64 text_a_tag[5] = {MULTI_CHAR('atext1_1'), MULTI_CHAR('atext1_2'), MULTI_CHAR('atext1_3'), MULTI_CHAR('atext1_4'), MULTI_CHAR('atext1_5')};
|
||||
@@ -1755,13 +1757,13 @@ void dMenu_Option_c::screenSet() {
|
||||
}
|
||||
}
|
||||
|
||||
void dMenu_Option_c::setSoundMode(u32 param_0) {
|
||||
switch (param_0) {
|
||||
case 0:
|
||||
void dMenu_Option_c::setSoundMode(u32 soundMode) {
|
||||
switch (soundMode) {
|
||||
case JAS_OUTPUT_MONO:
|
||||
OSSetSoundMode(OS_SOUND_MODE_MONO);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case JAS_OUTPUT_STEREO:
|
||||
case JAS_OUTPUT_SURROUND: // Via dolby pro logic 2, so it's over 2 output channels.
|
||||
OSSetSoundMode(OS_SOUND_MODE_STEREO);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1634,7 +1634,12 @@ void dMsgObject_c::readMessageGroupLocal(mDoDvdThd_mountXArchive_c** p_arcMount)
|
||||
#elif REGION_JPN
|
||||
sprintf(arcName, "/res/Msgjp/bmgres%d.arc", msgGroup);
|
||||
#else
|
||||
#if TARGET_PC
|
||||
// Original game UB
|
||||
snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup);
|
||||
#else
|
||||
sprintf(arcName, "/res/Msgus/bmgres%d.arc", msgGroup);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
*p_arcMount = mDoDvdThd_mountXArchive_c::create(arcName, 0, JKRArchive::MOUNT_MEM, NULL);
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "m_Do/m_Do_Reset.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "dusk/string.hpp"
|
||||
#if TARGET_PC
|
||||
#include <format>
|
||||
#include <fmt/ranges.h>
|
||||
@@ -151,7 +153,14 @@ static int dStage_RoomKeepDoorInit(dStage_dt_c* i_stage, void* i_data, int entry
|
||||
}
|
||||
|
||||
void dStage_startStage_c::set(const char* i_Name, s8 i_RoomNo, s16 i_Point, s8 i_Layer) {
|
||||
#if TARGET_PC
|
||||
// UB fix.
|
||||
if (mName != i_Name) {
|
||||
dusk::SafeStringCopy(mName, i_Name);
|
||||
}
|
||||
#else
|
||||
strcpy(mName, i_Name);
|
||||
#endif
|
||||
mRoomNo = i_RoomNo;
|
||||
mPoint = i_Point;
|
||||
mLayer = i_Layer;
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
#include <memory>
|
||||
|
||||
#include "JSystem/JKernel/JKRHeap.h"
|
||||
#include "dusk/os.h"
|
||||
|
||||
#if _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Side-table: native thread data per OSThread
|
||||
@@ -695,6 +701,30 @@ OSInterruptMask __OSUnmaskInterrupts(OSInterruptMask mask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSSetCurrentThreadName(const char* name) {
|
||||
// "Why is this current thread only?", you might ask?
|
||||
// Because macOS requires that. For some reason.
|
||||
|
||||
#if _WIN32
|
||||
wchar_t buffer[256];
|
||||
const auto converted = MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
name,
|
||||
-1,
|
||||
buffer,
|
||||
sizeof(buffer)/sizeof(wchar_t));
|
||||
if (converted == 0) {
|
||||
CRASH("OSSetThreadName: MultiByteToWideChar failed");
|
||||
}
|
||||
|
||||
const auto result = SetThreadDescription(GetCurrentThread(), buffer);
|
||||
if (!SUCCEEDED(result)) {
|
||||
CRASH("OSSetThreadName: SetThreadDescription failed");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "Adpcm.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
|
||||
#include "JSystem/JAudio2/JASAramStream.h"
|
||||
|
||||
// https://github.com/magcius/vgmtrans/blob/8e34ddc2fb43948dc1e1a8759c739a0c1c7b62d7/src/main/formats/JaiSeqScanner.cpp#L489-L531
|
||||
// https://github.com/XAYRGA/JaiSeqX/blob/f29c024ec3663503f506aa02bcd503ada6e7d8aa/JaiSeqXLJA/DSP/JAIDSPADPCM4.cs#L86-L87
|
||||
|
||||
static constexpr u16 Coefficient0[] = {
|
||||
0,0x0800,0,0x0400,
|
||||
0x1000,0x0e00,0x0c00,0x1200,
|
||||
0x1068,0x12c0,0x1400,0x0800,
|
||||
0x0400,0xfc00,0xfc00,0xf800
|
||||
};
|
||||
|
||||
static constexpr u16 Coefficient1[] = {
|
||||
0,0,0x0800,0x0400,0xf800,
|
||||
0xfa00,0xfc00,0xf600,0xf738,
|
||||
0xf704,0xf400,0xf800,0xfc00,
|
||||
0x0400,0,0,
|
||||
};
|
||||
|
||||
constexpr int AdpcmFrameSize = 9;
|
||||
|
||||
static s16 Clamp16(s32 value) {
|
||||
if (value > 0x7FFF) return 0x7FFF;
|
||||
if (value < -0x8000) return -0x8000;
|
||||
return value;
|
||||
}
|
||||
|
||||
void dusk::audio::Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, size_t pcmLength, s16& hist2, s16& hist1) {
|
||||
assert (adpcmLength % AdpcmFrameSize == 0 && "ADPCM must be divisible by frame size");
|
||||
|
||||
auto endPtr = pcm + pcmLength;
|
||||
|
||||
for (int i = 0; i < adpcmLength; i += AdpcmFrameSize) {
|
||||
u8 header = adpcm[i];
|
||||
|
||||
s32 scale = 1 << (header >> 4);
|
||||
u8 coefIndex = header & 0xF;
|
||||
|
||||
s16 coef0 = (s16)Coefficient0[coefIndex];
|
||||
s16 coef1 = (s16)Coefficient1[coefIndex];
|
||||
|
||||
for (int sampleIdx = 0; sampleIdx < 16; sampleIdx++) {
|
||||
u8 adpcmValue = adpcm[i + 1 + sampleIdx / 2];
|
||||
u8 unsignedNibble = sampleIdx % 2 == 0 ? adpcmValue >> 4 : adpcmValue & 0xF;
|
||||
s8 signedNibble = ((s8)(unsignedNibble << 4)) >> 4;
|
||||
s16 sample = Clamp16((((signedNibble * scale) << 11) + (coef0 * hist1 + coef1 * hist2)) >> 11);
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
|
||||
*pcm++ = sample;
|
||||
if (endPtr == pcm)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef DUSK_ADPCM_HPP
|
||||
#define DUSK_ADPCM_HPP
|
||||
|
||||
#include <dolphin/types.h>
|
||||
|
||||
namespace dusk::audio {
|
||||
constexpr u32 Adpcm4FrameSize = 9;
|
||||
constexpr u32 AdpcmSampleCount = 16;
|
||||
constexpr u32 Adpcm2FrameSize = 5;
|
||||
|
||||
void Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, size_t pcmLength, s16& hist1, s16& hist0);
|
||||
}
|
||||
|
||||
#endif // DUSK_ADPCM_HPP
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "JSystem/JAudio2/dspproc.h"
|
||||
#include "JSystem/JAudio2/osdsp_task.h"
|
||||
#include "JSystem/JAudio2/dsptask.h"
|
||||
#include "global.h"
|
||||
#include "os.h"
|
||||
|
||||
void DSPReleaseHalt2(u32) {
|
||||
CRASH("We do not directly emulate the DSP");
|
||||
}
|
||||
void DsetupTable(u32, u32, u32, u32, u32) {
|
||||
// Nada.
|
||||
}
|
||||
void DsetMixerLevel(f32) {
|
||||
// Nada for now, but maybe we should care about this?
|
||||
}
|
||||
void DsyncFrame2ch(u32, u32, u32) {
|
||||
CRASH("We do not directly emulate the DSP");
|
||||
}
|
||||
void DsyncFrame4ch(u32, u32, u32, u32, u32) {
|
||||
CRASH("We do not directly emulate the DSP");
|
||||
}
|
||||
|
||||
void DspBoot(void (*)(void*)) {
|
||||
CRASH("We do not directly emulate the DSP");
|
||||
}
|
||||
void DspFinishWork(u16) {
|
||||
CRASH("We do not directly emulate the DSP");
|
||||
}
|
||||
int DSPSendCommands2(u32*, u32, void (*)(u16)) {
|
||||
CRASH("We do not directly emulate the DSP");
|
||||
}
|
||||
|
||||
void DsyncFrame2(u32, u32, u32) {
|
||||
CRASH("We do not directly emulate the DSP");
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#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 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());
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
#include <ar.h>
|
||||
#include <dolphin/os.h>
|
||||
|
||||
#include "DuskDsp.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <span>
|
||||
|
||||
#include "Adpcm.hpp"
|
||||
#include "JSystem/JAudio2/JASDriverIF.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/endian.h"
|
||||
#include "global.h"
|
||||
|
||||
using namespace dusk::audio;
|
||||
|
||||
ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {};
|
||||
|
||||
f32 dusk::audio::MasterVolume = 1.0f;
|
||||
f32 dusk::audio::PrevMasterVolume = 1.0f;
|
||||
|
||||
/**
|
||||
* Validate that a DSP channel's format is actually something we know how to play.
|
||||
*/
|
||||
static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) {
|
||||
if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm4FrameSize)
|
||||
return true;
|
||||
if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 16)
|
||||
return true;
|
||||
/*
|
||||
if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm2FrameSize)
|
||||
return true;
|
||||
if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 8)
|
||||
return true;
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a DSP channel is actually something we know how to play.
|
||||
*/
|
||||
static void ValidateChannel(const JASDsp::TChannel& channel) {
|
||||
if (!ValidateChannelWaveFormat(channel)) {
|
||||
CRASH(
|
||||
"Unable to handle channel format: %02x, %02x\n",
|
||||
channel.mSamplesPerBlock,
|
||||
channel.mBytesPerBlock);
|
||||
}
|
||||
}
|
||||
|
||||
static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 samples) {
|
||||
if (samples % channel.mSamplesPerBlock != 0) {
|
||||
// Ensure we round up.
|
||||
samples += channel.mSamplesPerBlock;
|
||||
//CRASH("Indivisible sample count: %d\n", samples);
|
||||
}
|
||||
|
||||
return (samples / channel.mSamplesPerBlock) * BlockBytes(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the audio data contributed by a single DSP channel. Reads & decodes new input samples.
|
||||
*/
|
||||
static void RenderChannel(
|
||||
JASDsp::TChannel& channel,
|
||||
ChannelAuxData& channelAux,
|
||||
OutputSubframe& subframe);
|
||||
|
||||
/**
|
||||
* Converts a pitch value on a DSP channel to a sample rate.
|
||||
*/
|
||||
constexpr static int PitchToSampleRate(u16 value) {
|
||||
return static_cast<int>(static_cast<u64>(SampleRate) * value / 4096);
|
||||
}
|
||||
|
||||
static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& aux) {
|
||||
auto sampleRate = PitchToSampleRate(channel.mPitch);
|
||||
|
||||
const SDL_AudioSpec spec = {
|
||||
SDL_AUDIO_S16,
|
||||
1,
|
||||
sampleRate
|
||||
};
|
||||
|
||||
SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr);
|
||||
aux.prevPitch = channel.mPitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset state for a DSP channel between independent playbacks.
|
||||
*/
|
||||
static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) {
|
||||
aux.resetCount += 1;
|
||||
|
||||
channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition;
|
||||
|
||||
aux.hist0 = 0;
|
||||
aux.hist1 = 0;
|
||||
|
||||
SDL_ClearAudioStream(aux.resampleStream);
|
||||
UpdateSampleRate(channel, aux);
|
||||
|
||||
for (auto& volume : aux.prevVolume) {
|
||||
volume = NAN;
|
||||
}
|
||||
|
||||
channel.mResetFlag = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix subframe data from src into dst.
|
||||
*/
|
||||
static void MixSubframe(DspSubframe& dst, const DspSubframe& src) {
|
||||
for (int i = 0; i < dst.size(); i++) {
|
||||
dst[i] += src[i];
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::audio::DspRender(OutputSubframe& subframe) {
|
||||
std::span channels(JASDsp::CH_BUF, DSP_CHANNELS);
|
||||
|
||||
for (int i = 0; i < channels.size(); i++) {
|
||||
auto& channel = channels[i];
|
||||
auto& channelAux = ChannelAux[i];
|
||||
|
||||
if (!channel.mIsActive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (channel.mPauseFlag) {
|
||||
// Not really sure what the practical difference between pause and
|
||||
// deactivation is. Either avoids clearing state or allows the DSP to avoid popping?
|
||||
continue;
|
||||
}
|
||||
|
||||
if (channel.mForcedStop) {
|
||||
channel.mIsFinished = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (channel.mWaveAramAddress == 0) {
|
||||
// I think these are oscillator channels? Not backed by audio.
|
||||
// No idea how to implement these yet, so skip them.
|
||||
channel.mIsFinished = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
ValidateChannel(channel);
|
||||
|
||||
OutputSubframe channelSubframe = {};
|
||||
RenderChannel(channel, channelAux, channelSubframe);
|
||||
|
||||
for (int o = 0; o < subframe.channels.size(); o++) {
|
||||
MixSubframe(subframe.channels[o], channelSubframe.channels[o]);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& channel : subframe.channels) {
|
||||
ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume);
|
||||
}
|
||||
PrevMasterVolume = MasterVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually decode samples from memory for the given audio channel.
|
||||
*/
|
||||
static void ReadSampleData(
|
||||
const JASDsp::TChannel& channel,
|
||||
ChannelAuxData& aux,
|
||||
const u8* data,
|
||||
size_t dataLength,
|
||||
s16* pcm,
|
||||
size_t pcmLength) {
|
||||
if (channel.mSamplesPerBlock == 1) {
|
||||
if (channel.mBytesPerBlock == 0x10) {
|
||||
// PCM16
|
||||
assert(reinterpret_cast<uintptr_t>(data) % 2 == 0 && "PCM data must be aligned");
|
||||
assert(dataLength % 2 == 0 && "Data length must be multiple of 2");
|
||||
assert(dataLength * 2 >= pcmLength && "Input too small!");
|
||||
|
||||
auto srcPcm = reinterpret_cast<const BE(s16)*>(data);
|
||||
for (size_t i = 0; i < pcmLength; i++) {
|
||||
pcm[i] = srcPcm[i];
|
||||
}
|
||||
} else {
|
||||
CRASH("Unsupported format: PCM8");
|
||||
}
|
||||
} else {
|
||||
if (channel.mBytesPerBlock == 9) {
|
||||
Adpcm4ToPcm16(data, dataLength, pcm, pcmLength, aux.hist1, aux.hist0);
|
||||
} else {
|
||||
CRASH("Unsupported format: ADPCM2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single *contiguous* chunk of sample data from a channel,
|
||||
* writes the samples to the channel's resampler stream.
|
||||
*
|
||||
* @returns Amount of samples actually read. Can be greater than the amount requested.
|
||||
*/
|
||||
static int ReadChannelSamplesChunk(
|
||||
JASDsp::TChannel& channel,
|
||||
ChannelAuxData& aux,
|
||||
int desiredSamples) {
|
||||
|
||||
assert(desiredSamples >= 0);
|
||||
|
||||
auto aramBase = static_cast<u8*>(ARGetStorageAddress()) + channel.mWaveAramAddress;
|
||||
|
||||
// Streaming logic directly modifies mSamplesLeft.
|
||||
// So we use that as our tracking of where we are.
|
||||
auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft;
|
||||
|
||||
u32 skipSamples = curSamplePosition % channel.mSamplesPerBlock;
|
||||
if (skipSamples != 0) {
|
||||
// We need to start reading in the middle of a block. This can happen thanks to loops.
|
||||
// So we move back to the start of the block and keep track that those samples should
|
||||
// *not* be emitted.
|
||||
desiredSamples += static_cast<int>(skipSamples);
|
||||
curSamplePosition -= skipSamples;
|
||||
|
||||
channel.mSamplesLeft += skipSamples;
|
||||
channel.mSamplePosition -= skipSamples;
|
||||
}
|
||||
|
||||
// Pad desiredSamples so that we always leave the channel block-aligned.
|
||||
desiredSamples = ALIGN_NEXT(desiredSamples, channel.mSamplesPerBlock);
|
||||
|
||||
assert(curSamplePosition % channel.mSamplesPerBlock == 0);
|
||||
auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition);
|
||||
|
||||
u32 renderSamples = std::min(channel.mSamplesLeft, static_cast<u32>(desiredSamples));
|
||||
|
||||
int renderSize = static_cast<int>(sizeof(s16) * renderSamples);
|
||||
auto renderData = static_cast<s16*>(alloca(renderSize));
|
||||
memset(renderData, 0, renderSize);
|
||||
|
||||
ReadSampleData(
|
||||
channel,
|
||||
aux,
|
||||
aramBase + dataPosition,
|
||||
ConvertSamplesToDataLength(channel, renderSamples),
|
||||
renderData,
|
||||
renderSamples);
|
||||
|
||||
channel.mSamplesLeft -= renderSamples;
|
||||
channel.mSamplePosition += renderSamples;
|
||||
|
||||
SDL_PutAudioStreamData(
|
||||
aux.resampleStream,
|
||||
renderData + skipSamples,
|
||||
static_cast<int>(renderSize - skipSamples * sizeof(u16)));
|
||||
|
||||
assert(channel.mSamplePosition % channel.mSamplesPerBlock == 0 || channel.mSamplesLeft == 0);
|
||||
|
||||
return static_cast<int>(renderSamples - skipSamples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads new audio channels from a DSP channel and writes them to the resampler stream.
|
||||
*/
|
||||
static void SDLCALL ReadChannelSamples(
|
||||
void *userdata,
|
||||
SDL_AudioStream*,
|
||||
int additional_amount,
|
||||
int) {
|
||||
|
||||
if (additional_amount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = static_cast<u32>(reinterpret_cast<uintptr_t>(userdata));
|
||||
auto& channel = JASDsp::CH_BUF[index];
|
||||
auto& aux = ChannelAux[index];
|
||||
|
||||
if (channel.mSamplesLeft == 0 && !channel.mLoopFlag) {
|
||||
// May get called when we're out of data to read.
|
||||
// This is expected, as we need to drain the resampler channel before we mark the channel as finished.
|
||||
return;
|
||||
}
|
||||
|
||||
auto samplesRead = ReadChannelSamplesChunk(channel, aux, additional_amount);
|
||||
additional_amount -= samplesRead;
|
||||
|
||||
if (channel.mSamplesLeft == 0) {
|
||||
// Reached end of buffer.
|
||||
if (!channel.mLoopFlag) {
|
||||
return;
|
||||
}
|
||||
|
||||
channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample;
|
||||
channel.mSamplePosition = channel.mLoopStartSample;
|
||||
|
||||
aux.hist1 = channel.mpPenult;
|
||||
aux.hist0 = channel.mpLast;
|
||||
}
|
||||
|
||||
if (additional_amount >= 0) {
|
||||
ReadChannelSamplesChunk(channel, aux, additional_amount);
|
||||
}
|
||||
|
||||
channel.mAramStreamPosition = channel.mWaveAramAddress
|
||||
+ ConvertSamplesToDataLength(channel, channel.mSamplePosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expected BusConnect value needed to define the given output channel in a DSP channel.
|
||||
*/
|
||||
constexpr u16 GetBusConnect(const OutputChannel channel) {
|
||||
switch (channel) {
|
||||
// TODO: This is a guess for now.
|
||||
case OutputChannel::LEFT:
|
||||
return 0x0D00;
|
||||
case OutputChannel::RIGHT:
|
||||
return 0x0D60;
|
||||
default:
|
||||
CRASH("Invalid output channel!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a DSP channel the JASDsp::OutputChannelConfig value targeting the given output channel.
|
||||
* Returns null if the DSP channel does not output to this output channel.
|
||||
*/
|
||||
static const JASDsp::OutputChannelConfig* GetOutputConfig(
|
||||
const JASDsp::TChannel& sourceChannel,
|
||||
OutputChannel channel) {
|
||||
|
||||
auto busConnect = GetBusConnect(channel);
|
||||
for (const auto& mOutputChannel : sourceChannel.mOutputChannels) {
|
||||
auto config = &mOutputChannel;
|
||||
if (config->mBusConnect == busConnect) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct VolumeValue {
|
||||
f32 Target;
|
||||
f32 Init;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the volume that the given DSP channel should render to the given output channel at.
|
||||
*/
|
||||
static VolumeValue GetVolumeForOutputChannel(
|
||||
const JASDsp::TChannel& sourceChannel,
|
||||
OutputChannel outputChannel) {
|
||||
|
||||
u16 volume;
|
||||
u16 initVolume;
|
||||
f32 panValue = 1;
|
||||
if (sourceChannel.mAutoMixerBeenSet) {
|
||||
volume = sourceChannel.mAutoMixerVolume;
|
||||
initVolume = sourceChannel.mAutoMixerInitVolume;
|
||||
|
||||
auto autoMixerPan = static_cast<f32>(sourceChannel.mAutoMixerPanDolby >> 8) / 127;
|
||||
|
||||
switch (outputChannel) {
|
||||
case OutputChannel::LEFT:
|
||||
panValue = 1 - autoMixerPan;
|
||||
break;
|
||||
case OutputChannel::RIGHT:
|
||||
panValue = autoMixerPan;
|
||||
break;
|
||||
default:
|
||||
CRASH("Unhandled output channel: OutputChannel");
|
||||
}
|
||||
|
||||
} else {
|
||||
auto config = GetOutputConfig(sourceChannel, outputChannel);
|
||||
if (config == nullptr) {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
volume = config->mTargetVolume;
|
||||
initVolume = config->mCurrentVolume;
|
||||
}
|
||||
|
||||
// TODO: interpolate to avoid popping.
|
||||
f32 targetRatio = VolumeFromU16(volume);
|
||||
targetRatio *= panValue;
|
||||
|
||||
f32 initRatio = VolumeFromU16(initVolume);
|
||||
initRatio *= panValue;
|
||||
|
||||
return {targetRatio, initRatio};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given decoded & resampled input samples, render a DSP channel to a given output channel.
|
||||
*/
|
||||
static void RenderOutputChannel(
|
||||
const JASDsp::TChannel& sourceChannel,
|
||||
ChannelAuxData& aux,
|
||||
OutputChannel outputChannel,
|
||||
const std::span<f32> inputSamples,
|
||||
OutputSubframe& fullOutputSubframe) {
|
||||
|
||||
auto& outputSubframe = fullOutputSubframe[outputChannel];
|
||||
assert(inputSamples.size() <= outputSubframe.size());
|
||||
|
||||
auto volume = GetVolumeForOutputChannel(sourceChannel, outputChannel);
|
||||
|
||||
f32 targetVolume = volume.Target;
|
||||
auto& prevVolume = aux.PrevVolume(outputChannel);
|
||||
if (std::isnan(prevVolume)) {
|
||||
// Initialize previous volume to new volume on first render.
|
||||
prevVolume = volume.Init;
|
||||
}
|
||||
|
||||
if (prevVolume == 0 && targetVolume == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyVolume(outputSubframe, inputSamples, prevVolume, targetVolume);
|
||||
prevVolume = targetVolume;
|
||||
}
|
||||
|
||||
static void RenderChannel(
|
||||
JASDsp::TChannel& channel,
|
||||
ChannelAuxData& channelAux,
|
||||
OutputSubframe& subframe) {
|
||||
if (channel.mResetFlag) {
|
||||
ResetChannel(channel, channelAux);
|
||||
} else if (channelAux.prevPitch != channel.mPitch) {
|
||||
UpdateSampleRate(channel, channelAux);
|
||||
}
|
||||
|
||||
DspSubframe audioLoadBuffer = {};
|
||||
|
||||
int wantRead = sizeof(audioLoadBuffer);
|
||||
auto read = SDL_GetAudioStreamData(
|
||||
channelAux.resampleStream,
|
||||
&audioLoadBuffer,
|
||||
wantRead);
|
||||
|
||||
if (read < wantRead) {
|
||||
channel.mIsFinished = true;
|
||||
}
|
||||
|
||||
auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, wantRead / sizeof(f32));
|
||||
|
||||
static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!");
|
||||
|
||||
RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, hasReadSamples, subframe);
|
||||
RenderOutputChannel(channel, channelAux,OutputChannel::RIGHT, hasReadSamples, subframe);
|
||||
}
|
||||
|
||||
void dusk::audio::DspInit() {
|
||||
constexpr SDL_AudioSpec srcSpec = {
|
||||
SDL_AUDIO_S16,
|
||||
1,
|
||||
SampleRate
|
||||
};
|
||||
constexpr SDL_AudioSpec dstSpec = {
|
||||
SDL_AUDIO_F32,
|
||||
1,
|
||||
SampleRate
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < DSP_CHANNELS; i++) {
|
||||
auto& aux = ChannelAux[i];
|
||||
aux.resampleStream = SDL_CreateAudioStream(&srcSpec, &dstSpec);
|
||||
|
||||
SDL_SetAudioStreamGetCallback(
|
||||
aux.resampleStream,
|
||||
ReadChannelSamples,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::audio::ApplyVolume(
|
||||
std::span<f32> dst,
|
||||
const std::span<f32> src,
|
||||
const f32 startVolume,
|
||||
const f32 endVolume) {
|
||||
assert(dst.size() >= src.size());
|
||||
|
||||
if (startVolume == endVolume) {
|
||||
for (int i = 0; i < src.size(); i++) {
|
||||
dst[i] = src[i] * startVolume;
|
||||
}
|
||||
} else {
|
||||
const f32 step = (endVolume - startVolume) / static_cast<f32>(src.size());
|
||||
auto curVolume = startVolume;
|
||||
for (int i = 0; i < src.size(); i++) {
|
||||
dst[i] = src[i] * curVolume;
|
||||
curVolume += step;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include "JSystem/JAudio2/JASDSPInterface.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
|
||||
#include "SDL3/SDL_audio.h"
|
||||
#include <span>
|
||||
|
||||
// ReSharper disable once CppUnusedIncludeDirective
|
||||
#include "global.h"
|
||||
|
||||
namespace dusk::audio {
|
||||
constexpr int SampleRate = 32000;
|
||||
|
||||
enum class OutputChannel : u8 {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
OutputChannel_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* Data stored by DSP implementation for each DSP channel.
|
||||
*/
|
||||
struct ChannelAuxData {
|
||||
s16 hist1;
|
||||
s16 hist0;
|
||||
SDL_AudioStream* resampleStream;
|
||||
u16 prevPitch;
|
||||
|
||||
// Used for debugging tools.
|
||||
u32 resetCount;
|
||||
|
||||
/**
|
||||
* Previous volume values, per output channel.
|
||||
* Used to avoid clicking when volumes change.
|
||||
* Set to NaN after channel reset, indicating that initial volume value is previous.
|
||||
*/
|
||||
f32 prevVolume[static_cast<int>(OutputChannel::OutputChannel_MAX)];
|
||||
|
||||
f32& PrevVolume(OutputChannel channel) {
|
||||
assert(channel < OutputChannel::OutputChannel_MAX);
|
||||
return prevVolume[static_cast<int>(channel)];
|
||||
}
|
||||
};
|
||||
|
||||
extern ChannelAuxData ChannelAux[DSP_CHANNELS];
|
||||
|
||||
/**
|
||||
* Data storage for a single subframe and output channel's worth of samples.
|
||||
*/
|
||||
using DspSubframe = std::array<f32, DSP_SUBFRAME_SIZE>;
|
||||
|
||||
/**
|
||||
* Data storage for a single subframe's worth of samples, across all output channels.
|
||||
*/
|
||||
struct OutputSubframe {
|
||||
static constexpr int NUM_CHANNELS = static_cast<int>(OutputChannel::OutputChannel_MAX);
|
||||
|
||||
std::array<DspSubframe, NUM_CHANNELS> channels;
|
||||
|
||||
DspSubframe& operator[](OutputChannel channel) {
|
||||
assert(channel < OutputChannel::OutputChannel_MAX);
|
||||
return channels[static_cast<int>(channel)];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the DSP system, creating data storage needed for channels and such.
|
||||
*/
|
||||
void DspInit();
|
||||
|
||||
/**
|
||||
* Render a subframe of audio with the current DSP state.
|
||||
*/
|
||||
void DspRender(OutputSubframe& subframe);
|
||||
|
||||
/**
|
||||
* Get the amount of samples a single "block" of this channel's data has.
|
||||
*/
|
||||
constexpr u32 BlockSamples(const JASDsp::TChannel& channel) {
|
||||
return channel.mSamplesPerBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of bytes a single "block" of this channel's data has.
|
||||
*/
|
||||
constexpr u32 BlockBytes(const JASDsp::TChannel& channel) {
|
||||
if (channel.mSamplesPerBlock == 1) {
|
||||
if (channel.mBytesPerBlock == 16) {
|
||||
return 2;
|
||||
}
|
||||
if (channel.mBytesPerBlock == 8) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
CRASH("Unknown format");
|
||||
}
|
||||
|
||||
return channel.mBytesPerBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a volume level to audio data.
|
||||
* Interpolates across the two provided volume levels to avoid clicking.
|
||||
*/
|
||||
void ApplyVolume(std::span<f32> dst, std::span<f32> src, f32 startVolume, f32 endVolume);
|
||||
|
||||
extern f32 MasterVolume;
|
||||
extern f32 PrevMasterVolume;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "JSystem/JAudio2/JASCriticalSection.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
static std::recursive_mutex gAudioThreadMutex;
|
||||
|
||||
JASCriticalSection::JASCriticalSection() {
|
||||
gAudioThreadMutex.lock();
|
||||
}
|
||||
|
||||
JASCriticalSection::~JASCriticalSection() {
|
||||
gAudioThreadMutex.unlock();
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "JSystem/JAudio2/JAISeMgr.h"
|
||||
#include "JSystem/JAudio2/JAISeqMgr.h"
|
||||
#include "JSystem/JAudio2/JAIStreamMgr.h"
|
||||
#include "JSystem/JAudio2/JASCriticalSection.h"
|
||||
#include "JSystem/JAudio2/JASDSPChannel.h"
|
||||
#include "JSystem/JAudio2/JASDSPInterface.h"
|
||||
#include "JSystem/JAudio2/JASTrack.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
|
||||
static std::array<u8, DSP_CHANNELS> channelSortIndices = {};
|
||||
static std::array<u32, DSP_CHANNELS> lastResetCounts = {};
|
||||
|
||||
static bool sortUpdateCount = true;
|
||||
|
||||
static void DisplayDspChannel(int i) {
|
||||
using namespace dusk::audio;
|
||||
|
||||
auto& channel = JASDsp::CH_BUF[i];
|
||||
auto& jasChannel = JASDSPChannel::sDspChannels[i];
|
||||
if (!channel.mIsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%d", i);
|
||||
|
||||
if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) {
|
||||
ImGui::Text("[%02X]", i);
|
||||
ImGui::SameLine();
|
||||
|
||||
auto resetCount = GetResetCount(i);
|
||||
ImColor color = IM_COL32_WHITE;
|
||||
if (lastResetCounts[i] != resetCount) {
|
||||
lastResetCounts[i] = resetCount;
|
||||
color = IM_COL32(0, 0xFF, 0, 0xFF);
|
||||
}
|
||||
ImGui::TextColored(color, "Update count: %d, reset count: %ud", jasChannel.mUpdateCounter, resetCount);
|
||||
ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Priority: %hd", jasChannel.mPriority);
|
||||
ImGui::Text("Format: %02X/%02X", channel.mSamplesPerBlock, channel.mBytesPerBlock);
|
||||
ImGui::Text("Position: %08X/%08X", channel.mSamplePosition, channel.mEndSample);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Memory: %08X/%08X", channel.mWaveAramAddress, channel.mAramStreamPosition);
|
||||
|
||||
if (channel.mAutoMixerBeenSet) {
|
||||
auto pan = (channel.mAutoMixerPanDolby >> 8) / 127.5f;
|
||||
auto dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.5f;
|
||||
auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f;
|
||||
auto volume = VolumeFromU16(channel.mAutoMixerVolume);
|
||||
ImGui::Text(
|
||||
"Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f)",
|
||||
pan, dolby, fxMix, volume);
|
||||
} else {
|
||||
ImGui::Text(
|
||||
"Bus connect: %04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f)",
|
||||
channel.mOutputChannels[0].mBusConnect,
|
||||
VolumeFromU16(channel.mOutputChannels[0].mTargetVolume),
|
||||
channel.mOutputChannels[1].mBusConnect,
|
||||
VolumeFromU16(channel.mOutputChannels[1].mTargetVolume),
|
||||
channel.mOutputChannels[2].mBusConnect,
|
||||
VolumeFromU16(channel.mOutputChannels[2].mTargetVolume),
|
||||
channel.mOutputChannels[3].mBusConnect,
|
||||
VolumeFromU16(channel.mOutputChannels[3].mTargetVolume),
|
||||
channel.mOutputChannels[4].mBusConnect,
|
||||
VolumeFromU16(channel.mOutputChannels[4].mTargetVolume),
|
||||
channel.mOutputChannels[5].mBusConnect,
|
||||
VolumeFromU16(channel.mOutputChannels[5].mTargetVolume));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
static void InitChannelSortIndices() {
|
||||
for (int i = 0; i < channelSortIndices.size(); i++) {
|
||||
channelSortIndices[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
static void SortChannelsByUpdateCount() {
|
||||
InitChannelSortIndices();
|
||||
std::ranges::stable_sort(
|
||||
channelSortIndices,
|
||||
[](u8 a, u8 b) {
|
||||
auto& jasChannelA = JASDSPChannel::sDspChannels[a];
|
||||
auto& jasChannelB = JASDSPChannel::sDspChannels[b];
|
||||
|
||||
return jasChannelA.mUpdateCounter > jasChannelB.mUpdateCounter;
|
||||
});
|
||||
}
|
||||
|
||||
static void ShowAllDspChannels() {
|
||||
int activeChannels = 0;
|
||||
for (int i = 0; i < DSP_CHANNELS; i++) {
|
||||
if (JASDsp::CH_BUF[i].mIsActive) {
|
||||
activeChannels++;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Active channels: %d", activeChannels);
|
||||
ImGui::Checkbox("Sort by update count", &sortUpdateCount);
|
||||
|
||||
if (sortUpdateCount) {
|
||||
SortChannelsByUpdateCount();
|
||||
for (u8 index : channelSortIndices) {
|
||||
DisplayDspChannel(index);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < DSP_CHANNELS; i++) {
|
||||
DisplayDspChannel(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ShowAllTracks() {
|
||||
if (ImGui::Button("Pause all")) {
|
||||
for (auto& track : JASTrack::sTrackList) {
|
||||
track.pause(true);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& track : JASTrack::sTrackList) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%p", &track);
|
||||
|
||||
if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) {
|
||||
ImGui::Text("[%p]", &track);
|
||||
bool paused = track.mFlags.pause;
|
||||
ImGui::Checkbox("Paused", &paused);
|
||||
track.mFlags.pause = paused;
|
||||
bool muted = track.mFlags.mute;
|
||||
ImGui::Checkbox("Muted", &muted);
|
||||
track.mFlags.mute = muted;
|
||||
|
||||
for (int i = 0; i < JASTrack::MAX_CHILDREN; i++) {
|
||||
const auto child = track.getChild(i);
|
||||
if (child != nullptr) {
|
||||
ImGui::Text("child: [%p]", child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
static void ShowAllJAIStreams() {
|
||||
auto& mgr = *JAIStreamMgr::getInstance();
|
||||
|
||||
for (auto streamLink = mgr.getStreamList()->getFirst(); streamLink != nullptr; streamLink = streamLink->getNext()) {
|
||||
auto& stream = *streamLink->getObject();
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%p", &stream);
|
||||
|
||||
if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) {
|
||||
ImGui::Text("[%p]", &stream);
|
||||
bool paused = stream.status_.field_0x0.flags.paused;
|
||||
ImGui::Checkbox("Paused", &paused);
|
||||
stream.status_.field_0x0.flags.paused = paused;
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
static void ShowAllJAISes() {
|
||||
auto& mgr = *JAISeMgr::getInstance();
|
||||
|
||||
for (int i = 0; i < JAISeMgr::NUM_CATEGORIES; i++) {
|
||||
const auto category = mgr.getCategory(i);
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%i", i);
|
||||
|
||||
if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) {
|
||||
ImGui::Text("Category: %i", i);
|
||||
if (ImGui::Button("Pause All")) {
|
||||
category->pause(true);
|
||||
}
|
||||
|
||||
for (auto seLink = category->getSeList()->getFirst(); seLink != nullptr; seLink = seLink->getNext()) {
|
||||
const auto se = seLink->getObject();
|
||||
ImGui::Text("[%p]", se);
|
||||
ImGui::Text(se->status_.field_0x0.flags.paused ? "Paused" : "Not paused");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void ShowAllJAISeqs() {
|
||||
auto& mgr = *JAISeqMgr::getInstance();
|
||||
|
||||
if (ImGui::Button("Pause")) {
|
||||
mgr.pause(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Unpause")) {
|
||||
mgr.pause(false);
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::ImGuiMenuTools::ShowAudioDebug() {
|
||||
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showAudioDebug)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui::Begin("Audio Debug", &m_showAudioDebug)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
JASCriticalSection cs;
|
||||
|
||||
if (ImGui::BeginTabBar("Tabs")) {
|
||||
if (ImGui::BeginTabItem("DSP channels")) {
|
||||
ShowAllDspChannels();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("JAITrack")) {
|
||||
ShowAllTracks();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("JAIStream")) {
|
||||
ShowAllJAIStreams();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("JAISe")) {
|
||||
ShowAllJAISes();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("JAISeq")) {
|
||||
ShowAllJAISeqs();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -7,8 +7,9 @@
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "JSystem/JUtility/JUTGamePad.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
namespace dusk {
|
||||
ImGuiMenuGame::ImGuiMenuGame() {}
|
||||
@@ -35,6 +36,8 @@ namespace dusk {
|
||||
ImGui::Text("Master Volume");
|
||||
ImGui::SliderFloat("##m_masterVolume", &m_audioSettings.m_masterVolume, 0.0f, 1.0f, "");
|
||||
|
||||
/*
|
||||
// TODO: implement additional settings
|
||||
ImGui::Text("Main Music Volume");
|
||||
ImGui::SliderFloat("##m_mainMusicVolume", &m_audioSettings.m_mainMusicVolume, 0.0f, 1.0f, "");
|
||||
|
||||
@@ -49,8 +52,10 @@ namespace dusk {
|
||||
|
||||
Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface();
|
||||
if (audioMgr != nullptr) {
|
||||
// TODO: actually apply volume settings
|
||||
}
|
||||
*/
|
||||
|
||||
audio::SetMasterVolume(m_audioSettings.m_masterVolume);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace dusk {
|
||||
if (ImGui::Checkbox("Development Mode", &m_isDevelopmentMode)) {
|
||||
isToggleDevelopmentMode = true;
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginMenu("Collision View")) {
|
||||
@@ -45,6 +45,7 @@ namespace dusk {
|
||||
ImGui::MenuItem("Map Loader", nullptr, &m_showMapLoader);
|
||||
ImGui::MenuItem("Player Info", nullptr, &m_showPlayerInfo);
|
||||
ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor);
|
||||
ImGui::MenuItem("Audio Debug", "F7", &m_showAudioDebug);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
@@ -59,6 +60,7 @@ namespace dusk {
|
||||
ShowStubLog();
|
||||
ShowMapLoader();
|
||||
ShowPlayerInfo();
|
||||
ShowAudioDebug();
|
||||
|
||||
if (m_showSaveEditor) {
|
||||
m_saveEditor.draw(m_showSaveEditor);
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace dusk {
|
||||
void ShowStubLog();
|
||||
void ShowMapLoader();
|
||||
void ShowPlayerInfo();
|
||||
void ShowAudioDebug();
|
||||
|
||||
CollisionViewSettings& getCollisionViewSettings() { return m_collisionViewSettings; }
|
||||
|
||||
@@ -48,6 +49,8 @@ namespace dusk {
|
||||
bool m_showStubLog = false;
|
||||
|
||||
bool m_showMapLoader = false;
|
||||
|
||||
bool m_showAudioDebug = false;
|
||||
struct {
|
||||
int mapIdx = -1;
|
||||
int regionIdx = -1;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include "dusk/logging.h"
|
||||
#include "imgui.h"
|
||||
@@ -9,6 +10,7 @@ namespace dusk {
|
||||
static ImGuiTextBuffer StubLogBuffer;
|
||||
static std::vector<int> LineOffsets;
|
||||
static bool StubLogPaused;
|
||||
static std::mutex StubLogMutex;
|
||||
|
||||
const char* LogLevelName(const AuroraLogLevel level) {
|
||||
switch (level) {
|
||||
@@ -32,6 +34,8 @@ namespace dusk {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(StubLogMutex);
|
||||
|
||||
LineOffsets.push_back(StubLogBuffer.size());
|
||||
const auto levelName = LogLevelName(level);
|
||||
StubLogBuffer.appendf("[%s | %s] %s\n", levelName, module, message);
|
||||
@@ -40,6 +44,8 @@ namespace dusk {
|
||||
static void ClearPastFrame();
|
||||
|
||||
void ImGuiMenuTools::ShowStubLog() {
|
||||
std::lock_guard lock(StubLogMutex);
|
||||
|
||||
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F5, m_showStubLog)) {
|
||||
ClearPastFrame();
|
||||
return;
|
||||
@@ -56,7 +62,7 @@ namespace dusk {
|
||||
|
||||
if (ImGui::BeginChild("scrolling")) {
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin(LineOffsets.size());
|
||||
clipper.Begin(static_cast<int>(LineOffsets.size()));
|
||||
while (clipper.Step()) {
|
||||
for (int idx = clipper.DisplayStart; idx < clipper.DisplayEnd; idx++) {
|
||||
const char* lineStart = StubLogBuffer.begin() + LineOffsets[idx];
|
||||
|
||||
@@ -367,46 +367,6 @@ VIRetraceCallback VISetPreRetraceCallback(VIRetraceCallback cb) {
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#pragma mark DSP
|
||||
#include <dolphin/dsp.h>
|
||||
extern "C" void __DSP_insert_task(DSPTaskInfo* task) {
|
||||
STUB_LOG();
|
||||
}
|
||||
|
||||
extern "C" void __DSP_boot_task(DSPTaskInfo*) {
|
||||
STUB_LOG();
|
||||
}
|
||||
|
||||
extern "C" void __DSP_exec_task(DSPTaskInfo*, DSPTaskInfo*) {
|
||||
STUB_LOG();
|
||||
}
|
||||
|
||||
extern "C" void __DSP_remove_task(DSPTaskInfo* task) {
|
||||
STUB_LOG();
|
||||
}
|
||||
|
||||
void DSPAssertInt(void) {
|
||||
STUB_LOG();
|
||||
}
|
||||
u32 DSPCheckMailFromDSP(void) {
|
||||
STUB_LOG();
|
||||
return 0;
|
||||
}
|
||||
u32 DSPCheckMailToDSP(void) {
|
||||
STUB_LOG();
|
||||
return 0;
|
||||
}
|
||||
void DSPInit(void) {
|
||||
STUB_LOG();
|
||||
}
|
||||
u32 DSPReadMailFromDSP(void) {
|
||||
STUB_LOG();
|
||||
return 0;
|
||||
}
|
||||
void DSPSendMailToDSP(u32 mail) {
|
||||
STUB_LOG();
|
||||
}
|
||||
|
||||
#pragma mark Z2Audio
|
||||
class Z2AudioCS {
|
||||
public:
|
||||
|
||||
@@ -1019,10 +1019,15 @@ cull_sphere l_cullSizeSphere[fopAc_CULLSPHERE_MAX_e] = {
|
||||
|
||||
s32 fopAcM_cullingCheck(fopAc_ac_c const* i_actor) {
|
||||
MtxP mtx_p;
|
||||
#if AVOID_UB
|
||||
Mtx concat_mtx;
|
||||
#endif
|
||||
if (fopAcM_GetMtx(i_actor) == NULL) {
|
||||
mtx_p = j3dSys.getViewMtx();
|
||||
} else {
|
||||
#if !AVOID_UB
|
||||
Mtx concat_mtx;
|
||||
#endif
|
||||
cMtx_concat(j3dSys.getViewMtx(), fopAcM_GetMtx(i_actor), concat_mtx);
|
||||
mtx_p = concat_mtx;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "dusk/os.h"
|
||||
|
||||
#if PLATFORM_GCN
|
||||
const int stack_size = 3072;
|
||||
#else
|
||||
@@ -65,6 +67,11 @@ static void mDoDvdErr_Watch(void*) {
|
||||
OSDisableInterrupts();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
OSSetCurrentThreadName("DVD error thread");
|
||||
#endif
|
||||
|
||||
JKRThread(OSGetCurrentThread(), 0);
|
||||
|
||||
JKRSetCurrentHeap(mDoExt_getAssertHeap());
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "m_Do/m_Do_MemCardRWmng.h"
|
||||
#include "m_Do/m_Do_Reset.h"
|
||||
#include "os_report.h"
|
||||
#include "dusk/os.h"
|
||||
|
||||
#if PLATFORM_WII || PLATFORM_SHIELD
|
||||
#include <revolution/nand.h>
|
||||
@@ -866,6 +867,10 @@ mDoMemCd_Ctrl_c g_mDoMemCd_control;
|
||||
static int mDoMemCd_main(void*) {
|
||||
JKRThread(OSGetCurrentThread(), 0);
|
||||
|
||||
#if TARGET_PC
|
||||
OSSetCurrentThreadName("MemCardThread");
|
||||
#endif
|
||||
|
||||
JKRSetCurrentHeap(mDoExt_getAssertHeap());
|
||||
|
||||
g_mDoMemCd_control.main();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "JSystem/JKernel/JKRDvdRipper.h"
|
||||
#include "JSystem/JKernel/JKRExpHeap.h"
|
||||
#include "JSystem/JKernel/JKRMemArchive.h"
|
||||
#include "dusk/os.h"
|
||||
#include "m_Do/m_Do_Reset.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include "m_Do/m_Do_ext.h"
|
||||
@@ -17,6 +18,9 @@
|
||||
|
||||
s32 mDoDvdThd::main(void* param_0) {
|
||||
JKRThread(OSGetCurrentThread(), 0);
|
||||
#if TARGET_PC
|
||||
OSSetCurrentThreadName("DVD thread");
|
||||
#endif
|
||||
JKRSetCurrentHeap(mDoExt_getAssertHeap());
|
||||
mDoDvdThd_param_c* param = static_cast<mDoDvdThd_param_c*>(param_0);
|
||||
param->mainLoop();
|
||||
@@ -159,7 +163,11 @@ void mDoDvdThd_param_c::mainLoop() {
|
||||
while ((command = this->getFirstCommand())) {
|
||||
this->cut(command);
|
||||
if (mDoDvdThd::SyncWidthSound) {
|
||||
#if TARGET_PC
|
||||
JASDvd::getThreadPointer()->sendCmdMsg(cb, &command, sizeof(void*));
|
||||
#else
|
||||
JASDvd::getThreadPointer()->sendCmdMsg(cb, &command, 4);
|
||||
#endif
|
||||
} else {
|
||||
cb(&command);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "dusk/dusk.h"
|
||||
#include "dusk/logging.h"
|
||||
#include "dusk/time.h"
|
||||
#include "dusk/main.h"
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <aurora/event.h>
|
||||
@@ -73,6 +74,10 @@ const int audioHeapSize = 0x14D800;
|
||||
// =========================================================================
|
||||
#define COPYDATE_PATH "/str/Final/Release/COPYDATE"
|
||||
|
||||
#if TARGET_PC
|
||||
bool dusk::IsShuttingDown = false;
|
||||
#endif
|
||||
|
||||
s32 LOAD_COPYDATE(void*) {
|
||||
char buffer[32];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
@@ -307,6 +312,8 @@ int game_main(int argc, char* argv[]) {
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
|
||||
dusk::IsShuttingDown = true;
|
||||
|
||||
aurora_shutdown();
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user