diff options
| author | spicyjpeg <thatspicyjpeg@gmail.com> | 2022-07-31 18:04:59 +0200 |
|---|---|---|
| committer | spicyjpeg <thatspicyjpeg@gmail.com> | 2022-07-31 18:04:59 +0200 |
| commit | 9560a1427aec1681c5d0c2bc30190ce4b1ad8557 (patch) | |
| tree | 3aad9d0f632687b86b9639c714d49a1a9f1c2397 | |
| parent | 073a859acf16ccbc0f49364e38126bf2bf03aa3d (diff) | |
| download | psn00bsdk-9560a1427aec1681c5d0c2bc30190ce4b1ad8557.tar.gz | |
Rewrite libpsxspu in C and update sound examples
| -rw-r--r-- | examples/sound/spustream/main.c | 48 | ||||
| -rw-r--r-- | examples/sound/vagsample/main.c | 10 | ||||
| -rw-r--r-- | libpsn00b/include/hwregs_a.h | 11 | ||||
| -rw-r--r-- | libpsn00b/include/hwregs_c.h | 143 | ||||
| -rw-r--r-- | libpsn00b/include/psxspu.h | 55 | ||||
| -rw-r--r-- | libpsn00b/psxgpu/common.c | 3 | ||||
| -rw-r--r-- | libpsn00b/psxgpu/image.c | 14 | ||||
| -rw-r--r-- | libpsn00b/psxpress/mdec.c | 8 | ||||
| -rw-r--r-- | libpsn00b/psxspu/common.c | 154 | ||||
| -rw-r--r-- | libpsn00b/psxspu/readme.txt | 21 | ||||
| -rw-r--r-- | libpsn00b/psxspu/spuinit.s | 124 | ||||
| -rw-r--r-- | libpsn00b/psxspu/spukeyon.s | 17 | ||||
| -rw-r--r-- | libpsn00b/psxspu/spureverbon.s | 16 | ||||
| -rw-r--r-- | libpsn00b/psxspu/spusetkey.s | 26 | ||||
| -rw-r--r-- | libpsn00b/psxspu/spusetreverb.s | 25 | ||||
| -rw-r--r-- | libpsn00b/psxspu/spusetreverbaddr.s | 25 | ||||
| -rw-r--r-- | libpsn00b/psxspu/spusetvoiceraw.s | 60 | ||||
| -rw-r--r-- | libpsn00b/psxspu/transfer.s | 108 |
18 files changed, 318 insertions, 550 deletions
diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c index 6b9db93..2ad122c 100644 --- a/examples/sound/spustream/main.c +++ b/examples/sound/spustream/main.c @@ -123,8 +123,8 @@ typedef struct { } DB; typedef struct { - DB db[2]; - uint32_t db_active; + DB db[2]; + int db_active; } CONTEXT; void init_context(CONTEXT *ctx) { @@ -170,23 +170,13 @@ void display(CONTEXT *ctx) { /* Stream interrupt handlers */ -// This is a silent looping sample used to keep unused SPU channels busy, -// preventing them from accidentally triggering the SPU RAM interrupt and -// throwing off the timing (all channels are always reading sample data, even -// when "stopped"). It is 64 bytes as that is the minimum size for SPU DMA -// transfers, however only the first 16 bytes are kept. The rest is going to be -// overwritten by chunks. -// https://problemkaputt.de/psx-spx.htm#spuinterrupt -const uint8_t SPU_DUMMY_BLOCK[] = { - 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - // The first 4 KB of SPU RAM are reserved for capture buffers, so we have to -// place stream buffers after those. Sony's SPU library additionally places a -// dummy sample at 0x1000; we are going to do the same with the block above. +// place stream buffers after those. A dummy sample is additionally placed by +// default by the SPU library at 0x1000; it is going to be used here to keep +// unused SPU channels busy, preventing them from accidentally triggering the +// SPU RAM interrupt and throwing off the timing (all channels are always +// reading sample data, even when "stopped"). +// https://problemkaputt.de/psx-spx.htm#spuinterrupt #define DUMMY_BLOCK_ADDR 0x1000 #define BUFFER_START_ADDR 0x1010 #define CHUNK_SIZE (BUFFER_SIZE * NUM_CHANNELS) @@ -207,7 +197,7 @@ static volatile StreamContext str_ctx; // read from the CD and uploaded to SPU RAM. Due to DMA limitations it can't be // allocated on the stack (especially not in the interrupt callbacks' stack, // whose size is very limited). -static uint8_t sector_buffer[2048]; +static uint32_t sector_buffer[512]; void spu_irq_handler(void) { // Acknowledge the interrupt to ensure it can be triggered again. The only @@ -231,7 +221,7 @@ void spu_irq_handler(void) { str_ctx.spu_addr = BUFFER_START_ADDR + CHUNK_SIZE * str_ctx.db_active; SPU_IRQ_ADDR = SPU_RAM_ADDR(str_ctx.spu_addr); - for (uint32_t i = 0; i < NUM_CHANNELS; i++) + for (int i = 0; i < NUM_CHANNELS; i++) SPU_CH_LOOP_ADDR(i) = SPU_RAM_ADDR(str_ctx.spu_addr + BUFFER_SIZE * i); // Start loading the next chunk. cd_event_handler() will be called @@ -241,7 +231,7 @@ void spu_irq_handler(void) { CdControlF(CdlReadN, &pos); } -void cd_event_handler(int32_t event, uint8_t *payload) { +void cd_event_handler(int event, uint8_t *payload) { // Ignore all events other than a sector being ready. // TODO: read errors should be handled properly if (event != CdlDataReady) @@ -255,7 +245,7 @@ void cd_event_handler(int32_t event, uint8_t *payload) { // other buffer, as we're overriding loop addresses) at the end. // NOTE: this isn't actually necessary here as the stream converter script // already sets these flags in the file. - /*for (uint32_t i = 0; i < NUM_CHANNELS; i++) { + /*for (int i = 0; i < NUM_CHANNELS; i++) { if ( str_ctx.spu_pos >= (BUFFER_SIZE * i - 2048) && str_ctx.spu_pos < (BUFFER_SIZE * i) @@ -268,7 +258,7 @@ void cd_event_handler(int32_t event, uint8_t *payload) { // just treat the chunk as a single blob of data and copy it as-is; we only // have to trim the padding at the end (if any) to avoid overwriting other // data in SPU RAM. - uint32_t length = CHUNK_SIZE - str_ctx.spu_pos; + size_t length = CHUNK_SIZE - str_ctx.spu_pos; if (length > 2048) length = 2048; @@ -288,15 +278,9 @@ void cd_event_handler(int32_t event, uint8_t *payload) { /* Stream helpers */ void init_spu_channels(void) { - // Upload the dummy block to the SPU and play it on all channels, locking - // them up and stopping them from messing with the SPU interrupt. - // TODO: is this really necessary? (needs testing on real hardware) - SpuSetTransferStartAddr(DUMMY_BLOCK_ADDR); - SpuWrite(SPU_DUMMY_BLOCK, 64); - SPU_KEY_OFF = 0x00ffffff; - for (uint32_t i = 0; i < 24; i++) + for (int i = 0; i < 24; i++) SPU_CH_ADDR(i) = SPU_RAM_ADDR(DUMMY_BLOCK_ADDR); SPU_KEY_ON = 0x00ffffff; @@ -330,7 +314,7 @@ void init_stream(CdlFILE *file) { void start_stream(void) { SPU_KEY_OFF = CHANNEL_MASK; - for (uint32_t i = 0; i < NUM_CHANNELS; i++) { + for (int i = 0; i < NUM_CHANNELS; i++) { SPU_CH_ADDR(i) = SPU_RAM_ADDR(BUFFER_START_ADDR + BUFFER_SIZE * i); SPU_CH_FREQ(i) = SAMPLE_RATE; SPU_CH_ADSR(i) = 0x1fee80ff; // or 0x9fc080ff, 0xdff18087 @@ -429,7 +413,7 @@ int main(int argc, const char* argv[]) { // Only set the sample rate registers if necessary. if (pad->btn != 0xffff) { - for (uint32_t i = 0; i < NUM_CHANNELS; i++) + for (int i = 0; i < NUM_CHANNELS; i++) SPU_CH_FREQ(i) = sample_rate; } diff --git a/examples/sound/vagsample/main.c b/examples/sound/vagsample/main.c index 1ec3b8a..c79e68e 100644 --- a/examples/sound/vagsample/main.c +++ b/examples/sound/vagsample/main.c @@ -32,7 +32,7 @@ */ #include <stdio.h> -#include <sys/types.h> +#include <stdint.h> #include <psxetc.h> #include <psxgte.h> #include <psxgpu.h> @@ -104,8 +104,8 @@ void init(void) SpuSetTransferStartAddr(addr_temp); // Upload first sound clip and wait for transfer to finish - SpuWrite(((unsigned char*)proyt)+48, proyt_size-48); - SpuWait(); + SpuWrite((const uint32_t *) &proyt[48], proyt_size-48); + SpuIsTransferCompleted(SPU_TRANSFER_WAIT); // Obtain the address of the sound and advance address for the next one // Samples are addressed in 8-byte units, so it'll have to be divided by 8 @@ -116,8 +116,8 @@ void init(void) // Upload second sound clip SpuSetTransferStartAddr(addr_temp); - SpuWrite(((unsigned char*)tdfx)+48, tdfx_size-48); - SpuWait(); + SpuWrite((const uint32_t *) &tdfx[48], tdfx_size-48); + SpuIsTransferCompleted(SPU_TRANSFER_WAIT); // Obtain the address of the second sound clip tdfx_addr = addr_temp/8; diff --git a/libpsn00b/include/hwregs_a.h b/libpsn00b/include/hwregs_a.h index d8f6c72..c78b41a 100644 --- a/libpsn00b/include/hwregs_a.h +++ b/libpsn00b/include/hwregs_a.h @@ -1,10 +1,11 @@ -# Hardware register definitions for GNU assembler (as) -# -# Part of the PSn00bSDK Project by Lameguy64 -# 2019 Meido-Tek Productions +# PSn00bSDK hardware registers definitions +# (C) 2019-2022 Lameguy64, spicyjpeg - MPL licensed +## Constants -.set IOBASE, 0xbf80 # IO segment base (KSEG1) +.set IOBASE, 0x1f80 +.set F_CPU, 33868800 +.set F_GPU, 53222400 ## GPU diff --git a/libpsn00b/include/hwregs_c.h b/libpsn00b/include/hwregs_c.h index a9f4ee3..7b80590 100644 --- a/libpsn00b/include/hwregs_c.h +++ b/libpsn00b/include/hwregs_c.h @@ -14,116 +14,117 @@ /* Constants */ +#define IOBASE 0x1f800000 #define F_CPU 33868800UL #define F_GPU 53222400UL /* GPU */ -#define GPU_GP0 _MMIO32(0xbf801810) -#define GPU_GP1 _MMIO32(0xbf801814) +#define GPU_GP0 _MMIO32(IOBASE | 0x1810) +#define GPU_GP1 _MMIO32(IOBASE | 0x1814) /* CD drive */ -#define CD_STAT _MMIO8(0xbf801800) -#define CD_CMD _MMIO8(0xbf801801) -#define CD_DATA _MMIO8(0xbf801802) -#define CD_IRQ _MMIO8(0xbf801803) +#define CD_STAT _MMIO8(IOBASE | 0x1800) +#define CD_CMD _MMIO8(IOBASE | 0x1801) +#define CD_DATA _MMIO8(IOBASE | 0x1802) +#define CD_IRQ _MMIO8(IOBASE | 0x1803) -#define CD_REG(N) _MMIO8(0xbf801800 + (N)) +#define CD_REG(N) _MMIO8(IOBASE | 0x1800 + (N)) /* SPU */ -#define SPU_MASTER_VOL_L _MMIO16(0xbf801d80) -#define SPU_MASTER_VOL_R _MMIO16(0xbf801d82) -#define SPU_REVERB_VOL_L _MMIO16(0xbf801d84) -#define SPU_REVERB_VOL_R _MMIO16(0xbf801d86) -#define SPU_KEY_ON _MMIO32(0xbf801d88) -#define SPU_KEY_OFF _MMIO32(0xbf801d8c) -#define SPU_FM_MODE _MMIO32(0xbf801d90) -#define SPU_NOISE_MODE _MMIO32(0xbf801d94) -#define SPU_REVERB_ON _MMIO32(0xbf801d98) -#define SPU_CHAN_STATUS _MMIO32(0xbf801d9c) - -#define SPU_REVERB_ADDR _MMIO16(0xbf801da2) -#define SPU_IRQ_ADDR _MMIO16(0xbf801da4) -#define SPU_ADDR _MMIO16(0xbf801da6) -#define SPU_DATA _MMIO16(0xbf801da8) - -#define SPU_CTRL _MMIO16(0xbf801daa) -#define SPU_DMA_CTRL _MMIO16(0xbf801dac) -#define SPU_STAT _MMIO16(0xbf801dae) - -#define SPU_CD_VOL_L _MMIO16(0xbf801db0) -#define SPU_CD_VOL_R _MMIO16(0xbf801db2) -#define SPU_EXT_VOL_L _MMIO16(0xbf801db4) -#define SPU_EXT_VOL_R _MMIO16(0xbf801db6) -#define SPU_CURRENT_VOL_L _MMIO16(0xbf801db8) -#define SPU_CURRENT_VOL_R _MMIO16(0xbf801dba) +#define SPU_MASTER_VOL_L _MMIO16(IOBASE | 0x1d80) +#define SPU_MASTER_VOL_R _MMIO16(IOBASE | 0x1d82) +#define SPU_REVERB_VOL_L _MMIO16(IOBASE | 0x1d84) +#define SPU_REVERB_VOL_R _MMIO16(IOBASE | 0x1d86) +#define SPU_KEY_ON _MMIO32(IOBASE | 0x1d88) +#define SPU_KEY_OFF _MMIO32(IOBASE | 0x1d8c) +#define SPU_FM_MODE _MMIO32(IOBASE | 0x1d90) +#define SPU_NOISE_MODE _MMIO32(IOBASE | 0x1d94) +#define SPU_REVERB_ON _MMIO32(IOBASE | 0x1d98) +#define SPU_CHAN_STATUS _MMIO32(IOBASE | 0x1d9c) + +#define SPU_REVERB_ADDR _MMIO16(IOBASE | 0x1da2) +#define SPU_IRQ_ADDR _MMIO16(IOBASE | 0x1da4) +#define SPU_ADDR _MMIO16(IOBASE | 0x1da6) +#define SPU_DATA _MMIO16(IOBASE | 0x1da8) + +#define SPU_CTRL _MMIO16(IOBASE | 0x1daa) +#define SPU_DMA_CTRL _MMIO16(IOBASE | 0x1dac) +#define SPU_STAT _MMIO16(IOBASE | 0x1dae) + +#define SPU_CD_VOL_L _MMIO16(IOBASE | 0x1db0) +#define SPU_CD_VOL_R _MMIO16(IOBASE | 0x1db2) +#define SPU_EXT_VOL_L _MMIO16(IOBASE | 0x1db4) +#define SPU_EXT_VOL_R _MMIO16(IOBASE | 0x1db6) +#define SPU_CURRENT_VOL_L _MMIO16(IOBASE | 0x1db8) +#define SPU_CURRENT_VOL_R _MMIO16(IOBASE | 0x1dba) // These are not named SPU_VOICE_* to avoid name clashes with SPU attribute // flags defined in psxspu.h. -#define SPU_CH_VOL_L(N) _MMIO16(0xbf801c00 + 16 * (N)) -#define SPU_CH_VOL_R(N) _MMIO16(0xbf801c02 + 16 * (N)) -#define SPU_CH_FREQ(N) _MMIO16(0xbf801c04 + 16 * (N)) -#define SPU_CH_ADDR(N) _MMIO16(0xbf801c06 + 16 * (N)) -#define SPU_CH_ADSR(N) _MMIO32(0xbf801c08 + 16 * (N)) -#define SPU_CH_LOOP_ADDR(N) _MMIO16(0xbf801c0e + 16 * (N)) +#define SPU_CH_VOL_L(N) _MMIO16(IOBASE | 0x1c00 + 16 * (N)) +#define SPU_CH_VOL_R(N) _MMIO16(IOBASE | 0x1c02 + 16 * (N)) +#define SPU_CH_FREQ(N) _MMIO16(IOBASE | 0x1c04 + 16 * (N)) +#define SPU_CH_ADDR(N) _MMIO16(IOBASE | 0x1c06 + 16 * (N)) +#define SPU_CH_ADSR(N) _MMIO32(IOBASE | 0x1c08 + 16 * (N)) +#define SPU_CH_LOOP_ADDR(N) _MMIO16(IOBASE | 0x1c0e + 16 * (N)) /* MDEC */ -#define MDEC0 _MMIO32(0xbf801820) -#define MDEC1 _MMIO32(0xbf801824) +#define MDEC0 _MMIO32(IOBASE | 0x1820) +#define MDEC1 _MMIO32(IOBASE | 0x1824) /* SPI controller port */ // IMPORTANT: even though JOY_TXRX is a 32-bit register, it should only be // accessed as 8-bit. Reading it as 16 or 32-bit works fine on real hardware, // but leads to problems in some emulators. -#define JOY_TXRX _MMIO8(0xbf801040) -#define JOY_STAT _MMIO16(0xbf801044) -#define JOY_MODE _MMIO16(0xbf801048) -#define JOY_CTRL _MMIO16(0xbf80104a) -#define JOY_BAUD _MMIO16(0xbf80104e) +#define JOY_TXRX _MMIO8 (IOBASE | 0x1040) +#define JOY_STAT _MMIO16(IOBASE | 0x1044) +#define JOY_MODE _MMIO16(IOBASE | 0x1048) +#define JOY_CTRL _MMIO16(IOBASE | 0x104a) +#define JOY_BAUD _MMIO16(IOBASE | 0x104e) /* Serial port */ -#define SIO_TXRX _MMIO8(0xbf801050) -#define SIO_STAT _MMIO16(0xbf801054) -#define SIO_MODE _MMIO16(0xbf801058) -#define SIO_CTRL _MMIO16(0xbf80105a) -#define SIO_BAUD _MMIO16(0xbf80105e) +#define SIO_TXRX _MMIO8 (IOBASE | 0x1050) +#define SIO_STAT _MMIO16(IOBASE | 0x1054) +#define SIO_MODE _MMIO16(IOBASE | 0x1058) +#define SIO_CTRL _MMIO16(IOBASE | 0x105a) +#define SIO_BAUD _MMIO16(IOBASE | 0x105e) /* IRQ controller */ -#define IRQ_STAT _MMIO32(0xbf801070) -#define IRQ_MASK _MMIO32(0xbf801074) +#define IRQ_STAT _MMIO32(IOBASE | 0x1070) +#define IRQ_MASK _MMIO32(IOBASE | 0x1074) /* DMA */ -#define DMA_DPCR _MMIO32(0xbf8010f0) -#define DMA_DICR _MMIO32(0xbf8010f4) +#define DMA_DPCR _MMIO32(IOBASE | 0x10f0) +#define DMA_DICR _MMIO32(IOBASE | 0x10f4) -#define DMA_MADR(N) _MMIO32(0xbf801080 + 16 * (N)) -#define DMA_BCR(N) _MMIO32(0xbf801084 + 16 * (N)) -#define DMA_CHCR(N) _MMIO32(0xbf801088 + 16 * (N)) +#define DMA_MADR(N) _MMIO32(IOBASE | 0x1080 + 16 * (N)) +#define DMA_BCR(N) _MMIO32(IOBASE | 0x1084 + 16 * (N)) +#define DMA_CHCR(N) _MMIO32(IOBASE | 0x1088 + 16 * (N)) /* Timers */ -#define TIMER_VALUE(N) _MMIO32(0xbf801100 + 16 * (N)) -#define TIMER_CTRL(N) _MMIO32(0xbf801104 + 16 * (N)) -#define TIMER_RELOAD(N) _MMIO32(0xbf801108 + 16 * (N)) +#define TIMER_VALUE(N) _MMIO32(IOBASE | 0x1100 + 16 * (N)) +#define TIMER_CTRL(N) _MMIO32(IOBASE | 0x1104 + 16 * (N)) +#define TIMER_RELOAD(N) _MMIO32(IOBASE | 0x1108 + 16 * (N)) /* Memory control */ -#define EXP1_ADDR _MMIO32(0xbf801000) -#define EXP2_ADDR _MMIO32(0xbf801004) -#define EXP1_DELAY_SIZE _MMIO32(0xbf801008) -#define EXP3_DELAY_SIZE _MMIO32(0xbf80100c) -#define BIOS_DELAY_SIZE _MMIO32(0xbf801010) -#define SPU_DELAY_SIZE _MMIO32(0xbf801014) -#define CD_DELAY_SIZE _MMIO32(0xbf801018) -#define EXP2_DELAY_SIZE _MMIO32(0xbf80101c) -#define COM_DELAY_CFG _MMIO32(0xbf801020) -#define RAM_SIZE_CFG _MMIO32(0xbf801060) +#define EXP1_ADDR _MMIO32(IOBASE | 0x1000) +#define EXP2_ADDR _MMIO32(IOBASE | 0x1004) +#define EXP1_DELAY_SIZE _MMIO32(IOBASE | 0x1008) +#define EXP3_DELAY_SIZE _MMIO32(IOBASE | 0x100c) +#define BIOS_DELAY_SIZE _MMIO32(IOBASE | 0x1010) +#define SPU_DELAY_SIZE _MMIO32(IOBASE | 0x1014) +#define CD_DELAY_SIZE _MMIO32(IOBASE | 0x1018) +#define EXP2_DELAY_SIZE _MMIO32(IOBASE | 0x101c) +#define COM_DELAY_CFG _MMIO32(IOBASE | 0x1020) +#define RAM_SIZE_CFG _MMIO32(IOBASE | 0x1060) #endif diff --git a/libpsn00b/include/psxspu.h b/libpsn00b/include/psxspu.h index 36bbe70..e4c667a 100644 --- a/libpsn00b/include/psxspu.h +++ b/libpsn00b/include/psxspu.h @@ -7,6 +7,8 @@ #define __PSXSPU_H #include <stdint.h> +#include <stddef.h> +#include <hwregs_c.h> /* Definitions */ @@ -33,10 +35,15 @@ typedef enum _SPU_AttrMask { } SPU_AttrMask; typedef enum _SPU_TransferMode { - SPU_TRANSFER_BY_DMA = 0, - SPU_TRANSFER_BY_IO = 1 + SPU_TRANSFER_BY_DMA = 0, + SPU_TRANSFER_BY_IO = 1 } SPU_TransferMode; +typedef enum _SPU_WaitMode { + SPU_TRANSFER_PEEK = 0, + SPU_TRANSFER_WAIT = 1 +} SPU_WaitMode; + /* Structure definitions */ typedef struct _SpuVolume { @@ -66,6 +73,36 @@ typedef struct _SpuCommonAttr { SpuExtAttr cd, ext; } SpuCommonAttr; +/* "Useless" macros for official SDK compatibility */ + +#define SpuSetCommonMasterVolume(left, right) \ + (SPU_MASTER_VOL_L = (left), SPU_MASTER_VOL_R = (right)) +#define SpuSetCommonCDVolume(left, right) \ + (SPU_CD_VOL_L = (left), SPU_CD_VOL_R = (right)) +#define SpuSetCommonCDReverb(enable) \ + ((enable) ? (SPU_CTRL |= 0x0004) : (SPU_CTRL &= 0xfffb)) +#define SpuSetCommonExtVolume(left, right) \ + (SPU_EXT_VOL_L = (left), SPU_EXT_VOL_R = (right)) +#define SpuSetCommonExtReverb(enable) \ + ((enable) ? (SPU_CTRL |= 0x0002) : (SPU_CTRL &= 0xfffd)) + +#define SpuSetReverbAddr(addr) \ + (SPU_REVERB_ADDR = ((addr) + 7) / 8) +#define SpuSetIRQAddr(addr) \ + (SPU_IRQ_ADDR = ((addr) + 7) / 8) + +#define SpuSetVoiceVolume(ch, left, right) \ + (SPU_CH_VOL_L(ch) = (left), SPU_CH_VOL_R(ch) = (right)) +#define SpuSetVoicePitch(ch, pitch) \ + (SPU_CH_FREQ(ch) = (pitch)) +#define SpuSetVoiceStartAddr(ch, addr) \ + (SPU_CH_ADDR(ch) = ((addr) + 7) / 8) +#define SpuSetVoiceADSR(ch, ar, dr, sr, rr, sl) \ + (SPU_CH_ADSR(ch) = ((sl)) | ((dr) << 4) | ((ar) << 8) | ((rr) << 16) | ((sr) << 22) | (1 << 30)) + +#define SpuSetKey(enable, voice_bit) \ + ((enable) ? (SPU_KEY_ON = (voice_bit)) : (SPU_KEY_OFF = (voice_bit))) + /* Public API */ #ifdef __cplusplus @@ -74,15 +111,11 @@ extern "C" { void SpuInit(void); -void SpuReverbOn(int voice); -void SpuSetReverbAddr(int addr); -void SpuSetReverbVolume(int left, int right); -void SpuSetKey(int on_off, uint32_t voice_bit); - -int SpuSetTransferMode(int mode); -int SpuSetTransferStartAddr(int addr); -int SpuWrite(const uint8_t *addr, int size); -void SpuWait(void); +void SpuRead(const uint32_t *data, size_t size); +void SpuWrite(const uint32_t *data, size_t size); +SPU_TransferMode SpuSetTransferMode(SPU_TransferMode mode); +uint32_t SpuSetTransferStartAddr(uint32_t addr); +int SpuIsTransferCompleted(int mode); #ifdef __cplusplus } diff --git a/libpsn00b/psxgpu/common.c b/libpsn00b/psxgpu/common.c index bf115a1..a65e0c9 100644 --- a/libpsn00b/psxgpu/common.c +++ b/libpsn00b/psxgpu/common.c @@ -117,7 +117,6 @@ static void _vsync_halt(void) { printf("psxgpu: VSync() timeout\n"); ChangeClearPAD(0); ChangeClearRCnt(3, 0); - return; } int VSync(int mode) { @@ -152,7 +151,7 @@ int DrawSync(int mode) { if (mode) return (DMA_BCR(2) >> 16); - // Wait for the queue to become empty, to make sure no . + // Wait for the queue to become empty. // TODO: add a timeout while (_queue_length) __asm__ volatile(""); diff --git a/libpsn00b/psxgpu/image.c b/libpsn00b/psxgpu/image.c index b3e5678..da51e7d 100644 --- a/libpsn00b/psxgpu/image.c +++ b/libpsn00b/psxgpu/image.c @@ -10,7 +10,7 @@ #define DMA_CHUNK_LENGTH 8 -/* Common internal load/store function */ +/* VRAM transfer API */ static void _load_store_image( uint32_t command, @@ -23,8 +23,10 @@ static void _load_store_image( printf("psxgpu: can't transfer an odd number of pixels\n"); length /= 2; - if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) - printf("psxgpu: transfer data length (%d) is not a multiple of %d\n", length, DMA_CHUNK_LENGTH); + if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) { + printf("psxgpu: transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH); + length += DMA_CHUNK_LENGTH - 1; + } DrawSync(0); GPU_GP1 = 0x04000000; // Disable DMA request @@ -45,17 +47,15 @@ static void _load_store_image( else DMA_BCR(2) = DMA_CHUNK_LENGTH | ((length / DMA_CHUNK_LENGTH) << 16); - DMA_CHCR(2) = 0x01000200 | !(mode & 1); + DMA_CHCR(2) = 0x01000200 | ((mode & 1) ^ 1); } -/* Public VRAM API */ - void LoadImage(const RECT *rect, const uint32_t *data) { _load_store_image(0xa0000000, 2, rect, (uint32_t *) data); } void StoreImage(const RECT *rect, uint32_t *data) { - _load_store_image(0xc0000000, 3, rect, (uint32_t *) data); + _load_store_image(0xc0000000, 3, rect, data); } /* .TIM image parsers */ diff --git a/libpsn00b/psxpress/mdec.c b/libpsn00b/psxpress/mdec.c index b8d16b5..9c82d6b 100644 --- a/libpsn00b/psxpress/mdec.c +++ b/libpsn00b/psxpress/mdec.c @@ -126,9 +126,11 @@ void DecDCTin(const uint32_t *data, int mode) { // data length as an argument rather than parsing it from the first 4 bytes of // the stream. void DecDCTinRaw(const uint32_t *data, size_t length) { - // NOTE: if length >= DMA_CHUNK_LENGTH then it also has to be a multiple of - // DMA_CHUNK_LENGTH, otherwise the DMA channel will get stuck waiting for - // more data indefinitely. + if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) { + printf("psxmdec: transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH); + length += DMA_CHUNK_LENGTH - 1; + } + DMA_MADR(0) = (uint32_t) data; if (length < DMA_CHUNK_LENGTH) DMA_BCR(0) = 0x00010000 | length; diff --git a/libpsn00b/psxspu/common.c b/libpsn00b/psxspu/common.c new file mode 100644 index 0000000..306cfd2 --- /dev/null +++ b/libpsn00b/psxspu/common.c @@ -0,0 +1,154 @@ +/* + * PSn00bSDK SPU library (common functions) + * (C) 2022 spicyjpeg - MPL licensed + */ + +#include <stdint.h> +#include <stdio.h> +#include <psxspu.h> +#include <hwregs_c.h> + +#define WRITABLE_AREA_ADDR 0x200 +#define DMA_CHUNK_LENGTH 16 +#define STATUS_TIMEOUT 0x100000 + +/* Internal globals */ + +static SPU_TransferMode _transfer_mode = SPU_TRANSFER_BY_DMA; +static uint16_t _transfer_addr = WRITABLE_AREA_ADDR; + +/* SPU initialization */ + +static void _wait_status(uint16_t mask, uint16_t value) { + for (int i = STATUS_TIMEOUT; i; i--) { + if ((SPU_STAT & mask) == value) + return; + } + + printf("psxspu: status register timeout (0x%04x)\n", SPU_STAT); +} + +void SpuInit(void) { + SPU_CTRL = 0x0000; // SPU disabled + _wait_status(0x001f, 0x0000); + + SPU_MASTER_VOL_L = 0; + SPU_MASTER_VOL_R = 0; + SPU_REVERB_VOL_L = 0; + SPU_REVERB_VOL_R = 0; + SPU_KEY_OFF = 0x00ffffff; + SPU_FM_MODE = 0; + SPU_NOISE_MODE = 0; + SPU_REVERB_ON = 0; + SPU_REVERB_ADDR = 0xfffe; + SPU_CD_VOL_L = 0; + SPU_CD_VOL_R = 0; + SPU_EXT_VOL_L = 0; + SPU_EXT_VOL_R = 0; + + for (int i = 0; i < 24; i++) { + SPU_CH_VOL_L(i) = 0; + SPU_CH_VOL_R(i) = 0; + SPU_CH_FREQ(i) = 0; + SPU_CH_ADDR(i) = 0; + } + + DMA_DPCR |= 0x000b0000; // Enable DMA4 + DMA_CHCR(4) = 0x00000201; // Stop DMA4 + + SPU_CTRL = 0xc011; // Enable SPU, DAC, CD audio, set manual transfer mode + _wait_status(0x001f, 0x0011); + + // Upload a dummy ADPCM block to the first 16 bytes of SPU RAM. This may be + // freely used or overwritten. + SPU_ADDR = WRITABLE_AREA_ADDR; + _wait_status(0x0400, 0x0000); + + SPU_DATA = 0x0500; + for (int i = 7; i; i--) + SPU_DATA = 0x0000; + + // Sony's implementation leaves everything muted, however it makes sense to + // turn up at least the master and CD audio volume by default. + SPU_MASTER_VOL_L = 0x3fff; + SPU_MASTER_VOL_R = 0x3fff; + SPU_CD_VOL_L = 0x3fff; + SPU_CD_VOL_R = 0x3fff; +} + +/* SPU RAM transfer API */ + +static void _load_store_data(uint32_t *data, size_t length, int mode) { + if (length % 4) + printf("psxspu: can't transfer a number of bytes that isn't multiple of 4\n"); + + length /= 4; + if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) { + printf("psxspu: transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH); + length += DMA_CHUNK_LENGTH - 1; + } + + SPU_CTRL &= 0xffcf; // Disable DMA request + _wait_status(0x0030, 0x0000); + + // Enable DMA request for writing (2) or reading (3) + SPU_ADDR = _transfer_addr; + SPU_CTRL |= mode << 4; + _wait_status(0x0400, 0x0000); + + DMA_MADR(4) = (uint32_t) data; + if (length < DMA_CHUNK_LENGTH) + DMA_BCR(4) = 0x00010000 | length; + else + DMA_BCR(4) = DMA_CHUNK_LENGTH | ((length / DMA_CHUNK_LENGTH) << 16); + + DMA_CHCR(4) = 0x01000200 | ((mode & 1) ^ 1); +} + +void SpuRead(const uint32_t *data, size_t size) { + _load_store_data(data, size, 3); +} + +void SpuWrite(const uint32_t *data, size_t size) { + if (_transfer_addr < WRITABLE_AREA_ADDR) + return; + + // I/O transfer mode is not that useful, but whatever. + if (_transfer_mode) { + SPU_ADDR = _transfer_addr; + SPU_CTRL = (SPU_CTRL & 0xffcf) | 0x0010; // Manual transfer mode + _wait_status(0x0400, 0x0000); + + for (int i = size; i; i -= 4) { + uint32_t value = *(data++); + + SPU_DATA = (uint16_t) value; + SPU_DATA = (uint16_t) (value >> 16); + } + + return; + } + + _load_store_data(data, size, 2); +} + +SPU_TransferMode SpuSetTransferMode(SPU_TransferMode mode) { + _transfer_mode = mode; + return mode; +} + +uint32_t SpuSetTransferStartAddr(uint32_t addr) { + if (addr > 0x7ffff) + return 0; + + _transfer_addr = (addr + 7) / 8; + return addr; +} + +int SpuIsTransferCompleted(int mode) { + if (!mode) + return ((SPU_STAT >> 10) & 1) ^ 1; + + _wait_status(0x0400, 0x0000); + return 1; +} diff --git a/libpsn00b/psxspu/readme.txt b/libpsn00b/psxspu/readme.txt index 3ed90d0..07bbf97 100644 --- a/libpsn00b/psxspu/readme.txt +++ b/libpsn00b/psxspu/readme.txt @@ -3,29 +3,24 @@ PSX SPU Library, part of PSn00bSDK Licensed under Mozilla Public License - Open source implementation of the SPU library written mostly in MIPS -assembly. Currently only supports SPU init, uploading sample data using DMA -transfer and basic sample playback but is currently lacking a bunch of -important functions. - - Very work in progress currently. - +Open source implementation of the SPU library written entirely in C. Currently +only supports SPU initialization, reading/writing SPU RAM using DMA and basic +sample playback. Most of the official API is not going to be implemented as the +vast majority of it is just inefficient wrappers around accessing SPU registers +directly, which can be done already using the macros defined in hwregs_c.h. Library developer(s): - Lameguy64 - - + Lameguy64 (initial implementation in assembly) + spicyjpeg + Library header(s): psxspu.h - Todo list: * SPU RAM allocation routines yet to be implemented (heap must only be stored in main RAM and not SPU RAM like in the official SDK). - * SpuKeyOn() is actually not part of the official library. - * SPU reverb configuration functions yet to be implemented. diff --git a/libpsn00b/psxspu/spuinit.s b/libpsn00b/psxspu/spuinit.s deleted file mode 100644 index 6966213..0000000 --- a/libpsn00b/psxspu/spuinit.s +++ /dev/null @@ -1,124 +0,0 @@ -.set noreorder -.set noat - -.include "hwregs_a.h" - -.section .text - - -.global SpuInit -.type SpuInit, @function -SpuInit: - - addiu $sp, -4 - sw $ra, 0($sp) - - lui $v1, IOBASE - - # Stop and mute everything - - sh $0 , SPU_CTRL($v1) # Clear control settings - jal SpuCtrlSync - move $a0, $0 - - sh $0 , SPU_MASTER_VOL_L($v1) # Clear master volume - sh $0 , SPU_MASTER_VOL_R($v1) - - sh $0 , SPU_REVERB_VOL_L($v1) # Clear reverb volume - sh $0 , SPU_REVERB_VOL_R($v1) - - sh $0 , SPU_CD_VOL_L($v1) # Clear CD volume - sh $0 , SPU_CD_VOL_R($v1) - - sh $0 , SPU_EXT_VOL_L($v1) # Clear external audio volume - sh $0 , SPU_EXT_VOL_R($v1) - - sw $0 , SPU_FM_MODE($v1) # Turn off FM modes - sw $0 , SPU_NOISE_MODE($v1) # Turn off noise modes - sw $0 , SPU_REVERB_ON($v1) # Turn off reverb modes - - li $v0, 0xfffe - sh $v0, SPU_REVERB_ADDR($v1) - - lui $v0, 0x0200; - ori $v0, 0x3fff; - - # Clear all voices - - addiu $a1, $sp, -20 - sw $0 , 0($a1) - sw $0 , 4($a1) - sw $0 , 8($a1) - sw $0 , 12($a1) - - li $a2, 23 - -.Lclear_voices: - jal SpuSetVoiceRaw - move $a0, $a2 - addiu $a2, -1 - bgez $a2, .Lclear_voices - nop - - addiu $v0, $0, -1 # Set all keys to off - sw $v0, SPU_KEY_OFF($v1) - - li $v0, 0x4 # Set SPU data transfer control - sh $v0, SPU_DMA_CTRL($v1) # (usually always 0x4) - - lw $v0, DMA_DPCR($v1) # Enable DMA channel 4 (SPU DMA) - lui $at, 0xb - or $v0, $at - sw $v0, DMA_DPCR($v1) - - li $v0, 0xC001 # Enable SPU - sh $v0, SPU_CTRL($v1) - jal SpuCtrlSync - move $a0, $v0 - - li $v0, 0x3fff # Activate master volume - sh $v0, SPU_MASTER_VOL_L($v1) - sh $v0, SPU_MASTER_VOL_R($v1) - - sh $v0, SPU_CD_VOL_L($v1) # Activate CD volume - sh $v0, SPU_CD_VOL_R($v1) - - lw $ra, 0($sp) - addiu $sp, 4 - jr $ra - nop - - -# Waits until bits 0-5 of SPUSTAT are equal to SPUCNT -# -# Destroys v0, v1, a0 -# -.global SpuCtrlSync -.type SpuCtrlSync, @function -SpuCtrlSync: - lui $v1, IOBASE - andi $a0, 0x3f -.Lctrl_wait: - lhu $v0, SPU_STAT($v1) # Get SPUSTAT value - nop - andi $v0, 0x3f - bne $v0, $a0, .Lctrl_wait # Wait until SPUCNT and SPUSTAT are equal - nop - jr $ra - nop - - -# Waits until SPU has finished transfers -# -.global SpuWait -.type SpuWait, @function -SpuWait: - lui $v0, IOBASE - lhu $v0, SPU_STAT($v0) - nop - andi $v0, 0x400 - bnez $v0, SpuWait - nop - jr $ra - nop -
\ No newline at end of file diff --git a/libpsn00b/psxspu/spukeyon.s b/libpsn00b/psxspu/spukeyon.s deleted file mode 100644 index 33592c2..0000000 --- a/libpsn00b/psxspu/spukeyon.s +++ /dev/null @@ -1,17 +0,0 @@ -.set noreorder - -.include "hwregs_a.h" - -.section .data - - -.global SpuKeyOn -.type SpuKeyOn, @function -SpuKeyOn: - lui $v1, IOBASE - li $v0, 1 - sll $v0, $a0 - sw $v0, SPU_KEY_ON($v1) - sw $v0, SPU_KEY_ON($v1) - jr $ra - nop
\ No newline at end of file diff --git a/libpsn00b/psxspu/spureverbon.s b/libpsn00b/psxspu/spureverbon.s deleted file mode 100644 index 635fac3..0000000 --- a/libpsn00b/psxspu/spureverbon.s +++ /dev/null @@ -1,16 +0,0 @@ -.set noreorder - -.include "hwregs_a.h" - -.section .data - - -.global SpuReverbOn -.type SpuReverbOn, @function -SpuReverbOn: - lui $v1, IOBASE - li $v0, 1 - sll $v0, $a0 - sw $v0, SPU_REVERB_ON($v1) - jr $ra - nop
\ No newline at end of file diff --git a/libpsn00b/psxspu/spusetkey.s b/libpsn00b/psxspu/spusetkey.s deleted file mode 100644 index 4ad0cff..0000000 --- a/libpsn00b/psxspu/spusetkey.s +++ /dev/null @@ -1,26 +0,0 @@ -.set noreorder - -.include "hwregs_a.h" - -.section .data - - -.global SpuSetKey -.type SpuSetKey, @function -SpuSetKey: - # a0 - 0: key off, 1: key on - # a1 - Voice bit mask - - lui $a2, IOBASE - - beqz $a0, .Lkey_off - nop - - jr $ra - sw $a1, SPU_KEY_ON($a2) - -.Lkey_off: - - jr $ra - sw $a1, SPU_KEY_OFF($a2) -
\ No newline at end of file diff --git a/libpsn00b/psxspu/spusetreverb.s b/libpsn00b/psxspu/spusetreverb.s deleted file mode 100644 index 8257812..0000000 --- a/libpsn00b/psxspu/spusetreverb.s +++ /dev/null @@ -1,25 +0,0 @@ -.set noreorder - -.include "hwregs_a.h" - -.section .data - - -.global SpuSetReverb -.type SpuSetReverb, @function -SpuSetReverb: - addiu $sp, -4 - sw $ra, 0($sp) - - lui $v1, IOBASE - lhu $v0, SPU_CTRL($v1) - nop - ori $v0, 0x80 # Enable reverb - sh $v0, SPU_CTRL($v1) - jal SpuCtrlSync - move $a0, $v0 - - lw $ra, 0($sp) - addiu $sp, 4 - jr $ra - nop
\ No newline at end of file diff --git a/libpsn00b/psxspu/spusetreverbaddr.s b/libpsn00b/psxspu/spusetreverbaddr.s deleted file mode 100644 index 089a91a..0000000 --- a/libpsn00b/psxspu/spusetreverbaddr.s +++ /dev/null @@ -1,25 +0,0 @@ -.set noreorder - -.include "hwregs_a.h" - - -.section .text - -.global SpuSetReverbAddr -.type SpuSetReverbAddr, @function -SpuSetReverbAddr: - lui $a3, IOBASE - srl $a0, 3 - sh $a0, SPU_REVERB_ADDR($a3) - jr $ra - nop - - -.global SpuSetReverbVolume -.type SpuSetReverbVolume, @function -SpuSetReverbVolume: - lui $a3, IOBASE - sh $a0, SPU_REVERB_VOL_L($a3) - sh $a1, SPU_REVERB_VOL_R($a3) - jr $ra - nop
\ No newline at end of file diff --git a/libpsn00b/psxspu/spusetvoiceraw.s b/libpsn00b/psxspu/spusetvoiceraw.s deleted file mode 100644 index 43450f6..0000000 --- a/libpsn00b/psxspu/spusetvoiceraw.s +++ /dev/null @@ -1,60 +0,0 @@ -.set noreorder - -.include "hwregs_a.h" - - -.set PARAM_L, 0 -.set PARAM_R, 2 -.set PARAM_FREQ, 4 -.set PARAM_ADDR, 6 -.set PARAM_LOOP, 8 -.set PARAM_RES, 10 -.set PARAM_ADSR, 12 - - -.section .text - -.global SpuSetVoiceRaw -.type SpuSetVoiceRaw, @function -SpuSetVoiceRaw: - - # a0 - Voice number - # a1 - Address to parameters - - sll $a0, 4 - addiu $a0, SPU_VOICE_BASE - - lui $v1, IOBASE - or $a0, $v1 - - lhu $v0, PARAM_L($a1) - nop - sh $v0, SPU_VOICE_VOL_L($a0) - - lhu $v0, PARAM_R($a1) - nop - sh $v0, SPU_VOICE_VOL_R($a0) - - lhu $v0, PARAM_FREQ($a1) - nop - sh $v0, SPU_VOICE_FREQ($a0) - - lhu $v0, PARAM_ADDR($a1) - nop - sh $v0, SPU_VOICE_ADDR($a0) - - lhu $v0, PARAM_LOOP($a1) - nop - sh $v0, SPU_VOICE_LOOP($a0) - - - lw $v0, PARAM_ADSR($a1) - nop - sh $v0, SPU_VOICE_ADSR_L($a0) - srl $v0, 16 - sh $v0, SPU_VOICE_ADSR_H($a0) - - - jr $ra - nop -
\ No newline at end of file diff --git a/libpsn00b/psxspu/transfer.s b/libpsn00b/psxspu/transfer.s deleted file mode 100644 index adcdb33..0000000 --- a/libpsn00b/psxspu/transfer.s +++ /dev/null @@ -1,108 +0,0 @@ -.set noreorder - -.include "hwregs_a.h" - - -.section .text - -.global SpuSetTransferMode -.type SpuSetTransferMode, @function -SpuSetTransferMode: - la $v0, _spu_transfer_mode - sb $a0, 0($v0) - jr $ra - move $v0, $a0 - - -.global SpuSetTransferStartAddr -.type SpuSetTransferStartAddr, @function -SpuSetTransferStartAddr: - li $v0, 0x1000 # Check if value is valid - blt $a0, $v0, .Lbad_value - nop - lui $v0, 8 # 0x7ffff = (8<<16)-1 - addiu $v0, -1 - bgt $a0, $v0, .Lbad_value - nop - - la $v1, _spu_transfer_addr - srl $v0, $a0, 3 # Set transfer destination address - sh $v0, 0($v1) - - jr $ra - move $v0, $a0 - -.Lbad_value: - jr $ra - move $v0, $0 - - -.global SpuWrite -.type SpuWrite, @function -SpuWrite: - addiu $sp, -8 - sw $ra, 0($sp) - sw $a0, 4($sp) - - lui $a3, IOBASE - - lhu $v0, SPU_CTRL($a3) # Set transfer mode to Stop - nop - andi $v0, 0xffcf - sh $v0, SPU_CTRL($a3) - jal SpuCtrlSync - move $a0, $v0 - - la $v1, _spu_transfer_addr # Set SPU write address - lhu $v1, 0($v1) - nop - sh $v1, SPU_ADDR($a3) - - lhu $v0, SPU_CTRL($a3) # Set transfer mode to DMA write - nop - ori $v0, 0x20 - sh $v0, SPU_CTRL($a3) - #jal SpuCtrlSync # Locks up on most emulators (bit 5 in - #move $a0, $v0 # SPUSTAT likely not updating, seems to - # be okay to not wait for it on real HW) - - lw $a0, 4($sp) - -.Ldma_wait: # Wait for SPU to be ready for DMA - lhu $v0, SPU_STAT($a3) - nop - andi $v0, 0x400 # Bit 8 in SPUSTAT never changes to 1 on - bnez $v0, .Ldma_wait # emulators so use bit 10 instead - nop - - sw $a0, DMA4_MADR($a3) # Set DMA source address - - li $v0, 0x10 # 16 words per block (64 bytes) - addiu $a1, 63 # Add by 63 to ensure all bytes get sent - srl $a1, 6 # Equivalent to divide by 64 - andi $a1, 0xffff - sll $a1, 16 - or $v0, $a1 - sw $v0, DMA4_BCR($a3) - - lui $v0, 0x0100 # Commence transfer - ori $v0, 0x0201 - sw $v0, DMA4_CHCR($a3) - - lw $ra, 0($sp) - addiu $sp, 8 - jr $ra - nop - - -.section .data - -.global _spu_transfer_mode -.type _spu_transfer_mode, @object -_spu_transfer_mode: - .word 0x0 - -.global _spu_transfer_addr -.type _spu_transfer_addr, @object -_spu_transfer_addr: - .word 0x200
\ No newline at end of file |
