First implementation of sound and .XA playback

This commit is contained in:
Lucas S. Vieira 2024-07-19 03:20:04 -03:00
parent 907f0b0b6c
commit 2732d65bed
12 changed files with 283 additions and 10 deletions

2
assets/bgm/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
\rustyruin1.xa
\rustyruin2.xa

Binary file not shown.

4
assets/bgm/BGM001.txt Normal file
View file

@ -0,0 +1,4 @@
1 xa rustyruin1.xa 1 0
1 xa rustyruin2.xa 1 1
1 null
1 null

9
assets/bgm/generate_xa.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
for f in *.flac; do
psxavenc -f 37800 -t xa -b 4 -c 2 -F 1 -C 0 "$f" "${f%%.flac}.xa";
done
for f in *.txt; do
xainterleave 1 "$f" "${f%%.txt}.XA";
done

BIN
assets/bgm/rustyruin1.flac Normal file

Binary file not shown.

BIN
assets/bgm/rustyruin2.flac Normal file

Binary file not shown.

48
include/sound.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef SOUND_H
#define SOUND_H
#include <stdint.h>
#include <psxcd.h>
// .XA audio header
typedef struct {
uint8_t file;
uint8_t channel;
uint8_t submode;
uint8_t coding_info;
} XAHeader;
// .XA audio submode flags
typedef enum {
XA_END_OF_RECORD = 0x01,
XA_TYPE_VIDEO = 0x02,
XA_TYPE_AUDIO = 0x04,
XA_TYPE_DATA = 0x08,
XA_TRIGGER = 0x10,
XA_FORM2 = 0x20,
XA_REAL_TIME = 0x40,
XA_END_OF_FILE = 0x80,
} XASubmode;
// CD-ROM Sector encoding (for .XA playback)
typedef struct {
CdlLOC pos;
XAHeader xa_header[2];
uint8_t data[2048];
uint32_t edc;
uint8_t ecc[276];
} XACDSector;
void sound_init(void);
void sound_reset_mem(void);
void sound_update(void);
uint32_t sound_upload_sample(const uint32_t *data, uint32_t size);
uint32_t sound_get_cd_status(void);
void sound_play_xa(const char *filename, int double_speed, uint8_t channel);
void sound_stop_xa(void);
void sound_xa_set_channel(uint8_t channel);
void sound_xa_get_pos(uint8_t *minute, uint8_t *second, uint8_t *sector);
#endif

View file

@ -4,6 +4,8 @@
#include <psxgpu.h>
#include <psxgte.h>
#define BCD_TO_DEC(x) (((x & 0xF0) >> 4) * 10 + (x & 0x0F))
int RotTransPers(SVECTOR *v, uint32_t *xy0);
int RotAverageNclip4(SVECTOR *a, SVECTOR *b, SVECTOR *c, SVECTOR *d,
uint32_t *xy0, uint32_t *xy1, uint32_t *xy2, uint32_t *xy3,

View file

@ -21,11 +21,11 @@
type="data"
source="${PROJECT_SOURCE_DIR}/assets/sprites/SONIC.CHARA" />
</dir>
<dir name="AUDIO">
<!-- <dir name="AUDIO"> -->
<file name="BGM001.XA"
type="xa"
source="${PROJECT_SOURCE_DIR}/assets/bgm/BGM001.XA" />
</dir>
<!-- </dir> -->
<dummy sectors="1024"/>
</directory_tree>
</track>

View file

@ -123,13 +123,16 @@ chara_render_frame(Chara *chara, int16_t framenum, int16_t vx, int16_t vy, uint8
uint8_t tw, th;
tw = (u0 < 248 ? 8 : 7);
th = (v0 < 248 ? 8 : 7);
if(flipx) {
if(u0 > 0) { u0--; }
xy0.vx -= (right << 4);
} else {
xy0.vx -= (left << 3);
if (u0 > 0) u0--;
if (u0 + tw >= 254) tw--;
}
/* if(flipx) { */
/* xy0.vx -= (right << 4); */
/* } else { */
/* xy0.vx -= (left << 3); */
/* } */
xy0.vx -= (chara->width >> 1);
@ -156,7 +159,7 @@ chara_render_frame(Chara *chara, int16_t framenum, int16_t vx, int16_t vy, uint8
}
}
#define TEST_ADVANCE_TIMER 30
#define TEST_ADVANCE_TIMER 7
void
chara_render_test(Chara *chara)

View file

@ -1,3 +1,5 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
@ -8,8 +10,7 @@
#include "render.h"
#include "util.h"
#include "chara.h"
#include <stdlib.h>
#include <stdio.h>
#include "sound.h"
#define SPRTSZ 56
@ -50,6 +51,7 @@ engine_init()
{
setup_context();
set_clear_color(63, 0, 127);
sound_init();
CdInit();
uint32_t filelength;
@ -61,11 +63,19 @@ engine_init()
}
sonic_chara = load_chara("\\SPRITES\\SONIC.CHARA;1", &tim);
// Start playback after we don't need the CD anymore.
// NOTE: This .XA contains songs of different lengths, so there is a
// huge silence at the end of channel 0.
sound_play_xa("\\BGM001.XA;1", 0, 1);
}
void
engine_update()
{
sound_update();
if(x < (SPRTSZ >> 1) || x > (SCREEN_XRES - 32)) dx = -dx;
if(y < (SPRTSZ >> 1) || y > (SCREEN_YRES - 32)) dy = -dy;
@ -129,6 +139,16 @@ engine_draw()
increment_prim(sizeof(POLY_G4));
}
}
uint8_t minute, second, sector;
sound_xa_get_pos(&minute, &second, &sector);
char buffer[255] = { 0 };
snprintf(buffer, 255,
"%02u:%02u:%03u\n"
"CD status: %02x\n",
minute, second, sector,
sound_get_cd_status());
draw_text(8, 216, 0, buffer);
}
int

185
src/sound.c Normal file
View file

@ -0,0 +1,185 @@
#include "sound.h"
#include "util.h"
#include <psxspu.h>
#include <psxapi.h>
#include <assert.h>
// First 4KB of SPU RAM are reserved for capture buffers.
// psxspu additionally uploads a dummy sample (16 bytes) at 0x1000
// by default, so the samples must be placed after those.
#define SPU_ALLOC_START_ADDR 0x01010
#define SPU_ALLOC_MAX_ADDR 0x80000
// So for the SPU, just like we're doing for primitives,
// we're going to have an arena allocator. In other words,
// we hold a pointer to the first available address and keep
// incrementing it; the only way to deallocate is by removing
// everything (in this case, resetting this pointer).
static uint32_t next_sample_addr = SPU_ALLOC_START_ADDR;
// Used by the CD handler callback as a temporary area for
// sectors read from the CD. Due to DMA limitations, it can't
// be allocated on the stack -- especially not in the interrupt
// callback's stack, whose size is very limited.
static XACDSector _sector;
// Current .XA audio data start location.
static CdlLOC _xa_loc;
static int _xa_should_play = 0;
static uint32_t _cd_status = 0;
void
sound_init(void)
{
SpuInit();
sound_reset_mem();
}
void
sound_reset_mem(void)
{
next_sample_addr = SPU_ALLOC_START_ADDR;
}
void
sound_update(void)
{
CdControl(CdlNop, 0, 0);
_cd_status = CdStatus();
if(_cd_status == 0 && _xa_should_play) {
CdControl(CdlReadS, &_xa_loc, 0);
}
}
uint32_t
sound_get_cd_status(void)
{
return _cd_status;
}
uint32_t
sound_upload_sample(const uint32_t *data, uint32_t size)
{
uint32_t addr = next_sample_addr;
// Round size up to a multiple of 64, since DMA transfers
// 64-byte packages at once.
size = (size + 63) & ~63;
// Crash if we're going beyond allowed!
assert(addr + size <= SPU_ALLOC_MAX_ADDR);
SpuSetTransferMode(SPU_TRANSFER_BY_DMA);
SpuSetTransferStartAddr(addr);
SpuWrite(data, size);
SpuIsTransferCompleted(SPU_TRANSFER_WAIT);
next_sample_addr = addr + size;
return addr;
}
void _xacd_event_callback(CdlIntrResult, uint8_t *);
void
sound_play_xa(const char *filename, int double_speed, uint8_t channel)
{
CdlFILE file;
CdlFILTER filter;
if(!CdSearchFile(&file, filename)) {
printf("Could not find .XA file %s.\n", filename);
return;
}
int sectorn = CdPosToInt(&file.pos);
printf("%s: Sector %d (size: %d).\n", filename, sectorn, file.size);
// Save current file location
_xa_loc = file.pos;
// Hook .XA callback for auto stop/loop
EnterCriticalSection();
CdReadyCallback(_xacd_event_callback);
ExitCriticalSection();
// Set read mode for XA streaming (send XA to SPU, enable filter)
uint32_t mode = CdlModeRT | CdlModeSF;
if(double_speed) mode |= CdlModeSpeed;
CdControl(CdlSetmode, &mode, 0);
filter.file = 1;
filter.chan = channel;
CdControl(CdlSetfilter, &filter, 0);
CdControl(CdlReadS, &_xa_loc, 0);
_xa_should_play = 1;
}
// Callback for XA audio playback.
void
_xacd_event_callback(CdlIntrResult event, uint8_t * /* payload */)
{
// Only handle sector-ready events.
//if(event != CdlDataReady) {
//printf("Event: %d\n", event);
// return;
//}
/* // Fetch sector */
/* CdGetSector(&_sector, sizeof(XACDSector) / 4); */
/* // Check if it is part of the .XA audio file. */
/* // If it isn't, restart playback. */
/* // Be wary that the XA header has two copies of itself. */
/* // See: https://problemkaputt.de/psx-spx.htm#cdromsectorencoding */
/* if(!(_sector.xa_header[0].submode & XA_TYPE_AUDIO) && */
/* !(_sector.xa_header[1].submode & XA_TYPE_AUDIO)) { */
/* printf("Got submode %x / %x\n", _sector.xa_header[0].submode, _sector.xa_header[1].submode); */
/* //CdControlF(CdlReadS, &_xa_loc); */
/* } */
// End of playback is issuing an error, so...
if(event != CdlDiskError) return;
CdControlF(CdlReadS, &_xa_loc);
}
void
sound_stop_xa(void)
{
// It is more desirable to use a pause command instead
// of a full stop. Halting the playback completely also
// stops CD from spinning and may increase time until
// next playback
_xa_should_play = 0;
CdControl(CdlPause, 0, 0);
}
void
sound_xa_set_channel(uint8_t channel)
{
// Seamlessly change channel
CdlFILTER filter;
filter.file = 1;
filter.chan = channel;
CdControl(CdlSetfilter, &filter, 0);
}
void
sound_xa_get_pos(uint8_t *minute, uint8_t *second, uint8_t *sector)
{
static CdlLOCINFOL info;
if(_cd_status != 0x22) {
if(minute) *minute = 0;
if(second) *second = 0;
if(sector) *sector = 0;
return;
}
CdControl(CdlGetlocL, 0, (uint8_t *)&info);
CdSync(0, 0);
if(minute) *minute = BCD_TO_DEC(info.minute);
if(second) *second = BCD_TO_DEC(info.second);
if(sector) *sector = info.sector;
}