mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
161 lines
5.6 KiB
C
161 lines
5.6 KiB
C
/*
|
|
* Regression test for an ADPCM END block without REPEAT.
|
|
*
|
|
* The test includes spu.c so it can stage a voice exactly at the block
|
|
* boundary, without exposing emulator internals in the runtime API.
|
|
*
|
|
* Build/run: ctest -R spu_end_without_repeat_test
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
#include "../src/spu.c"
|
|
|
|
/* Dependencies owned by the surrounding runtime are inert in this unit test. */
|
|
uint64_t s_frame_count;
|
|
|
|
uint64_t psx_get_cycle_count(void) { return 0; }
|
|
|
|
void audio_trace_pcm(int tap, const int16_t *stereo, int frames) {
|
|
(void)tap;
|
|
(void)stereo;
|
|
(void)frames;
|
|
}
|
|
|
|
void audio_trace_event(uint16_t kind, uint32_t a, uint32_t b) {
|
|
(void)kind;
|
|
(void)a;
|
|
(void)b;
|
|
}
|
|
|
|
void psx_irq_raise(uint32_t bit, uint32_t detail) { (void)bit; (void)detail; }
|
|
|
|
uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t len) {
|
|
(void)data;
|
|
(void)len;
|
|
return crc;
|
|
}
|
|
|
|
bool spu_shadow_enabled(void) { return false; }
|
|
void spu_shadow_reset(void) {}
|
|
void spu_shadow_process(int16_t *canon, int frames) {
|
|
(void)canon;
|
|
(void)frames;
|
|
}
|
|
|
|
static int failures;
|
|
|
|
#define CHECK(condition, message) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
fprintf(stderr, "FAIL: %s\n", message); \
|
|
failures++; \
|
|
} else { \
|
|
printf("ok: %s\n", message); \
|
|
} \
|
|
} while (0)
|
|
|
|
static void fill_block(uint32_t addr, uint8_t flags, uint8_t packed) {
|
|
spu_ram[addr] = 0x00; /* filter 0, shift 0 */
|
|
spu_ram[addr + 1u] = flags;
|
|
memset(spu_ram + addr + 2u, packed, 14u);
|
|
}
|
|
|
|
static SpuVoice *stage_end_boundary(uint8_t flags, uint32_t repeat_addr) {
|
|
SpuVoice *v = &voices[0];
|
|
memset(v, 0, sizeof(*v));
|
|
v->active = 1;
|
|
v->cur_addr = 0x310u; /* the block after the terminator */
|
|
v->repeat_addr = repeat_addr;
|
|
v->sample_idx = SPU_BLOCK_SAMPLES;
|
|
v->env_level = 0x6000u;
|
|
v->adsr_phase = ADSR_SUSTAIN;
|
|
v->flags = flags;
|
|
spu_regs[2] = 0x1000u; /* one decoded sample per output sample */
|
|
return v;
|
|
}
|
|
|
|
static void test_end_without_repeat_is_silent(void) {
|
|
const uint32_t repeat_addr = 0x120u;
|
|
spu_init();
|
|
|
|
/* Distinguish the required repeat-address decode from an incorrect
|
|
* forward decode. Both blocks are deliberately non-silent: the envelope,
|
|
* rather than convenient zero sample data, must make the result silent. */
|
|
fill_block(repeat_addr, 0x04u, 0x11u);
|
|
fill_block(0x310u, 0x00u, 0x77u);
|
|
SpuVoice *v = stage_end_boundary(0x01u, repeat_addr);
|
|
|
|
int16_t sample = voice_next_sample(0);
|
|
|
|
CHECK(sample == 0, "END without REPEAT emits silence at the boundary");
|
|
CHECK(v->env_level == 0, "END without REPEAT forces the envelope to zero");
|
|
CHECK(v->adsr_phase == ADSR_RELEASE,
|
|
"END without REPEAT enters Release");
|
|
CHECK(!v->active, "zero-level Release deactivates the voice");
|
|
CHECK(v->samples[0] == 4096,
|
|
"decoder resumes at the latched repeat address, not forward RAM");
|
|
CHECK(spu_event_total() == 1, "END stop records exactly one event");
|
|
|
|
SpuEvent event;
|
|
CHECK(spu_event_get(&event, 1) == 1 && event.kind == SPU_EV_END_STOP,
|
|
"END stop event has the stop kind");
|
|
CHECK(event.addr == repeat_addr,
|
|
"END stop event reports the redirected repeat address");
|
|
|
|
(void)voice_next_sample(0);
|
|
CHECK(spu_event_total() == 1,
|
|
"an inactive one-shot does not retrigger its END transition");
|
|
}
|
|
|
|
static void test_end_with_repeat_keeps_playing(void) {
|
|
const uint32_t repeat_addr = 0x180u;
|
|
spu_init();
|
|
fill_block(repeat_addr, 0x00u, 0x11u);
|
|
SpuVoice *v = stage_end_boundary(0x03u, repeat_addr);
|
|
|
|
int16_t sample = voice_next_sample(0);
|
|
|
|
CHECK(sample != 0, "END with REPEAT remains audible");
|
|
CHECK(v->env_level != 0, "END with REPEAT preserves the envelope");
|
|
CHECK(v->adsr_phase == ADSR_SUSTAIN,
|
|
"END with REPEAT preserves the ADSR phase");
|
|
CHECK(v->active, "END with REPEAT keeps the voice active");
|
|
|
|
SpuEvent event;
|
|
CHECK(spu_event_get(&event, 1) == 1 && event.kind == SPU_EV_END_LOOP,
|
|
"END with REPEAT records a loop event");
|
|
}
|
|
|
|
static uint32_t overlay_frames;
|
|
static void test_overlay(int16_t* stereo, uint32_t frames) {
|
|
overlay_frames += frames;
|
|
for (uint32_t i = 0; i < frames * 2; ++i) stereo[i] = 1234;
|
|
}
|
|
static void test_pcm_overlay_paths(void) {
|
|
int16_t output[14];
|
|
spu_init();
|
|
spu_set_pcm_overlay(test_overlay);
|
|
spu_render_block(output, 7); /* disabled SPU / general path */
|
|
CHECK(overlay_frames == 7 && output[0] == 1234 && output[13] == 1234,
|
|
"overlay plays even with disabled native SPU");
|
|
spu_regs[reg_index(0x1F801DAAu)] = 0x8000;
|
|
spu_render_block(output, 7); /* enabled SPU without voices / fast path */
|
|
CHECK(overlay_frames == 14 && output[13] == 1234,
|
|
"voice-free SPU path also advances the overlay");
|
|
spu_set_pcm_overlay(NULL);
|
|
spu_render_block(output, 7);
|
|
CHECK(overlay_frames == 14 && output[0] == 0 && output[13] == 0,
|
|
"unbinding preserves native silence without advancing playback");
|
|
}
|
|
|
|
int main(void) {
|
|
test_end_without_repeat_is_silent();
|
|
test_end_with_repeat_keeps_playing();
|
|
test_pcm_overlay_paths();
|
|
printf(failures ? "FAILED (%d)\n" : "ALL PASS\n", failures);
|
|
return failures ? 1 : 0;
|
|
}
|