From cab2361f5f95f8b1bb50dace3d2ffffd776909ff Mon Sep 17 00:00:00 2001 From: Sezz Date: Mon, 30 Mar 2026 14:28:55 +1100 Subject: [PATCH 1/6] Copy virtual SPU from PsyDoom --- Source/Audio/Audio.cpp | 26 +- Source/Audio/Audio.h | 3 + Source/Audio/Spu.cpp | 814 +++++++++++++++++++++++++- Source/Audio/Spu.h | 453 ++++++++++++++ Source/Game/Bodyprog/Sound/SdData.cpp | 470 +++++++-------- 5 files changed, 1514 insertions(+), 252 deletions(-) diff --git a/Source/Audio/Audio.cpp b/Source/Audio/Audio.cpp index a1b7cf0..0e2947a 100644 --- a/Source/Audio/Audio.cpp +++ b/Source/Audio/Audio.cpp @@ -2,6 +2,7 @@ #include "Audio/Audio.h" #include "Application.h" +#include "Audio/Spu.h" #include "Utils/Video.h" using namespace Silent::Utils; @@ -11,16 +12,16 @@ namespace Silent::Audio constexpr int SAMPLE_RATE = 44100; constexpr int BUFFER_SAMPLES = 4096; - struct Voice + struct VoiceTest { - float phase; - float frequency; - float volumeLeft; - float volumeRight; - bool active; + float phase = 0.0f; + float frequency = 0.0f; + float volumeLeft = 0.0f; + float volumeRight = 0.0f; + bool active = false; }; - static Voice voice; + static VoiceTest voice; void GenerateSamples(int16* buffer, int sampleCount) { @@ -76,12 +77,19 @@ namespace Silent::Audio auto destSpec = SDL_AudioSpec{}; SDL_GetAudioDeviceFormat(_device, &destSpec, nullptr); - auto srcSpec = SDL_AudioSpec{ SDL_AUDIO_F32, 2, SAMPLE_RATE }; - _stream = SDL_CreateAudioStream(&destSpec, &destSpec); + auto srcSpec = SDL_AudioSpec + { + .format = SDL_AUDIO_F32, + .channels = 2, + .freq = SAMPLE_RATE + }; + _stream = SDL_CreateAudioStream(&destSpec, &destSpec); SDL_BindAudioStream(_device, _stream); SDL_ResumeAudioDevice(_device); + _spu.Initialize(); + // Init fake voice. voice.phase = 0.0f; voice.frequency = 440.0f; // A4 diff --git a/Source/Audio/Audio.h b/Source/Audio/Audio.h index 71d98a5..4131133 100644 --- a/Source/Audio/Audio.h +++ b/Source/Audio/Audio.h @@ -1,5 +1,7 @@ #pragma once +#include "Audio/Spu.h" + namespace Silent::Audio { /** @brief Audio manager. */ @@ -12,6 +14,7 @@ namespace Silent::Audio SDL_AudioDeviceID _device = 0; SDL_AudioStream* _stream = nullptr; + VirtualSpu _spu = VirtualSpu(); public: // ============= diff --git a/Source/Audio/Spu.cpp b/Source/Audio/Spu.cpp index db8469d..cb4febf 100644 --- a/Source/Audio/Spu.cpp +++ b/Source/Audio/Spu.cpp @@ -1,14 +1,812 @@ #include "Framework.h" -#include "Psx.h" #include "Audio/Spu.h" namespace Silent::Audio { - // @todo Create temporary compatibility layer. - //uint SpuGetReverbVoice(); - //int SpuGetKeyStatus(uint voice_bitmask); - //void SpuGetVoiceAttr(SpuVoiceAttr* attr); - //void SpuSetKey(long on_off, u_long voice_bit); - //int SpuReserveReverbWorkArea(int rev_mode); - //void SpuQuit(void); + // A series of co-efficients used by the SPU's gaussian sample interpolation. + // For more details on this see: https://problemkaputt.de/psx-spx.htm#cdromxaaudioadpcmcompression + constexpr auto INTERP_GAUSS_TABLE = std::array + { + -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, + -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, + 0x0003, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, 0x0007, + 0x0008, 0x0009, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, + 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0015, 0x0016, 0x0018, + 0x0019, 0x001B, 0x001C, 0x001E, 0x0020, 0x0021, 0x0023, 0x0025, + 0x0027, 0x0029, 0x002C, 0x002E, 0x0030, 0x0033, 0x0035, 0x0038, + 0x003A, 0x003D, 0x0040, 0x0043, 0x0046, 0x0049, 0x004D, 0x0050, + 0x0054, 0x0057, 0x005B, 0x005F, 0x0063, 0x0067, 0x006B, 0x006F, + 0x0074, 0x0078, 0x007D, 0x0082, 0x0087, 0x008C, 0x0091, 0x0096, + 0x009C, 0x00A1, 0x00A7, 0x00AD, 0x00B3, 0x00BA, 0x00C0, 0x00C7, + 0x00CD, 0x00D4, 0x00DB, 0x00E3, 0x00EA, 0x00F2, 0x00FA, 0x0101, + 0x010A, 0x0112, 0x011B, 0x0123, 0x012C, 0x0135, 0x013F, 0x0148, + 0x0152, 0x015C, 0x0166, 0x0171, 0x017B, 0x0186, 0x0191, 0x019C, + 0x01A8, 0x01B4, 0x01C0, 0x01CC, 0x01D9, 0x01E5, 0x01F2, 0x0200, + 0x020D, 0x021B, 0x0229, 0x0237, 0x0246, 0x0255, 0x0264, 0x0273, + 0x0283, 0x0293, 0x02A3, 0x02B4, 0x02C4, 0x02D6, 0x02E7, 0x02F9, + 0x030B, 0x031D, 0x0330, 0x0343, 0x0356, 0x036A, 0x037E, 0x0392, + 0x03A7, 0x03BC, 0x03D1, 0x03E7, 0x03FC, 0x0413, 0x042A, 0x0441, + 0x0458, 0x0470, 0x0488, 0x04A0, 0x04B9, 0x04D2, 0x04EC, 0x0506, + 0x0520, 0x053B, 0x0556, 0x0572, 0x058E, 0x05AA, 0x05C7, 0x05E4, + 0x0601, 0x061F, 0x063E, 0x065C, 0x067C, 0x069B, 0x06BB, 0x06DC, + 0x06FD, 0x071E, 0x0740, 0x0762, 0x0784, 0x07A7, 0x07CB, 0x07EF, + 0x0813, 0x0838, 0x085D, 0x0883, 0x08A9, 0x08D0, 0x08F7, 0x091E, + 0x0946, 0x096F, 0x0998, 0x09C1, 0x09EB, 0x0A16, 0x0A40, 0x0A6C, + 0x0A98, 0x0AC4, 0x0AF1, 0x0B1E, 0x0B4C, 0x0B7A, 0x0BA9, 0x0BD8, + 0x0C07, 0x0C38, 0x0C68, 0x0C99, 0x0CCB, 0x0CFD, 0x0D30, 0x0D63, + 0x0D97, 0x0DCB, 0x0E00, 0x0E35, 0x0E6B, 0x0EA1, 0x0ED7, 0x0F0F, + 0x0F46, 0x0F7F, 0x0FB7, 0x0FF1, 0x102A, 0x1065, 0x109F, 0x10DB, + 0x1116, 0x1153, 0x118F, 0x11CD, 0x120B, 0x1249, 0x1288, 0x12C7, + 0x1307, 0x1347, 0x1388, 0x13C9, 0x140B, 0x144D, 0x1490, 0x14D4, + 0x1517, 0x155C, 0x15A0, 0x15E6, 0x162C, 0x1672, 0x16B9, 0x1700, + 0x1747, 0x1790, 0x17D8, 0x1821, 0x186B, 0x18B5, 0x1900, 0x194B, + 0x1996, 0x19E2, 0x1A2E, 0x1A7B, 0x1AC8, 0x1B16, 0x1B64, 0x1BB3, + 0x1C02, 0x1C51, 0x1CA1, 0x1CF1, 0x1D42, 0x1D93, 0x1DE5, 0x1E37, + 0x1E89, 0x1EDC, 0x1F2F, 0x1F82, 0x1FD6, 0x202A, 0x207F, 0x20D4, + 0x2129, 0x217F, 0x21D5, 0x222C, 0x2282, 0x22DA, 0x2331, 0x2389, + 0x23E1, 0x2439, 0x2492, 0x24EB, 0x2545, 0x259E, 0x25F8, 0x2653, + 0x26AD, 0x2708, 0x2763, 0x27BE, 0x281A, 0x2876, 0x28D2, 0x292E, + 0x298B, 0x29E7, 0x2A44, 0x2AA1, 0x2AFF, 0x2B5C, 0x2BBA, 0x2C18, + 0x2C76, 0x2CD4, 0x2D33, 0x2D91, 0x2DF0, 0x2E4F, 0x2EAE, 0x2F0D, + 0x2F6C, 0x2FCC, 0x302B, 0x308B, 0x30EA, 0x314A, 0x31AA, 0x3209, + 0x3269, 0x32C9, 0x3329, 0x3389, 0x33E9, 0x3449, 0x34A9, 0x3509, + 0x3569, 0x35C9, 0x3629, 0x3689, 0x36E8, 0x3748, 0x37A8, 0x3807, + 0x3867, 0x38C6, 0x3926, 0x3985, 0x39E4, 0x3A43, 0x3AA2, 0x3B00, + 0x3B5F, 0x3BBD, 0x3C1B, 0x3C79, 0x3CD7, 0x3D35, 0x3D92, 0x3DEF, + 0x3E4C, 0x3EA9, 0x3F05, 0x3F62, 0x3FBD, 0x4019, 0x4074, 0x40D0, + 0x412A, 0x4185, 0x41DF, 0x4239, 0x4292, 0x42EB, 0x4344, 0x439C, + 0x43F4, 0x444C, 0x44A3, 0x44FA, 0x4550, 0x45A6, 0x45FC, 0x4651, + 0x46A6, 0x46FA, 0x474E, 0x47A1, 0x47F4, 0x4846, 0x4898, 0x48E9, + 0x493A, 0x498A, 0x49D9, 0x4A29, 0x4A77, 0x4AC5, 0x4B13, 0x4B5F, + 0x4BAC, 0x4BF7, 0x4C42, 0x4C8D, 0x4CD7, 0x4D20, 0x4D68, 0x4DB0, + 0x4DF7, 0x4E3E, 0x4E84, 0x4EC9, 0x4F0E, 0x4F52, 0x4F95, 0x4FD7, + 0x5019, 0x505A, 0x509A, 0x50DA, 0x5118, 0x5156, 0x5194, 0x51D0, + 0x520C, 0x5247, 0x5281, 0x52BA, 0x52F3, 0x532A, 0x5361, 0x5397, + 0x53CC, 0x5401, 0x5434, 0x5467, 0x5499, 0x54CA, 0x54FA, 0x5529, + 0x5558, 0x5585, 0x55B2, 0x55DE, 0x5609, 0x5632, 0x565B, 0x5684, + 0x56AB, 0x56D1, 0x56F6, 0x571B, 0x573E, 0x5761, 0x5782, 0x57A3, + 0x57C3, 0x57E2, 0x57FF, 0x581C, 0x5838, 0x5853, 0x586D, 0x5886, + 0x589E, 0x58B5, 0x58CB, 0x58E0, 0x58F4, 0x5907, 0x5919, 0x592A, + 0x593A, 0x5949, 0x5958, 0x5965, 0x5971, 0x597C, 0x5986, 0x598F, + 0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3 + }; + + // Read from sound memory with bounds checking. + // Any portion read beyond the end of sound memory will be zeroed. + static void sramRead(const std::byte* pRam, uint ramSize, uint offset, uint numBytes, std::byte* pDst) + { + //ASSERT(pDst); + uint endOffset = offset + numBytes; + uint bytesToZero = (endOffset > ramSize) ? endOffset - ramSize : 0; + uint bytesToRead = numBytes - bytesToZero; + std::memset(pDst + bytesToRead, 0, bytesToZero); + std::memcpy(pDst, pRam + offset, bytesToRead); + } + + // Decode an ADPCM block for the given voice + static void decodeAdpcmBlock(Voice& voice, std::byte adpcmBlock[ADPCM_BLOCK_SIZE]) + { +#if SIMPLE_SPU_FLOAT_SPU + constexpr float FILTER_COEF_POS[5] = { 0, 60.0f / 64.0f, 115.0f / 64.0f, 98.0f / 64.0f, 122.0f / 64.0f }; + constexpr float FILTER_COEF_NEG[5] = { 0, 0, -52.0f / 64.0f, -55.0f / 64.0f, -60.0f / 64.0f }; +#else + constexpr int FILTER_COEF_POS[5] = { 0, 60, 115, 98, 122 }; + constexpr int FILTER_COEF_NEG[5] = { 0, 0, -52, -55, -60 }; +#endif + + // Hold the last 2 ADPCM samples we decoded here with the newest first. + // They are required for the adaptive decoding throughout and carry across ADPCM blocks. + Sample prevSamples[2] = + { + voice.samples[Voice::SAMPLE_BUFFER_SIZE - 1], + voice.samples[Voice::SAMPLE_BUFFER_SIZE - 2], + }; + + // Save the last 3 samples of the previous ADPCM block in the part of the samples buffer reserved for that. + // We'll need them later for interpolation. + voice.samples[0] = voice.samples[Voice::SAMPLE_BUFFER_SIZE - 3]; + voice.samples[1] = voice.samples[Voice::SAMPLE_BUFFER_SIZE - 2]; + voice.samples[2] = voice.samples[Voice::SAMPLE_BUFFER_SIZE - 1]; + + // Get the shift and filter to use from the first ADPCM header byte. + // Note that the filter must be from 0-4 so if it goes beyond that then use filter mode '0' (no filter). + // Also according to NO$PSX: "For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift = 9" + uint adpcmFilter = ((uint)adpcmBlock[0] & 0x70) >> 4; + if (adpcmFilter > 4) + { + adpcmFilter = 0; + } + + uint sampleShift = (uint)adpcmBlock[0] & 0x0F; + if (sampleShift > 12) + { + sampleShift = 9; + } + + // Get the ADPCM filter co-efficients, both positive and negative. + // For more details on this see: https://problemkaputt.de/psx-spx.htm#cdromxaaudioadpcmcompression +#if SIMPLE_SPU_FLOAT_SPU + float filterCoefPos = FILTER_COEF_POS[adpcmFilter]; + float filterCoefNeg = FILTER_COEF_NEG[adpcmFilter]; +#else + int filterCoefPos = FILTER_COEF_POS[adpcmFilter]; + int filterCoefNeg = FILTER_COEF_NEG[adpcmFilter]; +#endif + + // Decode all of the samples + for (int sampleIdx = 0; sampleIdx < ADPCM_BLOCK_NUM_SAMPLES; sampleIdx++) + { + // Read this samples 4-bit data + ushort nibble = ((sampleIdx % 2) == 0) ? ((ushort)adpcmBlock[2 + sampleIdx / 2] & 0x0F) >> 0: + ((ushort)adpcmBlock[2 + sampleIdx / 2] & 0xF0) >> 4; + +#if SIMPLE_SPU_FLOAT_SPU + // The 4-bit sample gets extended to 16-bit by shifting and is sign extended to 32-bit. + // After that we scale by the sample shift. + short sampleI16 = ((short)(nibble << 12)) >> sampleShift; + Sample sample(sampleI16); + + // Mix in previous samples using the filter coefficients chosen and scale the result + sample += prevSamples[0].value * filterCoefPos + prevSamples[1].value * filterCoefNeg; + sample = std::clamp(sample.value, -1.0f, 1.0f); + voice.samples[Voice::NUM_PREV_SAMPLES + sampleIdx] = sample; + + // Move previous samples forward + prevSamples[1] = prevSamples[0]; + prevSamples[0] = sample; +#else + // The 4-bit sample gets extended to 16-bit by shifting and is sign extended to 32-bit. + // After that we scale by the sample shift. + int sample = (short)(nibble << 12); + sample >>= sampleShift; + + // Mix in previous samples using the filter coefficients chosen and scale the result; also clamp to a 16-bit range + sample += (prevSamples[0].value * filterCoefPos + prevSamples[1].value * filterCoefNeg + 32) / 64; + sample = std::clamp(sample, INT16_MIN, INT16_MAX); + voice.samples[Voice::NUM_PREV_SAMPLES + sampleIdx] = (short) sample; + + // Move previous samples forward + prevSamples[1] = prevSamples[0]; + prevSamples[0] = (short) sample; +#endif + } + } + + // Get the next phase for a given envelope phase + static EnvPhase getNextEnvPhase(EnvPhase phase) + { + switch (phase) + { + case EnvPhase::Attack: return EnvPhase::Decay; + case EnvPhase::Decay: return EnvPhase::Sustain; + case EnvPhase::Sustain: return EnvPhase::Release; + case EnvPhase::Release: return EnvPhase::Off; + default: return EnvPhase::Off; + } + } + + // Get the phase parameters for the given envelope, phase and current envelope level + static EnvPhaseParams getEnvPhaseParams(const AdsrEnvelope env, EnvPhase phase, short envLevel) + { + // Envelope level shouldn't be negative, but just in case... + const int absEnvLevel = std::abs((int) envLevel); + + // Gather basic info for the envelope phase + int targetLevel; + int stepUnscaled; + uint stepScale; + bool bExponential; + + switch (phase) + { + // Attack phase: ramps up to the maximum volume always + case EnvPhase::Attack: + { + targetLevel = MAX_ENV_LEVEL; + stepScale = env.attackShift; + stepUnscaled = 7 - (int)env.attackStep; + bExponential = env.bAttackExp; + } break; + + // Decay phase: ramps down to the sustain level and always exponentially + case EnvPhase::Decay: + { + targetLevel = std::min(((int) env.sustainLevel + 1) * 2048, MAX_ENV_LEVEL); + stepScale = env.decayShift; + stepUnscaled = -8; + bExponential = true; + } break; + + // Sustain phase: has no target level (-1, lasts forever) and can ramp up or down + case EnvPhase::Sustain: + { + targetLevel = -1; + stepScale = env.sustainShift; + stepUnscaled = (env.bSustainDec) ? (int) env.sustainStep - 8 : 7 - (int) env.sustainStep; + bExponential = env.bSustainExp; + } break; + + // Release, off or unknown phase: fade out to zero + case EnvPhase::Release: + case EnvPhase::Off: + default: + { + targetLevel = MIN_ENV_LEVEL; + stepScale = env.releaseShift; + stepUnscaled = -8; + bExponential = env.bReleaseExp; + } break; + } + + // Scale the step accordingly and decide how many cycles an envelope step takes + int stepScaleMultiplier = 1 << std::max(0, 11 - stepScale); + + EnvPhaseParams params; + params.targetLevel = targetLevel; + params.stepCycles = 1 << std::max(0, stepScale - 11); + params.step = stepUnscaled * stepScaleMultiplier; + + if (bExponential) + { + // Adjustments based on the current envelope level when the envelope mode is 'exponential'. + // Slower fade-outs as the envelope level decreases & 4x slower fade-ins when envelope level surpasses '0x6000'. + if ((stepUnscaled > 0) && (absEnvLevel > 0x6000)) + { + params.stepCycles *= 4; + } + else if (stepUnscaled < 0) + { + params.step = (int)(((int64_t) params.step * absEnvLevel) >> 15); + } + } + + return params; + } + + // Step the ADSR envelope for the given voice + static void stepVoiceEnvelope(Voice& voice) + { + // Don't process the envelope if we must wait a few more cycles + if (voice.envWaitCycles > 0) + { + voice.envWaitCycles--; + + if (voice.envWaitCycles > 0) + return; + } + + // Step the envelope in it's current phase and compute the new envelope level + auto envParams = getEnvPhaseParams(voice.env, voice.envPhase, voice.envLevel); + int newEnvLevel = std::clamp(voice.envLevel + envParams.step, MIN_ENV_LEVEL, MAX_ENV_LEVEL); + + // Do state transitions when ramping up or down, unless we're in the 'sustain' phase (targetLevel < 0) + bool bReachedTargetLevel = false; + + if (envParams.targetLevel >= 0) + { + if (envParams.step > 0) + { + bReachedTargetLevel = (newEnvLevel >= envParams.targetLevel); + } else if (envParams.step < 0) + { + bReachedTargetLevel = (newEnvLevel <= envParams.targetLevel); + } + } + + if (bReachedTargetLevel) + { + newEnvLevel = envParams.targetLevel; + voice.envPhase = getNextEnvPhase(voice.envPhase); + voice.envWaitCycles = 0; + } + else + { + voice.envWaitCycles = envParams.stepCycles; + } + + voice.envLevel = (short)newEnvLevel; + } + + // Get a requested sample from the voice's sample buffer. + // Returns a zeroed sample if the sample buffer has not been filled or if the index is out of range. + static Sample getVoiceSample(const Voice& voice, uint index) + { + if (voice.bSamplesLoaded) + { + if (index < Voice::SAMPLE_BUFFER_SIZE) + { + return voice.samples[index]; + } + } + + return {}; + } + + // Get the current (interpolated) sample for the given voice + static Sample getInterpolatedVoiceSample(const Voice& voice) + { + // What sample and interpolation index should we use? + int curSampleIdx = (int)voice.adpcmBlockPos.fields.sampleIdx; + int gaussTableIdx = (int)(uint8)voice.adpcmBlockPos.fields.gaussIdx; + + // Get the most recent sample and previous 3 samples + auto samp1 = getVoiceSample(voice, Voice::NUM_PREV_SAMPLES + curSampleIdx - 3); + auto samp2 = getVoiceSample(voice, Voice::NUM_PREV_SAMPLES + curSampleIdx - 2); + auto samp3 = getVoiceSample(voice, Voice::NUM_PREV_SAMPLES + curSampleIdx - 1); + auto samp4 = getVoiceSample(voice, Voice::NUM_PREV_SAMPLES + curSampleIdx ); + + // Sanity check... + static_assert(-1 >> 1 == -1, "Right shift on signed types must be an arithmetic shift!"); + + // Note: MSVC: for some reason bad code is sometimes generated in release builds for the gauss factors - prevent this by use of 'volatile'. + // I'm not sure why this is occuring, it doesn't seem like there is any UB here... + int gaussIdx1 = (255 - gaussTableIdx) & 0x1FF; + int gaussIdx2 = (511 - gaussTableIdx) & 0x1FF; + int gaussIdx3 = (256 + gaussTableIdx) & 0x1FF; + int gaussIdx4 = ( gaussTableIdx) & 0x1FF; + volatile int gaussFactor1 = INTERP_GAUSS_TABLE[gaussIdx1]; + volatile int gaussFactor2 = INTERP_GAUSS_TABLE[gaussIdx2]; + volatile int gaussFactor3 = INTERP_GAUSS_TABLE[gaussIdx3]; + volatile int gaussFactor4 = INTERP_GAUSS_TABLE[gaussIdx4]; + + // According to No$PSX it shouldn't be possible for this table to cause an overflow past 16-bits. + // Hence I'm not bothering to clamp here... +#if SIMPLE_SPU_FLOAT_SPU + auto sampMix1 = samp1 * (short)gaussFactor1; + auto sampMix2 = samp2 * (short)gaussFactor2; + auto sampMix3 = samp3 * (short)gaussFactor3; + auto sampMix4 = samp4 * (short)gaussFactor4; + return sampMix1 + sampMix2 + sampMix3 + sampMix4; +#else + int sampMix1 = (gaussFactor1 * samp1.value) >> 15; + int sampMix2 = (gaussFactor2 * samp2.value) >> 15; + int sampMix3 = (gaussFactor3 * samp3.value) >> 15; + int sampMix4 = (gaussFactor4 * samp4.value) >> 15; + return (short)(sampMix1 + sampMix2 + sampMix3 + sampMix4); +#endif + } + + // Process/update a single voice and return it's output and output to be reverberated + void VirtualSpu::stepVoice(Voice& voice, StereoSample& output, StereoSample& outputToReverb) + { + // Nothing to do if the voice is switched off + if (voice.envPhase == EnvPhase::Off) + return; + + // Read and decode the next ADPCM block if it is time. + // Note that if we read in a new block then we'll have to handle the ADPCM flags at the end. + std::byte adpcmBlock[ADPCM_BLOCK_SIZE]; + bool bHandleAdpcmFlags = false; + + if (!voice.bSamplesLoaded) + { + uint samplesAddr = voice.adpcmCurAddr8 * 8; + sramRead(pRam, ramSize, samplesAddr, ADPCM_BLOCK_SIZE, adpcmBlock); + decodeAdpcmBlock(voice, adpcmBlock); + voice.bSamplesLoaded = true; + bHandleAdpcmFlags = true; + } + + // Process the ADSR envelope for the voice + stepVoiceEnvelope(voice); + + // Get the interpolated sample for the voice, attenuate by the volume envelope and voice volume, and add to the output. + // Only bother doing this however if the voice is actually turned on. + if (!voice.bDisabled) + { + auto rawSample = getInterpolatedVoiceSample(voice); + auto sampleEnvScaled = rawSample * voice.envLevel; + short realVoiceVolL = (short)std::clamp((int)voice.volume.left * 2, INT16_MIN, +INT16_MAX); // N.B: voice volume was divided by 2 + short realVoiceVolR = (short)std::clamp((int)voice.volume.right * 2, INT16_MIN, +INT16_MAX); + + auto sampleVolScaled = StereoSample + { + sampleEnvScaled * realVoiceVolL, + sampleEnvScaled * realVoiceVolR + }; + + output += sampleVolScaled; + + // Only include in the output to reverberate if reverb is enabled for the voice + if (voice.bDoReverb) + { + outputToReverb += sampleVolScaled; + } + } + + // Advance the position of the voice within the current sample block. + // Note that the original PSX SPU wouldn't allow frequencies of more than 176,400 Hz (0x4000), hence we clamp the frequency here. + // Certain pieces of music in Doom need this clamping to be done in order to sound correct. + voice.adpcmBlockPos.counter += std::min(voice.sampleRate, MAX_SAMPLE_RATE); + + // Is it time to read another ADPCM block because we have consumed the current one? + if (voice.adpcmBlockPos.fields.sampleIdx >= ADPCM_BLOCK_NUM_SAMPLES) + { + voice.adpcmBlockPos.fields.sampleIdx -= ADPCM_BLOCK_NUM_SAMPLES; + voice.adpcmCurAddr8 += ADPCM_BLOCK_SIZE / 8; + voice.bSamplesLoaded = false; + + // Time to go to the loop address? + if (voice.bRepeat) + { + voice.bRepeat = false; + voice.adpcmCurAddr8 = voice.adpcmRepeatAddr8; + } + } + + // Handle processing flags for the current ADPCM block we just read (if we read one) + if (bHandleAdpcmFlags) + { + // The ADPCM flags are in the 2nd byte of the ADPCM block + const uint8_t adpcmFlags = (uint8_t) adpcmBlock[1]; + + // Is this where we jump to restart a loop? + if (adpcmFlags & ADPCM_FLAG_LOOP_START) + { + voice.adpcmRepeatAddr8 = voice.adpcmCurAddr8; + } + + // Jump to the repeat address after this sample block is done? + if (adpcmFlags & ADPCM_FLAG_LOOP_END) + { + voice.bReachedLoopEnd = true; + voice.bRepeat = true; + + // If the repeat flag is not set then the voice will be silenced upon 'repeating' + if ((adpcmFlags & ADPCM_FLAG_REPEAT) == 0) + { + voice.envLevel = 0; + SetKeyOff(voice); + } + } + } + } + + // Process/update all voices and get 1 sample of output from them + void VirtualSpu::stepVoices(StereoSample& output, StereoSample& outputToReverb) + { + //ASSERT(pVoices || numVoices == 0); + + for (auto& voice : pVoices) + { + stepVoice(voice, output, outputToReverb); + } + } + + // Mixes sound from an external input; does nothing if there is no current external input + void VirtualSpu::mixExternalInput(const ExtInputCallback pExtCallback, void* pExtCallbackUserData, Volume extVolume, + bool bExtReverbEnabled, + StereoSample& output, StereoSample& outputToReverb) + { + if (!pExtCallback) + { + return; + } + + auto extSample = pExtCallback(pExtCallbackUserData); + auto extSampleScaled = extSample * extVolume; + output += extSampleScaled; + + if (bExtReverbEnabled) + { + outputToReverb += extSampleScaled; + } + } + + // Add the given sample to reverb input and return a sample of reverb output + static void doReverb( +#if SIMPLE_SPU_FLOAT_SPU + float* pReverbRam, uint reverbRamSampleCount, +#else + std::byte* pRam, +#endif + uint ramSize, uint reverbBaseAddr8, uint& reverbCurAddr, Volume reverbVol, + bool bReverbWriteEnable, ReverbRegs& reverbRegs, + StereoSample reverbInput, StereoSample& reverbOutput) + { + // Helper: wrap an address to be within the reverb work area and guarantee that 16-bits (or a single float, for the float SPU) can be read safely. + // If there is no reverb work area (which should never be the case) then the address '0' is returned. + // Note that for the float SPU reverb addresses are still specified in terms of the main SPU ram, so that we can use the original SPU reverb settings. + uint reverbBaseAddr = reverbBaseAddr8 * 8; + uint reverbBaseAddr2 = reverbBaseAddr / 2; + +#if SIMPLE_SPU_FLOAT_SPU + uint reverbWorkAreaSize2 = std::min((ramSize - reverbBaseAddr) / 2, reverbRamSampleCount); +#else + uint reverbWorkAreaSize2 = (ramSize - reverbBaseAddr) / 2; +#endif + + auto wrapRevAddr16 = [=](uint addr) -> uint + { + if (reverbWorkAreaSize2 > 0) + { + uint addr2 = addr / 2; + uint relativeAddr2 = (addr2 - reverbBaseAddr2) % reverbWorkAreaSize2; + +#if SIMPLE_SPU_FLOAT_SPU + // For the float SPU the reverb work area always starts at element '0' in reverb RAM + return relativeAddr2 * 2; +#else + return (reverbBaseAddr2 + relativeAddr2) * 2; +#endif + } + + return 0; + }; + + // Helpers: read and write a 16-bit sample relative to the current reverb address. + // Wraps the read or write to be within the work area for reverb. + auto revR = [=](uint addrRelative) -> Sample + { + uint addr = wrapRevAddr16(reverbCurAddr + addrRelative); + +#if SIMPLE_SPU_FLOAT_SPU + return pReverbRam[addr / 2]; +#else + ushort data = (ushort)pRam[addr] | ((ushort)pRam[addr + 1] << 8); + return (short)data; +#endif + }; + + auto revW = [=](uint addrRelative, const Sample& sample) + { + if (bReverbWriteEnable) + { + uint addr = wrapRevAddr16(reverbCurAddr + addrRelative); + +#if SIMPLE_SPU_FLOAT_SPU + pReverbRam[addr / 2] = sample.value; +#else + ushort data = (ushort)sample; + pRam[addr] = (std::byte)data; + pRam[addr + 1] = (std::byte)(data >> 8); +#endif + } + }; + + // This is based almost exactly on: https://problemkaputt.de/psx-spx.htm#spureverbformula. + // First grab all of the reverb inputs we will be dealing with and the real relative reverb addresses (need to x8 them). + uint addrLSame1 = (uint)reverbRegs.addrLSame1 * 8; + uint addrLSame2 = (uint)reverbRegs.addrLSame2 * 8; + uint addrRSame1 = (uint)reverbRegs.addrRSame1 * 8; + uint addrRSame2 = (uint)reverbRegs.addrRSame2 * 8; + uint addrLDiff1 = (uint)reverbRegs.addrLDiff1 * 8; + uint addrLDiff2 = (uint)reverbRegs.addrLDiff2 * 8; + uint addrRDiff1 = (uint)reverbRegs.addrRDiff1 * 8; + uint addrRDiff2 = (uint)reverbRegs.addrRDiff2 * 8; + uint addrLComb1 = (uint)reverbRegs.addrLComb1 * 8; + uint addrLComb2 = (uint)reverbRegs.addrLComb2 * 8; + uint addrLComb3 = (uint)reverbRegs.addrLComb3 * 8; + uint addrLComb4 = (uint)reverbRegs.addrLComb4 * 8; + uint addrRComb1 = (uint)reverbRegs.addrRComb1 * 8; + uint addrRComb2 = (uint)reverbRegs.addrRComb2 * 8; + uint addrRComb3 = (uint)reverbRegs.addrRComb3 * 8; + uint addrRComb4 = (uint)reverbRegs.addrRComb4 * 8; + uint addrLAPF1 = (uint)reverbRegs.addrLAPF1 * 8; + uint addrLAPF2 = (uint)reverbRegs.addrLAPF2 * 8; + uint addrRAPF1 = (uint)reverbRegs.addrRAPF1 * 8; + uint addrRAPF2 = (uint)reverbRegs.addrRAPF2 * 8; + uint dispAPF1 = (uint)reverbRegs.dispAPF1 * 8; + uint dispAPF2 = (uint)reverbRegs.dispAPF2 * 8; + + short volWall = reverbRegs.volWall; + short volIIR = reverbRegs.volIIR; + short volComb1 = reverbRegs.volComb1; + short volComb2 = reverbRegs.volComb2; + short volComb3 = reverbRegs.volComb3; + short volComb4 = reverbRegs.volComb4; + short volAPF1 = reverbRegs.volAPF1; + short volAPF2 = reverbRegs.volAPF2; + + // Scale the sample which is being fed into the reverb: + auto inputL = reverbInput.left * reverbRegs.volLIn; + auto inputR = reverbInput.right * reverbRegs.volRIn; + + // Same side reflection (left-to-left and right-to-right) + { + auto l1 = revR(addrLSame2); + auto r1 = revR(addrRSame2); + auto l2 = revR(addrLSame1 - 2); + auto r2 = revR(addrRSame1 - 2); + + revW(addrLSame1, (inputL + l1 * volWall - l2) * volIIR + l2); // Left to left + revW(addrRSame1, (inputR + r1 * volWall - r2) * volIIR + r2); // Right to right + } + + // Different side reflection (left-to-right and right-to-left) + { + auto l1 = revR(addrLDiff2); + auto r1 = revR(addrRDiff2); + auto l2 = revR(addrLDiff1 - 2); + auto r2 = revR(addrRDiff1 - 2); + + revW(addrLDiff1, (((inputL + (r1 * volWall)) - l2) * volIIR) + l2); // Right to left + revW(addrRDiff1, (((inputR + (l1 * volWall)) - r2) * volIIR) + r2); // Left to right + } + + // Early echo (comb filter, with input from buffer) + auto outL = Sample(); + auto outR = Sample(); + + outL = Sample(revR(addrLComb1) * volComb1 + + revR(addrLComb2) * volComb2 + + revR(addrLComb3) * volComb3 + + revR(addrLComb4) * volComb4); + + outR = Sample(revR(addrRComb1) * volComb1 + + revR(addrRComb2) * volComb2 + + revR(addrRComb3) * volComb3 + + revR(addrRComb4) * volComb4); + + // Late reverb APF1 (all pass filter 1, with input from COMB) + outL = outL - revR(addrLAPF1 - dispAPF1) * volAPF1; + revW(addrLAPF1, outL); + outL = outL * volAPF1 + revR(addrLAPF1 - dispAPF1); + + outR = outR - revR(addrRAPF1 - dispAPF1) * volAPF1; + revW(addrRAPF1, outR); + outR = outR * volAPF1 + revR(addrRAPF1 - dispAPF1); + + // Late reverb APF2 (all pass filter 2, with input from APF1) + outL = outL - revR(addrLAPF2 - dispAPF2) * volAPF2; + revW(addrLAPF2, outL); + outL = outL * volAPF2 + revR(addrLAPF2 - dispAPF2); + + outR = outR - revR(addrRAPF2 - dispAPF2) * volAPF2; + revW(addrRAPF2, outR); + outR = outR * volAPF2 + revR(addrRAPF2 - dispAPF2); + + // Move along the reverb address for the next update by 1 16-bit sample +#if SIMPLE_SPU_FLOAT_SPU + reverbCurAddr = reverbBaseAddr + wrapRevAddr16(reverbCurAddr + 2); // 'wrapRevAddr16' returns the address starting from '0' for the float SPU, need to fix up +#else + reverbCurAddr = wrapRevAddr16(reverbCurAddr + 2); +#endif + + // Scale and return the reverb output + reverbOutput = StereoSample + { + .left = outL * reverbVol.left, + .right = outR * reverbVol.right + }; + } + + // Does the final mix and attenuation of dry sound and reverb sound, and scales according to the master volume + void VirtualSpu::doMasterMix(const StereoSample& dryOutput, const StereoSample& reverbOutput, const Volume& masterVol, + StereoSample& output) + { + // Note: master volume is expected to be +/- 0x3FFF. + // Need to clamp if exceeding this and also scale by 2. + auto wetOutput = dryOutput + reverbOutput; + auto scaledMasterVol = Volume + { + (short)(std::clamp(masterVol.left, MIN_MASTER_VOLUME, MAX_MASTER_VOLUME) * 2), + (short)(std::clamp(masterVol.right, MIN_MASTER_VOLUME, MAX_MASTER_VOLUME) * 2), + }; + + output = wetOutput * scaledMasterVol; + } + + // Core initialization and teardown +#if SIMPLE_SPU_FLOAT_SPU + void VirtualSpu::Initialize(int reverbRamSampleCount) +#else + void VirtualSpu::Initialize() +#endif + { + constexpr int VOICE_COUNT = 24; + constexpr int RAM_SIZE = 1024 * 512; + + // Zero init everything by default + *this = {}; + + // Zero voices is a valid use-case, if for example you wanted to use this as a PS1 reverb DSP + pVoices.resize(VOICE_COUNT); + + // Note: pad RAM size to the nearest 16-bytes to ensure the 8-byte addressing mode of the SPU always works. + uint roundedRamSize = ((RAM_SIZE + 15) / 16) * 16; + + pRam = new std::byte[roundedRamSize]; + ramSize = roundedRamSize; + std::memset(pRam, 0, roundedRamSize); + + // For floating point SPUs allocate reverb RAM too +#if SIMPLE_SPU_FLOAT_SPU + //ASSERT(reverbRamSampleCount > 0); + pReverbRam = new float[reverbRamSampleCount]; + reverbRamSampleCount = reverbRamSampleCount; + std::memset(pReverbRam, 0, reverbRamSampleCount * sizeof(float)); +#endif + } + + void VirtualSpu::Deinitialize() + { +#if SIMPLE_SPU_FLOAT_SPU + delete[] pReverbRam; +#endif + + delete[] pRam; + *this = {}; + } + + // Start playing the given voice + StereoSample VirtualSpu::Step() + { + // Process all voices firstly and silence the output if we are not unmuted + auto output = StereoSample{}; + auto outputToReverb = StereoSample{}; + stepVoices(output, outputToReverb); + + if (!bUnmute) + { + output = {}; + outputToReverb = {}; + } + + // Mix any external input + if (bExtEnabled) + { + mixExternalInput(pExtInputCallback, pExtInputUserData, extInputVol, bExtReverbEnable, output, outputToReverb); + } + + // Do reverb every 2 cycles: PSX reverb operates at 22,050 Hz and the SPU operates at 44,100 Hz + if ((cycleCount & 0x1) == 0) + { + doReverb( +#if SIMPLE_SPU_FLOAT_SPU + pReverbRam, + reverbRamSampleCount, +#else + pRam, +#endif + ramSize, + reverbBaseAddr8, + reverbCurAddr, + reverbVol, + bReverbWriteEnable, + reverbRegs, + outputToReverb, + processedReverb); + } + + // Do the final mixing and finish up + doMasterMix(output, processedReverb, masterVol, output); + cycleCount++; + return output; + } + + // Start playing the given voice + void VirtualSpu::SetKeyOn(Voice& voice) + { + // Jump to the sample start address and flag that we need to load samples + voice.bSamplesLoaded = false; + voice.adpcmBlockPos = {}; + voice.adpcmCurAddr8 = voice.adpcmStartAddr8; + + // Initialize the envelope + voice.envPhase = EnvPhase::Attack; + voice.envLevel = 0; + voice.envWaitCycles = 0; + + // Initialize flags + voice.bReachedLoopEnd = false; + voice.bRepeat = false; + + // Zero the 3 previous samples used for interpolation and previous 2 samples used for ADPCM decoding + //static_assert(Voice::NUM_PREV_SAMPLES == 3); + voice.samples[0] = {}; + voice.samples[1] = {}; + voice.samples[2] = {}; + voice.samples[Voice::SAMPLE_BUFFER_SIZE - 2] = {}; + voice.samples[Voice::SAMPLE_BUFFER_SIZE - 1] = {}; + } + + // Puts the given voice into release mode + void VirtualSpu::SetKeyOff(Voice& voice) + { + voice.envPhase = EnvPhase::Release; + voice.envWaitCycles = 0; + } } diff --git a/Source/Audio/Spu.h b/Source/Audio/Spu.h index 18c4258..6bd298e 100644 --- a/Source/Audio/Spu.h +++ b/Source/Audio/Spu.h @@ -1,6 +1,459 @@ #pragma once +// A stripped down emulation of a PlayStation 1 SPU, and potentially most of the PS2 SPU if the voice and RAM limits are increased. +// Implements the most commonly used functionality of the SPU, and specifically all the functionality required by PlayStation Doom. +// It is completely self isolated (apart from supplied external inputs) and can safely run in a separate thread. +// Largely based on the SPU implementation of the Avocado PlayStation emulator, and follows it's approach in various places. +// +// What was removed from this SPU emulation: +// - All links to a host system, interrupts, system bus reading/writing, DMA etc. +// - Noise generation +// - Voice pitch modulation by another voice (used for LFO style effects) +// - Sweep volume mode for SPU voices + +#define SIMPLE_SPU_FLOAT_SPU 1 + namespace Silent::Audio { + constexpr int ADPCM_BLOCK_SIZE = 16; // The size in bytes of a PSX format ADPCM block + constexpr int ADPCM_BLOCK_NUM_SAMPLES = 28; // The number of samples in a PSX format ADPCM block + constexpr ushort MAX_SAMPLE_RATE = 0x4000; // The PSX cannot do sample rates over 176,400 Hz + constexpr short MIN_MASTER_VOLUME = -0x3FFF; // Minimum master volume level (divided by 2) + constexpr short MAX_MASTER_VOLUME = 0x3FFF; // Maximum master volume level (divided by 2) + constexpr short MIN_ENV_LEVEL = 0; // Minimum allowed envelope level + constexpr short MAX_ENV_LEVEL = 0x7FFF; // Maximum allowed envelope level + + // Flags read from the 2nd byte of a PSX ADPCM block. + // + // Meanings: + // LOOP_END: If set then goto the repeat address after we are finished with the current ADPCM block + // REPEAT: Only used if 'LOOP_END' is set, whether we are repeating normally or silencing the voice. + // If NOT set when reaching a sample end , then the volumne envelope is immediately silenced. + // LOOP_START: If set then save the current ADPCM address as the repeat address + constexpr uint8 ADPCM_FLAG_LOOP_END = 1 << 0; + constexpr uint8 ADPCM_FLAG_REPEAT = 1 << 1; + constexpr uint8 ADPCM_FLAG_LOOP_START = 1 << 2; + + // Holds information about where we are sampling from in a block of ADPCM samples. + union AdpcmBlockPos + { + // These are what the bits of the counter mean, starting with the least significant bits + struct + { + uint gaussIdxFrac : 4 = 0; // Lower fractional bits of the gauss index + uint gaussIdx : 8 = 0; // Gauss index: this is an index into an interpolation table for interpolating the 4 most recent samples + uint sampleIdx : 20 = 0; // Index of the current sample in the ADPCM block. Once this exceeds '28' we need to read more ADPCM blocks + } fields; + + // This is simply incremented by the pitch of the voice. + // If incremented by '0x1000' then it means we are playing the sample @ 44.1 KHz since the SPU samples at that rate and + // discarding the lower 12 bits of that number leaves us with '1', or an advancement of 1 sample per 44.1 KHz SPU sample. + uint counter = 0; + }; + + // Stores the settings for a voice ADSR envelope. + // Note: this is same bit layout that the PSX spu uses also. + struct AdsrEnvelope + { + uint sustainLevel : 4 = 0; // 0-15: At what envelope level (inclusive) do we go from the decay phase into the sustain phase. The actual envelope level is computed as follows: max((sustainLevel + 1) << 11, 0x7FFF) + uint decayShift : 4 = 0; // 0-15: Affects how long the decay portion of the envelope lasts. Lower values mean a faster decay. + + // 0-3: Affects how long the attack portion of the envelope lasts; lower values mean a faster attack. + // The actual step is computed as follows: 7 - step. + uint attackStep : 2 = 0; + + // 0-31: Affects how long the attack portion of the envelope lasts. + // Lower values mean a faster attack. + uint attackShift : 5 = 0; + uint bAttackExp : 1 = 0; // If set then attack mode is exponential rather than linear + uint releaseShift : 5 = 0; // 0-31: Affects how long the release portion of the envelope lasts. Lower values mean a faster release. + uint bReleaseExp : 1 = 0; // If set then release mode is exponential rather than linear + + // How much to step the envelope in sustain mode. + // The meaning of this depends on whether the sustain direction is 'increase' or 'decrease'. + // Increase: step = 7 - sustainStep + // Decrease: step = -8 + sustainStep + uint sustainStep : 2 = 0; + + // 0-31: Affects the scaling of the sustain envelope phase step. Lower values mean a bigger step amount. + uint sustainShift : 5 = 0; + + + uint _unused : 1 = 0; // An unused bit of the envelope + uint bSustainDec : 1 = 0; // If set then the sustain envelope is decreased over time. If NOT set then it increases. + uint bSustainExp : 1 = 0; // Whether the sustain portion of the envelope increases or decreases exponentially. If set then the change is exponential. + }; + + // Settings/params for a particular phase of the envelope. Note that due to the way PSX envelopes work in exponential mode, + // these parameters can sometimes change midway through the envelope phase depending on the current envelope level. + struct EnvPhaseParams + { + int targetLevel = 0; // What level the current envelope phase is trying to reach: -1 if not applicable + int step = 0; // The size of the envelope step + int stepCycles = 0; // How many cycles must be waited before doing an envelope step + }; + + // What phase of an envelope a voice is in + enum class EnvPhase : uint8 + { + Off, + Attack, + Decay, + Sustain, + Release + }; + + // Holds stereo volume levels. + // Note that if the volumes are negative then the wave is phase inverted. + struct Volume + { + short left; + short right; + }; + + // Convert between a 16-bit sample and floating point + constexpr float toFloatSample(short sample) + { + return (float)sample * (1.0f / 32768.0f); + } + + constexpr short toInt16Sample(float sample) + { + return (short)std::clamp(sample * 32768.0f, float(INT16_MIN), float(INT16_MAX)); + } + +#if !SIMPLE_SPU_FLOAT_SPU + // Do a saturated/clamped addition and subtraction of 16-bit sample values + short sampleAdd(short sample1, short sample2) + { + int result32 = (int)sample1 + (int)sample2; + return (short)std::clamp(result32, INT16_MIN, INT16_MAX); + } + + short sampleSub(short sample1, short sample2) + { + int result32 = (int)sample1 - (int)sample2; + return (short)std::clamp(result32, INT16_MIN, INT16_MAX); + } + + // Do volume attenuation/scaling for a sample; the volume level is a completely fractional number. + // Note that due to the way 2s complement works, +1.0 can never be expressed fully so we lose a little volume on each multiply. + // I.E the volume range multiplier is from -0x8000 to +0x7FFF and we divide by 0x8000 essentially via shifting. + short sampleAttenuate(short sample, short volume) + { + int frac32 = (int)sample * (int)volume; + return (short)(frac32 >> 15); + } +#endif + + // Convenience container for a 16-bit sample which supports sample add, subtract & attenuate operations + struct Sample + { +#if SIMPLE_SPU_FLOAT_SPU + float value = 0.0f; + + Sample() = default; + Sample(float value) : value(value) {} + Sample(short value) : value(toFloatSample(value)) {} // Convenience auto-conversion from 16-bit + + Sample(const Sample& other) = default; + Sample& operator=(const Sample& other) = default; + + // Convenience overloads for attenuating by a 16-bit volume level + Sample operator*(const short other) const + { + return value * toFloatSample(other); + } + + void operator*=(const short other) + { + value *= toFloatSample(other); + } + + Sample operator*(float other) const { return value * other; } + void operator*=(float other) { value *= other; } + Sample operator+(float other) const { return value + other; } + void operator+=(float other) { value += other; } + Sample operator-(float other) const { return value - other; } + void operator-=(float other) { value -= other; } + + operator float() const { return value; } +#else + short value = 0; + + Sample() = default; + Sample(short value) : value(value) {} + + Sample(const Sample& other) = default; + Sample& operator=(const Sample& other) = default; + + // Convenience overloads for attenuating by a float volume level + Sample operator*(float other) const + { + return toInt16Sample(toFloatSample(value) * other); + } + + void operator*=(float other) + { + value = (*this) * other; + } + + Sample operator*(short other) const { return sampleAttenuate(value, other); } + void operator*=(short other) { value = sampleAttenuate(value, other); } + Sample operator+(short other) const { return sampleAdd(value, other); } + void operator+=(short other) { value = sampleAdd(value, other); } + Sample operator-(short other) const { return sampleSub(value, other); } + void operator-=(short other) { value = sampleSub(value, other); } + + operator short() const { return value; } +#endif + }; + + // Holds a stereo sample and allows add/subtract/attenuate operations on that stereo sample + struct StereoSample + { + Sample left = Sample(); + Sample right = Sample(); + + StereoSample operator+(const StereoSample other) const + { + return StereoSample + { + .left = left + other.left, + .right = right + other.right + }; + } + + void operator+=(const StereoSample other) + { + left += other.left; + right += other.right; + } + + StereoSample operator-(const StereoSample& other) const + { + return StereoSample + { + .left = left - other.left, + .right = right - other.right + }; + } + + void operator-=(const StereoSample& other) + { + left -= other.left; + right -= other.right; + } + + StereoSample operator*(const Volume& volume) const + { + return StereoSample + { + .left = left * volume.left, + .right = right * volume.right + }; + } + + StereoSample operator*(float scale) const + { + return StereoSample + { + .left = left * scale, + .right = right * scale + }; + } + + void operator*=(const Volume& volume) + { + left *= volume.left; + right *= volume.right; + } + + void operator*=(float scale) + { + left *= scale; + right *= scale; + } + }; + + // Reverb registers: determine how reverb is processed. + // These are from the NO$PSX spec and in the same memory arrangement as the PSX. + // + // Notes: + // (1) All address offset values are in terms of 8 byte multiples; multiply by 8 to get the real offset. + // (2) For more details see the NO$PSX spec: https://problemkaputt.de/psx-spx.htm#soundprocessingunitspu + // + struct ReverbRegs + { + ushort dispAPF1 = 0; // Reverb APF Offset 1 + ushort dispAPF2 = 0; // Reverb APF Offset 2 + short volIIR = 0; // Reverb Reflection Volume 1 + short volComb1 = 0; // Reverb Comb Volume 1 + short volComb2 = 0; // Reverb Comb Volume 2 + short volComb3 = 0; // Reverb Comb Volume 3 + short volComb4 = 0; // Reverb Comb Volume 4 + short volWall = 0; // Reverb Reflection Volume 2 + short volAPF1 = 0; // Reverb APF Volume 1 + short volAPF2 = 0; // Reverb APF Volume 2 + ushort addrLSame1 = 0; // Reverb Same Side Reflection Address 1: Left + ushort addrRSame1 = 0; // Reverb Same Side Reflection Address 1: Right + ushort addrLComb1 = 0; // Reverb Comb Address 1: Left + ushort addrRComb1 = 0; // Reverb Comb Address 1: Right + ushort addrLComb2 = 0; // Reverb Comb Address 2: Left + ushort addrRComb2 = 0; // Reverb Comb Address 2: Right + ushort addrLSame2 = 0; // Reverb Same Side Reflection Address 2: Left + ushort addrRSame2 = 0; // Reverb Same Side Reflection Address 2: Right + ushort addrLDiff1 = 0; // Reverb Different Side Reflect Address 1: Left + ushort addrRDiff1 = 0; // Reverb Different Side Reflect Address 1: Right + ushort addrLComb3 = 0; // Reverb Comb Address 3: Left + ushort addrRComb3 = 0; // Reverb Comb Address 3: Right + ushort addrLComb4 = 0; // Reverb Comb Address 4: Left + ushort addrRComb4 = 0; // Reverb Comb Address 4: Right + ushort addrLDiff2 = 0; // Reverb Different Side Reflect Address 2: Left + ushort addrRDiff2 = 0; // Reverb Different Side Reflect Address 2: Right + ushort addrLAPF1 = 0; // Reverb APF Address 1: Left + ushort addrRAPF1 = 0; // Reverb APF Address 1: Right + ushort addrLAPF2 = 0; // Reverb APF Address 2: Left + ushort addrRAPF2 = 0; // Reverb APF Address 2: Right + short volLIn = 0; // Reverb Input Volume: Left + short volRIn = 0; // Reverb Input Volume: Right + }; + + // Holds all of the state for a hardware SPU voice + struct Voice + { + // How many previous decoded samples to store for an SPU voice; these are required sometimes for sample interpolation + static constexpr int NUM_PREV_SAMPLES = 3; + + // How many samples there are in the sample buffer for the voice + static constexpr int SAMPLE_BUFFER_SIZE = NUM_PREV_SAMPLES + ADPCM_BLOCK_NUM_SAMPLES; + + // Start address (in 8 byte units) of the current sound. + // The current address of the voice is set from this on the 'key on' event. + // Note: on the original PSX SPU this was a 16-bit quantity, so it could only reference up to 512 KiB of SRAM. + uint adpcmStartAddr8 = 0; + + // Current address that the voice is reading ADPCM samples from in SRAM, in 8 byte units. + // Note: on the original PSX SPU this was a 16-bit quantity, so it could only reference up to 512 KiB of SRAM. + uint adpcmCurAddr8 = 0; + + // Repeat address (in 8 byte units) of the current sound. + // If an ADPCM packet header has a 'loop start' flag set, then this field will be set. + // If an ADPCM packet header has a 'loop end' flag set, then the SPU will jump to this address. + // Note: on the original PSX SPU this was a 16-bit quantity, so it could only reference up to 512 KiB of SRAM. + uint adpcmRepeatAddr8 = 0; + + AdpcmBlockPos adpcmBlockPos = {}; // Where we are currently in the ADPCM block + + // Current pitch/sample-rate of the voice. + // A value of 0x1000 means 44,100 Hz, half that is 22,050 Hz and so on. + ushort sampleRate = 0; + + uint8 bDisabled : 1 = 0; // Mix in this voice? + uint8 bRepeat : 1 = 0; // If set then goto the repeat address next time we load samples + uint8 bReachedLoopEnd : 1 = 0; // Set when we reach an ADPCM block with 'ADPCM_FLAG_LOOP_END' set and cleared on 'key on'; tells if the sample has reached the end at least once + uint8 bSamplesLoaded : 1 = 0; // If set then the voice has loaded an ADPCM sample block + uint8 bDoReverb : 1 = 0; // If set then reverb is enabled for the voice + uint8 _unused : 3 = 0; // Unused bit flags + + EnvPhase envPhase; // Current envelope phase + AdsrEnvelope env; // The ADSR envelope to use + + // How many cycles to wait before processing the envelope. + // This is generally always '1' but can be larger for really slow envelopes. + int envWaitCycles = 0; + + // Left and right volume levels, divided by 2. + // Note: I am not supporting the 'sweep volume' mode that original PSX SPU used, when the highest bit of these volume level fields was set. + // These values are just purely fixed volume levels instead. + Volume volume = {}; + + short envLevel = 0; // Current ADSR envelope volume level + + // Previously decoded samples from the last ADPCM block (3 previous samples) and the currently decoded ADPCM block. + // At the beginning of the buffer there is 'NUM_PREV_SAMPLES' samples from the last ADPCM block, with the most recent sample last. + // Those previous samples are used for gaussian interpolation. + Sample samples[SAMPLE_BUFFER_SIZE]; + }; + + // A callback which is invoked by the SPU to provide external input. + // Can be used to mix in CD audio or anything else and run it through the reverb processing of the SPU. + // The callback takes a single piece of user data and must return 1 sound sample. + typedef StereoSample (*ExtInputCallback)(void* pUserData); + + /** @brief Virutal PSX SPU device core. */ + class VirtualSpu + { + public: + // ======= + // Fields + // ======= + + std::byte* pRam = {}; // Sound RAM used by the SPU core + uint ramSize = 0; // How big the RAM size for the SPU core +#if SIMPLE_SPU_FLOAT_SPU + float* pReverbRam = nullptr; // Holds floating point reverb samples for the extended floating point SPU + uint reverbRamSampleCount = 0; // The number of floating point samples in reverb RAM +#endif + std::vector pVoices = {}; // Each of the hardware voices for the SPU + Volume masterVol = {}; // Master volume. Note: expected to be from -0x3FFF to +0x3FFF. + Volume reverbVol = {}; // Reverb volume level + Volume extInputVol = {}; // External input volume (I'm using this for CD audio mixing) + bool bUnmute = false; // If 'true' then the output from voices is mixed into the output + bool bReverbWriteEnable = false; // Whether reverb can write output to the reverb work area + bool bExtEnabled = false; // Whether to mix input from the external input source + bool bExtReverbEnable = false; // Whether to apply reverb on the input from the external source + ExtInputCallback pExtInputCallback = nullptr; // Callback used to source external input: if null no external input is mixed with SPU voices + void* pExtInputUserData = nullptr; // User data passed to the external input callback + uint cycleCount = 0; // How many cycles has the SPU done (44,100 == 1 second of audio): each cycle is generating a 16-bit left & right audio sample + uint reverbBaseAddr8 = 0; // Start address of the reverb work area in 8 byte units; anything past this address in SPU RAM is for reverb + uint reverbCurAddr = 0; // Used for relative reads and writes to the reverb work area; continously incremented and wrapped as reverb is processed + StereoSample processedReverb = {}; // The processed reverb that is to be added into the final mix: only updated at 22,050 Hz instead of 44,100 Hz (every 2 SPU steps) + ReverbRegs reverbRegs = {}; // Registers with settings determining how reverb is processed: determines the type of reverb + + // ============= + // Constructors + // ============= + + VirtualSpu() = default; + + // ========== + // Utilities + // ========== + +#if SIMPLE_SPU_FLOAT_SPU + void Initialize(int reverbRamSampleCount = 128 * 1024); // More than big enough for any of the reverb modes in LIBSPU +#else + void Initialize(); +#endif + + void Deinitialize(); + + /** @brief Steps the SPU core. */ + StereoSample Step(); + + // Key on or off the given SPU voice + void SetKeyOn(Voice& voice); + void SetKeyOff(Voice& voice); + + private: + void stepVoice(Voice& voice, StereoSample& output, StereoSample& outputToReverb); + + void stepVoices(StereoSample& output, StereoSample& outputToReverb); + + void mixExternalInput(const ExtInputCallback pExtCallback, void* pExtCallbackUserData, Volume extVolume, + bool bExtReverbEnabled, + StereoSample& output, StereoSample& outputToReverb); + + void doMasterMix(const StereoSample& dryOutput, const StereoSample& reverbOutput, const Volume& masterVol, + StereoSample& output); + }; + // @todo Create LIBSPU compatibility layer. + //uint SpuGetReverbVoice(); + //int SpuGetKeyStatus(uint voice_bitmask); + //void SpuGetVoiceAttr(SpuVoiceAttr* attr); + //void SpuSetKey(long on_off, u_long voice_bit); + //int SpuReserveReverbWorkArea(int rev_mode); + //void SpuQuit(void); } diff --git a/Source/Game/Bodyprog/Sound/SdData.cpp b/Source/Game/Bodyprog/Sound/SdData.cpp index e6cb176..f00db4a 100644 --- a/Source/Game/Bodyprog/Sound/SdData.cpp +++ b/Source/Game/Bodyprog/Sound/SdData.cpp @@ -209,59 +209,59 @@ namespace Silent::Game s_XaItemData g_XaItemData[727] = { - { 0, 0, 0, 0, 0, 0, 0, 0 }, - { 1, 0, 0, 0, 0, 0, 362, 1 }, - { 1, 0, 0, 0, 1, 1, 295, 1 }, - { 1, 0, 0, 0, 2, 2, 282, 1 }, - { 1, 0, 0, 0, 3, 3, 247, 1 }, - { 1, 0, 0, 0, 4, 4, 244, 1 }, - { 1, 0, 0, 0, 5, 5, 244, 1 }, - { 1, 0, 0, 0, 6, 6, 231, 1 }, - { 1, 0, 0, 0, 7, 7, 209, 1 }, - { 1, 0, 0, 0, 527, 7, 209, 2 }, - { 1, 0, 0, 0, 582, 6, 205, 2 }, - { 1, 0, 0, 0, 612, 4, 199, 2 }, - { 1, 0, 0, 0, 613, 5, 196, 2 }, - { 1, 0, 0, 0, 619, 3, 177, 2 }, - { 1, 0, 0, 0, 706, 2, 173, 2 }, - { 1, 0, 0, 0, 737, 1, 164, 2 }, - { 1, 0, 0, 0, 904, 0, 145, 2 }, - { 1, 0, 0, 0, 1047, 7, 141, 3 }, - { 1, 0, 0, 0, 1059, 3, 141, 3 }, - { 1, 0, 0, 0, 1094, 6, 138, 3 }, - { 1, 0, 0, 0, 1101, 5, 138, 3 }, - { 1, 0, 0, 0, 1108, 4, 138, 3 }, - { 1, 0, 0, 0, 1138, 2, 129, 3 }, - { 1, 0, 0, 0, 1145, 1, 125, 3 }, - { 1, 0, 0, 0, 1264, 0, 122, 3 }, - { 1, 0, 0, 0, 1399, 7, 116, 4 }, - { 1, 0, 0, 0, 1411, 3, 113, 4 }, - { 1, 0, 0, 0, 1438, 6, 106, 4 }, - { 1, 0, 0, 0, 1445, 5, 100, 4 }, - { 1, 0, 0, 0, 1452, 4, 100, 4 }, - { 1, 0, 0, 0, 1457, 1, 97, 4 }, - { 1, 0, 0, 0, 1458, 2, 93, 4 }, - { 1, 0, 0, 0, 1568, 0, 90, 4 }, - { 1, 0, 0, 0, 1687, 7, 90, 5 }, - { 1, 0, 0, 0, 1690, 2, 90, 5 }, - { 1, 0, 0, 0, 1691, 3, 90, 5 }, - { 1, 0, 0, 0, 1693, 5, 87, 5 }, - { 1, 0, 0, 0, 1697, 1, 77, 5 }, - { 1, 0, 0, 0, 1700, 4, 77, 5 }, - { 1, 0, 0, 0, 1702, 6, 74, 5 }, - { 1, 0, 0, 0, 1792, 0, 71, 5 }, - { 1, 0, 0, 0, 1886, 6, 68, 6 }, - { 1, 0, 0, 0, 1889, 1, 65, 6 }, - { 1, 0, 0, 0, 1892, 4, 58, 6 }, - { 1, 0, 0, 0, 1909, 5, 58, 6 }, - { 1, 0, 0, 0, 1911, 7, 52, 6 }, - { 1, 0, 0, 0, 1914, 2, 52, 6 }, - { 1, 0, 0, 0, 1915, 3, 52, 6 }, - { 1, 0, 0, 0, 1968, 0, 52, 6 }, - { 1, 0, 0, 0, 2036, 4, 45, 7 }, - { 1, 0, 0, 0, 2039, 7, 45, 7 }, - { 1, 0, 0, 0, 2042, 2, 45, 7 }, - { 1, 0, 0, 0, 2043, 3, 36, 7 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0, 0, 362, 1 }, + { 1, 0, 0, 0, 1, 1, 295, 1 }, + { 1, 0, 0, 0, 2, 2, 282, 1 }, + { 1, 0, 0, 0, 3, 3, 247, 1 }, + { 1, 0, 0, 0, 4, 4, 244, 1 }, + { 1, 0, 0, 0, 5, 5, 244, 1 }, + { 1, 0, 0, 0, 6, 6, 231, 1 }, + { 1, 0, 0, 0, 7, 7, 209, 1 }, + { 1, 0, 0, 0, 527, 7, 209, 2 }, + { 1, 0, 0, 0, 582, 6, 205, 2 }, + { 1, 0, 0, 0, 612, 4, 199, 2 }, + { 1, 0, 0, 0, 613, 5, 196, 2 }, + { 1, 0, 0, 0, 619, 3, 177, 2 }, + { 1, 0, 0, 0, 706, 2, 173, 2 }, + { 1, 0, 0, 0, 737, 1, 164, 2 }, + { 1, 0, 0, 0, 904, 0, 145, 2 }, + { 1, 0, 0, 0, 1047, 7, 141, 3 }, + { 1, 0, 0, 0, 1059, 3, 141, 3 }, + { 1, 0, 0, 0, 1094, 6, 138, 3 }, + { 1, 0, 0, 0, 1101, 5, 138, 3 }, + { 1, 0, 0, 0, 1108, 4, 138, 3 }, + { 1, 0, 0, 0, 1138, 2, 129, 3 }, + { 1, 0, 0, 0, 1145, 1, 125, 3 }, + { 1, 0, 0, 0, 1264, 0, 122, 3 }, + { 1, 0, 0, 0, 1399, 7, 116, 4 }, + { 1, 0, 0, 0, 1411, 3, 113, 4 }, + { 1, 0, 0, 0, 1438, 6, 106, 4 }, + { 1, 0, 0, 0, 1445, 5, 100, 4 }, + { 1, 0, 0, 0, 1452, 4, 100, 4 }, + { 1, 0, 0, 0, 1457, 1, 97, 4 }, + { 1, 0, 0, 0, 1458, 2, 93, 4 }, + { 1, 0, 0, 0, 1568, 0, 90, 4 }, + { 1, 0, 0, 0, 1687, 7, 90, 5 }, + { 1, 0, 0, 0, 1690, 2, 90, 5 }, + { 1, 0, 0, 0, 1691, 3, 90, 5 }, + { 1, 0, 0, 0, 1693, 5, 87, 5 }, + { 1, 0, 0, 0, 1697, 1, 77, 5 }, + { 1, 0, 0, 0, 1700, 4, 77, 5 }, + { 1, 0, 0, 0, 1702, 6, 74, 5 }, + { 1, 0, 0, 0, 1792, 0, 71, 5 }, + { 1, 0, 0, 0, 1886, 6, 68, 6 }, + { 1, 0, 0, 0, 1889, 1, 65, 6 }, + { 1, 0, 0, 0, 1892, 4, 58, 6 }, + { 1, 0, 0, 0, 1909, 5, 58, 6 }, + { 1, 0, 0, 0, 1911, 7, 52, 6 }, + { 1, 0, 0, 0, 1914, 2, 52, 6 }, + { 1, 0, 0, 0, 1915, 3, 52, 6 }, + { 1, 0, 0, 0, 1968, 0, 52, 6 }, + { 1, 0, 0, 0, 2036, 4, 45, 7 }, + { 1, 0, 0, 0, 2039, 7, 45, 7 }, + { 1, 0, 0, 0, 2042, 2, 45, 7 }, + { 1, 0, 0, 0, 2043, 3, 36, 7 }, { 2, 0, 0, 0, 0, 0, 1773, 10 }, { 2, 0, 0, 0, 1, 1, 1479, 10 }, { 2, 0, 0, 0, 2, 2, 465, 10 }, @@ -295,78 +295,78 @@ namespace Silent::Game { 2, 0, 0, 0, 2140, 4, 61, 15 }, { 2, 0, 0, 0, 2151, 7, 58, 15 }, { 2, 0, 0, 0, 2157, 5, 52, 15 }, - { 3, 0, 0, 0, 0, 0, 365, 1 }, - { 3, 0, 0, 0, 1, 1, 349, 1 }, - { 3, 0, 0, 0, 2, 2, 333, 1 }, - { 3, 0, 0, 0, 3, 3, 282, 1 }, - { 3, 0, 0, 0, 4, 4, 276, 1 }, - { 3, 0, 0, 0, 5, 5, 276, 1 }, - { 3, 0, 0, 0, 6, 6, 273, 1 }, - { 3, 0, 0, 0, 7, 7, 266, 1 }, - { 3, 0, 0, 0, 671, 7, 266, 2 }, - { 3, 0, 0, 0, 686, 6, 266, 2 }, - { 3, 0, 0, 0, 692, 4, 247, 2 }, - { 3, 0, 0, 0, 693, 5, 237, 2 }, - { 3, 0, 0, 0, 707, 3, 234, 2 }, - { 3, 0, 0, 0, 834, 2, 231, 2 }, - { 3, 0, 0, 0, 873, 1, 228, 2 }, - { 3, 0, 0, 0, 912, 0, 228, 2 }, - { 3, 0, 0, 0, 1285, 5, 228, 3 }, - { 3, 0, 0, 0, 1291, 3, 228, 3 }, - { 3, 0, 0, 0, 1308, 4, 225, 3 }, - { 3, 0, 0, 0, 1335, 7, 225, 3 }, - { 3, 0, 0, 0, 1350, 6, 225, 3 }, - { 3, 0, 0, 0, 1410, 2, 215, 3 }, - { 3, 0, 0, 0, 1441, 1, 212, 3 }, - { 3, 0, 0, 0, 1480, 0, 212, 3 }, - { 3, 0, 0, 0, 1853, 5, 212, 4 }, - { 3, 0, 0, 0, 1859, 3, 209, 4 }, - { 3, 0, 0, 0, 1868, 4, 209, 4 }, - { 3, 0, 0, 0, 1895, 7, 209, 4 }, - { 3, 0, 0, 0, 1910, 6, 209, 4 }, - { 3, 0, 0, 0, 1946, 2, 209, 4 }, - { 3, 0, 0, 0, 1969, 1, 205, 4 }, - { 3, 0, 0, 0, 2008, 0, 205, 4 }, - { 3, 0, 0, 0, 2379, 3, 202, 5 }, - { 3, 0, 0, 0, 2381, 5, 199, 5 }, - { 3, 0, 0, 0, 2388, 4, 196, 5 }, - { 3, 0, 0, 0, 2415, 7, 196, 5 }, - { 3, 0, 0, 0, 2430, 6, 193, 5 }, - { 3, 0, 0, 0, 2466, 2, 189, 5 }, - { 3, 0, 0, 0, 2481, 1, 189, 5 }, - { 3, 0, 0, 0, 2520, 0, 186, 5 }, - { 3, 0, 0, 0, 2876, 4, 183, 6 }, - { 3, 0, 0, 0, 2877, 5, 183, 6 }, - { 3, 0, 0, 0, 2883, 3, 180, 6 }, - { 3, 0, 0, 0, 2903, 7, 180, 6 }, - { 3, 0, 0, 0, 2910, 6, 180, 6 }, - { 3, 0, 0, 0, 2938, 2, 180, 6 }, - { 3, 0, 0, 0, 2953, 1, 177, 6 }, - { 3, 0, 0, 0, 2984, 0, 177, 6 }, - { 3, 0, 0, 0, 3331, 3, 177, 7 }, - { 3, 0, 0, 0, 3332, 4, 177, 7 }, - { 3, 0, 0, 0, 3333, 5, 177, 7 }, - { 3, 0, 0, 0, 3351, 7, 177, 7 }, - { 3, 0, 0, 0, 3358, 6, 173, 7 }, - { 3, 0, 0, 0, 3386, 2, 167, 7 }, - { 3, 0, 0, 0, 3393, 1, 164, 7 }, - { 3, 0, 0, 0, 3424, 0, 164, 7 }, - { 3, 0, 0, 0, 3771, 3, 161, 8 }, - { 3, 0, 0, 0, 3772, 4, 161, 8 }, - { 3, 0, 0, 0, 3773, 5, 161, 8 }, - { 3, 0, 0, 0, 3790, 6, 157, 8 }, - { 3, 0, 0, 0, 3791, 7, 157, 8 }, - { 3, 0, 0, 0, 3801, 1, 157, 8 }, - { 3, 0, 0, 0, 3802, 2, 157, 8 }, - { 3, 0, 0, 0, 3832, 0, 157, 8 }, - { 3, 0, 0, 0, 4171, 3, 154, 9 }, - { 3, 0, 0, 0, 4172, 4, 154, 9 }, - { 3, 0, 0, 0, 4173, 5, 154, 9 }, - { 3, 0, 0, 0, 4182, 6, 154, 9 }, - { 3, 0, 0, 0, 4183, 7, 154, 9 }, - { 3, 0, 0, 0, 4193, 1, 154, 9 }, - { 3, 0, 0, 0, 4194, 2, 151, 9 }, - { 3, 0, 0, 0, 4224, 0, 151, 9 }, + { 3, 0, 0, 0, 0, 0, 365, 1 }, + { 3, 0, 0, 0, 1, 1, 349, 1 }, + { 3, 0, 0, 0, 2, 2, 333, 1 }, + { 3, 0, 0, 0, 3, 3, 282, 1 }, + { 3, 0, 0, 0, 4, 4, 276, 1 }, + { 3, 0, 0, 0, 5, 5, 276, 1 }, + { 3, 0, 0, 0, 6, 6, 273, 1 }, + { 3, 0, 0, 0, 7, 7, 266, 1 }, + { 3, 0, 0, 0, 671, 7, 266, 2 }, + { 3, 0, 0, 0, 686, 6, 266, 2 }, + { 3, 0, 0, 0, 692, 4, 247, 2 }, + { 3, 0, 0, 0, 693, 5, 237, 2 }, + { 3, 0, 0, 0, 707, 3, 234, 2 }, + { 3, 0, 0, 0, 834, 2, 231, 2 }, + { 3, 0, 0, 0, 873, 1, 228, 2 }, + { 3, 0, 0, 0, 912, 0, 228, 2 }, + { 3, 0, 0, 0, 1285, 5, 228, 3 }, + { 3, 0, 0, 0, 1291, 3, 228, 3 }, + { 3, 0, 0, 0, 1308, 4, 225, 3 }, + { 3, 0, 0, 0, 1335, 7, 225, 3 }, + { 3, 0, 0, 0, 1350, 6, 225, 3 }, + { 3, 0, 0, 0, 1410, 2, 215, 3 }, + { 3, 0, 0, 0, 1441, 1, 212, 3 }, + { 3, 0, 0, 0, 1480, 0, 212, 3 }, + { 3, 0, 0, 0, 1853, 5, 212, 4 }, + { 3, 0, 0, 0, 1859, 3, 209, 4 }, + { 3, 0, 0, 0, 1868, 4, 209, 4 }, + { 3, 0, 0, 0, 1895, 7, 209, 4 }, + { 3, 0, 0, 0, 1910, 6, 209, 4 }, + { 3, 0, 0, 0, 1946, 2, 209, 4 }, + { 3, 0, 0, 0, 1969, 1, 205, 4 }, + { 3, 0, 0, 0, 2008, 0, 205, 4 }, + { 3, 0, 0, 0, 2379, 3, 202, 5 }, + { 3, 0, 0, 0, 2381, 5, 199, 5 }, + { 3, 0, 0, 0, 2388, 4, 196, 5 }, + { 3, 0, 0, 0, 2415, 7, 196, 5 }, + { 3, 0, 0, 0, 2430, 6, 193, 5 }, + { 3, 0, 0, 0, 2466, 2, 189, 5 }, + { 3, 0, 0, 0, 2481, 1, 189, 5 }, + { 3, 0, 0, 0, 2520, 0, 186, 5 }, + { 3, 0, 0, 0, 2876, 4, 183, 6 }, + { 3, 0, 0, 0, 2877, 5, 183, 6 }, + { 3, 0, 0, 0, 2883, 3, 180, 6 }, + { 3, 0, 0, 0, 2903, 7, 180, 6 }, + { 3, 0, 0, 0, 2910, 6, 180, 6 }, + { 3, 0, 0, 0, 2938, 2, 180, 6 }, + { 3, 0, 0, 0, 2953, 1, 177, 6 }, + { 3, 0, 0, 0, 2984, 0, 177, 6 }, + { 3, 0, 0, 0, 3331, 3, 177, 7 }, + { 3, 0, 0, 0, 3332, 4, 177, 7 }, + { 3, 0, 0, 0, 3333, 5, 177, 7 }, + { 3, 0, 0, 0, 3351, 7, 177, 7 }, + { 3, 0, 0, 0, 3358, 6, 173, 7 }, + { 3, 0, 0, 0, 3386, 2, 167, 7 }, + { 3, 0, 0, 0, 3393, 1, 164, 7 }, + { 3, 0, 0, 0, 3424, 0, 164, 7 }, + { 3, 0, 0, 0, 3771, 3, 161, 8 }, + { 3, 0, 0, 0, 3772, 4, 161, 8 }, + { 3, 0, 0, 0, 3773, 5, 161, 8 }, + { 3, 0, 0, 0, 3790, 6, 157, 8 }, + { 3, 0, 0, 0, 3791, 7, 157, 8 }, + { 3, 0, 0, 0, 3801, 1, 157, 8 }, + { 3, 0, 0, 0, 3802, 2, 157, 8 }, + { 3, 0, 0, 0, 3832, 0, 157, 8 }, + { 3, 0, 0, 0, 4171, 3, 154, 9 }, + { 3, 0, 0, 0, 4172, 4, 154, 9 }, + { 3, 0, 0, 0, 4173, 5, 154, 9 }, + { 3, 0, 0, 0, 4182, 6, 154, 9 }, + { 3, 0, 0, 0, 4183, 7, 154, 9 }, + { 3, 0, 0, 0, 4193, 1, 154, 9 }, + { 3, 0, 0, 0, 4194, 2, 151, 9 }, + { 3, 0, 0, 0, 4224, 0, 151, 9 }, { 3, 0, 0, 0, 4555, 3, 151, 10 }, { 3, 0, 0, 0, 4556, 4, 148, 10 }, { 3, 0, 0, 0, 4557, 5, 145, 10 }, @@ -585,78 +585,78 @@ namespace Silent::Game { 4, 0, 0, 0, 6395, 3, 42, 24 }, { 4, 0, 0, 0, 6396, 4, 39, 24 }, { 4, 0, 0, 0, 6492, 4, 26, 25 }, - { 5, 0, 0, 0, 0, 0, 311, 1 }, - { 5, 0, 0, 0, 1, 1, 292, 1 }, - { 5, 0, 0, 0, 2, 2, 279, 1 }, - { 5, 0, 0, 0, 3, 3, 260, 1 }, - { 5, 0, 0, 0, 4, 4, 257, 1 }, - { 5, 0, 0, 0, 5, 5, 241, 1 }, - { 5, 0, 0, 0, 6, 6, 241, 1 }, - { 5, 0, 0, 0, 7, 7, 237, 1 }, - { 5, 0, 0, 0, 599, 7, 237, 2 }, - { 5, 0, 0, 0, 605, 5, 231, 2 }, - { 5, 0, 0, 0, 606, 6, 225, 2 }, - { 5, 0, 0, 0, 644, 4, 218, 2 }, - { 5, 0, 0, 0, 651, 3, 212, 2 }, - { 5, 0, 0, 0, 698, 2, 205, 2 }, - { 5, 0, 0, 0, 729, 1, 202, 2 }, - { 5, 0, 0, 0, 776, 0, 202, 2 }, - { 5, 0, 0, 0, 1166, 6, 199, 3 }, - { 5, 0, 0, 0, 1179, 3, 199, 3 }, - { 5, 0, 0, 0, 1181, 5, 196, 3 }, - { 5, 0, 0, 0, 1188, 4, 189, 3 }, - { 5, 0, 0, 0, 1191, 7, 186, 3 }, - { 5, 0, 0, 0, 1210, 2, 186, 3 }, - { 5, 0, 0, 0, 1233, 1, 186, 3 }, - { 5, 0, 0, 0, 1280, 0, 183, 3 }, - { 5, 0, 0, 0, 1655, 7, 183, 4 }, - { 5, 0, 0, 0, 1660, 4, 183, 4 }, - { 5, 0, 0, 0, 1662, 6, 177, 4 }, - { 5, 0, 0, 0, 1669, 5, 177, 4 }, - { 5, 0, 0, 0, 1674, 2, 173, 4 }, - { 5, 0, 0, 0, 1675, 3, 173, 4 }, - { 5, 0, 0, 0, 1697, 1, 173, 4 }, - { 5, 0, 0, 0, 1736, 0, 173, 4 }, - { 5, 0, 0, 0, 2102, 6, 170, 5 }, - { 5, 0, 0, 0, 2106, 2, 167, 5 }, - { 5, 0, 0, 0, 2107, 3, 167, 5 }, - { 5, 0, 0, 0, 2109, 5, 161, 5 }, - { 5, 0, 0, 0, 2111, 7, 154, 5 }, - { 5, 0, 0, 0, 2116, 4, 154, 5 }, - { 5, 0, 0, 0, 2129, 1, 148, 5 }, - { 5, 0, 0, 0, 2168, 0, 148, 5 }, - { 5, 0, 0, 0, 2495, 7, 145, 6 }, - { 5, 0, 0, 0, 2497, 1, 141, 6 }, - { 5, 0, 0, 0, 2500, 4, 132, 6 }, - { 5, 0, 0, 0, 2509, 5, 132, 6 }, - { 5, 0, 0, 0, 2522, 2, 125, 6 }, - { 5, 0, 0, 0, 2523, 3, 122, 6 }, - { 5, 0, 0, 0, 2526, 6, 119, 6 }, - { 5, 0, 0, 0, 2536, 0, 119, 6 }, - { 5, 0, 0, 0, 2822, 6, 119, 7 }, - { 5, 0, 0, 0, 2827, 3, 119, 7 }, - { 5, 0, 0, 0, 2828, 4, 116, 7 }, - { 5, 0, 0, 0, 2832, 0, 116, 7 }, - { 5, 0, 0, 0, 2834, 2, 116, 7 }, - { 5, 0, 0, 0, 2837, 5, 113, 7 }, - { 5, 0, 0, 0, 2849, 1, 103, 7 }, - { 5, 0, 0, 0, 2855, 7, 100, 7 }, - { 5, 0, 0, 0, 3103, 7, 100, 8 }, - { 5, 0, 0, 0, 3105, 1, 100, 8 }, - { 5, 0, 0, 0, 3116, 4, 97, 8 }, - { 5, 0, 0, 0, 3117, 5, 93, 8 }, - { 5, 0, 0, 0, 3118, 6, 93, 8 }, - { 5, 0, 0, 0, 3120, 0, 93, 8 }, - { 5, 0, 0, 0, 3122, 2, 90, 8 }, - { 5, 0, 0, 0, 3123, 3, 90, 8 }, - { 5, 0, 0, 0, 3346, 2, 87, 9 }, - { 5, 0, 0, 0, 3347, 3, 87, 9 }, - { 5, 0, 0, 0, 3349, 5, 87, 9 }, - { 5, 0, 0, 0, 3350, 6, 87, 9 }, - { 5, 0, 0, 0, 3351, 7, 84, 9 }, - { 5, 0, 0, 0, 3352, 0, 84, 9 }, - { 5, 0, 0, 0, 3353, 1, 81, 9 }, - { 5, 0, 0, 0, 3356, 4, 81, 9 }, + { 5, 0, 0, 0, 0, 0, 311, 1 }, + { 5, 0, 0, 0, 1, 1, 292, 1 }, + { 5, 0, 0, 0, 2, 2, 279, 1 }, + { 5, 0, 0, 0, 3, 3, 260, 1 }, + { 5, 0, 0, 0, 4, 4, 257, 1 }, + { 5, 0, 0, 0, 5, 5, 241, 1 }, + { 5, 0, 0, 0, 6, 6, 241, 1 }, + { 5, 0, 0, 0, 7, 7, 237, 1 }, + { 5, 0, 0, 0, 599, 7, 237, 2 }, + { 5, 0, 0, 0, 605, 5, 231, 2 }, + { 5, 0, 0, 0, 606, 6, 225, 2 }, + { 5, 0, 0, 0, 644, 4, 218, 2 }, + { 5, 0, 0, 0, 651, 3, 212, 2 }, + { 5, 0, 0, 0, 698, 2, 205, 2 }, + { 5, 0, 0, 0, 729, 1, 202, 2 }, + { 5, 0, 0, 0, 776, 0, 202, 2 }, + { 5, 0, 0, 0, 1166, 6, 199, 3 }, + { 5, 0, 0, 0, 1179, 3, 199, 3 }, + { 5, 0, 0, 0, 1181, 5, 196, 3 }, + { 5, 0, 0, 0, 1188, 4, 189, 3 }, + { 5, 0, 0, 0, 1191, 7, 186, 3 }, + { 5, 0, 0, 0, 1210, 2, 186, 3 }, + { 5, 0, 0, 0, 1233, 1, 186, 3 }, + { 5, 0, 0, 0, 1280, 0, 183, 3 }, + { 5, 0, 0, 0, 1655, 7, 183, 4 }, + { 5, 0, 0, 0, 1660, 4, 183, 4 }, + { 5, 0, 0, 0, 1662, 6, 177, 4 }, + { 5, 0, 0, 0, 1669, 5, 177, 4 }, + { 5, 0, 0, 0, 1674, 2, 173, 4 }, + { 5, 0, 0, 0, 1675, 3, 173, 4 }, + { 5, 0, 0, 0, 1697, 1, 173, 4 }, + { 5, 0, 0, 0, 1736, 0, 173, 4 }, + { 5, 0, 0, 0, 2102, 6, 170, 5 }, + { 5, 0, 0, 0, 2106, 2, 167, 5 }, + { 5, 0, 0, 0, 2107, 3, 167, 5 }, + { 5, 0, 0, 0, 2109, 5, 161, 5 }, + { 5, 0, 0, 0, 2111, 7, 154, 5 }, + { 5, 0, 0, 0, 2116, 4, 154, 5 }, + { 5, 0, 0, 0, 2129, 1, 148, 5 }, + { 5, 0, 0, 0, 2168, 0, 148, 5 }, + { 5, 0, 0, 0, 2495, 7, 145, 6 }, + { 5, 0, 0, 0, 2497, 1, 141, 6 }, + { 5, 0, 0, 0, 2500, 4, 132, 6 }, + { 5, 0, 0, 0, 2509, 5, 132, 6 }, + { 5, 0, 0, 0, 2522, 2, 125, 6 }, + { 5, 0, 0, 0, 2523, 3, 122, 6 }, + { 5, 0, 0, 0, 2526, 6, 119, 6 }, + { 5, 0, 0, 0, 2536, 0, 119, 6 }, + { 5, 0, 0, 0, 2822, 6, 119, 7 }, + { 5, 0, 0, 0, 2827, 3, 119, 7 }, + { 5, 0, 0, 0, 2828, 4, 116, 7 }, + { 5, 0, 0, 0, 2832, 0, 116, 7 }, + { 5, 0, 0, 0, 2834, 2, 116, 7 }, + { 5, 0, 0, 0, 2837, 5, 113, 7 }, + { 5, 0, 0, 0, 2849, 1, 103, 7 }, + { 5, 0, 0, 0, 2855, 7, 100, 7 }, + { 5, 0, 0, 0, 3103, 7, 100, 8 }, + { 5, 0, 0, 0, 3105, 1, 100, 8 }, + { 5, 0, 0, 0, 3116, 4, 97, 8 }, + { 5, 0, 0, 0, 3117, 5, 93, 8 }, + { 5, 0, 0, 0, 3118, 6, 93, 8 }, + { 5, 0, 0, 0, 3120, 0, 93, 8 }, + { 5, 0, 0, 0, 3122, 2, 90, 8 }, + { 5, 0, 0, 0, 3123, 3, 90, 8 }, + { 5, 0, 0, 0, 3346, 2, 87, 9 }, + { 5, 0, 0, 0, 3347, 3, 87, 9 }, + { 5, 0, 0, 0, 3349, 5, 87, 9 }, + { 5, 0, 0, 0, 3350, 6, 87, 9 }, + { 5, 0, 0, 0, 3351, 7, 84, 9 }, + { 5, 0, 0, 0, 3352, 0, 84, 9 }, + { 5, 0, 0, 0, 3353, 1, 81, 9 }, + { 5, 0, 0, 0, 3356, 4, 81, 9 }, { 5, 0, 0, 0, 3553, 1, 74, 10 }, { 5, 0, 0, 0, 3556, 4, 71, 10 }, { 5, 0, 0, 0, 3559, 7, 71, 10 }, @@ -771,40 +771,40 @@ namespace Silent::Game { 6, 0, 0, 0, 3958, 6, 42, 21 }, { 6, 0, 0, 0, 3959, 7, 33, 21 }, { 6, 0, 0, 0, 3963, 3, 20, 21 }, - { 7, 0, 0, 0, 0, 0, 10404, 1 }, - { 7, 0, 0, 0, 1, 1, 2365, 1 }, - { 7, 0, 0, 0, 2, 2, 2100, 1 }, - { 7, 0, 0, 0, 3, 3, 938, 1 }, - { 7, 0, 0, 0, 4, 4, 554, 1 }, - { 7, 0, 0, 0, 5, 5, 410, 1 }, - { 7, 0, 0, 0, 6, 6, 369, 1 }, - { 7, 0, 0, 0, 7, 7, 359, 1 }, - { 7, 0, 0, 0, 903, 7, 295, 2 }, - { 7, 0, 0, 0, 926, 6, 295, 2 }, - { 7, 0, 0, 0, 1029, 5, 253, 2 }, - { 7, 0, 0, 0, 1388, 4, 212, 2 }, - { 7, 0, 0, 0, 1639, 7, 209, 3 }, - { 7, 0, 0, 0, 1661, 5, 205, 3 }, - { 7, 0, 0, 0, 1662, 6, 202, 3 }, - { 7, 0, 0, 0, 1916, 4, 180, 3 }, - { 7, 0, 0, 0, 2159, 7, 173, 4 }, - { 7, 0, 0, 0, 2166, 6, 170, 4 }, - { 7, 0, 0, 0, 2173, 5, 148, 4 }, - { 7, 0, 0, 0, 2347, 3, 145, 2 }, - { 7, 0, 0, 0, 2364, 4, 141, 4 }, - { 7, 0, 0, 0, 2541, 5, 141, 5 }, - { 7, 0, 0, 0, 2590, 6, 132, 5 }, - { 7, 0, 0, 0, 2591, 7, 132, 5 }, - { 7, 0, 0, 0, 2707, 3, 129, 3 }, - { 7, 0, 0, 0, 2716, 4, 116, 5 }, - { 7, 0, 0, 0, 2893, 5, 100, 6 }, - { 7, 0, 0, 0, 2918, 6, 93, 6 }, - { 7, 0, 0, 0, 2919, 7, 77, 6 }, - { 7, 0, 0, 0, 3004, 4, 71, 6 }, - { 7, 0, 0, 0, 3027, 3, 65, 4 }, - { 7, 0, 0, 0, 3111, 7, 55, 7 }, - { 7, 0, 0, 0, 3141, 5, 55, 7 }, - { 7, 0, 0, 0, 3150, 6, 45, 7 }, + { 7, 0, 0, 0, 0, 0, 10404, 1 }, + { 7, 0, 0, 0, 1, 1, 2365, 1 }, + { 7, 0, 0, 0, 2, 2, 2100, 1 }, + { 7, 0, 0, 0, 3, 3, 938, 1 }, + { 7, 0, 0, 0, 4, 4, 554, 1 }, + { 7, 0, 0, 0, 5, 5, 410, 1 }, + { 7, 0, 0, 0, 6, 6, 369, 1 }, + { 7, 0, 0, 0, 7, 7, 359, 1 }, + { 7, 0, 0, 0, 903, 7, 295, 2 }, + { 7, 0, 0, 0, 926, 6, 295, 2 }, + { 7, 0, 0, 0, 1029, 5, 253, 2 }, + { 7, 0, 0, 0, 1388, 4, 212, 2 }, + { 7, 0, 0, 0, 1639, 7, 209, 3 }, + { 7, 0, 0, 0, 1661, 5, 205, 3 }, + { 7, 0, 0, 0, 1662, 6, 202, 3 }, + { 7, 0, 0, 0, 1916, 4, 180, 3 }, + { 7, 0, 0, 0, 2159, 7, 173, 4 }, + { 7, 0, 0, 0, 2166, 6, 170, 4 }, + { 7, 0, 0, 0, 2173, 5, 148, 4 }, + { 7, 0, 0, 0, 2347, 3, 145, 2 }, + { 7, 0, 0, 0, 2364, 4, 141, 4 }, + { 7, 0, 0, 0, 2541, 5, 141, 5 }, + { 7, 0, 0, 0, 2590, 6, 132, 5 }, + { 7, 0, 0, 0, 2591, 7, 132, 5 }, + { 7, 0, 0, 0, 2707, 3, 129, 3 }, + { 7, 0, 0, 0, 2716, 4, 116, 5 }, + { 7, 0, 0, 0, 2893, 5, 100, 6 }, + { 7, 0, 0, 0, 2918, 6, 93, 6 }, + { 7, 0, 0, 0, 2919, 7, 77, 6 }, + { 7, 0, 0, 0, 3004, 4, 71, 6 }, + { 7, 0, 0, 0, 3027, 3, 65, 4 }, + { 7, 0, 0, 0, 3111, 7, 55, 7 }, + { 7, 0, 0, 0, 3141, 5, 55, 7 }, + { 7, 0, 0, 0, 3150, 6, 45, 7 }, { 8, 0, 0, 0, 0, 0, 1770, 10 }, { 8, 0, 0, 0, 1, 1, 925, 10 }, { 8, 0, 0, 0, 2, 2, 797, 10 }, @@ -932,10 +932,10 @@ namespace Silent::Game { 8, 0, 0, 0, 10275, 3, 45, 26 }, { 8, 0, 0, 0, 10292, 4, 26, 26 }, { 8, 0, 0, 0, 10297, 1, 26, 25 }, - { 9, 0, 0, 0, 0, 0, 11514, 1 }, - { 9, 0, 0, 0, 1, 1, 10231, 1 }, - { 9, 0, 0, 0, 2, 2, 9101, 1 }, - { 9, 0, 0, 0, 3, 3, 9085, 1 } + { 9, 0, 0, 0, 0, 0, 11514, 1 }, + { 9, 0, 0, 0, 1, 1, 10231, 1 }, + { 9, 0, 0, 0, 2, 2, 9101, 1 }, + { 9, 0, 0, 0, 3, 3, 9085, 1 } }; s_VabInfo g_Vab_InfoTable[420] = From f4c2144bbb052b71152f11d6ab7332a56b384eee Mon Sep 17 00:00:00 2001 From: Sezz Date: Mon, 30 Mar 2026 22:53:20 +1100 Subject: [PATCH 2/6] Sync sound system deltas --- Source/Game/Bodyprog/LibSd/SmfIo.cpp | 238 ++++++++++++----------- Source/Game/Bodyprog/Sound/SdCall.cpp | 92 ++++----- Source/Game/Bodyprog/Sound/SoundSystem.h | 39 ++-- Source/Game/Bodyprog/Text/TextDraw.h | 2 +- 4 files changed, 195 insertions(+), 176 deletions(-) diff --git a/Source/Game/Bodyprog/LibSd/SmfIo.cpp b/Source/Game/Bodyprog/LibSd/SmfIo.cpp index bd71498..5931111 100644 --- a/Source/Game/Bodyprog/LibSd/SmfIo.cpp +++ b/Source/Game/Bodyprog/LibSd/SmfIo.cpp @@ -901,14 +901,14 @@ namespace Silent::Game { u16 tone; s32 sp28; - u16 sp30; - SD_VAB_H* sp38; - SD_VAB_H* sp3C; - ProgAtr* sp40; - s32 sp44; + u16 progNo; + SD_VAB_H* vab1; + SD_VAB_H* vab0; + ProgAtr* prog; + s32 vadId; s32 sp48; - MIDI* m; - PORT* p; + MIDI* midi; + PORT* port; VagAtr* sd_vag_atr; s16 vo; s16 temp_s0_5; @@ -918,41 +918,41 @@ namespace Silent::Game u16 note; u16* addr_p; - m = &smf_midi[chan]; - sp30 = m->prog_no_0; + midi = &smf_midi[chan]; + progNo = midi->prog_no_0; - if (m->bank_change_5A > 0x10) + if (midi->bank_change_5A > 16) { - sp44 = smf_song[chan >> 4].sd_seq_vab_id_508; + vadId = smf_song[chan >> 4].sd_seq_vab_id_508; } else { - sp44 = m->bank_change_5A; + vadId = midi->bank_change_5A; } - sp3C = vab_h[sp44].vh_addr_4; - sp38 = vab_h[sp44].vh_addr_4; + vab0 = vab_h[vadId].vh_addr_4; + vab1 = vab_h[vadId].vh_addr_4; sp28 = 0; - for (i = 0; i < sp30; i++) + for (i = 0; i < progNo; i++) { - if (sp3C->vab_prog[i].tones != 0) + if (vab0->vab_prog[i].tones != 0) { sp28++; } } - sp40 = &sp38->vab_prog[sp30]; + prog = &vab1->vab_prog[progNo]; note = c1; - for (tone = 0; tone < sp40->tones; tone++) + for (tone = 0; tone < prog->tones; tone++) { - sd_vag_atr = &sp38->vag_atr[(sp28 * 16) + tone]; + sd_vag_atr = &vab1->vag_atr[(sp28 * 16) + tone]; sp48 = 0; if (sd_vag_atr->vag != 0 && c1 >= sd_vag_atr->min && sd_vag_atr->max >= c1) { vo = NO_VALUE; - if (m->mode_12 != 0) + if (midi->mode_12 != 0) { vo = 0; while (true) @@ -993,62 +993,62 @@ namespace Silent::Game vo = voice_check(chan, c1, true); } - if (m->porta_28 != 0) + if (midi->porta_28 != 0) { - note = (m->before_note_13 & 0x7F); - m->porta_depth_2A = 0; + note = (midi->before_note_13 & 0x7F); + midi->porta_depth_2A = 0; - if (m->before_note_13 < c1) + if (midi->before_note_13 < c1) { - m->porta_wk_30 = 1; - m->porta_limit_2E = (c1 - m->before_note_13) << 7; - m->porta_add_2C = (m->porta_limit_2E * 4) / m->porta_28; + midi->porta_wk_30 = 1; + midi->porta_limit_2E = (c1 - midi->before_note_13) << 7; + midi->porta_add_2C = (midi->porta_limit_2E * 4) / midi->porta_28; } - else if (m->before_note_13 == c1) + else if (midi->before_note_13 == c1) { - m->porta_add_2C = 0; + midi->porta_add_2C = 0; note = c1; } else { - m->porta_wk_30 = 0; - m->porta_limit_2E = (m->before_note_13 - c1) << 7; - m->porta_add_2C = (m->porta_limit_2E * 4) / m->porta_28; + midi->porta_wk_30 = 0; + midi->porta_limit_2E = (midi->before_note_13 - c1) << 7; + midi->porta_add_2C = (midi->porta_limit_2E * 4) / midi->porta_28; } } else { - m->porta_depth_2A = 0; + midi->porta_depth_2A = 0; } } else { - if (m->porta_28 != 0) + if (midi->porta_28 != 0) { - note = m->before_note_13 & 0x7F; - m->porta_depth_2A = 0; + note = midi->before_note_13 & 0x7F; + midi->porta_depth_2A = 0; - if (m->before_note_13 < c1) + if (midi->before_note_13 < c1) { - m->porta_wk_30 = 1; - m->porta_limit_2E = (c1 - m->before_note_13) << 7; - m->porta_add_2C = ((m->porta_limit_2E * 4) / m->porta_28); + midi->porta_wk_30 = 1; + midi->porta_limit_2E = (c1 - midi->before_note_13) << 7; + midi->porta_add_2C = ((midi->porta_limit_2E * 4) / midi->porta_28); } - else if (m->before_note_13 == c1) + else if (midi->before_note_13 == c1) { - m->porta_add_2C = 0; + midi->porta_add_2C = 0; note = c1; } else { - m->porta_wk_30 = 0; - m->porta_limit_2E = (m->before_note_13 - c1) << 7; - m->porta_add_2C = (m->porta_limit_2E * 4) / m->porta_28; + midi->porta_wk_30 = 0; + midi->porta_limit_2E = (midi->before_note_13 - c1) << 7; + midi->porta_add_2C = (midi->porta_limit_2E * 4) / midi->porta_28; } } else { - m->porta_depth_2A = 0; + midi->porta_depth_2A = 0; } } @@ -1061,8 +1061,8 @@ namespace Silent::Game } } - p = &smf_port[vo]; - if (m->mode_12 == 0) + port = &smf_port[vo]; + if (midi->mode_12 == 0) { rr_off(vo); do @@ -1073,7 +1073,9 @@ namespace Silent::Game while (stat != 2 && stat != 0); } - for (i = 0, vag_addr = 0, addr_p = (u16*)((u8*)vab_h[sp44].vh_addr_4 + (sp3C->vab_h.ps * 0x200) + 0x820); i < sd_vag_atr->vag; addr_p++, i++) + for (i = 0, vag_addr = 0, addr_p = (u16*)((u8*)vab_h[vadId].vh_addr_4 + (vab0->vab_h.ps * 512) + 2080); + i < sd_vag_atr->vag; + addr_p++, i++) { vag_addr += *addr_p; } @@ -1083,11 +1085,11 @@ namespace Silent::Game s_attr.volmode.right = 0; s_attr.voice = spu_ch_tbl[vo]; vag_addr = vag_addr * 8; - s_attr.addr = vab_h[sp44].vb_start_addr_10 + vag_addr; + s_attr.addr = vab_h[vadId].vb_start_addr_10 + vag_addr; s_attr.adsr1 = sd_vag_atr->adsr1; - p->adsr1_46 = s_attr.adsr1; + port->adsr1_46 = s_attr.adsr1; s_attr.adsr2 = sd_vag_atr->adsr2; - p->adsr2_48 = s_attr.adsr2; + port->adsr2_48 = s_attr.adsr2; if (!(sd_vag_atr->adsr1 & 0x80)) { @@ -1098,94 +1100,94 @@ namespace Silent::Game s_attr.a_mode = 5; } - p->a_mode_4A = s_attr.a_mode; - m->before_note_13 = c1; - m->velo_4 = c2; - p->center_1E = sd_vag_atr->center; - p->shift_1F = sd_vag_atr->shift; - p->bend_min_1D = sd_vag_atr->pbmin; - p->bend_max_1C = sd_vag_atr->pbmax; - p->vc_0 = vo; - p->prog_2 = sp30; - p->tone_4 = tone; - p->note_6 = note & 0x7F; - p->note_wk_8 = note & 0x7F; - p->midi_ch_3 = chan; - p->stat_16 = 1; - m->mod_depth_1C = 0; - p->pvol_10 = sp40->mvol; - p->ppan_12 = sp40->mpan; - p->tvol_11 = sd_vag_atr->vol; - p->tpan_13 = sd_vag_atr->pan; - p->velo_1A = c2; - p->vibdm_26 = 0; - - if (m->vibdm_3E != 0) + port->a_mode_4A = s_attr.a_mode; + midi->before_note_13 = c1; + midi->velo_4 = c2; + port->center_1E = sd_vag_atr->center; + port->shift_1F = sd_vag_atr->shift; + port->bend_min_1D = sd_vag_atr->pbmin; + port->bend_max_1C = sd_vag_atr->pbmax; + port->vc_0 = vo; + port->prog_2 = progNo; + port->tone_4 = tone; + port->note_6 = note & 0x7F; + port->note_wk_8 = note & 0x7F; + port->midi_ch_3 = chan; + port->stat_16 = 1; + midi->mod_depth_1C = 0; + port->pvol_10 = prog->mvol; + port->ppan_12 = prog->mpan; + port->tvol_11 = sd_vag_atr->vol; + port->tpan_13 = sd_vag_atr->pan; + port->velo_1A = c2; + port->vibdm_26 = 0; + + if (midi->vibdm_3E != 0) { - p->vibc_23 = 0; - p->vibhc_22 = 0; - p->vibcc_28 = 0; - p->vibhs_29 = m->vibhs_39; - p->vibcad_2B = m->vibcad_3B; - p->vibad_2C = m->vibad_40; - p->vibdm_26 = m->vibdm_3E; - p->vibcs_2A = m->vibcs_3A; - p->vibcadw_20 = m->vibcadw_34; + port->vibc_23 = 0; + port->vibhc_22 = 0; + port->vibcc_28 = 0; + port->vibhs_29 = midi->vibhs_39; + port->vibcad_2B = midi->vibcad_3B; + port->vibad_2C = midi->vibad_40; + port->vibdm_26 = midi->vibdm_3E; + port->vibcs_2A = midi->vibcs_3A; + port->vibcadw_20 = midi->vibcadw_34; } - p->vibd_24 = 0; - p->vib_data_2E = 0; - p->tredm_36 = 0; + port->vibd_24 = 0; + port->vib_data_2E = 0; + port->tredm_36 = 0; - if (m->tredm_4A != 0) + if (midi->tredm_4A != 0) { - p->trec_33 = 0; - p->trehc_32 = 0; - p->trecc_38 = 0; - p->trehs_39 = m->trehs_51; - p->trecad_3B = m->trecad_53; - p->tread_3C = m->tread_4C; - p->tredm_36 = m->tredm_4A; - p->trecs_3A = m->trecs_52; - p->trecadw_30 = m->trecadw_44; + port->trec_33 = 0; + port->trehc_32 = 0; + port->trecc_38 = 0; + port->trehs_39 = midi->trehs_51; + port->trecad_3B = midi->trecad_53; + port->tread_3C = midi->tread_4C; + port->tredm_36 = midi->tredm_4A; + port->trecs_3A = midi->trecs_52; + port->trecadw_30 = midi->trecadw_44; } - p->tred_34 = 0; - p->tre_data_3E = 0; - p->rdmdm_45 = 0; + port->tred_34 = 0; + port->tre_data_3E = 0; + port->rdmdm_45 = 0; - if (m->rdms_57 != 0) + if (midi->rdms_57 != 0) { - p->rdmd_40 = m->rdmd_54; - p->rdms_43 = m->rdms_57; - p->rdmc_44 = m->rdmc_58; - p->rdmdm_45 = m->rdmdm_59; + port->rdmd_40 = midi->rdmd_54; + port->rdms_43 = midi->rdms_57; + port->rdmc_44 = midi->rdmc_58; + port->rdmdm_45 = midi->rdmdm_59; } volume_calc(&smf_port[vo], &smf_midi[chan]); - s_attr.volume.left = (p->l_vol_C * smf_song[chan >> 4].sd_seq_mvoll_50C) >> 7; - s_attr.volume.right = (p->r_vol_E * smf_song[chan >> 4].sd_seq_mvolr_50E) >> 7; + s_attr.volume.left = (port->l_vol_C * smf_song[chan >> 4].sd_seq_mvoll_50C) >> 7; + s_attr.volume.right = (port->r_vol_E * smf_song[chan >> 4].sd_seq_mvolr_50E) >> 7; - if (m->wide_flag_21 != 0 || smf_song[chan >> 4].seq_wide_flag_534 != 0) + if (midi->wide_flag_21 != 0 || smf_song[chan >> 4].seq_wide_flag_534 != 0) { s_attr.volume.right = -s_attr.volume.right; } - p->pedal_1B = m->pedal_6; - p->note_6 = c1 & 0x7F; - p->pbend_50 = (u16)m->pbend_7; - p->pbend_wk_4E = 0xFFFF; + port->pedal_1B = midi->pedal_6; + port->note_6 = c1 & 0x7F; + port->pbend_50 = (u16)midi->pbend_7; + port->pbend_wk_4E = 0xFFFF; - if (m->bend_mode_10 < 0x40u) + if (midi->bend_mode_10 < 0x40u) { - temp_s0_5 = p->vib_data_2E + (m->mod_depth_1C + m->porta_depth_2A); - temp_s0_5 += (p->note_wk_8 << 7) + pitch_bend_calc(p, m->pbend_7); + temp_s0_5 = port->vib_data_2E + (midi->mod_depth_1C + midi->porta_depth_2A); + temp_s0_5 += (port->note_wk_8 << 7) + pitch_bend_calc(port, midi->pbend_7); s_attr.pitch = Note2Pitch(temp_s0_5 >> 7, temp_s0_5 & 0x7F, sd_vag_atr->center, sd_vag_atr->shift); } else { - m->pbend_7 = 0x40; + midi->pbend_7 = 0x40; s_attr.pitch = Note2Pitch(note, 0, sd_vag_atr->center, sd_vag_atr->shift); } @@ -1200,7 +1202,7 @@ namespace Silent::Game //while (SpuGetKeyStatus(spu_ch_tbl[vo] == 1) == 0); } - if (m->rev_depth_24 == 0) + if (midi->rev_depth_24 == 0) { if (sd_vag_atr->mode & (1 << 2)) { @@ -1217,7 +1219,7 @@ namespace Silent::Game } } } - else if (m->rev_depth_24 != 1) + else if (midi->rev_depth_24 != 1) { //while (!(SpuGetReverbVoice() & spu_ch_tbl[vo])) { diff --git a/Source/Game/Bodyprog/Sound/SdCall.cpp b/Source/Game/Bodyprog/Sound/SdCall.cpp index e97979a..d1e2402 100644 --- a/Source/Game/Bodyprog/Sound/SdCall.cpp +++ b/Source/Game/Bodyprog/Sound/SdCall.cpp @@ -331,7 +331,7 @@ namespace Silent::Game D_800C37DC = false; g_Sd_AudioWork.field_E = 0; g_Sd_AudioWork.field_10 = 0; - g_Sd_AudioStreamingStates.audioLoadState_0 = 0; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Reset; g_Sd_AudioStreamingStates.xaLoadState_1 = 0; g_Sd_AudioStreamingStates.xaStopState_2 = 0; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 0; @@ -893,7 +893,7 @@ namespace Silent::Game //xaFileOffsetsPtr = g_FileXaLoc; xaFileOffsetTargetPtr = &xaFileOffsetsPtr[g_XaItemData[xaAudioIdx].xaFileIdx_0]; xaFileOffset = *xaFileOffsetTargetPtr; - xaFileOffset += 0x96 + g_XaItemData[xaAudioIdx].sector_4; + xaFileOffset += 150 + g_XaItemData[xaAudioIdx].sector_4; D_800C1688.field_0 = g_XaItemData[xaAudioIdx].audioLength_8 + 32; @@ -1245,9 +1245,9 @@ namespace Silent::Game switch (g_Sd_AudioStreamingStates.audioLoadState_0) { - case 0: + case AudioLoadState_Reset: cmd = g_Sd_TaskPool[0]; - g_Sd_VabTargetLoad = &g_AudioData[cmd-160]; + g_Sd_VabTargetLoad = &g_AudioData[cmd - 160]; g_Sd_AudioType = g_Sd_VabTargetLoad->typeIdx_0; // If audio being loaded isn't BASE.VAB or KDT file. @@ -1255,7 +1255,7 @@ namespace Silent::Game { if (g_Sd_AudioWork.lastVabAudioLoadedIdx_8[g_Sd_AudioType - 1] == cmd) { - g_Sd_AudioStreamingStates.audioLoadState_0 = 0; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Reset; g_Sd_AudioWork.isAudioLoading_15 = false; Sd_TaskPoolUpdate(); break; @@ -1274,42 +1274,42 @@ namespace Silent::Game } } - g_Sd_AudioStreamingStates.audioLoadState_0 = 1; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; break; - case 1: + case AudioLoadState_Stop: Sd_VabLoad_TypeClear(); break; - case 2: + case AudioLoadState_OffSet: Sd_VabLoad_OffSet(); break; - case 3: + case AudioLoadState_LoadFile: Sd_VabLoad_FileLoad(); break; - case 4: + case AudioLoadState_CheckLoad: Sd_VabLoad_OffVagDataSet(); break; - case 5: + case AudioLoadState_Move: Sd_VabLoad_VagDataMove(); break; - case 6: + case AudioLoadState_SetNext: Sd_VabLoad_OffVagNextDataSet(); break; - case 7: + case AudioLoadState_MoveNext: Sd_VabLoad_NextVagDataMove(); break; - case 8: + case AudioLoadState_MoveLast: Sd_VabLoad_LastVagDataMove(); break; - case 9: + case AudioLoadState_Finalize: Sd_VabLoad_Finalization(); break; @@ -1322,7 +1322,7 @@ namespace Silent::Game { g_Sd_DataMoved = 0; SdVabClose(g_Sd_AudioType); - g_Sd_AudioStreamingStates.audioLoadState_0 = 2; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_OffSet; } void Sd_VabLoad_OffSet(void) // 0x80047D50 @@ -1331,7 +1331,7 @@ namespace Silent::Game //if (!Sd_CdPrimitiveCmdTry(CdlSetloc, (u8*)CdIntToPos(g_Sd_VabTargetLoad->fileOffset_8 + (g_Sd_DataMoved / 2048), &sp10), 0)) { - g_Sd_AudioStreamingStates.audioLoadState_0 = 3; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_LoadFile; } } @@ -1354,7 +1354,7 @@ namespace Silent::Game char unk = -unk; } - g_Sd_AudioStreamingStates.audioLoadState_0 = 4; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_CheckLoad; g_Sd_AudioWork.cdErrorCount_0 = 0; } @@ -1378,7 +1378,7 @@ namespace Silent::Game } SdVabOpenHeadSticky(g_Sd_VabBuffers[g_Sd_AudioType], g_Sd_AudioType, D_800A9FDC[g_Sd_AudioType]); - g_Sd_AudioStreamingStates.audioLoadState_0 = 5; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Move; } g_Sd_AudioWork.cdErrorCount_0++; @@ -1395,20 +1395,20 @@ namespace Silent::Game ptr = (s32*)&g_Sd_VabTargetLoad->fileSize_4; g_Sd_DataMoved = *ptr; - g_Sd_AudioStreamingStates.audioLoadState_0 = 9; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Finalize; } else { dataMoveCheck = SdVabTransBodyPartly((u8*)CD_ADDR_0 + g_Sd_VabTargetLoad->vagDataOffset_2, VAB_BUFFER_LIMIT - g_Sd_VabTargetLoad->vagDataOffset_2, g_Sd_AudioType); g_Sd_DataMoved = VAB_BUFFER_LIMIT; - g_Sd_AudioStreamingStates.audioLoadState_0 = 6; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_SetNext; } if (dataMoveCheck == NO_VALUE && g_Sd_VabLoadAttemps < 16) { g_Sd_VabLoadAttemps++; - g_Sd_AudioStreamingStates.audioLoadState_0 = 1; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; } } @@ -1425,7 +1425,7 @@ namespace Silent::Game //if (!Sd_CdPrimitiveCmdTry(CdlSetloc, (u8*)cdLocRes, 0)) { - g_Sd_AudioStreamingStates.audioLoadState_0 = 7; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_MoveNext; } } } @@ -1449,7 +1449,7 @@ namespace Silent::Game //CdRead(25, CD_ADDR_0, 128); } - g_Sd_AudioStreamingStates.audioLoadState_0 = 8; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_MoveLast; } void Sd_VabLoad_LastVagDataMove(void) // 0x800480FC @@ -1467,19 +1467,19 @@ namespace Silent::Game { dataMoveCheck = SdVabTransBodyPartly((u8*)CD_ADDR_0, remainingData, g_Sd_AudioType); g_Sd_DataMoved = g_Sd_VabTargetLoad->fileSize_4; - g_Sd_AudioStreamingStates.audioLoadState_0 = 9; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Finalize; } else { dataMoveCheck = SdVabTransBodyPartly((u8*)CD_ADDR_0, VAB_BUFFER_LIMIT, g_Sd_AudioType); g_Sd_DataMoved += VAB_BUFFER_LIMIT; - g_Sd_AudioStreamingStates.audioLoadState_0 = 6; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_SetNext; } if (dataMoveCheck == NO_VALUE && g_Sd_VabLoadAttemps < 16) { g_Sd_VabLoadAttemps++; - g_Sd_AudioStreamingStates.audioLoadState_0 = 1; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; } } @@ -1490,7 +1490,7 @@ namespace Silent::Game return; } - g_Sd_AudioStreamingStates.audioLoadState_0 = 0; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Reset; g_Sd_AudioWork.cdErrorCount_0 = 0; g_Sd_AudioWork.isAudioLoading_15 = false; Sd_TaskPoolUpdate(); @@ -1525,25 +1525,25 @@ namespace Silent::Game { switch (g_Sd_AudioStreamingStates.audioLoadState_0) { - case 0: - g_Sd_KdtTargetLoad = &g_AudioData[54+g_Sd_TaskPool[0]]; + case AudioLoadState_Reset: + g_Sd_KdtTargetLoad = &g_AudioData[54 + g_Sd_TaskPool[0]]; g_Sd_AudioType = g_Sd_KdtTargetLoad->typeIdx_0; - g_Sd_AudioStreamingStates.audioLoadState_0 = 1; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; break; - case 1: + case AudioLoadState_Stop: Sd_KdtLoad_StopSeq(); break; - case 2: + case AudioLoadState_OffSet: Sd_KdtLoad_OffSet(); break; - case 3: + case AudioLoadState_LoadFile: Sd_KdtLoad_FileLoad(); break; - case 4: + case AudioLoadState_CheckLoad: Sd_KdtLoad_LoadCheck(); break; @@ -1557,7 +1557,7 @@ namespace Silent::Game Sd_StopBgmStep(); SdSeqClose(g_Sd_AudioType); - g_Sd_AudioStreamingStates.audioLoadState_0 = 2; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_OffSet; } void Sd_KdtLoad_OffSet(void) // 0x800483D4 @@ -1566,7 +1566,7 @@ namespace Silent::Game //if (!Sd_CdPrimitiveCmdTry(CdlSetloc, (u8*)CdIntToPos(g_Sd_KdtTargetLoad->fileOffset_8, &cdLoc), 0)) { - g_Sd_AudioStreamingStates.audioLoadState_0 = 3; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_LoadFile; } } @@ -1576,7 +1576,7 @@ namespace Silent::Game { // CdRead((g_Sd_KdtTargetLoad->fileSize_4 + 2047) / 2048, FS_BUFFER_1, 128); - g_Sd_AudioStreamingStates.audioLoadState_0 = 4; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_CheckLoad; g_Sd_AudioWork.cdErrorCount_0 = 0; } @@ -1604,11 +1604,11 @@ namespace Silent::Game if (i == NO_VALUE && g_Sd_VabLoadAttemps < 16) { g_Sd_VabLoadAttemps++; - g_Sd_AudioStreamingStates.audioLoadState_0 = 1; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; } else { - g_Sd_AudioStreamingStates.audioLoadState_0 = 0; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Reset; g_Sd_AudioWork.isAudioLoading_15 = false; Sd_TaskPoolUpdate(); @@ -1746,7 +1746,7 @@ namespace Silent::Game } } - // Slowly fade in/out game audio based if `g_Sd_AudioWork.muteGame_17` enabled. + // Slowly fade in/out game audio based if `g_Sd_AudioWork.muteGame_17` is enabled. if (g_Sd_AudioWork.muteGame_17 == true) { if (gSDVolConfig.volumeGlobal_A > 0) @@ -1779,9 +1779,10 @@ namespace Silent::Game { //CdReset(0); //CdControlB(CdlNop, NULL, NULL); - if (g_Sd_AudioStreamingStates.audioLoadState_0 != 0) + + if (g_Sd_AudioStreamingStates.audioLoadState_0 != AudioLoadState_Reset) { - g_Sd_AudioStreamingStates.audioLoadState_0 = 1; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; } g_Sd_AudioStreamingStates.xaLoadState_1 = 0; @@ -1805,16 +1806,15 @@ namespace Silent::Game } g_Sd_AudioWork.cdErrorCount_0++; - if (g_Sd_AudioWork.cdErrorCount_0 > CD_ERROR_LIMIT) { //CdReset(0); //CdControlB(CdlNop, NULL, NULL); VSync(SyncMode_Wait3); - if (g_Sd_AudioStreamingStates.audioLoadState_0 != 0) + if (g_Sd_AudioStreamingStates.audioLoadState_0 != AudioLoadState_Reset) { - g_Sd_AudioStreamingStates.audioLoadState_0 = 1; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; } g_Sd_AudioStreamingStates.xaLoadState_1 = 0; diff --git a/Source/Game/Bodyprog/Sound/SoundSystem.h b/Source/Game/Bodyprog/Sound/SoundSystem.h index c2b6d60..2a2071d 100644 --- a/Source/Game/Bodyprog/Sound/SoundSystem.h +++ b/Source/Game/Bodyprog/Sound/SoundSystem.h @@ -2,22 +2,24 @@ namespace Silent::Game { - /** @brief Packs an audio type and program index into a single value. + /** * @brief Packs an audio type (VAB ID) and program index into a single 16-bit value. * - * The third field from `s_VabInfo` is obfuscated. - * The value get pass to `SdVoKeyOn` through the first argument (`vab_pro`) where it is used to give a - * value to the variables `prog` (by doing the equivalent of `vab_progIdx_2 & 0x7F`) and `vabid` - * (by doing the equivalent of `vab_progIdx_2 >> 8`). - * This indicates from the values that are 516 (0x204), `prog` receives the value of - * 2 while `vabid` would receive 4. This also fits with other values like 256 (0x100) and 514 (0x202). + * This macro replicates the encoding used in the third field of `s_VabInfo`. + * The resulting value is passed to `SdVoKeyOn` via the `vab_pro` argument, + * where the function extracts: + * - The Program Index: derived via `(vab_pro & 0x7F)`. + * - The VAB ID: derived via `vab_pro >> 8`. * - * The first argument is related to `e_AudioType` and `g_Sd_AudioType`, used to - * access the index of `vab_h`, which apparently allocates VAG data in memory. + * For example, a value of 516 (0x0204) results in a program index of 4 and a + * VAB ID of 2. Similarly, 256 (0x0100) and 514 (0x0202) follow this bit-packed + * structure. * - * The second value is the index for a VAB-specific attribute named `program`. + * @param audioType ID used to index `vab_h`, which manages VAG data allocation in SPU memory. + * @param progIdx The specific program index within the VAB attribute table. + * @return Packed audio type and program index. */ #define TYPE_AND_PROG_SFX(audioType, progIdx) \ - (audioType << 8) + progIdx + ((audioType << 8) + progIdx) /** @brief Audio modes. */ typedef enum _AudioMode @@ -37,6 +39,21 @@ namespace Silent::Game AudioType_MusicBank = 3 } e_AudioType; + /** @brief VAB audio load states. */ + typedef enum _AudioLoadState + { + AudioLoadState_Reset = 0, + AudioLoadState_Stop = 1, + AudioLoadState_OffSet = 2, + AudioLoadState_LoadFile = 3, + AudioLoadState_CheckLoad = 4, + AudioLoadState_Move = 5, + AudioLoadState_SetNext = 6, + AudioLoadState_MoveNext = 7, + AudioLoadState_MoveLast = 8, + AudioLoadState_Finalize = 9 + } e_AudioLoadState; + // Used for loading XA files. `field_0` holds commands for `Sd_CdPrimitiveCmdTry` typedef struct { diff --git a/Source/Game/Bodyprog/Text/TextDraw.h b/Source/Game/Bodyprog/Text/TextDraw.h index cac446d..aa02ef3 100644 --- a/Source/Game/Bodyprog/Text/TextDraw.h +++ b/Source/Game/Bodyprog/Text/TextDraw.h @@ -23,7 +23,7 @@ namespace Silent::Game struct s_800C38B0 { s8 field_0; - s8 positionIdx_1; + u8 positionIdx_1; }; extern s_800C38B0 D_800C38B0; From a69c7abf51a46e6748bce57c4186c88dc7567fb0 Mon Sep 17 00:00:00 2001 From: Sezz Date: Wed, 1 Apr 2026 21:48:27 +1100 Subject: [PATCH 3/6] Sync deltas --- Source/Game/Bodyprog/LibSd/LibSd.h | 22 ++-- Source/Game/Bodyprog/LibSd/SmfIo.cpp | 2 - Source/Game/Bodyprog/Sound/SdCall.cpp | 129 ++++++++++++----------- Source/Game/Bodyprog/Sound/SoundSystem.h | 46 +++++--- Source/Psx.h | 7 ++ 5 files changed, 116 insertions(+), 90 deletions(-) diff --git a/Source/Game/Bodyprog/LibSd/LibSd.h b/Source/Game/Bodyprog/LibSd/LibSd.h index b637a76..d63cf5f 100644 --- a/Source/Game/Bodyprog/LibSd/LibSd.h +++ b/Source/Game/Bodyprog/LibSd/LibSd.h @@ -2,8 +2,7 @@ namespace Silent::Game { - /** - * libsd: konami-customized version of libsnd? + /** libsd: konami-customized version of libsnd? * Majority of the functions work like the libsnd Ss versions. * libref.pdf v4.4 may be useful, though was likely based on an earlier SDK. */ @@ -272,23 +271,20 @@ namespace Silent::Game s32 size_4; } SD_SPU_ALLOC; - // smf_snd.c BSS + // `SmfSnd.cpp` BSS extern VAB_H vab_h[SD_VAB_SLOTS]; extern u8 sd_vb_malloc_rec[136]; extern s16 sd_seq_loop_mode; - extern s16 pad_bss_800C7662; extern s32 sd_tick_mode; extern SD_SPU_ALLOC sd_spu_alloc[SD_ALLOC_SLOTS]; - // smf_io.c BSS - extern s32 pad_bss_800C7730[2]; + // `SmfIo.cpp` BSS extern MIDI smf_midi[2 * 16]; // 2 devices with 16 channels each? extern MIDI smf_midi_sound_off; // Set by `sound_off`, could be `smf_midi[32]`, but game doesn't use offsets for `[32]`? - extern s32 pad_bss_800C8314; extern PORT smf_port[24]; extern s32 sd_timer_event; - // smf_mid.c BSS + // `SmfMid.cpp` BSS extern SMF_SONG smf_song[2]; extern s32 sd_reverb_area_size[10]; @@ -311,7 +307,7 @@ namespace Silent::Game return &smf_port[voice]; } - // smf_snd.c + // `SmfSnd.cpp` void tone_adsr_mem(s16 vab_id); void tone_adsr_back(s16 vab_id); @@ -404,7 +400,7 @@ namespace Silent::Game void SsSetSerialVol(char s_num, s16 voll, s16 volr); void SsUtAllKeyOff(s16 mode); - // smf_io.c + // `SmfIo.cpp` void set_note_on(s16 arg0, u8 arg1, u8 arg2, s16 arg3, s16 arg4); void set_midi_info(s32 type, u8 midiChannel, u32 value); /** type = `SMF_MIDI_STAT` */ @@ -441,7 +437,7 @@ namespace Silent::Game void control_code_set(s32 seq_access_num); - // smf_main.c + // `SmfMain.cpp` bool smf_timer(void); void smf_timer_set(void); @@ -449,7 +445,7 @@ namespace Silent::Game void smf_timer_stop(void); void smf_vsync(void); - // smf_mid.c + // `SmfMid.cpp` s32 MemCmp(u8* src, u8* des, u32 num); u32 readMThd(u32 loc); @@ -458,7 +454,7 @@ namespace Silent::Game u32 egetc(SMF* p); u32 readvarinum(SMF* p); - // `to32bit`/`to16bit`/`len_add` only seem used inside `smf_mid.c`, can probably be removed from header. + // `to32bit`/`to16bit`/`len_add` only seem used inside `SmfMid.cpp`, can probably be removed from header. u32 to32bit(u8 c1, u8 c2, u8 c3, u8 c4); u32 to16bit(u8 c1, u8 c2); u32 read32bit(SMF* p); diff --git a/Source/Game/Bodyprog/LibSd/SmfIo.cpp b/Source/Game/Bodyprog/LibSd/SmfIo.cpp index 5931111..51277ef 100644 --- a/Source/Game/Bodyprog/LibSd/SmfIo.cpp +++ b/Source/Game/Bodyprog/LibSd/SmfIo.cpp @@ -10,10 +10,8 @@ namespace Silent::Game extern SpuVoiceAttr s_attr; // @hack BSS SpuVoiceAttr s_attr; - s32 pad_bss_800C7730[2]; MIDI smf_midi[2 * 16]; MIDI smf_midi_sound_off; - s32 pad_bss_800C8314; PORT smf_port[24]; s32 sd_timer_event; diff --git a/Source/Game/Bodyprog/Sound/SdCall.cpp b/Source/Game/Bodyprog/Sound/SdCall.cpp index d1e2402..c425e9a 100644 --- a/Source/Game/Bodyprog/Sound/SdCall.cpp +++ b/Source/Game/Bodyprog/Sound/SdCall.cpp @@ -11,7 +11,7 @@ namespace Silent::Game { constexpr uint VAB_BUFFER_LIMIT = 0xC800u; - //static CdlLOC D_800C15E8; + static CdlLOC XaCdLocation; static s_800C15F0 D_800C15F0[4]; /** @brief `e_SfxId` | Stores the index of the currently playing SFX. */ @@ -301,7 +301,7 @@ namespace Silent::Game Sd_SetVolXa(0, 0); //D_800C15F0[0].field_0 = CdlModeSpeed; - //Sd_CdPrimitiveCmdTry(CdlSetmode, &D_800C15F0[0].field_0, NULL); + //Sd_CdPrimitiveCmdTry(CdlSetmode, &D_800C15F0[0].field_0, nullptr); for (i = 0; i < (ARRAY_SIZE(g_Sd_TaskPool) - 1); i++) { @@ -318,7 +318,7 @@ namespace Silent::Game g_Sd_AudioWork.lastVabAudioLoadedIdx_8[1] = 0; g_Sd_AudioWork.lastVabAudioLoadedIdx_8[2] = 0; g_Sd_AudioWork.xaAudioIdx_4 = 0; - g_Sd_AudioWork.isXaStopping_13 = 0; + g_Sd_AudioWork.isXaStopping_13 = false; g_Sd_AudioWork.cdErrorCount_0 = 0; g_Sd_AudioWork.bgmFadeSpeed_14 = 0; g_Sd_AudioWork.isAudioLoading_15 = false; @@ -332,8 +332,8 @@ namespace Silent::Game g_Sd_AudioWork.field_E = 0; g_Sd_AudioWork.field_10 = 0; g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Reset; - g_Sd_AudioStreamingStates.xaLoadState_1 = 0; - g_Sd_AudioStreamingStates.xaStopState_2 = 0; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_Initialize; + g_Sd_AudioStreamingStates.xaStopState_2 = XaStopState_FadeOut; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 0; gSDVolConfig.volumeXa_0 = 84; gSDVolConfig.volumeBgm_8 = 40; @@ -810,6 +810,8 @@ namespace Silent::Game void Sd_XaAudioPlay(void) // 0x80046E00 { + constexpr int PREGAP = 150; + static u16 xaAudioIdx; static u32 xaFileOffset; u32* xaFileOffsetsPtr; @@ -819,7 +821,7 @@ namespace Silent::Game switch (g_Sd_AudioStreamingStates.xaLoadState_1) { - case 0: + case XaLoadState_Initialize: if (g_Sd_AudioWork.bgmFadeSpeed_14 == 0) { gSDVolConfig.volumeBgm_6 = 24; @@ -860,81 +862,83 @@ namespace Silent::Game gSDVolConfig.volumeXa_0 = gSDVolConfig.volumeXa_2; Sd_SetVolXa(gSDVolConfig.volumeXa_2, gSDVolConfig.volumeXa_2); //D_800C15F0[0].field_0 = CdlModeSF | CdlModeRT | CdlModeSpeed; - g_Sd_AudioStreamingStates.xaLoadState_1 = 1; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_SetMode; break; - case 1: - /*if (!Sd_CdPrimitiveCmdTry(CdlSetmode, &D_800C15F0[0].field_0, NULL)) + case XaLoadState_SetMode: + /*if (!Sd_CdPrimitiveCmdTry(CdlSetmode, &D_800C15F0[0].field_0, nullptr)) { g_Sd_AudioWork.cdErrorCount_0 = 0; - g_Sd_AudioStreamingStates.xaLoadState_1 = 2; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_PrepareFilter; }*/ break; default: break; - case 2: + case XaLoadState_PrepareFilter: D_800C15F0[0].field_0 = g_XaItemData[xaAudioIdx].field_8_24; D_800C15F0[0].field_1 = g_XaItemData[xaAudioIdx].field_4_24; - g_Sd_AudioStreamingStates.xaLoadState_1 = 3; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_SetFilter; break; - case 3: - /*if (!Sd_CdPrimitiveCmdTry(CdlSetfilter, &D_800C15F0[0].field_0, NULL)) + case XaLoadState_SetFilter: + /*if (!Sd_CdPrimitiveCmdTry(CdlSetfilter, &D_800C15F0[0].field_0, nullptr)) { g_Sd_AudioWork.cdErrorCount_0 = 0; - g_Sd_AudioStreamingStates.xaLoadState_1 = 4; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_CalculateLba; }*/ break; - case 4: + case XaLoadState_CalculateLba: // @hack Needed for match, weird code. //xaFileOffsetsPtr = g_FileXaLoc; xaFileOffsetTargetPtr = &xaFileOffsetsPtr[g_XaItemData[xaAudioIdx].xaFileIdx_0]; xaFileOffset = *xaFileOffsetTargetPtr; - xaFileOffset += 150 + g_XaItemData[xaAudioIdx].sector_4; + xaFileOffset += PREGAP + g_XaItemData[xaAudioIdx].sector_4; D_800C1688.field_0 = g_XaItemData[xaAudioIdx].audioLength_8 + 32; - g_Sd_AudioStreamingStates.xaLoadState_1 = 5; - //D_800C15E8.sector = itob(xaFileOffset % 75); + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_Seek; + XaCdLocation.sector = itob(xaFileOffset % 75); xaFileOffset /= 75; - //D_800C15E8.second = itob(xaFileOffset % 60); + XaCdLocation.second = itob(xaFileOffset % 60); xaFileOffset /= 60; - //D_800C15E8.minute = itob(xaFileOffset); + XaCdLocation.minute = itob(xaFileOffset); break; - case 5: - //if (!Sd_CdPrimitiveCmdTry(CdlSeekL, &D_800C15E8.minute, NULL)) + case XaLoadState_Seek: + //if (!Sd_CdPrimitiveCmdTry(CdlSeekL, &XaCdLocation.minute, nullptr)) { g_Sd_AudioWork.cdErrorCount_0 = 0; - g_Sd_AudioStreamingStates.xaLoadState_1 = 6; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_StartRead; } break; - case 6: - //if (!Sd_CdPrimitiveCmdTry(CdlReadN, NULL, NULL)) + case XaLoadState_StartRead: + //if (!Sd_CdPrimitiveCmdTry(CdlReadN, nullptr, nullptr)) { g_Sd_AudioWork.cdErrorCount_0 = 0; D_800C37DC = false; - g_Sd_AudioStreamingStates.xaLoadState_1 = 7; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_EnableAudio; } break; - case 7: + case XaLoadState_EnableAudio: g_Sd_AudioWork.xaAudioIdx_4 = xaAudioIdx; SdSetSerialAttr(0, 0, 1); D_800C1688.field_8 = VSync(SyncMode_Count); D_800C1688.field_4 = 0; - g_Sd_AudioStreamingStates.xaLoadState_1 = 0; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_Initialize; Sd_TaskPoolUpdate(); g_Sd_AudioWork.cdErrorCount_0 = 0; g_Sd_AudioWork.isXaNotPlaying_16 = false; break; } + + #undef PREGAP } void Sd_XaPreLoadAudioPreTaskAdd(u16 xaIdx) // 0x8004729C @@ -958,7 +962,6 @@ namespace Silent::Game void Sd_XaPreLoadAudio(void) // 0x80047308 { static u16 xaAudioIdx; - static u16 pad_800C15D2; static u32 xaFileOffset; u32* xaFileOffsetsPtr; u32* xaFileOffsetTargetPtr; @@ -975,7 +978,7 @@ namespace Silent::Game break; case 1: - //if (!Sd_CdPrimitiveCmdTry(CdlSetmode, &D_800C15F0[0].field_0, NULL)) + //if (!Sd_CdPrimitiveCmdTry(CdlSetmode, &D_800C15F0[0].field_0, nullptr)) { g_Sd_AudioWork.cdErrorCount_0 = 0; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 2; @@ -989,7 +992,7 @@ namespace Silent::Game return; case 3: - //if (!Sd_CdPrimitiveCmdTry(CdlSetfilter, &D_800C15F0[0].field_0, NULL)) + //if (!Sd_CdPrimitiveCmdTry(CdlSetfilter, &D_800C15F0[0].field_0, nullptr)) { g_Sd_AudioWork.cdErrorCount_0 = 0; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 4; @@ -1006,15 +1009,15 @@ namespace Silent::Game D_800C1688.field_0 = g_XaItemData[xaAudioIdx].audioLength_8 + 32; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 5; - //D_800C15E8.sector = itob(xaFileOffset % 75); + XaCdLocation.sector = itob(xaFileOffset % 75); xaFileOffset /= 75; - //D_800C15E8.second = itob(xaFileOffset % 60); + XaCdLocation.second = itob(xaFileOffset % 60); xaFileOffset /= 60; - //D_800C15E8.minute = itob(xaFileOffset); + XaCdLocation.minute = itob(xaFileOffset); break; case 5: - //if (!Sd_CdPrimitiveCmdTry(CdlSeekL, &D_800C15E8.minute, NULL)) + //if (!Sd_CdPrimitiveCmdTry(CdlSeekL, &XaCdLocation.minute, nullptr)) { g_Sd_AudioWork.cdErrorCount_0 = 0; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 6; @@ -1022,7 +1025,7 @@ namespace Silent::Game break; case 6: - //if (!Sd_CdPrimitiveCmdTry(CdlPause, NULL, NULL)) + //if (!Sd_CdPrimitiveCmdTry(CdlPause, nullptr, nullptr)) { g_Sd_AudioStreamingStates.xaPreLoadState_3 = 0; D_800C37DC = false; @@ -1044,45 +1047,45 @@ namespace Silent::Game void Sd_XaAudioStop(void) // 0x80047634 { - g_Sd_AudioWork.isXaStopping_13 = 1; + g_Sd_AudioWork.isXaStopping_13 = true; switch (g_Sd_AudioStreamingStates.xaStopState_2) { - case 0: + case XaStopState_FadeOut: Sd_SetVolXa(gSDVolConfig.volumeXa_0, gSDVolConfig.volumeXa_0); gSDVolConfig.volumeXa_0 -= 24; gSDVolConfig.volumeXa_2 = gSDVolConfig.volumeXa_0; if (gSDVolConfig.volumeXa_0 < 2) { - g_Sd_AudioStreamingStates.xaStopState_2 = 1; + g_Sd_AudioStreamingStates.xaStopState_2 = XaStopState_Mute; } break; - case 1: + case XaStopState_Mute: gSDVolConfig.volumeXa_0 = 0; gSDVolConfig.volumeXa_2 = 0; Sd_SetVolXa(0, 0); SdSetSerialAttr(0, 0, 0); - g_Sd_AudioStreamingStates.xaStopState_2 = 2; + g_Sd_AudioStreamingStates.xaStopState_2 = XaStopState_PauseDisc; break; - case 2: - //if (!Sd_CdPrimitiveCmdTry(CdlPause, NULL, NULL)) + case XaStopState_PauseDisc: + //if (!Sd_CdPrimitiveCmdTry(CdlPause, nullptr, nullptr)) { - g_Sd_AudioWork.cdErrorCount_0 = 0; - g_Sd_AudioStreamingStates.xaStopState_2 = 3; + g_Sd_AudioWork.cdErrorCount_0 = 0; + g_Sd_AudioStreamingStates.xaStopState_2 = XaStopState_Cleanup; } g_Sd_AudioWork.cdErrorCount_0++; break; - case 3: - g_Sd_AudioWork.isXaStopping_13 = 0; - g_Sd_AudioWork.xaAudioIdx_4 = 0; - g_Sd_AudioStreamingStates.xaStopState_2 = 0; + case XaStopState_Cleanup: + g_Sd_AudioWork.isXaStopping_13 = false; + g_Sd_AudioWork.xaAudioIdx_4 = 0; + g_Sd_AudioStreamingStates.xaStopState_2 = XaStopState_FadeOut; if (g_Sd_AudioWork.bgmFadeSpeed_14 == 0) { @@ -1191,7 +1194,7 @@ namespace Silent::Game void Sd_TaskPoolUpdate(void) // 0x80047A70 { static s32 i; - static s32 pad_800C15E4; + if (g_Sd_TaskPool[0] != 0) { for (i = 0; i < (ARRAY_SIZE(g_Sd_TaskPool) - 1); i++) @@ -1281,7 +1284,7 @@ namespace Silent::Game Sd_VabLoad_TypeClear(); break; - case AudioLoadState_OffSet: + case AudioLoadState_SetOff: Sd_VabLoad_OffSet(); break; @@ -1322,7 +1325,7 @@ namespace Silent::Game { g_Sd_DataMoved = 0; SdVabClose(g_Sd_AudioType); - g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_OffSet; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_SetOff; } void Sd_VabLoad_OffSet(void) // 0x80047D50 @@ -1367,7 +1370,7 @@ namespace Silent::Game u8* ptr0; u8* ptr1; - //if (CdReadSync(1, NULL) == 0) + //if (CdReadSync(1, nullptr) == 0) { ptr1 = (u8*)CD_ADDR_0; ptr0 = g_Sd_VabBuffers[g_Sd_AudioType]; @@ -1535,7 +1538,7 @@ namespace Silent::Game Sd_KdtLoad_StopSeq(); break; - case AudioLoadState_OffSet: + case AudioLoadState_SetOff: Sd_KdtLoad_OffSet(); break; @@ -1557,7 +1560,7 @@ namespace Silent::Game Sd_StopBgmStep(); SdSeqClose(g_Sd_AudioType); - g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_OffSet; + g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_SetOff; } void Sd_KdtLoad_OffSet(void) // 0x800483D4 @@ -1589,7 +1592,7 @@ namespace Silent::Game u8* ptr0; u8* ptr1; - //if (CdReadSync(1, NULL) == 0) + //if (CdReadSync(1, nullptr) == 0) { ptr1 = (u8*)FS_BUFFER_1; ptr0 = g_Sd_KdtBuffer[g_Sd_AudioType]; @@ -1778,15 +1781,15 @@ namespace Silent::Game if (g_Sd_AudioWork.cdErrorCount_0 > CD_ERROR_LIMIT) { //CdReset(0); - //CdControlB(CdlNop, NULL, NULL); + //CdControlB(CdlNop, nullptr, nullptr); if (g_Sd_AudioStreamingStates.audioLoadState_0 != AudioLoadState_Reset) { g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; } - g_Sd_AudioStreamingStates.xaLoadState_1 = 0; - g_Sd_AudioStreamingStates.xaStopState_2 = 0; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_Initialize; + g_Sd_AudioStreamingStates.xaStopState_2 = XaStopState_FadeOut; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 0; g_Sd_AudioWork.cdErrorCount_0 = 0; } @@ -1809,7 +1812,7 @@ namespace Silent::Game if (g_Sd_AudioWork.cdErrorCount_0 > CD_ERROR_LIMIT) { //CdReset(0); - //CdControlB(CdlNop, NULL, NULL); + //CdControlB(CdlNop, nullptr, nullptr); VSync(SyncMode_Wait3); if (g_Sd_AudioStreamingStates.audioLoadState_0 != AudioLoadState_Reset) @@ -1817,8 +1820,8 @@ namespace Silent::Game g_Sd_AudioStreamingStates.audioLoadState_0 = AudioLoadState_Stop; } - g_Sd_AudioStreamingStates.xaLoadState_1 = 0; - g_Sd_AudioStreamingStates.xaStopState_2 = 0; + g_Sd_AudioStreamingStates.xaLoadState_1 = XaLoadState_Initialize; + g_Sd_AudioStreamingStates.xaStopState_2 = XaStopState_FadeOut; g_Sd_AudioStreamingStates.xaPreLoadState_3 = 0; g_Sd_AudioWork.cdErrorCount_0 = 0; } diff --git a/Source/Game/Bodyprog/Sound/SoundSystem.h b/Source/Game/Bodyprog/Sound/SoundSystem.h index 2a2071d..777df89 100644 --- a/Source/Game/Bodyprog/Sound/SoundSystem.h +++ b/Source/Game/Bodyprog/Sound/SoundSystem.h @@ -22,14 +22,14 @@ namespace Silent::Game ((audioType << 8) + progIdx) /** @brief Audio modes. */ - typedef enum _AudioMode + enum e_AudioMode { AudioMode_Mono = 1, AudioMode_Stereo = 2 - } e_AudioMode; + }; /** @brief Audio types. */ - typedef enum _AudioType + enum e_AudioType { AudioType_MusicKey = 0, AudioType_BaseAudio = 0, @@ -37,14 +37,14 @@ namespace Silent::Game AudioType_Ambient = 2, AudioType_SpecialScreen = 2, AudioType_MusicBank = 3 - } e_AudioType; + }; - /** @brief VAB audio load states. */ - typedef enum _AudioLoadState + /** @brief VAB audio and KDT sequence load states. */ + enum e_AudioLoadState { AudioLoadState_Reset = 0, AudioLoadState_Stop = 1, - AudioLoadState_OffSet = 2, + AudioLoadState_SetOff = 2, AudioLoadState_LoadFile = 3, AudioLoadState_CheckLoad = 4, AudioLoadState_Move = 5, @@ -52,7 +52,29 @@ namespace Silent::Game AudioLoadState_MoveNext = 7, AudioLoadState_MoveLast = 8, AudioLoadState_Finalize = 9 - } e_AudioLoadState; + }; + + /** @brief XA load states. */ + enum e_XaLoadState + { + XaLoadState_Initialize = 0, + XaLoadState_SetMode = 1, + XaLoadState_PrepareFilter = 2, + XaLoadState_SetFilter = 3, + XaLoadState_CalculateLba = 4, + XaLoadState_Seek = 5, + XaLoadState_StartRead = 6, + XaLoadState_EnableAudio = 7 + }; + + /** @brief XA stop states. */ + enum e_XaStopState + { + XaStopState_FadeOut = 0, + XaStopState_Mute = 1, + XaStopState_PauseDisc = 2, + XaStopState_Cleanup = 3 + }; // Used for loading XA files. `field_0` holds commands for `Sd_CdPrimitiveCmdTry` typedef struct @@ -94,10 +116,10 @@ namespace Silent::Game typedef struct { - u8 audioLoadState_0; /** Load VAB audio and KDT music key notes state. */ - u8 xaLoadState_1; /** Load XA audio state. */ - u8 xaStopState_2; /** Stop XA audio streaming state. */ - u8 xaPreLoadState_3; /** Prepare Load XA audio state. Positions the current read point to the one where the XA audio to load resides. */ + u8 audioLoadState_0; /** `e_AudioLoadState` */ + u8 xaLoadState_1; /** `e_XaLoadState` */ + u8 xaStopState_2; /** `e_XaStopState` */ + u8 xaPreLoadState_3; /** Prepare Load XA audio state. Positions the current read point to one where the XA audio to load resides. */ } s_AudioStreamingStates; // Game audio channels volume configuration struct. diff --git a/Source/Psx.h b/Source/Psx.h index f2e8812..05bfb58 100644 --- a/Source/Psx.h +++ b/Source/Psx.h @@ -8,6 +8,7 @@ extern "C" #include #include + #include #include #include #include @@ -48,3 +49,9 @@ enum PrimRectFlags RECT_BLEND = 1 << 1, /** Semi-transparency flag. */ RECT_MODULATE = 1 << 0 /** Use primitive color to modulate texture. */ }; + +/** @brief LBA Integer to BCD. */ +constexpr int itob(int i) +{ + return ((i / 10) * 16) + (i % 10); +} From 27a9f8ea67e7009b7cd3f8a63466ae97fde2e5c7 Mon Sep 17 00:00:00 2001 From: Sezz Date: Thu, 2 Apr 2026 21:13:39 +1100 Subject: [PATCH 4/6] Start immediate-mode 3D triangle rendering --- Source/Math/Compatibility.h | 1 - Source/Math/Constants.h | 1 + Source/Psx.cpp | 4 + .../Backends/SdlGpu/Resources/Buffer.h | 2 +- .../SdlGpu/Resources/PingPongTexture.cpp | 5 - .../SdlGpu/Resources/PingPongTexture.h | 24 +--- Source/Renderer/Backends/SdlGpu/SdlGpu.cpp | 122 ++++++++++++++++-- Source/Renderer/Backends/SdlGpu/SdlGpu.h | 37 ++++-- Source/Renderer/Common/Constants.h | 1 + .../Common/Resources/PingPongTexture.cpp | 10 ++ .../Common/Resources/PingPongTexture.h | 39 ++++++ .../Resources/Primitive/Primitive3d.cpp | 4 +- .../Common/Resources/Primitive/Primitive3d.h | 8 +- .../Common/Resources/Primitive/Triangle3d.h | 10 -- .../Renderer/Common/Resources/Scene/Shape2d.h | 2 +- .../Common/Resources/Scene/Triangle3d.h | 25 ++++ Source/Renderer/Renderer.cpp | 78 ++++++++++- Source/Renderer/Renderer.h | 37 ++++-- 18 files changed, 329 insertions(+), 81 deletions(-) create mode 100644 Source/Psx.cpp create mode 100644 Source/Renderer/Common/Resources/PingPongTexture.cpp create mode 100644 Source/Renderer/Common/Resources/PingPongTexture.h delete mode 100644 Source/Renderer/Common/Resources/Primitive/Triangle3d.h create mode 100644 Source/Renderer/Common/Resources/Scene/Triangle3d.h diff --git a/Source/Math/Compatibility.h b/Source/Math/Compatibility.h index 72376ce..bfa87e9 100644 --- a/Source/Math/Compatibility.h +++ b/Source/Math/Compatibility.h @@ -7,7 +7,6 @@ namespace Silent::Math { - constexpr int LINE_VERTEX_COUNT = 2; constexpr int RECT_VERTEX_COUNT = 4; constexpr int BOX_VERTEX_COUNT = 8; diff --git a/Source/Math/Constants.h b/Source/Math/Constants.h index b294f83..2be2c67 100644 --- a/Source/Math/Constants.h +++ b/Source/Math/Constants.h @@ -13,6 +13,7 @@ namespace Silent::Math constexpr float EPSILON = 0.00001f; constexpr uint GOLDEN_RATIO = 0x9E3779B9; + constexpr int LINE_VERTEX_COUNT = 2; constexpr int TRI_VERTEX_COUNT = 3; constexpr int QUAD_VERTEX_COUNT = 4; diff --git a/Source/Psx.cpp b/Source/Psx.cpp new file mode 100644 index 0000000..29272f9 --- /dev/null +++ b/Source/Psx.cpp @@ -0,0 +1,4 @@ +#include "Framework.h" +#include "Psx.h" + +// @todo Try making a PSX graphics shim. diff --git a/Source/Renderer/Backends/SdlGpu/Resources/Buffer.h b/Source/Renderer/Backends/SdlGpu/Resources/Buffer.h index 7755509..b3e6f6f 100644 --- a/Source/Renderer/Backends/SdlGpu/Resources/Buffer.h +++ b/Source/Renderer/Backends/SdlGpu/Resources/Buffer.h @@ -136,7 +136,7 @@ namespace Silent::Renderer::SdlGpu }; // Create buffer. - _resourceBuffer = SDL_CreateGPUBuffer(&device, &bufferInfo); + _resourceBuffer = SDL_CreateGPUBuffer(_device, &bufferInfo); if (_resourceBuffer == nullptr) { Debug::Log(Fmt("Failed to create buffer `{}`: {}", name, SDL_GetError()), Debug::LogLevel::Error); diff --git a/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.cpp b/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.cpp index 744a947..d058bf5 100644 --- a/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.cpp +++ b/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.cpp @@ -13,11 +13,6 @@ namespace Silent::Renderer _device = &device; } - void PingPongTexture::Swap() - { - _writeIdx = 1 - _writeIdx; - } - void PingPongTexture::Release() { if (_device == nullptr) diff --git a/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.h b/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.h index 4976035..b30dd3e 100644 --- a/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.h +++ b/Source/Renderer/Backends/SdlGpu/Resources/PingPongTexture.h @@ -1,24 +1,19 @@ #pragma once +#include "Renderer/Common/Resources/PingPongTexture.h" + namespace Silent::Renderer { /** @brief GPU ping-pong render target texture. */ - class PingPongTexture + class PingPongTexture : public PingPongTextureBase { private: - // ========== - // Constants - // ========== - - static constexpr int TARGET_COUNT = 2; - // ======= // Fields // ======= - SDL_GPUDevice* _device = nullptr; - std::vector _targets = { nullptr, nullptr }; - int _writeIdx = 0; + SDL_GPUDevice* _device = nullptr; + std::array _targets = { nullptr, nullptr }; public: // ============= @@ -28,8 +23,7 @@ namespace Silent::Renderer /** @brief Creates a default uninitialized instance. */ PingPongTexture() = default; - /** @brief Gracefully destroys the instance and frees GPU resources. */ - ~PingPongTexture(); + ~PingPongTexture() override; // ======== // Getters @@ -59,10 +53,6 @@ namespace Silent::Renderer */ void Initialize(SDL_GPUDevice& device); - /** @brief Swaps the internal texture targets. */ - void Swap(); - - /** @brief Releases GPU texture resources. */ - void Release(); + void Release() override; }; } diff --git a/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp b/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp index 85feda7..65ad09e 100644 --- a/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp +++ b/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp @@ -359,6 +359,8 @@ namespace Silent::Renderer::SdlGpu // Process copy pass. auto* copyPass = SDL_BeginGPUCopyPass(_commandBuffer); + CopyGpuPrimitives3d(*copyPass); + SDL_EndGPUCopyPass(copyPass); // Process render pass. @@ -546,7 +548,7 @@ namespace Silent::Renderer::SdlGpu auto* renderPass = SDL_BeginGPURenderPass(_commandBuffer, &colorTargetInfo, 1, nullptr); - _gpuBuffers.ViewportVertices2d.Bind(*renderPass, 0, 0); + _gpuBuffers.ViewportVertices.Bind(*renderPass, 0, 0); _pipelines.Bind(*renderPass, renderStage, BlendMode::Opaque); pushUniforms(); @@ -623,7 +625,7 @@ namespace Silent::Renderer::SdlGpu auto& renderPass = *SDL_BeginGPURenderPass(_commandBuffer, &colorTargetInfo, 1, nullptr); // Bind viewport quad. - _gpuBuffers.ViewportVertices2d.Bind(renderPass, 0, 0); + _gpuBuffers.ViewportVertices.Bind(renderPass, 0, 0); _pipelines.Bind(renderPass, RenderStage::Blit, BlendMode::Opaque); @@ -696,6 +698,8 @@ namespace Silent::Renderer::SdlGpu constexpr int SHAPE_2D_IDX_COUNT_MAX = SHAPE_2D_VERT_COUNT_MAX; constexpr int GLYPH_2D_VERT_COUNT_MAX = GLYPH_2D_COUNT_MAX * QUAD_VERTEX_COUNT; constexpr int GLYPH_2D_IDX_COUNT_MAX = GLYPH_2D_COUNT_MAX * QUAD_IDX_COUNT; + constexpr int TRI_3D_VERT_COUNT_MAX = TRI_3D_COUNT_MAX * TRI_VERTEX_COUNT; + constexpr int TRI_3D_IDX_COUNT_MAX = TRI_3D_COUNT_MAX * TRI_IDX_COUNT; constexpr int VERT_2D_COUNT_MAX = SPRITE_2D_VERT_COUNT_MAX + SHAPE_2D_VERT_COUNT_MAX + @@ -706,13 +710,12 @@ namespace Silent::Renderer::SdlGpu constexpr int PRIM_2D_BATCH_COUNT_MAX = SPRITE_2D_COUNT_MAX + SHAPE_2D_COUNT_MAX + GLYPH_2D_COUNT_MAX; - - constexpr int VERT_3D_COUNT_MAX = SHRT_MAX - 1; + constexpr int VERT_3D_COUNT_MAX = TRI_3D_VERT_COUNT_MAX; constexpr int VERT_3D_IDX_COUNT_MAX = VERT_3D_COUNT_MAX; - constexpr int PRIM_3D_BATCH_COUNT_MAX = VERT_3D_IDX_COUNT_MAX / 3; + constexpr int PRIM_3D_BATCH_COUNT_MAX = VERT_3D_IDX_COUNT_MAX / TRI_VERTEX_COUNT; // Initialize GPU buffers. - _gpuBuffers.ViewportVertices2d.Initialize(*_device, QUAD_VERTEX_COUNT, QUAD_IDX_COUNT, "2D viewport vertices"); + _gpuBuffers.ViewportVertices.Initialize(*_device, QUAD_VERTEX_COUNT, QUAD_IDX_COUNT, "2D viewport vertices"); _gpuBuffers.Vertices2d.Initialize(*_device, VERT_2D_COUNT_MAX, VERT_2D_IDX_COUNT_MAX, "2D vertices"); _gpuBuffers.Vertices3d.Initialize(*_device, VERT_3D_COUNT_MAX, VERT_3D_IDX_COUNT_MAX, "3D vertices"); @@ -821,10 +824,14 @@ namespace Silent::Renderer::SdlGpu // Add vertices. for (int i = 0; i < prim.Vertices.size(); i++) { - // @todo Need depth texture. float depthZ = std::clamp((float)prim.Depth / (float)DEPTH_MAX, 0.0f, 1.0f); auto pos = Vector3(prim.Vertices[i].Position.x, prim.Vertices[i].Position.y, depthZ); - bufferVerts.push_back(BufferVertex2d{ pos, prim.Vertices[i].Uv, prim.Vertices[i].Col }); + bufferVerts.push_back(BufferVertex2d + { + .Position = pos, + .Uv = prim.Vertices[i].Uv, + .Col = prim.Vertices[i].Col + }); } int curVertCount = 0; @@ -839,6 +846,7 @@ namespace Silent::Renderer::SdlGpu bufferIdxs.push_back(i); } + // Set buffer region. curVertCount = TRI_VERTEX_COUNT; curIdxCount = TRI_IDX_COUNT; } @@ -851,6 +859,7 @@ namespace Silent::Renderer::SdlGpu bufferIdxs.push_back(i); } + // Set buffer region. curVertCount = QUAD_VERTEX_COUNT; curIdxCount = QUAD_IDX_COUNT; } @@ -878,6 +887,99 @@ namespace Silent::Renderer::SdlGpu _gpuBuffers.Vertices2d.UpdateIdxs(copyPass, ToSpan(bufferIdxs), 0); } + void Renderer::CopyGpuPrimitives3d(SDL_GPUCopyPass& copyPass) + { + auto bufferVerts = std::vector{}; + auto bufferIdxs = std::vector{}; + + // Reserve memory. + bufferVerts.reserve(_doubleBuffer.Render.Primitives3d.size() * QUAD_VERTEX_COUNT); + bufferIdxs.reserve(_doubleBuffer.Render.Primitives3d.size() * QUAD_IDX_COUNT); + + // Create batched GPU buffer data. + int vertOffset = 0; + int idxOffset = 0; + for (const auto& prim : _doubleBuffer.Render.Primitives3d) + { + // Add vertices. + for (int i = 0; i < prim.Vertices.size(); i++) + { + bufferVerts.push_back(BufferVertex3d + { + .Position = prim.Vertices[i].Position, + .Normal = prim.Vertices[i].Normal, + .Uv = prim.Vertices[i].Uv, + .Col = prim.Vertices[i].Col + }); + } + + int curVertCount = 0; + int curIdxCount = 0; + + // @todo + // Line. + /*if (prim.Vertices.size() == LINE_VERTEX_COUNT) + { + // Add indices. + for (int i = 0; i < LINE_IDX_COUNT; i++) + { + bufferIdxs.push_back(i); + } + + // Set buffer region. + curVertCount = LINE_VERTEX_COUNT; + curIdxCount = LINE_IDX_COUNT; + } + else */if (prim.Vertices.size() == TRI_VERTEX_COUNT) + { + // Add indices. + for (int i = 0; i < TRI_IDX_COUNT; i++) + { + bufferIdxs.push_back(i); + } + + // Set buffer region. + curVertCount = TRI_VERTEX_COUNT; + curIdxCount = TRI_IDX_COUNT; + } + // Quad. + else if (prim.Vertices.size() == QUAD_VERTEX_COUNT) + { + // Add indices. + for (int i : QUAD_TRI_IDXS) + { + bufferIdxs.push_back(i); + } + + // Set buffer region. + curVertCount = QUAD_VERTEX_COUNT; + curIdxCount = QUAD_IDX_COUNT; + } + + // Add batch. + // @todo Smarter way that strings together primitives with the same render stage, blend mode, and texture. + // What to do with uniforms? For now, collect each as its own batch of 2 triangles. + _drawBatches.Primitives3d.push_back(DrawBatch + { + .TextureName = prim.TextureName, + .RenderStg = prim.RenderStg, + .BlendMd = prim.BlendMd, + .Uniform = prim.Uniform, + .VertexCount = curIdxCount, + .VertexOffset = vertOffset, + .IdxOffset = idxOffset + }); + + vertOffset += curVertCount; + idxOffset += curIdxCount; + } + + // @todo Mesh cache also uses ths full span of this buffer. + // Update GPU buffer. + //_gpuBuffers.Vertices3d.UpdateVertices(copyPass, ToSpan(bufferVerts), 0); + //_gpuBuffers.Vertices3d.UpdateIdxs(copyPass, ToSpan(bufferIdxs), 0); + } + void Renderer::CopyGpuViewportQuad(SDL_GPUCopyPass& copyPass) { constexpr auto BUFFER_IDXS = std::array{ 0, 2, 1, 1, 2, 3 }; @@ -908,8 +1010,8 @@ namespace Silent::Renderer::SdlGpu }; // Update GPU buffer. - _gpuBuffers.ViewportVertices2d.UpdateVertices(copyPass, ToSpan(bufferVerts), 0); - _gpuBuffers.ViewportVertices2d.UpdateIdxs(copyPass, ToSpan(BUFFER_IDXS), 0); + _gpuBuffers.ViewportVertices.UpdateVertices(copyPass, ToSpan(bufferVerts), 0); + _gpuBuffers.ViewportVertices.UpdateIdxs(copyPass, ToSpan(BUFFER_IDXS), 0); } void Renderer::PushVertexUniform(const UniformType& uni, int slotIdx) diff --git a/Source/Renderer/Backends/SdlGpu/SdlGpu.h b/Source/Renderer/Backends/SdlGpu/SdlGpu.h index 16893e1..c779c86 100644 --- a/Source/Renderer/Backends/SdlGpu/SdlGpu.h +++ b/Source/Renderer/Backends/SdlGpu/SdlGpu.h @@ -36,10 +36,9 @@ namespace Silent::Renderer::SdlGpu /** @brief GPU buffers. */ struct GpuBuffers { - VertexBuffer ViewportVertices2d = {}; - - VertexBuffer Vertices2d = {}; - VertexBuffer Vertices3d = {}; + VertexBuffer ViewportVertices = {}; + VertexBuffer Vertices2d = {}; + VertexBuffer Vertices3d = {}; }; /** @brief SDL_gpu renderer backend. */ @@ -53,13 +52,13 @@ namespace Silent::Renderer::SdlGpu SDL_GPUDevice* _device = nullptr; std::vector _samplers = {}; PipelineManager _pipelines = PipelineManager(); - - PingPongTexture _renderTexture = PingPongTexture(); - SDL_GPUTexture* _depthTexture = nullptr; - SDL_GPUTexture* _swapchainTexture = nullptr; - SDL_GPUCommandBuffer* _commandBuffer = nullptr; - DrawBatches _drawBatches = {}; - GpuBuffers _gpuBuffers = {}; + + PingPongTexture _renderTexture = PingPongTexture(); + SDL_GPUTexture* _depthTexture = nullptr; + SDL_GPUTexture* _swapchainTexture = nullptr; + SDL_GPUCommandBuffer* _commandBuffer = nullptr; + DrawBatches _drawBatches = {}; + GpuBuffers _gpuBuffers = {}; public: // ============= @@ -132,12 +131,26 @@ namespace Silent::Renderer::SdlGpu /** @brief Converts render buffer data to 2D triangle GPU buffer data and uploads it to the GPU. * - * Processes 2D sprites and shapes. + * Processes immediate-mode 2D sprites, shapes, and glyphs. * * @param copyPass GPU copy pass. */ void CopyGpuPrimitives2d(SDL_GPUCopyPass& copyPass); + /** @brief Converts render buffer data to 3D triangle GPU buffer data and uploads it to the GPU. + * + * Processes immediate-mode 3D primitives. + * + * @param copyPass GPU copy pass. + */ + void CopyGpuPrimitives3d(SDL_GPUCopyPass& copyPass); + + /** @brief Converts render buffer data to viewport quad GPU buffer data and uploads it to the GPU. + * + * Processes a fullscreen quad. + * + * @param copyPass GPU copy pass. + */ void CopyGpuViewportQuad(SDL_GPUCopyPass& copyPass); /** @brief Pushes uniform data to the GPU for the vertex shader. diff --git a/Source/Renderer/Common/Constants.h b/Source/Renderer/Common/Constants.h index 6a3d6e5..d41cc1c 100644 --- a/Source/Renderer/Common/Constants.h +++ b/Source/Renderer/Common/Constants.h @@ -9,6 +9,7 @@ namespace Silent::Renderer constexpr int DEBUG_GUI_COUNT_MAX = 8; + constexpr int LINE_IDX_COUNT = 2; constexpr int TRI_IDX_COUNT = 3; constexpr int QUAD_IDX_COUNT = 6; constexpr int RGB_COMP_COUNT = 3; diff --git a/Source/Renderer/Common/Resources/PingPongTexture.cpp b/Source/Renderer/Common/Resources/PingPongTexture.cpp new file mode 100644 index 0000000..4b1e0b4 --- /dev/null +++ b/Source/Renderer/Common/Resources/PingPongTexture.cpp @@ -0,0 +1,10 @@ +#include "Framework.h" +#include "Renderer/Common/Resources/PingPongTexture.h" + +namespace Silent::Renderer +{ + void PingPongTextureBase::Swap() + { + _writeIdx = 1 - _writeIdx; + } +} diff --git a/Source/Renderer/Common/Resources/PingPongTexture.h b/Source/Renderer/Common/Resources/PingPongTexture.h new file mode 100644 index 0000000..bd5b4cf --- /dev/null +++ b/Source/Renderer/Common/Resources/PingPongTexture.h @@ -0,0 +1,39 @@ +#pragma once + +namespace Silent::Renderer +{ + /** @brief GPU ping-pong render target texture base. */ + class PingPongTextureBase + { + protected: + // ========== + // Constants + // ========== + + static constexpr int TARGET_COUNT = 2; + + // ======= + // Fields + // ======= + + int _writeIdx = 0; + + public: + // ============= + // Constructors + // ============= + + /** @brief Gracefully destroys the instance and frees GPU resources. */ + virtual ~PingPongTextureBase() = default; + + // ========== + // Utilities + // ========== + + /** @brief Swaps the internal texture targets. */ + void Swap(); + + /** @brief Releases GPU texture resources. */ + virtual void Release() = 0; + }; +} diff --git a/Source/Renderer/Common/Resources/Primitive/Primitive3d.cpp b/Source/Renderer/Common/Resources/Primitive/Primitive3d.cpp index 5605817..c5067c6 100644 --- a/Source/Renderer/Common/Resources/Primitive/Primitive3d.cpp +++ b/Source/Renderer/Common/Resources/Primitive/Primitive3d.cpp @@ -22,7 +22,7 @@ namespace Silent::Renderer .Col = color } }, - .BlendM = BlendMode::Add + .BlendMd = BlendMode::Add }; } @@ -45,7 +45,7 @@ namespace Silent::Renderer .Col = color } }, - .BlendM = BlendMode::Add + .BlendMd = BlendMode::Add }; } } diff --git a/Source/Renderer/Common/Resources/Primitive/Primitive3d.h b/Source/Renderer/Common/Resources/Primitive/Primitive3d.h index fd4326f..4d09581 100644 --- a/Source/Renderer/Common/Resources/Primitive/Primitive3d.h +++ b/Source/Renderer/Common/Resources/Primitive/Primitive3d.h @@ -2,14 +2,18 @@ #include "Renderer/Common/Enums.h" #include "Renderer/Common/Resources/Primitive/Vertex3d.h" +#include "Renderer/Common/Resources/Uniforms.h" namespace Silent::Renderer { /** @brief 3D world space primitive representing a line or triangle. */ struct Primitive3d { - std::vector Vertices = {}; - BlendMode BlendM = BlendMode::Alpha; + std::vector Vertices = {}; + std::string TextureName = {}; + RenderStage RenderStg = RenderStage::Sprite2d; + BlendMode BlendMd = BlendMode::Opaque; + UniformType Uniform = {}; /** @brief Creates a 3D debug line primitive with additive blending. * diff --git a/Source/Renderer/Common/Resources/Primitive/Triangle3d.h b/Source/Renderer/Common/Resources/Primitive/Triangle3d.h deleted file mode 100644 index 833b57c..0000000 --- a/Source/Renderer/Common/Resources/Primitive/Triangle3d.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace Silent::Renderer -{ - /** @brief 3D world triangle. */ - struct Triangle3d - { - std::array VertexIdxs = {}; - }; -} diff --git a/Source/Renderer/Common/Resources/Scene/Shape2d.h b/Source/Renderer/Common/Resources/Scene/Shape2d.h index 6905cd4..dddb5f6 100644 --- a/Source/Renderer/Common/Resources/Scene/Shape2d.h +++ b/Source/Renderer/Common/Resources/Scene/Shape2d.h @@ -7,7 +7,7 @@ namespace Silent::Renderer { constexpr int SHAPE_2D_COUNT_MAX = 128; - /** @brief 2D screen shape representing a line, triangle, or quad. */ + /** @brief 2D untextured screen shape representing a line, triangle, or quad. */ struct Shape2d { std::vector Vertices = {}; diff --git a/Source/Renderer/Common/Resources/Scene/Triangle3d.h b/Source/Renderer/Common/Resources/Scene/Triangle3d.h new file mode 100644 index 0000000..f62e9c6 --- /dev/null +++ b/Source/Renderer/Common/Resources/Scene/Triangle3d.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Renderer/Common/Enums.h" + +namespace Silent::Renderer +{ + constexpr int TRI_3D_COUNT_MAX = SHRT_MAX / 3; + + /** @brief 3D world triangle. */ + struct Triangle3d + { + struct Triangle3dVertex + { + Vector3 Position = Vector3::Zero; + Vector3 Normal = Vector3::One; + Color Col = Color::Clear; + Vector2 Uv = Vector2::Zero; + }; + + std::array Vertices = {}; + std::string TextureName = {}; + RenderStage RenderStg = RenderStage::Model; + BlendMode BlendMd = BlendMode::Opaque; + }; +} diff --git a/Source/Renderer/Renderer.cpp b/Source/Renderer/Renderer.cpp index 479a88c..ca35ea4 100644 --- a/Source/Renderer/Renderer.cpp +++ b/Source/Renderer/Renderer.cpp @@ -9,6 +9,7 @@ #include "Renderer/Common/Resources/Scene/Shape2d.h" #include "Renderer/Common/Resources/Scene/Sprite2d.h" #include "Renderer/Common/Resources/Scene/Text2d.h" +#include "Renderer/Common/Resources/Scene/Triangle3d.h" #include "Renderer/Common/Utils.h" #include "Renderer/Common/View.h" #include "Utils/Parallel.h" @@ -88,19 +89,20 @@ namespace Silent::Renderer //{ // TASK(ProcessShapes2d()), // TASK(ProcessSprites2d()), - // TASK(ProcessGlyphs2d()) + // TASK(ProcessGlyphs2d()), + // TASK(ProcessTriangles3d()) //}; //executor.AddTasks(tasks).wait(); ProcessShapes2d(); ProcessSprites2d(); ProcessGlyphs2d(); + ProcessTriangles3d(); // Swap double buffer. std::swap(_doubleBuffer.Render, _doubleBuffer.Active); _doubleBuffer.Active.DrawCallCount = 0; _doubleBuffer.Active.Primitives2d.clear(); _doubleBuffer.Active.Primitives3d.clear(); - _doubleBuffer.Active.DebugPrimitives3d.clear(); _doubleBuffer.Active.DebugGuiDrawCalls.clear(); _doubleBuffer.Active.TextureUploadQueue.clear(); _doubleBuffer.Active.TextureReleaseQueue.clear(); @@ -333,6 +335,19 @@ namespace Silent::Renderer return true; } + bool RendererBase::SubmitTriangle3d(const Triangle3d& tri) + { + if (_triangles3d.size() >= TRI_3D_COUNT_MAX) + { + Debug::Log("Attempted to submit 3D triangle to full container.", + Debug::LogLevel::Warning, Debug::LogMode::Debug); + return false; + } + + _triangles3d.push_back(tri); + return true; + } + void RendererBase::SubmitDebugGui(std::function drawFunc) { if (_doubleBuffer.Active.DebugGuiDrawCalls.size() >= DEBUG_GUI_COUNT_MAX) @@ -349,7 +364,6 @@ namespace Silent::Renderer } _doubleBuffer.Active.DebugGuiDrawCalls.push_back(drawFunc); - return; } void RendererBase::SubmitDebugLine(const Vector2& from, const Vector2& to, const Color& color, ScaleMode scaleMode, @@ -361,7 +375,7 @@ namespace Silent::Renderer } auto line = Shape2d::CreateLine(from, to, color, color, 0, scaleMode, BlendMode::Add); - _doubleBuffer.Active.DebugShapes2d.push_back(line); + _shapes2d.push_back(line); } void RendererBase::SubmitDebugLine(const Vector3& from, const Vector3& to, const Color& color, Debug::Page page) @@ -371,8 +385,9 @@ namespace Silent::Renderer return; } + // @todo Submit to `_triangles3d`. auto line = Primitive3d::CreateDebugLine(from, to, color); - _doubleBuffer.Active.DebugPrimitives3d.push_back(line); + //_doubleBuffer.Active.Primitives3d.push_back(line); } void RendererBase::SubmitDebugTriangle(const Vector2& vert0, const Vector2& vert1, const Vector2& vert2, @@ -384,7 +399,7 @@ namespace Silent::Renderer } auto tri = Shape2d::CreateTriangle(vert0, vert1, vert2, color, color, color, 0, scaleMode, BlendMode::Add); - _doubleBuffer.Active.DebugShapes2d.push_back(tri); + _shapes2d.push_back(tri); } void RendererBase::SubmitDebugTriangle(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, @@ -395,17 +410,20 @@ namespace Silent::Renderer return; } + // @todo Submit to `_triangles3d`. auto tri = Primitive3d::CreateDebugTriangle(vert0, vert1, vert2, color); - _doubleBuffer.Active.DebugPrimitives3d.push_back(tri); + //_doubleBuffer.Active.Primitives3d.push_back(tri); } void RendererBase::InitializeDoubleBuffer() { auto ReserveMemory = [](DoubleBuffer::Data& data) { + data.DebugGuiDrawCalls.reserve(DEBUG_GUI_COUNT_MAX); data.Primitives2d.reserve(SHAPE_2D_COUNT_MAX + SPRITE_2D_COUNT_MAX + GLYPH_2D_COUNT_MAX); + data.Primitives3d.reserve(TRI_3D_COUNT_MAX); }; ReserveMemory(_doubleBuffer.Active); ReserveMemory(_doubleBuffer.Render); @@ -413,6 +431,7 @@ namespace Silent::Renderer _shapes2d.reserve(SHAPE_2D_COUNT_MAX); _sprites2d.reserve(SPRITE_2D_COUNT_MAX); _glyphs2d.reserve(GLYPH_2D_COUNT_MAX); + _triangles3d.reserve(TRI_3D_COUNT_MAX); } void RendererBase::ProcessShapes2d() @@ -653,6 +672,51 @@ namespace Silent::Renderer _glyphs2d.clear(); } + void RendererBase::ProcessTriangles3d() + { + auto viewProjMat = _view.GetMatrix(glm::radians(45.0f), GetViewportAspectRatio(), 0.1f, 100.0f); + + for (const auto& tri : _triangles3d) + { + auto verts = std::vector{}; + verts.reserve(tri.Vertices.size()); + for (const auto& vert : tri.Vertices) + { + // Transform world position to clip space. + auto worldPos = Vector4(vert.Position.x, vert.Position.y, vert.Position.z, 1.0f); + auto clipSpace = viewProjMat * worldPos; + + // Perspective divide to reach NDC `[-1.0f, 1.0f]`. + auto ndc = Vector3(clipSpace.x / clipSpace.w, + clipSpace.y / clipSpace.w, + clipSpace.z / clipSpace.w); + + verts.push_back(Vertex3d + { + .Position = ndc, + .Normal = vert.Normal, + .Col = vert.Col, + .Uv = vert.Uv + }); + } + + // Add 3D primitive. + // @lock Restrict 3D primitives access. + { + auto lock = ParallelLock(_primitives3dMutex); + + _doubleBuffer.Active.Primitives3d.push_back(Primitive3d + { + // @todo More here. + .Vertices = std::move(verts), + .RenderStg = RenderStage::Model + }); + } + } + + _triangles3d.clear(); + } + void RendererBase::SortRenderBufferData() { auto& executor = g_App.GetExecutor(); diff --git a/Source/Renderer/Renderer.h b/Source/Renderer/Renderer.h index f02e136..396aedd 100644 --- a/Source/Renderer/Renderer.h +++ b/Source/Renderer/Renderer.h @@ -7,6 +7,7 @@ #include "Renderer/Common/Resources/Scene/Shape2d.h" #include "Renderer/Common/Resources/Scene/Sprite2d.h" #include "Renderer/Common/Resources/Scene/Text2d.h" +#include "Renderer/Common/Resources/Scene/Triangle3d.h" #include "Renderer/Common/Resources/MeshCache.h" #include "Renderer/Common/Resources/TextureCache.h" #include "Renderer/Common/View.h" @@ -28,8 +29,6 @@ namespace Silent::Renderer std::vector Primitives2d = {}; std::vector Primitives3d = {}; - std::vector DebugShapes2d = {}; - std::vector DebugPrimitives3d = {}; std::vector> DebugGuiDrawCalls = {}; std::vector TextureUploadQueue = {}; /** Asset names. */ @@ -52,19 +51,21 @@ namespace Silent::Renderer RendererType _type = RendererType::SdlGpu; SDL_Window* _window = nullptr; - View _view = View(); - Color _clearColor = Color::Clear; bool _isResized = false; + Color _clearColor = Color::Clear; + View _view = View(); DoubleBuffer _doubleBuffer = {}; std::unique_ptr _textures = nullptr; std::unique_ptr _meshes = nullptr; std::mutex _primitives2dMutex = {}; + std::mutex _primitives3dMutex = {}; - std::vector _shapes2d = {}; // } @todo Not really renderer objects. Should be part of an external system. - std::vector _sprites2d = {}; // } - std::vector _glyphs2d = {}; // } + std::vector _shapes2d = {}; // } @todo Not really renderer objects. Should be part of an external system. + std::vector _sprites2d = {}; // } + std::vector _glyphs2d = {}; // } + std::vector _triangles3d = {}; // } public: // ============= @@ -152,10 +153,10 @@ namespace Silent::Renderer */ void QueueMeshRelease(const std::string& assetName); - /** @brief Submits an immediate-mode 2D screen shape for drawing. + /** @brief Submits an immediate-mode 2D untextured screen shape for drawing. * - * @param prim 2D screen shape to draw. - * @return `true` if the 2D screen shape was successfully submitted, `false` otherwise. + * @param prim 2D untextured screen shape to draw. + * @return `true` if the 2D untextured screen shape was successfully submitted, `false` otherwise. */ bool SubmitShape2d(const Shape2d& shape); @@ -173,6 +174,13 @@ namespace Silent::Renderer */ bool SubmitText2d(const Text2d& text); + /** @brief Submits an immediate-mode 3D textured triangle for drawing. + * + * @param tri 3D textured triangle to draw. + * @return `true` if the 3D textured triangle was successfully submitted, `false` otherwise. + */ + bool SubmitTriangle3d(const Triangle3d& tri); + /** @brief Initializes the renderer and its subsystems. * * @param window Window to claim as the render surface. @@ -255,15 +263,18 @@ namespace Silent::Renderer /** @brief Initializes the double buffer. */ void InitializeDoubleBuffer(); - /** @brief Processes 2D sprites into 2d primitives. */ + /** @brief Processes immediate-mode 2D sprites into 2D primitives. */ void ProcessSprites2d(); - /** @brief Processes 2D shapes into 2d primitives. */ + /** @brief Processes immediate-mode 2D shapes into 2D primitives. */ void ProcessShapes2d(); - /** @brief Processes 2D glyphs into 2d primitives. */ + /** @brief Processes immediate-mode 2D glyphs into 2D primitives. */ void ProcessGlyphs2d(); + /** @brief Processes immediate-mode 3D triangles into 3D primitives. */ + void ProcessTriangles3d(); + /** @brief Sorts render buffer data in the double buffer. * Called at the start of `Update`. */ From 5571401b7f7de36ada5fb455806d10ffd8495cc3 Mon Sep 17 00:00:00 2001 From: Sezz Date: Fri, 3 Apr 2026 01:11:22 +1100 Subject: [PATCH 5/6] Immediate mode 3D progress --- .../Backends/SdlGpu/Resources/MeshCache.cpp | 21 +++--- .../Backends/SdlGpu/Resources/MeshCache.h | 16 +++-- Source/Renderer/Backends/SdlGpu/SdlGpu.cpp | 66 ++++++++++++------- Source/Renderer/Backends/SdlGpu/SdlGpu.h | 10 +-- ...ingPongTexture.cpp => PinkPongTexture.cpp} | 0 Source/Renderer/Renderer.cpp | 26 ++++---- Source/Renderer/Renderer.h | 6 +- 7 files changed, 88 insertions(+), 57 deletions(-) rename Source/Renderer/Common/Resources/{PingPongTexture.cpp => PinkPongTexture.cpp} (100%) diff --git a/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.cpp b/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.cpp index 7d96b15..33652dd 100644 --- a/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.cpp +++ b/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.cpp @@ -14,16 +14,16 @@ using namespace Silent::Utils; namespace Silent::Renderer::SdlGpu { - MeshCache::MeshCache(VertexBuffer& vertBuffer) + MeshCache::MeshCache(SDL_GPUDevice& device, int vertCount, int idxCount, const std::string& name) { - _vertexBuffer = &vertBuffer; - _vertexAllocator = BlockAllocator(vertBuffer.GetVertexCapacity()); - _idxAllocator = BlockAllocator(vertBuffer.GetIdxCapacity()); + _vertexBuffer.Initialize(device, vertCount, idxCount, name); + _vertexAllocator = BlockAllocator(_vertexBuffer.GetVertexCapacity()); + _idxAllocator = BlockAllocator(_vertexBuffer.GetIdxCapacity()); } void MeshCache::Upload(SDL_GPUCopyPass& copyPass, - const std::vector& verts, const std::vector& idxs, - const std::string& name) + const std::vector& verts, const std::vector& idxs, + const std::string& name) { // Check if mesh with same name already exists. if (Find(_meshes, name) != nullptr) @@ -45,8 +45,8 @@ namespace Silent::Renderer::SdlGpu }); // Update GPU vertex buffer. - _vertexBuffer->UpdateVertices(copyPass, ToSpan(verts), vertOffset); - _vertexBuffer->UpdateIdxs(copyPass, ToSpan(idxs), idxOffset); + _vertexBuffer.UpdateVertices(copyPass, ToSpan(verts), vertOffset); + _vertexBuffer.UpdateIdxs(copyPass, ToSpan(idxs), idxOffset); } void MeshCache::Upload(SDL_GPUCopyPass& copyPass, const std::string& assetName) @@ -94,6 +94,11 @@ namespace Silent::Renderer::SdlGpu } } + void MeshCache::Bind(SDL_GPURenderPass& renderPass) + { + _vertexBuffer.Bind(renderPass, 0, 0); + } + void MeshCache::UploadIlm(SDL_GPUCopyPass& copyPass, const Asset& asset) { const auto data = asset.GetData(); diff --git a/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.h b/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.h index 02d6462..88f8cdf 100644 --- a/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.h +++ b/Source/Renderer/Backends/SdlGpu/Resources/MeshCache.h @@ -1,16 +1,14 @@ #pragma once #include "Assets/AssetStreamer.h" +#include "Renderer/Backends/SdlGpu/Resources/VertexBuffer.h" +#include "Renderer/Common/Resources/Buffers.h" #include "Renderer/Common/Resources/MeshCache.h" using namespace Silent::Assets; -namespace Silent::Renderer{ struct BufferVertex3d; } - namespace Silent::Renderer::SdlGpu { - template class VertexBuffer; - /** @brief GPU mesh cache. */ class MeshCache : public MeshCacheBase { @@ -19,7 +17,7 @@ namespace Silent::Renderer::SdlGpu // Fields // ======= - VertexBuffer* _vertexBuffer = nullptr; + VertexBuffer _vertexBuffer = {}; public: // ============= @@ -30,7 +28,7 @@ namespace Silent::Renderer::SdlGpu * * @param vertBuffer GPU 3D vertex buffer. */ - MeshCache(VertexBuffer& vertBuffer); + MeshCache(SDL_GPUDevice& device, int vertCount, int idxCount, const std::string& name); // ========== // Utilities @@ -54,6 +52,12 @@ namespace Silent::Renderer::SdlGpu */ void Upload(SDL_GPUCopyPass& copyPass, const std::string& assetName); + /** @brief Binds the cached meshes GPU buffer for drawing. + * + * @param renderPass Render pass. + */ + void Bind(SDL_GPURenderPass& renderPass); + private: // ======== // Helpers diff --git a/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp b/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp index 65ad09e..810a7b0 100644 --- a/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp +++ b/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp @@ -359,7 +359,7 @@ namespace Silent::Renderer::SdlGpu // Process copy pass. auto* copyPass = SDL_BeginGPUCopyPass(_commandBuffer); - CopyGpuPrimitives3d(*copyPass); + CopyImmediatePrimitives3d(*copyPass); SDL_EndGPUCopyPass(copyPass); @@ -380,7 +380,7 @@ namespace Silent::Renderer::SdlGpu }; auto& renderPass = *SDL_BeginGPURenderPass(_commandBuffer, &colorTargetInfo, 1, &depthTargetInfo); - _gpuBuffers.Vertices3d.Bind(renderPass, 0, 0); + GetMeshes().Bind(renderPass); _pipelines.Bind(renderPass, RenderStage::Model, BlendMode::Opaque); // @temp @@ -430,6 +430,31 @@ namespace Silent::Renderer::SdlGpu //--------------------------- + _gpuBuffers.ImmediateVertices3d.Bind(renderPass, 0, 0); + + // Draw 3D primitives. + for (const auto& batch : _drawBatches.Primitives3d) + { + _pipelines.Bind(renderPass, batch.RenderStg, batch.BlendMd); + + // Push uniforms. + PushFragmentUniform(batch.Uniform, 0); + + // Bind texture. + if (!batch.TextureName.empty()) + { + auto* tex = GetTextures()[batch.TextureName]; + if (tex != nullptr) + { + tex->Bind(renderPass, GetActiveSampler()); + } + } + + // Draw. + SDL_DrawGPUIndexedPrimitives(&renderPass, batch.VertexCount, 1, batch.IdxOffset, batch.VertexOffset, 0); + _doubleBuffer.Active.DrawCallCount++; + } + SDL_EndGPURenderPass(&renderPass); } @@ -480,7 +505,7 @@ namespace Silent::Renderer::SdlGpu // Process copy pass. auto* copyPass = SDL_BeginGPUCopyPass(_commandBuffer); - CopyGpuPrimitives2d(*copyPass); + CopyImmediatePrimitives2d(*copyPass); SDL_EndGPUCopyPass(copyPass); @@ -501,7 +526,7 @@ namespace Silent::Renderer::SdlGpu auto& renderPass = *SDL_BeginGPURenderPass(_commandBuffer, &colorTargetInfo, 1, &depthTargetInfo); // Draw 2D primitives. - _gpuBuffers.Vertices2d.Bind(renderPass, 0, 0); + _gpuBuffers.ImmediateVertices2d.Bind(renderPass, 0, 0); for (const auto& batch : _drawBatches.Primitives2d) { _pipelines.Bind(renderPass, batch.RenderStg, batch.BlendMd); @@ -716,11 +741,9 @@ namespace Silent::Renderer::SdlGpu // Initialize GPU buffers. _gpuBuffers.ViewportVertices.Initialize(*_device, QUAD_VERTEX_COUNT, QUAD_IDX_COUNT, "2D viewport vertices"); - _gpuBuffers.Vertices2d.Initialize(*_device, VERT_2D_COUNT_MAX, VERT_2D_IDX_COUNT_MAX, "2D vertices"); - _gpuBuffers.Vertices3d.Initialize(*_device, VERT_3D_COUNT_MAX, VERT_3D_IDX_COUNT_MAX, "3D vertices"); - - // Reserve mesh cache. - _meshes = std::make_unique(_gpuBuffers.Vertices3d); + _gpuBuffers.ImmediateVertices2d.Initialize(*_device, VERT_2D_COUNT_MAX, VERT_2D_IDX_COUNT_MAX, "Immediate 2D vertices"); + _gpuBuffers.ImmediateVertices3d.Initialize(*_device, VERT_3D_COUNT_MAX, VERT_3D_IDX_COUNT_MAX, "Immediate 3D vertices"); + _meshes = std::make_unique(*_device, VERT_3D_COUNT_MAX, VERT_3D_IDX_COUNT_MAX, "3D meshes vertices"); // Reserve draw batches. _drawBatches.Primitives2d.reserve(PRIM_2D_BATCH_COUNT_MAX); @@ -807,19 +830,19 @@ namespace Silent::Renderer::SdlGpu } } - void Renderer::CopyGpuPrimitives2d(SDL_GPUCopyPass& copyPass) + void Renderer::CopyImmediatePrimitives2d(SDL_GPUCopyPass& copyPass) { auto bufferVerts = std::vector{}; auto bufferIdxs = std::vector{}; // Reserve memory. - bufferVerts.reserve(_doubleBuffer.Render.Primitives2d.size() * QUAD_VERTEX_COUNT); - bufferIdxs.reserve(_doubleBuffer.Render.Primitives2d.size() * QUAD_IDX_COUNT); + bufferVerts.reserve(_doubleBuffer.Render.ImmediatePrimitives2d.size() * QUAD_VERTEX_COUNT); + bufferIdxs.reserve(_doubleBuffer.Render.ImmediatePrimitives2d.size() * QUAD_IDX_COUNT); // Create batched GPU buffer data. int vertOffset = 0; int idxOffset = 0; - for (const auto& prim : _doubleBuffer.Render.Primitives2d) + for (const auto& prim : _doubleBuffer.Render.ImmediatePrimitives2d) { // Add vertices. for (int i = 0; i < prim.Vertices.size(); i++) @@ -883,23 +906,23 @@ namespace Silent::Renderer::SdlGpu } // Update GPU buffer. - _gpuBuffers.Vertices2d.UpdateVertices(copyPass, ToSpan(bufferVerts), 0); - _gpuBuffers.Vertices2d.UpdateIdxs(copyPass, ToSpan(bufferIdxs), 0); + _gpuBuffers.ImmediateVertices2d.UpdateVertices(copyPass, ToSpan(bufferVerts), 0); + _gpuBuffers.ImmediateVertices2d.UpdateIdxs(copyPass, ToSpan(bufferIdxs), 0); } - void Renderer::CopyGpuPrimitives3d(SDL_GPUCopyPass& copyPass) + void Renderer::CopyImmediatePrimitives3d(SDL_GPUCopyPass& copyPass) { auto bufferVerts = std::vector{}; auto bufferIdxs = std::vector{}; // Reserve memory. - bufferVerts.reserve(_doubleBuffer.Render.Primitives3d.size() * QUAD_VERTEX_COUNT); - bufferIdxs.reserve(_doubleBuffer.Render.Primitives3d.size() * QUAD_IDX_COUNT); + bufferVerts.reserve(_doubleBuffer.Render.ImmediatePrimitives3d.size() * QUAD_VERTEX_COUNT); + bufferIdxs.reserve(_doubleBuffer.Render.ImmediatePrimitives3d.size() * QUAD_IDX_COUNT); // Create batched GPU buffer data. int vertOffset = 0; int idxOffset = 0; - for (const auto& prim : _doubleBuffer.Render.Primitives3d) + for (const auto& prim : _doubleBuffer.Render.ImmediatePrimitives3d) { // Add vertices. for (int i = 0; i < prim.Vertices.size(); i++) @@ -974,10 +997,9 @@ namespace Silent::Renderer::SdlGpu idxOffset += curIdxCount; } - // @todo Mesh cache also uses ths full span of this buffer. // Update GPU buffer. - //_gpuBuffers.Vertices3d.UpdateVertices(copyPass, ToSpan(bufferVerts), 0); - //_gpuBuffers.Vertices3d.UpdateIdxs(copyPass, ToSpan(bufferIdxs), 0); + _gpuBuffers.ImmediateVertices3d.UpdateVertices(copyPass, ToSpan(bufferVerts), 0); + _gpuBuffers.ImmediateVertices3d.UpdateIdxs(copyPass, ToSpan(bufferIdxs), 0); } void Renderer::CopyGpuViewportQuad(SDL_GPUCopyPass& copyPass) diff --git a/Source/Renderer/Backends/SdlGpu/SdlGpu.h b/Source/Renderer/Backends/SdlGpu/SdlGpu.h index c779c86..187631d 100644 --- a/Source/Renderer/Backends/SdlGpu/SdlGpu.h +++ b/Source/Renderer/Backends/SdlGpu/SdlGpu.h @@ -36,9 +36,9 @@ namespace Silent::Renderer::SdlGpu /** @brief GPU buffers. */ struct GpuBuffers { - VertexBuffer ViewportVertices = {}; - VertexBuffer Vertices2d = {}; - VertexBuffer Vertices3d = {}; + VertexBuffer ViewportVertices = {}; + VertexBuffer ImmediateVertices2d = {}; + VertexBuffer ImmediateVertices3d = {}; }; /** @brief SDL_gpu renderer backend. */ @@ -135,7 +135,7 @@ namespace Silent::Renderer::SdlGpu * * @param copyPass GPU copy pass. */ - void CopyGpuPrimitives2d(SDL_GPUCopyPass& copyPass); + void CopyImmediatePrimitives2d(SDL_GPUCopyPass& copyPass); /** @brief Converts render buffer data to 3D triangle GPU buffer data and uploads it to the GPU. * @@ -143,7 +143,7 @@ namespace Silent::Renderer::SdlGpu * * @param copyPass GPU copy pass. */ - void CopyGpuPrimitives3d(SDL_GPUCopyPass& copyPass); + void CopyImmediatePrimitives3d(SDL_GPUCopyPass& copyPass); /** @brief Converts render buffer data to viewport quad GPU buffer data and uploads it to the GPU. * diff --git a/Source/Renderer/Common/Resources/PingPongTexture.cpp b/Source/Renderer/Common/Resources/PinkPongTexture.cpp similarity index 100% rename from Source/Renderer/Common/Resources/PingPongTexture.cpp rename to Source/Renderer/Common/Resources/PinkPongTexture.cpp diff --git a/Source/Renderer/Renderer.cpp b/Source/Renderer/Renderer.cpp index ca35ea4..7fb10ec 100644 --- a/Source/Renderer/Renderer.cpp +++ b/Source/Renderer/Renderer.cpp @@ -101,8 +101,8 @@ namespace Silent::Renderer // Swap double buffer. std::swap(_doubleBuffer.Render, _doubleBuffer.Active); _doubleBuffer.Active.DrawCallCount = 0; - _doubleBuffer.Active.Primitives2d.clear(); - _doubleBuffer.Active.Primitives3d.clear(); + _doubleBuffer.Active.ImmediatePrimitives2d.clear(); + _doubleBuffer.Active.ImmediatePrimitives3d.clear(); _doubleBuffer.Active.DebugGuiDrawCalls.clear(); _doubleBuffer.Active.TextureUploadQueue.clear(); _doubleBuffer.Active.TextureReleaseQueue.clear(); @@ -288,8 +288,8 @@ namespace Silent::Renderer auto pos = adjTextPos + (relPos * aspectCorrection); // Compute scale. - auto relScale = Vector2((float)(shapedGlyph.Attribs.AtlasSize.x) / (float)(shapedGlyph.Attribs.AtlasSize.y), 1.0f) * - Vector2((float)(shapedGlyph.Attribs.AtlasSize.y) / (float)font->GetPointSize()); + auto relScale = Vector2((float)shapedGlyph.Attribs.AtlasSize.x / (float)shapedGlyph.Attribs.AtlasSize.y, 1.0f) * + Vector2((float)shapedGlyph.Attribs.AtlasSize.y / (float)font->GetPointSize()); auto scale = relScale * text.Scale; // Concatenate name for texture atlas containing glyph. @@ -420,10 +420,10 @@ namespace Silent::Renderer auto ReserveMemory = [](DoubleBuffer::Data& data) { data.DebugGuiDrawCalls.reserve(DEBUG_GUI_COUNT_MAX); - data.Primitives2d.reserve(SHAPE_2D_COUNT_MAX + - SPRITE_2D_COUNT_MAX + - GLYPH_2D_COUNT_MAX); - data.Primitives3d.reserve(TRI_3D_COUNT_MAX); + data.ImmediatePrimitives2d.reserve(SHAPE_2D_COUNT_MAX + + SPRITE_2D_COUNT_MAX + + GLYPH_2D_COUNT_MAX); + data.ImmediatePrimitives3d.reserve(TRI_3D_COUNT_MAX); }; ReserveMemory(_doubleBuffer.Active); ReserveMemory(_doubleBuffer.Render); @@ -482,7 +482,7 @@ namespace Silent::Renderer { auto lock = ParallelLock(_primitives2dMutex); - _doubleBuffer.Active.Primitives2d.push_back(Primitive2d + _doubleBuffer.Active.ImmediatePrimitives2d.push_back(Primitive2d { .Vertices = std::move(verts), .Depth = shape.Depth, @@ -586,7 +586,7 @@ namespace Silent::Renderer { auto lock = ParallelLock(_primitives2dMutex); - _doubleBuffer.Active.Primitives2d.push_back(Primitive2d + _doubleBuffer.Active.ImmediatePrimitives2d.push_back(Primitive2d { .Vertices = { @@ -645,7 +645,7 @@ namespace Silent::Renderer { auto lock = ParallelLock(_primitives2dMutex); - _doubleBuffer.Active.Primitives2d.push_back(Primitive2d + _doubleBuffer.Active.ImmediatePrimitives2d.push_back(Primitive2d { .Vertices = { @@ -705,7 +705,7 @@ namespace Silent::Renderer { auto lock = ParallelLock(_primitives3dMutex); - _doubleBuffer.Active.Primitives3d.push_back(Primitive3d + _doubleBuffer.Active.ImmediatePrimitives3d.push_back(Primitive3d { // @todo More here. .Vertices = std::move(verts), @@ -726,7 +726,7 @@ namespace Silent::Renderer // Sort 2D primitives. @todo Use sort keys? [&]() { - Sort(_doubleBuffer.Render.Primitives2d, [](const Primitive2d& prim0, const Primitive2d& prim1) + Sort(_doubleBuffer.Render.ImmediatePrimitives2d, [](const Primitive2d& prim0, const Primitive2d& prim1) { return prim0.Depth > prim1.Depth; }); diff --git a/Source/Renderer/Renderer.h b/Source/Renderer/Renderer.h index 396aedd..95079f2 100644 --- a/Source/Renderer/Renderer.h +++ b/Source/Renderer/Renderer.h @@ -27,9 +27,9 @@ namespace Silent::Renderer { int DrawCallCount = 0; - std::vector Primitives2d = {}; - std::vector Primitives3d = {}; - std::vector> DebugGuiDrawCalls = {}; + std::vector ImmediatePrimitives2d = {}; + std::vector ImmediatePrimitives3d = {}; + std::vector> DebugGuiDrawCalls = {}; std::vector TextureUploadQueue = {}; /** Asset names. */ std::vector TextureReleaseQueue = {}; /** Asset names. */ From b53fd32b28332e6716546d1ff0ef340817bd9593 Mon Sep 17 00:00:00 2001 From: Sezz Date: Fri, 3 Apr 2026 13:10:15 +1100 Subject: [PATCH 6/6] Immediate-mode 3D progress --- Source/Renderer/Backends/SdlGpu/SdlGpu.cpp | 9 ++-- .../Common/Resources/Scene/Triangle3d.cpp | 41 +++++++++++++++++++ .../Common/Resources/Scene/Triangle3d.h | 5 ++- Source/Renderer/Common/Utils.cpp | 10 +++++ Source/Renderer/Common/Utils.h | 9 ++++ Source/Renderer/Renderer.cpp | 23 +++++++++-- Source/Utils/BlockAllocator.cpp | 1 + 7 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 Source/Renderer/Common/Resources/Scene/Triangle3d.cpp diff --git a/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp b/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp index 810a7b0..b67e66d 100644 --- a/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp +++ b/Source/Renderer/Backends/SdlGpu/SdlGpu.cpp @@ -435,9 +435,8 @@ namespace Silent::Renderer::SdlGpu // Draw 3D primitives. for (const auto& batch : _drawBatches.Primitives3d) { + // Bind pipeline. _pipelines.Bind(renderPass, batch.RenderStg, batch.BlendMd); - - // Push uniforms. PushFragmentUniform(batch.Uniform, 0); // Bind texture. @@ -482,6 +481,7 @@ namespace Silent::Renderer::SdlGpu // Dither. if (options->EnableDithering) { + // Bind pipeline. _pipelines.Bind(renderPass, RenderStage::Dither, BlendMode::Opaque); // Bind render texture. @@ -529,9 +529,8 @@ namespace Silent::Renderer::SdlGpu _gpuBuffers.ImmediateVertices2d.Bind(renderPass, 0, 0); for (const auto& batch : _drawBatches.Primitives2d) { + // Bind pipeline. _pipelines.Bind(renderPass, batch.RenderStg, batch.BlendMd); - - // Push uniform. PushFragmentUniform(batch.Uniform, 0); // Bind texture. @@ -737,7 +736,7 @@ namespace Silent::Renderer::SdlGpu GLYPH_2D_COUNT_MAX; constexpr int VERT_3D_COUNT_MAX = TRI_3D_VERT_COUNT_MAX; constexpr int VERT_3D_IDX_COUNT_MAX = VERT_3D_COUNT_MAX; - constexpr int PRIM_3D_BATCH_COUNT_MAX = VERT_3D_IDX_COUNT_MAX / TRI_VERTEX_COUNT; + constexpr int PRIM_3D_BATCH_COUNT_MAX = (VERT_3D_IDX_COUNT_MAX / TRI_VERTEX_COUNT) * 2; // @todo Check, because immediate + models. // Initialize GPU buffers. _gpuBuffers.ViewportVertices.Initialize(*_device, QUAD_VERTEX_COUNT, QUAD_IDX_COUNT, "2D viewport vertices"); diff --git a/Source/Renderer/Common/Resources/Scene/Triangle3d.cpp b/Source/Renderer/Common/Resources/Scene/Triangle3d.cpp new file mode 100644 index 0000000..dd6fba5 --- /dev/null +++ b/Source/Renderer/Common/Resources/Scene/Triangle3d.cpp @@ -0,0 +1,41 @@ +#include "Framework.h" +#include "Renderer/Common/Resources/Scene/Triangle3d.h" + +#include "Renderer/Common/Enums.h" +#include "Renderer/Common/Utils.h" + +namespace Silent::Renderer +{ + Triangle3d Triangle3d::CreateTriangle3d(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, + const Color& color, + BlendMode blendMode) + { + auto normal = GetNormal(vert0, vert1, vert2); + + return Triangle3d + { + .Vertices = + { + Triangle3dVertex + { + .Position = vert0, + .Normal = normal, + .Col = color + }, + Triangle3dVertex + { + .Position = vert1, + .Normal = normal, + .Col = color + }, + Triangle3dVertex + { + .Position = vert2, + .Normal = normal, + .Col = color + }, + }, + .BlendMd = blendMode + }; + } +} diff --git a/Source/Renderer/Common/Resources/Scene/Triangle3d.h b/Source/Renderer/Common/Resources/Scene/Triangle3d.h index f62e9c6..f720eb5 100644 --- a/Source/Renderer/Common/Resources/Scene/Triangle3d.h +++ b/Source/Renderer/Common/Resources/Scene/Triangle3d.h @@ -19,7 +19,10 @@ namespace Silent::Renderer std::array Vertices = {}; std::string TextureName = {}; - RenderStage RenderStg = RenderStage::Model; BlendMode BlendMd = BlendMode::Opaque; + + static Triangle3d CreateTriangle3d(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2, + const Color& color, + BlendMode blendMode = BlendMode::Opaque); }; } diff --git a/Source/Renderer/Common/Utils.cpp b/Source/Renderer/Common/Utils.cpp index 9dced48..74f74b7 100644 --- a/Source/Renderer/Common/Utils.cpp +++ b/Source/Renderer/Common/Utils.cpp @@ -8,6 +8,16 @@ namespace Silent::Renderer { + Vector3 GetNormal(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2) + { + // Compute edges. + auto edge0 = vert1 - vert0; + auto edge1 = vert2 - vert0; + + // Compute edge normal. + return Vector3::Normalize(Vector3::Cross(edge0, edge1)); + } + Vector2 GetAlignmentPivot(AlignMode alignMode) { switch (alignMode) diff --git a/Source/Renderer/Common/Utils.h b/Source/Renderer/Common/Utils.h index f4505fb..bca32f0 100644 --- a/Source/Renderer/Common/Utils.h +++ b/Source/Renderer/Common/Utils.h @@ -5,6 +5,15 @@ namespace Silent::Renderer enum class AlignMode; enum class ScaleMode; + /** @brief Computes a triangle normal from vertices in a clockwise winding order. + * + * @param vert0 First vertex. + * @param vert1 Second vertex. + * @param vert2 Third vertex. + * @return Triangle normal. + */ + Vector3 GetNormal(const Vector3& vert0, const Vector3& vert1, const Vector3& vert2); + /** @brief Gets the normalized alignment pivot for a given alignment mode. * * @param alignMode Alignment mode. diff --git a/Source/Renderer/Renderer.cpp b/Source/Renderer/Renderer.cpp index 7fb10ec..32e5bc1 100644 --- a/Source/Renderer/Renderer.cpp +++ b/Source/Renderer/Renderer.cpp @@ -707,9 +707,14 @@ namespace Silent::Renderer _doubleBuffer.Active.ImmediatePrimitives3d.push_back(Primitive3d { - // @todo More here. - .Vertices = std::move(verts), - .RenderStg = RenderStage::Model + .Vertices = std::move(verts), + .TextureName = tri.TextureName, + .RenderStg = RenderStage::Model, + .BlendMd = tri.BlendMd, + .Uniform = UniformModel + { + .IsFastAlpha = tri.BlendMd == BlendMode::FastAlpha + } }); } } @@ -721,15 +726,25 @@ namespace Silent::Renderer { auto& executor = g_App.GetExecutor(); + // @todo Use sort keys? auto sortTasks = ParallelTasks { - // Sort 2D primitives. @todo Use sort keys? + // Sort 2D primitives. [&]() { Sort(_doubleBuffer.Render.ImmediatePrimitives2d, [](const Primitive2d& prim0, const Primitive2d& prim1) { return prim0.Depth > prim1.Depth; }); + }, + + // Sort 3D primitives. + [&]() + { + Sort(_doubleBuffer.Render.ImmediatePrimitives3d, [](const Primitive3d& prim0, const Primitive3d& prim1) + { + return true; + }); } }; executor.AddTasks(sortTasks).wait(); diff --git a/Source/Utils/BlockAllocator.cpp b/Source/Utils/BlockAllocator.cpp index 74a4487..3e22170 100644 --- a/Source/Utils/BlockAllocator.cpp +++ b/Source/Utils/BlockAllocator.cpp @@ -57,6 +57,7 @@ namespace Silent::Utils } // Out of memory. + Debug::Log(Fmt("Attempted to allocate memory block of size {}: out of memory.", size), Debug::LogLevel::Warning); return NO_VALUE; }