mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 13:28:02 +03:00
First implementation of sound and .XA playback
This commit is contained in:
parent
907f0b0b6c
commit
2732d65bed
12 changed files with 283 additions and 10 deletions
2
assets/bgm/.gitignore
vendored
Normal file
2
assets/bgm/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
\rustyruin1.xa
|
||||
\rustyruin2.xa
|
Binary file not shown.
4
assets/bgm/BGM001.txt
Normal file
4
assets/bgm/BGM001.txt
Normal 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
9
assets/bgm/generate_xa.sh
Executable 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
BIN
assets/bgm/rustyruin1.flac
Normal file
Binary file not shown.
BIN
assets/bgm/rustyruin2.flac
Normal file
BIN
assets/bgm/rustyruin2.flac
Normal file
Binary file not shown.
48
include/sound.h
Normal file
48
include/sound.h
Normal 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
|
|
@ -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,
|
||||
|
|
4
iso.xml
4
iso.xml
|
@ -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>
|
||||
|
|
15
src/chara.c
15
src/chara.c
|
@ -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)
|
||||
|
|
24
src/main.c
24
src/main.c
|
@ -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, §or);
|
||||
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
185
src/sound.c
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue