Add multi XA playback example

This commit is contained in:
ABelliqueux 2021-08-13 16:42:03 +02:00
parent 38196b6590
commit 7783acf20d
7 changed files with 608 additions and 0 deletions

12
hello_multi_xa/Makefile Normal file
View File

@ -0,0 +1,12 @@
.PHONY: all cleansub
all:
mkpsxiso -y ./isoconfig.xml
cleansub:
$(MAKE) clean
rm -f hello_multi_xa.cue hello_multi_xa.bin
TARGET = hello_multi_xa
SRCS = hello_multi_xa.c \
include ../common.mk

181
hello_multi_xa/README.md Normal file
View File

@ -0,0 +1,181 @@
## Multi XA samples playback
Use an interleaved XA file to store multiple sound effects and use pre-calculated offset to play them back.
Use up, down, left, right, triangle, cross, circle, square to play various samples.
If this seems complicated, first study the simple XA playback example : https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_xa
You need [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) in your $PATH to generate a PSX disk image.
You also need [ffmpeg](https://ffmpeg.org/), [`psxavenc` and `xainterleave`](https://github.com/ABelliqueux/candyk-psx/tree/master/toolsrc/).
### Compile
This will compile and build an iso image :
```bash
make
```
### Clean directory
```bash
make cleansub
```
## Producing an interleaved XA sound bank
### Sound to WAV conversion with ffmpeg
```bash
ffmpeg -i input.mp3 -acodec pcm_s16le -ac 1 -ar 44100 output.wav
```
### WAV to XA conversion
See further down how you should adapt the `-F` and `-C` parameters.
```bash
psxavenc -f 37800 -t xa -b 4 -c 2 -F 1 -C 0 input.wav output.xa
```
### XA to multi XA track
You can use a concatenation tool to build your tracks like so :
```bash
cat track1.xa track2.xa track3.xa > channelX.xa
```
On windows, use :
```
copy track1.xa+track2.xa+track3.xa channelX.xa
```
### XA to interleaved XA
Use `xainterleave` as instructed here : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/XA#interleaving-xa-files
## Multi XA specifics
## Silence file
You should use a silent XA file to intersperse between your samples, so that you have a margin of error to avoid pops and noises.I.e:
```bash
cat track1.xa silence.xa track3.xa silence.xa track4.xa > channelX.xa
```
### XA file size and vertical density
An interleaved XA (4 or 8 channels) file will have the size of its largest channel multiplied by the number of channels.
Therefore, you should try to obtain a vertical density on all channels for your sample, i.e :
That :
```
channel 0 sample1.xa
channel 1 sample2.xa
channel 2 sample3.xa
channel 3 sample4.xa
```
instead of :
```
channel 0 sample1.xa sample2.xa sample2.xa sample4.xa
channel 1
channel 2
channel 3
```
You should use your longest sample as a base duration, and create the other channels to obtain a similar duration, i.e :
```
channel 0 <------------- sample1.xa ---------------> <-- silence.xa --> <-sample2.xa ->
channel 1 <-- sample3.xa --> <-- silence.xa --> < -------- sample4.xa ----------->
```
### Calculating sample's start and end offsets
Using the size of your XA sample file, you can find it's size in sector like so :
`(size/2336)-1 * 8`
For example, file with size `84096B` is `(((84096/2336) == 36) - 1 == 35) * 8 == 280`.
First sample's start will then be `0`, and end `280`.
The following sample's start position will have an offset of `8`, hence `288`, etc.
#### Example
These are the calculations for the files on 4 channels
```
// channel 1
// name size start end
5_come.xa 18688 0 56
5_sile_h.xa 23360 64 136
5_erro.xa 44384 144 288
// channel 2
6_cuek.xa 32704 0 104
6_sile_h.xa 23360 112 184
6_hehe.xa 56064 196 380
6_sile_h.xa 23360 388 460
6_wron.xa 53728 468 644
// channel 3
7_m4a1.xa 84096 0 280
7_sile_h.xa 23360 288 360
7_punch.xa 16352 368 416
// channel 4
8_yooo.xa 114464 0 384
```
and the corresponding structure in code :
```c
XAbank soundBank = {
8, // index
0, // file offset
{
// channel 5
// id size file channel start end cursor
{ 0, 18688, 0, 5, 0, 56, -1 },
// Ommit the silence.xa file
{ 1, 44384, 0, 5 , 144, 288, -1 },
// channel 6
{ 2, 32704, 0, 6 , 0, 104, -1 },
// Ommit the silence.xa file
{ 3, 56064, 0, 6 , 196, 380, -1 },
// Ommit the silence.xa file
{ 4, 53728, 0, 6 , 468, 644, -1 },
// channel 7
{ 5, 84096, 0, 7 , 0, 260, -1 },
// Ommit the silence.xa file
{ 6, 16352, 0, 7 , 368, 440, -1 },
// channel 8
{ 7, 114464, 0, 8 , 0, 384, -1 }
}
};
```
## Tools, sources and documentation
See the wiki for general informations, tools and sources about the XA format : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/XA
## Sound credits
All the sound effects come from https://www.myinstants.com and are :
```
comedy_pop_finger_in_mouth_001(1).mp3
cuek.swf.mp3
erro.mp3
m4a1_single-kibblesbob-8540445.mp3
punch.mp3
wrong-answer-sound-effect.mp3
yooooooooooooooooooooooooo_4.mp3
hehehehhehehehhehehheheehehe.mp3
```

View File

@ -0,0 +1,302 @@
// XA multi sample playback example
//
// Use an interleaved XA file to store multiple sound effects and use pre-calculated offset to play them back.
// Use up, down, left, right, triangle, cross, circle, square to play various samples.
// If this seems complicated, first study the simple XA playback example : https://github.com/ABelliqueux/nolibgs_hello_worlds/tree/main/hello_xa
//
// base on `psyq/addons/scee/CD/XAPLAYER`
// Refs : http://psx.arthus.net/code/XA/XATUT.pdf
// http://psx.arthus.net/code/XA/xatut.zip
// http://psx.arthus.net/code/XA/XA%20ADPCM%20documentation.txt
// Schnappy 2021
#include <sys/types.h>
#include <stdio.h>
#include <libgte.h>
#include <libetc.h>
#include <libgpu.h>
// CD library
#include <libcd.h>
// SPU library
#include <libspu.h>
#define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL
#define SCREENXRES 320 // Screen width
#define SCREENYRES 240 + (VMODE << 4) // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256
#define CENTERX SCREENXRES/2 // Center of screen on x
#define CENTERY SCREENYRES/2 // Center of screen on y
#define MARGINX 0 // margins for text display
#define MARGINY 32
#define FONTSIZE 8 * 16 // Text Field Height
DISPENV disp[2]; // Double buffered DISPENV and DRAWENV
DRAWENV draw[2];
short db = 0; // index of which buffer is used, values 0, 1
// SPU attributes
SpuCommonAttr spuSettings;
#define CD_SECTOR_SIZE 2048
// XA
// Sector offset for XA data 4: simple speed, 8: double speed
#define XA_CHANNELS 8
#define XA_CDSPEED XA_CHANNELS >> 2
// Number of XA samples ( != # of XA files )
#define XA_TRACKS 8
typedef struct XAsound {
u_int id;
u_int size;
// We can find size in sector : size / 2336, start value begins at 23, end value is at start + offset ( (size / 2336)-1 * #channels )
// subsequent start value have an 8 bytes offset (n-1.end + 8)
u_char file, channel;
u_int start, end;
int cursor;
} XAsound;
typedef struct XAbank {
u_int index;
int offset;
XAsound samples[];
} XAbank;
XAbank soundBank = {
8,
0,
{
// channel 5
// id size file channel start end cursor
{ 0, 18688, 0, 5, 0, 56, -1 },
{ 1, 44384, 0, 5 , 144, 288, -1 },
// channel 6
{ 2, 32704, 0, 6 , 0, 104, -1 },
{ 3, 56064, 0, 6 , 196, 380, -1 },
{ 4, 53728, 0, 6 , 468, 644, -1 },
// channel 7
{ 5, 84096, 0, 7 , 0, 260, -1 },
{ 6, 16352, 0, 7 , 368, 440, -1 },
// channel 8
{ 7, 114464, 0, 8 , 0, 384, -1 }
}
};
// XA file to load
static char * loadXA = "\\INTER8.XA;1";
// File informations : pos, size, name
CdlFILE XAPos = {0};
// CD filter
CdlFILTER filter;
// File position in m/s/f
CdlLOC loc;
void init(void)
{
ResetGraph(0); // Initialize drawing engine with a complete reset (0)
SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1]
SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1]
SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1]
SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1]
// Set video mode
if (VMODE){ SetVideoMode(MODE_PAL);}
SetDispMask(1); // Display on screen
setRGB0(&draw[0], 50, 50, 50); // set color for first draw area
setRGB0(&draw[1], 50, 50, 50); // set color for second draw area
draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame
draw[1].isbg = 1;
PutDispEnv(&disp[db]); // set the disp and draw environnments
PutDrawEnv(&draw[db]);
FntLoad(960, 0); // Load font to vram at 960,0(+128)
FntOpen(MARGINX, MARGINY, SCREENXRES - MARGINX * 2, FONTSIZE, 0, 512 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars
}
void display(void)
{
DrawSync(0); // Wait for all drawing to terminate
VSync(0); // Wait for the next vertical blank
PutDispEnv(&disp[db]); // set alternate disp and draw environnments
PutDrawEnv(&draw[db]);
db = !db; // flip db value (0 or 1)
}
void spuSetup(SpuCommonAttr * spuSettings)
{
// Init Spu
SpuInit();
// Set master & CD volume to max
spuSettings->mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR | SPU_COMMON_CDVOLL | SPU_COMMON_CDVOLR | SPU_COMMON_CDMIX);
spuSettings->mvol.left = 0x6000;
spuSettings->mvol.right = 0x6000;
spuSettings->cd.volume.left = 0x6000;
spuSettings->cd.volume.right = 0x6000;
// Enable CD input ON
spuSettings->cd.mix = SPU_ON;
// Apply settings
SpuSetCommonAttr(spuSettings);
// Set transfer mode
SpuSetTransferMode(SPU_TRANSFER_BY_DMA);
}
void XAsetup()
{
u_char param[4];
// ORing the parameters we need to set ; drive speed, ADPCM play, Subheader filter, sector size
// If using CdlModeSpeed(Double speed), you need to load an XA file that has 8 channels.
// In single speed, a 4 channels XA is to be used.
param[0] = CdlModeSpeed|CdlModeRT|CdlModeSF|CdlModeSize1;
// Issue primitive command to CD-ROM system (Blocking-type)
// Set the parameters above
CdControlB(CdlSetmode, param, 0);
// Pause at current pos
CdControlF(CdlPause,0);
}
int main(void)
{
// Init display
init();
// Set SPU
spuSetup(&spuSettings);
// Init CD system
CdInit();
// Pad
PadInit(0);
// Load XA file from cd
// Find XA file pos
CdSearchFile( &XAPos, loadXA);
// Set cd head to start of file
soundBank.offset = CdPosToInt(&XAPos.pos);
// Set cd XA playback parameters and pause cd
XAsetup();
// Pad variables to avoid multifire
int pad, oldpad;
// Keep track of XA Sample currently playing
int sample = -1;
while (1) // infinite loop
{
// if sample is set
if (sample != -1 ){
// Begin XA file playback...
// if sample's cursor is 0
if (soundBank.samples[sample].cursor == 0){
// Convert sector number to CD position in min/second/frame and set CdlLOC accordingly.
CdIntToPos( soundBank.samples[sample].start + soundBank.offset , &loc);
// Send CDROM read command
CdControlF(CdlReadS, (u_char *)&loc);
// Set playing flag
}
// if sample's cursor is close to sample's end position, stop playback
if ((soundBank.samples[sample].cursor += XA_CDSPEED) >= soundBank.samples[sample].end - soundBank.samples[sample].start ){
CdControlF(CdlStop,0);
soundBank.samples[sample].cursor = -1;
sample = -1;
}
}
// Pad
// Use up, down, left, right, triangle, cross, circle, square to play various samples
// Read pad values
pad = PadRead(0);
// Left
if (pad & PADLleft && !(oldpad & PADLleft)){
// Set sample ID for playback
sample = 7;
// Change file/channel in the filter struct
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
// Reset sample's cursor
soundBank.samples[sample].cursor = 0;
// Store pad value for release
oldpad = pad;
}
if (pad & PADLright && !(oldpad & PADLright)){
sample = 6;
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
soundBank.samples[sample].cursor = 0;
oldpad = pad;
}
if (pad & PADLup && !(oldpad & PADLup)){
sample = 5;
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
soundBank.samples[sample].cursor = 0;
oldpad = pad;
}
if (pad & PADLdown && !(oldpad & PADLdown)){
sample = 4;
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
soundBank.samples[sample].cursor = 0;
oldpad = pad;
}
if (pad & PADRleft && !(oldpad & PADRleft)){
sample = 3;
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
soundBank.samples[sample].cursor = 0;
oldpad = pad;
}
if (pad & PADRright && !(oldpad & PADRright)){
sample = 2;
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
soundBank.samples[sample].cursor = 0;
oldpad = pad;
}
if (pad & PADRup && !(oldpad & PADRup)){
sample = 1;
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
soundBank.samples[sample].cursor = 0;
oldpad = pad;
}
if (pad & PADRdown && !(oldpad & PADRdown)){
sample = 0;
filter.chan = soundBank.samples[sample].channel;
filter.file = soundBank.samples[sample].file;
// Set filter
CdControlF(CdlSetfilter, (u_char *)&filter);
soundBank.samples[sample].cursor = 0;
oldpad = pad;
}
// Reset oldpad
if (!(pad & PADRdown) ||
!(pad & PADRup) ||
!(pad & PADRleft) ||
!(pad & PADRright) ||
!(pad & PADLdown) ||
!(pad & PADLup) ||
!(pad & PADLleft) ||
!(pad & PADLright)
){oldpad = pad;}
FntPrint("Hello multi XA ! %d\n", VSync(-1));
FntPrint("Use the pad to play various samples.\n");
for (int i=0;i<soundBank.index;i++){
if (i == sample){
FntPrint(">");
}
FntPrint("%d: %d %d\n", i, soundBank.samples[i].start, soundBank.samples[i].end);
}
FntPrint("Cursor: %d\n", soundBank.samples[sample].cursor );
FntPrint("File offset: %d\n", soundBank.offset );
FntFlush(-1); // Draw print stream
display(); // Execute display()
}
return 0;
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- MKPSXISO example XML script -->
<!-- <iso_project>
Starts an ISO image project to build. Multiple <iso_project> elements may be
specified within the same xml script which useful for multi-disc projects.
<iso_project> elements must contain at least one <track> element.
Attributes:
image_name - File name of the ISO image file to generate.
cue_sheet - Optional, file name of the cue sheet for the image file
(required if more than one track is specified).
-->
<iso_project image_name="hello_multi_xa.bin" cue_sheet="hello_multi_xa.cue">
<!-- <track>
Specifies a track to the ISO project. This example element creates a data
track for storing data files and CD-XA/STR streams.
Only one data track is allowed and data tracks must only be specified as the
first track in the ISO image and cannot be specified after an audio track.
Attributes:
type - Track type (either data or audio).
source - For audio tracks only, specifies the file name of a wav audio
file to use for the audio track.
-->
<track type="data">
<!-- <identifiers>
Optional, Specifies the identifier strings to use for the data track.
Attributes:
system - Optional, specifies the system identifier (PLAYSTATION if unspecified).
application - Optional, specifies the application identifier (PLAYSTATION if unspecified).
volume - Optional, specifies the volume identifier.
volume_set - Optional, specifies the volume set identifier.
publisher - Optional, specifies the publisher identifier.
data_preparer - Optional, specifies the data preparer identifier. If unspecified, MKPSXISO
will fill it with lengthy text telling that the image file was generated
using MKPSXISO.
-->
<identifiers
system ="PLAYSTATION"
application ="PLAYSTATION"
volume ="HELOCD"
volume_set ="HELOCD"
publisher ="SCHNAPPY"
data_preparer ="MKPSXISO"
/>
<!-- <license>
Optional, specifies the license file to use, the format of the license file must be in
raw 2336 byte sector format, like the ones included with the PsyQ SDK in psyq\cdgen\LCNSFILE.
License data is not included within the MKPSXISO program to avoid possible legal problems
in the open source environment... Better be safe than sorry.
Attributes:
file - Specifies the license file to inject into the ISO image.
-->
<!--
<license file="LICENSEA.DAT"/>
-->
<!-- <directory_tree>
Specifies and contains the directory structure for the data track.
Attributes:
None.
-->
<directory_tree>
<!-- <file>
Specifies a file in the directory tree.
Attributes:
name - File name to use in the directory tree (can be used for renaming).
type - Optional, type of file (data for regular files and is the default, xa for
XA audio and str for MDEC video).
source - File name of the source file.
-->
<!-- Stores system.txt as system.cnf -->
<file name="system.cnf" type="data" source="system.cnf"/>
<file name="SCES_313.37" type="data" source="hello_multi_xa.ps-exe"/>
<file name="INTER8.XA" type="xa" source="xa/vert8.xa"/>
<dummy sectors="1024"/>
<!-- <dir>
Specifies a directory in the directory tree. <file> and <dir> elements inside the element
will be inside the specified directory.
-->
</directory_tree>
</track>
</iso_project>

View File

@ -0,0 +1,4 @@
BOOT=cdrom:\SCES_313.37;1
TCB=4
EVENT=10
STACK=801FFFF0

View File

@ -0,0 +1,8 @@
1 xa channel5.xa 0 5
1 xa channel6.xa 0 6
1 xa channel7.xa 0 7
1 xa channel8.xa 0 8
1 null
1 null
1 null
1 null

BIN
hello_multi_xa/xa/vert8.xa Normal file

Binary file not shown.