mirror of
https://github.com/libretro/picodrive.git
synced 2026-11-07 12:38:05 -10:00
sound: add vgm playback support
This commit is contained in:
1 parent
150c28559e
commit
9e304a66eb
13 files changed
+711
-17
No files matched your search
@@ -11,6 +11,8 @@
|
||||
*.a
|
||||
tags
|
||||
cscope.out
|
||||
compile_commands.json
|
||||
.gdb_history
|
||||
config.mak
|
||||
config.log
|
||||
cpu/musashi/m68kmake
|
||||
|
||||
+5
-1
@@ -710,7 +710,7 @@ static int DecodeSmd(unsigned char *data,int len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned char *PicoCartAlloc(int filesize, int is_sms)
|
||||
void *PicoCartAlloc(int filesize, int is_sms)
|
||||
{
|
||||
unsigned char *rom;
|
||||
|
||||
@@ -789,6 +789,7 @@ int PicoCartLoad(pm_file *f, const unsigned char *rom, unsigned int romsize,
|
||||
if (bytes_read <= 0) {
|
||||
elprintf(EL_STATUS, "read failed");
|
||||
plat_munmap(rom_data, rom_alloc_size);
|
||||
rom_alloc_size = 0;
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
@@ -868,6 +869,7 @@ int PicoCartInsert(unsigned char *rom, unsigned int romsize, const char *carthw_
|
||||
elprintf(EL_STATUS|EL_ANOMALY, "starting in unknown hw configuration: %x", PicoIn.AHW);
|
||||
case 0:
|
||||
case PAHW_SVP: PicoMemSetup(); break;
|
||||
case PAHW_MCD|PAHW_VGM:
|
||||
case PAHW_MCD: PicoMemSetupCD(); break;
|
||||
case PAHW_PICO: PicoMemSetupPico(); break;
|
||||
case PAHW_SMS: PicoMemSetupMS(); break;
|
||||
@@ -908,7 +910,9 @@ void PicoCartUnload(void)
|
||||
if (Pico.rom != NULL) {
|
||||
SekFinishIdleDet();
|
||||
plat_munmap(Pico.rom, rom_alloc_size);
|
||||
rom_alloc_size = 0;
|
||||
Pico.rom = NULL;
|
||||
Pico.romsize = 0;
|
||||
}
|
||||
PicoGameLoaded = 0;
|
||||
}
|
||||
|
||||
+13
-1
@@ -10,6 +10,7 @@
|
||||
#include <string.h>
|
||||
#include "pico_int.h"
|
||||
#include "cd/cd_parse.h"
|
||||
#include "sound/vgm.h"
|
||||
|
||||
unsigned char media_id_header[0x100];
|
||||
|
||||
@@ -55,9 +56,11 @@ static int detect_media(const char *fname, const unsigned char *rom, unsigned in
|
||||
if (!strcasecmp(ext, "srm") || !strcasecmp(ext, "gz")) // s.gz ~ .mds.gz
|
||||
return PM_BAD_DETECT;
|
||||
|
||||
/* don't believe in extensions, except .cue and .chd */
|
||||
/* don't believe in extensions, except .cue, .chd and .vgm */
|
||||
if (strcasecmp(ext, "cue") == 0 || strcasecmp(ext, "chd") == 0)
|
||||
return PM_CD;
|
||||
if (strcasecmp(ext, "vgm") == 0 || strcasecmp(ext, "vgz") == 0)
|
||||
return PM_VGM;
|
||||
|
||||
/* Open rom file, if required */
|
||||
if (!rom) {
|
||||
@@ -275,6 +278,8 @@ enum media_type_e PicoLoadMedia(const char *filename,
|
||||
|
||||
if ((PicoIn.AHW & PAHW_MCD) && Pico_mcd != NULL)
|
||||
cdd_unload();
|
||||
if (PicoIn.AHW & PAHW_VGM)
|
||||
vgm_finish();
|
||||
PicoCartUnload();
|
||||
PicoIn.AHW = 0;
|
||||
PicoIn.quirks = 0;
|
||||
@@ -334,6 +339,13 @@ enum media_type_e PicoLoadMedia(const char *filename,
|
||||
else if (media_type == PM_PICO) {
|
||||
PicoIn.AHW = PAHW_PICO;
|
||||
}
|
||||
else if (media_type == PM_VGM) {
|
||||
if (vgm_load(rom_fname) == 0)
|
||||
/* vgm_load() sets up PicoIn.AHW */;
|
||||
else
|
||||
media_type = PM_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rom == NULL && rom_fname != NULL) {
|
||||
rom_file = pm_open(rom_fname);
|
||||
|
||||
+10
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "pico_int.h"
|
||||
#include "sound/ym2612.h"
|
||||
#include "sound/vgm.h"
|
||||
|
||||
struct Pico Pico;
|
||||
struct PicoMem PicoMem;
|
||||
@@ -52,6 +53,8 @@ void PicoExit(void)
|
||||
PicoCartUnload();
|
||||
if (PicoIn.AHW & PAHW_MCD)
|
||||
PicoExitMCD();
|
||||
if (PicoIn.AHW & PAHW_VGM)
|
||||
vgm_finish();
|
||||
z80_exit();
|
||||
PsndExit();
|
||||
PicoCloseTape();
|
||||
@@ -202,6 +205,8 @@ int PicoReset(void)
|
||||
if (PicoIn.opt & POPT_EN_32X)
|
||||
PicoReset32x();
|
||||
|
||||
if (PicoIn.AHW & PAHW_VGM)
|
||||
vgm_reset();
|
||||
if (PicoIn.AHW & PAHW_MCD) {
|
||||
PicoResetMCD();
|
||||
return 0;
|
||||
@@ -284,6 +289,11 @@ void PicoFrame(void)
|
||||
|
||||
Pico.m.frame_count++;
|
||||
|
||||
if (PicoIn.AHW & PAHW_VGM) {
|
||||
vgm_frame();
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (PicoIn.AHW & PAHW_SMS) {
|
||||
PicoFrameMS();
|
||||
goto end;
|
||||
|
||||
@@ -93,6 +93,7 @@ extern void *p32x_bios_g, *p32x_bios_m, *p32x_bios_s;
|
||||
#define PAHW_SG (1<<6)
|
||||
#define PAHW_SC (1<<7)
|
||||
#define PAHW_8BIT (PAHW_SMS|PAHW_GG|PAHW_SG|PAHW_SC)
|
||||
#define PAHW_VGM (1<<8)
|
||||
|
||||
#define PHWS_AUTO 0
|
||||
#define PHWS_GG 1
|
||||
@@ -319,6 +320,7 @@ enum media_type_e {
|
||||
PM_MARK3,
|
||||
PM_PICO,
|
||||
PM_CD,
|
||||
PM_VGM,
|
||||
};
|
||||
|
||||
enum cd_track_type
|
||||
|
||||
@@ -711,6 +711,7 @@ extern carthw_state_chunk *carthw_chunks;
|
||||
|
||||
// cart.c
|
||||
extern int rom_strcmp(void *rom, int size, int offset, const char *s1);
|
||||
extern void *PicoCartAlloc(int filesize, int is_sms);
|
||||
extern int PicoCartResize(int newsize);
|
||||
extern void Byteswap(void *dst, const void *src, int len);
|
||||
extern void (*PicoCartMemSetup)(void);
|
||||
@@ -1025,6 +1026,7 @@ PICO_INTERNAL void PsndDoDAC(int cycle_to);
|
||||
PICO_INTERNAL void PsndDoPSG(int cyc_to);
|
||||
PICO_INTERNAL void PsndDoYM2413(int cyc_to);
|
||||
PICO_INTERNAL void PsndDoFM(int cyc_to);
|
||||
PICO_INTERNAL void PsndDoSMSFM(int cyc_to);
|
||||
PICO_INTERNAL void PsndDoPCM(int cyc_to);
|
||||
PICO_INTERNAL void PsndClear(void);
|
||||
PICO_INTERNAL void PsndGetSamples(int y);
|
||||
@@ -1206,6 +1208,7 @@ static __inline int isspace_(int c)
|
||||
#define EL_PWM 0x00100000 /* 32X PWM stuff (LOTS of output) */
|
||||
#define EL_32XP 0x00200000 /* 32X peripherals */
|
||||
#define EL_CD 0x00400000 /* MCD */
|
||||
#define EL_VGM 0x00800000
|
||||
|
||||
#define EL_STATUS 0x40000000 /* status messages */
|
||||
#define EL_ANOMALY 0x80000000 /* some unexpected conditions (during emulation) */
|
||||
|
||||
+19
-12
@@ -29,7 +29,8 @@ static s32 PsndBuffer[2*(54000+100)/50+2];
|
||||
s16 cdda_out_buffer[2*1152];
|
||||
|
||||
// FM resampling polyphase FIR
|
||||
static resampler_t *fmresampler;
|
||||
static resampler_t *ym2612_resampler;
|
||||
static resampler_t *ym2413_resampler;
|
||||
static int (*PsndFMUpdate)(s32 *buffer, int length, int stereo, int is_buf_empty);
|
||||
|
||||
PICO_INTERNAL void PsndInit(void)
|
||||
@@ -41,10 +42,12 @@ PICO_INTERNAL void PsndInit(void)
|
||||
|
||||
PICO_INTERNAL void PsndExit(void)
|
||||
{
|
||||
OPLL_delete(opll);
|
||||
if (opll)
|
||||
OPLL_delete(opll);
|
||||
opll = NULL;
|
||||
|
||||
resampler_free(fmresampler); fmresampler = NULL;
|
||||
resampler_free(ym2612_resampler); ym2612_resampler = NULL;
|
||||
resampler_free(ym2413_resampler); ym2413_resampler = NULL;
|
||||
}
|
||||
|
||||
PICO_INTERNAL void PsndReset(void)
|
||||
@@ -66,7 +69,7 @@ static void YM2612Update(s32 *buffer, int length, int stereo)
|
||||
|
||||
static int YM2612UpdateFIR(s32 *buffer, int length, int stereo, int is_buf_empty)
|
||||
{
|
||||
resampler_update(fmresampler, buffer, length, YM2612Update);
|
||||
resampler_update(ym2612_resampler, buffer, length, YM2612Update);
|
||||
return ymchans;
|
||||
}
|
||||
|
||||
@@ -83,12 +86,12 @@ static void YM2413Update(s32 *buffer, int length, int stereo)
|
||||
static int YM2413UpdateFIR(s32 *buffer, int length, int stereo, int is_buf_empty)
|
||||
{
|
||||
if (!is_buf_empty) memset(buffer, 0, (length << stereo) * sizeof(*buffer));
|
||||
resampler_update(fmresampler, buffer, length, YM2413Update);
|
||||
resampler_update(ym2413_resampler, buffer, length, YM2413Update);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FIR setup, looks for a close enough rational number matching the ratio
|
||||
static void YMFM_setup_FIR(int inrate, int outrate, int stereo)
|
||||
static resampler_t *YMFM_setup_FIR(int inrate, int outrate, int stereo)
|
||||
{
|
||||
int mindiff = 999;
|
||||
int diff, mul, div;
|
||||
@@ -108,8 +111,7 @@ static void YMFM_setup_FIR(int inrate, int outrate, int stereo)
|
||||
printf("FM polyphase FIR ratio=%d/%d error=%.3f%%\n",
|
||||
Pico.snd.fm_fir_mul, Pico.snd.fm_fir_div, 100.0*mindiff/inrate);
|
||||
|
||||
resampler_free(fmresampler);
|
||||
fmresampler = resampler_new(FMFIR_TAPS, Pico.snd.fm_fir_mul, Pico.snd.fm_fir_div,
|
||||
return resampler_new(FMFIR_TAPS, Pico.snd.fm_fir_mul, Pico.snd.fm_fir_div,
|
||||
0.85, 2, 2*inrate/50, stereo);
|
||||
}
|
||||
|
||||
@@ -149,11 +151,14 @@ void PsndRerate(int preserve_state)
|
||||
state_size = YM2612PicoStateSave3(state, state_size);
|
||||
}
|
||||
|
||||
if (PicoIn.AHW & PAHW_SMS) {
|
||||
if (opll && opll->rate != ym2413_rate) {
|
||||
OPLL_setRate(opll, ym2413_rate);
|
||||
if (!preserve_state)
|
||||
OPLL_reset(opll);
|
||||
YMFM_setup_FIR(ym2413_rate, PicoIn.sndRate, 0);
|
||||
resampler_free(ym2413_resampler);
|
||||
ym2413_resampler = YMFM_setup_FIR(ym2413_rate, PicoIn.sndRate, 0);
|
||||
}
|
||||
if (PicoIn.AHW & PAHW_SMS) {
|
||||
PsndFMUpdate = YM2413UpdateFIR;
|
||||
} else if ((PicoIn.opt & POPT_EN_FM_FILTER) && ym2612_rate != PicoIn.sndRate) {
|
||||
// polyphase FIR resampler, resampling directly from native to output rate
|
||||
@@ -161,7 +166,9 @@ void PsndRerate(int preserve_state)
|
||||
YM2612Init(ym2612_clock, ym2612_rate,
|
||||
((PicoIn.opt&POPT_DIS_FM_SSGEG) ? 0 : ST_SSG) |
|
||||
((PicoIn.opt&POPT_FM_YM2612) ? ST_DAC : 0));
|
||||
YMFM_setup_FIR(ym2612_rate, PicoIn.sndRate, PicoIn.opt & POPT_EN_STEREO);
|
||||
resampler_free(ym2612_resampler);
|
||||
ym2612_resampler = YMFM_setup_FIR(ym2612_rate, PicoIn.sndRate,
|
||||
PicoIn.opt & POPT_EN_STEREO);
|
||||
PsndFMUpdate = YM2612UpdateFIR;
|
||||
} else {
|
||||
if (ym2612_init)
|
||||
@@ -320,7 +327,7 @@ PICO_INTERNAL void PsndDoSMSFM(int cyc_to)
|
||||
|
||||
if (Pico.m.hardware & PMS_HW_FMUSED) {
|
||||
buf += pos;
|
||||
PsndFMUpdate(buf32, len, 0, 0);
|
||||
YM2413UpdateFIR(buf32, len, 0, 0);
|
||||
if (stereo)
|
||||
while (len--) {
|
||||
*buf++ += *buf32;
|
||||
|
||||
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
* VGM parser for PicoDrive
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* specs:
|
||||
* https://www.smspower.org/uploads/Music/vgmspec170.txt
|
||||
* https://vgmrips.net/wiki/VGM_Specification
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../pico_int.h"
|
||||
#include "sn76496.h"
|
||||
#include "ym2413.h"
|
||||
#include "ym2612.h"
|
||||
#include "vgm.h"
|
||||
#include <zlib.h>
|
||||
|
||||
#define READ_BLOCK (512 * 1024)
|
||||
#define OVERALLOC 16 // to avoid range checks in the parser
|
||||
|
||||
#define BLOCK_TYPE_YM2612 0
|
||||
#define BLOCK_TYPE_RF5C164 2
|
||||
#define CHIP_TYPE_YM2612 2
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
struct vgm_header
|
||||
{
|
||||
char vgm[4];
|
||||
u32 eof, version, sn76489_clock;
|
||||
u32 ym2413_clock, gd3_offset, total_samples, loop_offset; // 10
|
||||
u32 loop_samples, rate, sn_fb_w, ym2612_clock; // 20
|
||||
u32 ym2151_clock, data_offset, spcm_clock, spcm_interface; // 30
|
||||
};
|
||||
|
||||
struct vgm
|
||||
{
|
||||
size_t data_size;
|
||||
size_t data_pos;
|
||||
u32 sample_pos;
|
||||
u32 block_count;
|
||||
u32 stream_count;
|
||||
int dacout_nonstream;
|
||||
struct vgm_block {
|
||||
u32 start, end, pos;
|
||||
} blocks[0x40];
|
||||
struct vgm_stream {
|
||||
u8 chip_type, ioport, reg, block_i;
|
||||
u32 pos_inc, pos_fraction; // Q24
|
||||
u32 pos;
|
||||
u32 playing:1;
|
||||
u32 new_sample:1;
|
||||
//u32 looping:1;
|
||||
u32 flags_warned:1;
|
||||
u32 bad_start_warned:1;
|
||||
} *streams;
|
||||
union {
|
||||
struct vgm_header hdr;
|
||||
u8 data[0];
|
||||
};
|
||||
};
|
||||
|
||||
static struct vgm *g_vgm;
|
||||
|
||||
static size_t gzread_check(gzFile file, void *ptr, size_t size)
|
||||
{
|
||||
#if ZLIB_VERNUM >= 0x1290
|
||||
size_t read_size = gzfread(ptr, 1, size, file);
|
||||
#else
|
||||
int read_size = gzread(file, ptr, size);
|
||||
#endif
|
||||
if (read_size == 0 || (long)read_size == -1)
|
||||
{
|
||||
int erri = Z_OK;
|
||||
const char *err = gzerror(file, &erri);
|
||||
if (erri != Z_OK && erri != Z_STREAM_END) {
|
||||
elprintf(EL_VGM | EL_STATUS | EL_ANOMALY, "gzfread error %d: %s\n", erri, err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return read_size;
|
||||
}
|
||||
|
||||
static struct vgm_stream *stream_get(struct vgm *vgm, size_t cmd_pos, u8 id)
|
||||
{
|
||||
void *tmp;
|
||||
if (id < vgm->stream_count)
|
||||
return &vgm->streams[id];
|
||||
if (id == 0xff) // reserved?
|
||||
goto problem;
|
||||
tmp = realloc(vgm->streams, (id + 1) * sizeof(vgm->streams[0]));
|
||||
if (!tmp)
|
||||
goto problem;
|
||||
vgm->streams = tmp;
|
||||
memset(&vgm->streams[vgm->stream_count], 0,
|
||||
(id + 1 - vgm->stream_count) * sizeof(vgm->streams[0]));
|
||||
vgm->stream_count = id + 1;
|
||||
return &vgm->streams[id];
|
||||
problem:
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: broken stream %02x", cmd_pos, id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int vgm_reset_p(struct vgm *vgm, const char *fname)
|
||||
{
|
||||
vgm->data_pos = 0x40;
|
||||
if (vgm->hdr.data_offset != 0) {
|
||||
size_t data_offset = (size_t)0x34 + CPU_LE4(vgm->hdr.data_offset);
|
||||
if (data_offset > vgm->data_size) {
|
||||
elprintf(EL_VGM | EL_STATUS | EL_ANOMALY, "%s: broken data_offset %x",
|
||||
fname, CPU_LE4(vgm->hdr.data_offset));
|
||||
return 0;
|
||||
}
|
||||
vgm->data_pos = data_offset;
|
||||
}
|
||||
|
||||
Pico.m.pal = CPU_LE4(vgm->hdr.rate) == 50;
|
||||
// pcd_soft_reset() ?
|
||||
if (Pico_mcd)
|
||||
Pico_mcd->pcm.bank = 0;
|
||||
// conflics md vs sms PMS_HW_FMUSED
|
||||
Pico.m.hardware = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vgm_load(const char *fname)
|
||||
{
|
||||
struct vgm *vgm = NULL;
|
||||
gzFile file = NULL;
|
||||
size_t alloc_size = 0;
|
||||
int erri = Z_OK;
|
||||
size_t read_size;
|
||||
|
||||
file = gzopen(fname, "rb");
|
||||
if (!file) {
|
||||
perror("gzopen");
|
||||
goto fail;
|
||||
}
|
||||
alloc_size = sizeof(*vgm) + OVERALLOC;
|
||||
vgm = calloc(1, alloc_size);
|
||||
if (!vgm)
|
||||
goto fail;
|
||||
while (!gzeof(file))
|
||||
{
|
||||
void *tmp = realloc(vgm, (alloc_size += READ_BLOCK));
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
vgm = tmp;
|
||||
read_size = gzread_check(file, vgm->data + vgm->data_size, READ_BLOCK);
|
||||
if (read_size == 0) {
|
||||
if (vgm->data_size == 0)
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
vgm->data_size += read_size;
|
||||
}
|
||||
erri = gzclose(file);
|
||||
file = NULL;
|
||||
if (erri != Z_OK)
|
||||
elprintf(EL_VGM | EL_STATUS | EL_ANOMALY, "gzclose error %d", erri);
|
||||
memset(vgm->data + vgm->data_size, 0, OVERALLOC);
|
||||
if (vgm->data_size <= 0x40) {
|
||||
elprintf(EL_VGM | EL_STATUS | EL_ANOMALY, "%s: file too small: %zd",
|
||||
fname, vgm->data_size);
|
||||
goto fail;
|
||||
}
|
||||
if (memcmp(vgm->hdr.vgm, "Vgm ", 4) != 0) {
|
||||
elprintf(EL_VGM | EL_STATUS | EL_ANOMALY, "%s: wrong ident", fname);
|
||||
goto fail;
|
||||
}
|
||||
if (!vgm_reset_p(vgm, fname))
|
||||
goto fail;
|
||||
elprintf(EL_VGM | EL_STATUS, "vgm v%x.%02x pal=%d",
|
||||
CPU_LE4(vgm->hdr.version) >> 8, CPU_LE4(vgm->hdr.version) & 0xff, Pico.m.pal);
|
||||
// all supported chips may be used anytime, so partially initialize
|
||||
PicoIn.AHW |= PAHW_VGM | PAHW_MCD;
|
||||
PicoCreateMCD(NULL, 0);
|
||||
// various things assume rom presence, so make a dummy one
|
||||
if (!Pico.rom) {
|
||||
Pico.rom = PicoCartAlloc(0x10000, 0);
|
||||
if (Pico.rom) {
|
||||
Pico.romsize = 0x10000;
|
||||
memset(Pico.rom, 0, Pico.romsize);
|
||||
}
|
||||
}
|
||||
PicoCartInsert(Pico.rom, Pico.romsize, NULL);
|
||||
Pico.m.hardware = 0;
|
||||
g_vgm = vgm;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
elprintf(EL_VGM | EL_STATUS | EL_ANOMALY, "vgm: %s: failed", fname);
|
||||
if (file)
|
||||
gzclose(file);
|
||||
vgm_finish();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int try_start_stream(struct vgm *vgm, struct vgm_stream *stream,
|
||||
size_t cmd_pos, u32 block_i, u8 flags)
|
||||
{
|
||||
if (block_i >= ARRAY_SIZE(vgm->blocks) ||
|
||||
vgm->blocks[block_i].start >= vgm->blocks[block_i].end) {
|
||||
if (!stream->bad_start_warned) {
|
||||
elprintf(EL_VGM | EL_ANOMALY,
|
||||
"vgm: %06zx: stream start: bad block id %02x", cmd_pos, block_i);
|
||||
stream->bad_start_warned = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!stream->pos_inc) {
|
||||
if (!stream->bad_start_warned) {
|
||||
elprintf(EL_VGM | EL_ANOMALY,
|
||||
"vgm: %06zx: stream start: no sample rate", cmd_pos);
|
||||
stream->bad_start_warned = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (stream->chip_type == CHIP_TYPE_YM2612 || stream->reg == 0x2a)
|
||||
/* ok */;
|
||||
else {
|
||||
if (!stream->bad_start_warned) {
|
||||
elprintf(EL_VGM | EL_ANOMALY,
|
||||
"vgm: %06zx: stream start: unhandled chip config: %02x %02x",
|
||||
cmd_pos, stream->chip_type, stream->reg);
|
||||
stream->bad_start_warned = 1;
|
||||
}
|
||||
}
|
||||
if (flags && !stream->flags_warned) {
|
||||
elprintf(EL_VGM | EL_ANOMALY,
|
||||
"vgm: %06zx: stream start: ignoring flags %02x", cmd_pos, flags);
|
||||
stream->flags_warned = 1;
|
||||
}
|
||||
stream->block_i = block_i;
|
||||
stream->pos_fraction = 0;
|
||||
stream->pos = vgm->blocks[block_i].start;
|
||||
stream->new_sample = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rf5c164_write(struct vgm *vgm, size_t cmd_pos,
|
||||
u32 doffset, const u8 *src, u32 soffset, u32 length)
|
||||
{
|
||||
// affected by current bank, but can go to further banks by using offset
|
||||
size_t slength = vgm->blocks[0].end - vgm->blocks[0].start;
|
||||
if (soffset >= slength || soffset + length > slength ||
|
||||
(Pico_mcd->pcm.bank << 12) + doffset >= sizeof(Pico_mcd->pcm_ram) ||
|
||||
(Pico_mcd->pcm.bank << 12) + doffset + length > sizeof(Pico_mcd->pcm_ram))
|
||||
{
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: broken ram write %x->%x l=%x/%zx",
|
||||
cmd_pos, soffset, doffset, length, slength);
|
||||
return 0;
|
||||
}
|
||||
memcpy(Pico_mcd->pcm_ram_b[Pico_mcd->pcm.bank] + doffset, src + soffset, length);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static u32 get16(const u8 **p)
|
||||
{
|
||||
u32 d_ = *(*p)++; d_ |= *(*p)++ << 8;
|
||||
return d_;
|
||||
}
|
||||
|
||||
static u32 get24(const u8 **p)
|
||||
{
|
||||
u32 d_ = *(*p)++; d_ |= *(*p)++ << 8; d_ |= *(*p)++ << 16;
|
||||
return d_;
|
||||
}
|
||||
|
||||
static u32 get32(const u8 **p)
|
||||
{
|
||||
u32 d_ = *(*p)++; d_ |= *(*p)++ << 8; d_ |= *(*p)++ << 16; d_ |= *(*p)++ << 24;
|
||||
return d_;
|
||||
}
|
||||
|
||||
void vgm_frame(void)
|
||||
{
|
||||
u32 osc_cyc_per_smp = Pico.m.pal ? 256ull*OSC_PAL/44100+1 : 256ull*OSC_NTSC/44100+1;
|
||||
u32 scd_cyc_per_smp = 256ull*12500000/44100+1; // Q8
|
||||
u32 samples_per_frame = Pico.m.pal ? 44100/50 : 44100/60;
|
||||
u32 sample_pos_stream = 0;
|
||||
u32 s68k_base = SekCycleCntS68k;
|
||||
int dacout_stream = 0;
|
||||
int streams_playing = 0;
|
||||
struct vgm_stream *stream;
|
||||
struct vgm *vgm = g_vgm;
|
||||
u32 i;
|
||||
|
||||
if (!vgm)
|
||||
return;
|
||||
Pico.t.z80c_aim = 0;
|
||||
PsndStartFrame();
|
||||
|
||||
for (i = 0; i < vgm->stream_count; i++) {
|
||||
stream = &vgm->streams[i];
|
||||
if (stream->playing) {
|
||||
dacout_stream += ((int)vgm->data[stream->pos] - 0x80) << DAC_SHIFT;
|
||||
streams_playing = 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (vgm->data_pos < vgm->data_size && sample_pos_stream < samples_per_frame)
|
||||
{
|
||||
if (vgm->sample_pos < samples_per_frame)
|
||||
{
|
||||
size_t cmd_pos = vgm->data_pos; // file offset for logs
|
||||
const u8 *fdata = vgm->data + vgm->data_pos;
|
||||
u8 id, type, addr, data, flags, param[3];
|
||||
u32 offset32, data32, data16;
|
||||
u32 doffset, length;
|
||||
int samples, wait_samples;
|
||||
u8 cmd = *fdata++;
|
||||
switch (cmd)
|
||||
{
|
||||
case 0x4f: // Game Gear PSG stereo
|
||||
SN76496Config(*fdata++);
|
||||
break;
|
||||
case 0x50: // sn76489/sn76496 write
|
||||
PsndDoPSG(z80_cyclesDone());
|
||||
SN76496Write(*fdata++);
|
||||
break;
|
||||
case 0x51: // ym2413 write
|
||||
Pico.m.hardware |= PMS_HW_FMUSED;
|
||||
YM2413_regWrite(*fdata++);
|
||||
YM2413_dataWrite(*fdata++);
|
||||
break;
|
||||
case 0x52: // ym2612 port 0 write
|
||||
ym2612.OPN.ST.address = *fdata++;
|
||||
ym2612.addr_A1 = 0;
|
||||
ym2612.REGS[ym2612.OPN.ST.address] = data = *fdata++;
|
||||
if (ym2612.OPN.ST.address == 0x2a) {
|
||||
wait_samples = 0;
|
||||
goto write_ym2612_dac;
|
||||
}
|
||||
goto write_ym2612_d;
|
||||
case 0x53: // ym2612 port 1 write
|
||||
ym2612.OPN.ST.address = *fdata++;
|
||||
ym2612.addr_A1 = 1;
|
||||
ym2612.REGS[0x100 | ym2612.OPN.ST.address] = data = *fdata++;
|
||||
write_ym2612_d:
|
||||
PsndDoFM(z80_cyclesDone());
|
||||
YM2612Write_(1, data);
|
||||
break;
|
||||
case 0x61: // wait n samples
|
||||
wait_samples = get16(&fdata);
|
||||
goto wait;
|
||||
case 0x62: // wait 735 samples
|
||||
wait_samples = 735;
|
||||
goto wait;
|
||||
case 0x63: // wait 882 samples
|
||||
wait_samples = 882;
|
||||
goto wait;
|
||||
case 0x66: // end of sound data
|
||||
if (vgm->hdr.loop_offset) {
|
||||
size_t target = (size_t)0x1c + CPU_LE4(vgm->hdr.loop_offset);
|
||||
if (target <= vgm->data_size) {
|
||||
fdata = vgm->data + target;
|
||||
break;
|
||||
}
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: broken loop_offset %x",
|
||||
cmd_pos, CPU_LE4(vgm->hdr.loop_offset));
|
||||
}
|
||||
elprintf(EL_VGM, "vgm: %06zx: 0x66", cmd_pos);
|
||||
fdata = vgm->data + vgm->data_size;
|
||||
break;
|
||||
case 0x67: // data block
|
||||
fdata++;
|
||||
type = *fdata++;
|
||||
length = get32(&fdata);
|
||||
if (vgm->data_pos + 7u + length > vgm->data_size) {
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: broken data block, len=%u",
|
||||
cmd_pos, length);
|
||||
fdata = vgm->data + vgm->data_size;
|
||||
break;
|
||||
}
|
||||
switch (type) {
|
||||
case BLOCK_TYPE_YM2612:
|
||||
case BLOCK_TYPE_RF5C164:
|
||||
i = vgm->block_count;
|
||||
if (i < ARRAY_SIZE(vgm->blocks)) {
|
||||
vgm->blocks[i].start = vgm->blocks[i].pos = vgm->data_pos + 7u;
|
||||
vgm->blocks[i].end = vgm->data_pos + 7u + length;
|
||||
vgm->block_count++;
|
||||
}
|
||||
else
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: too many data blocks", cmd_pos);
|
||||
break;
|
||||
case 0xc1: // RF5C164 RAM write
|
||||
if (length < 2)
|
||||
goto bad;
|
||||
doffset = get16(&fdata);
|
||||
length -= 2;
|
||||
rf5c164_write(vgm, cmd_pos, doffset, fdata, 0, length);
|
||||
break;
|
||||
default:
|
||||
bad:
|
||||
elprintf(EL_VGM | EL_ANOMALY,
|
||||
"vgm: %06zx: unhandled data block type %02x, len %x",
|
||||
cmd_pos, type, length);
|
||||
}
|
||||
fdata += length;
|
||||
break;
|
||||
case 0x68: // PCM RAM write
|
||||
fdata++;
|
||||
type = *fdata++;
|
||||
offset32 = get24(&fdata);
|
||||
doffset = get24(&fdata);
|
||||
length = get24(&fdata);
|
||||
length = ((length - 1) & 0xffffff) + 1;
|
||||
switch (type)
|
||||
{
|
||||
case BLOCK_TYPE_RF5C164:
|
||||
rf5c164_write(vgm, cmd_pos, doffset, vgm->data + vgm->blocks[0].start,
|
||||
offset32, length);
|
||||
break;
|
||||
default:
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: unhandled pcm ram type %02x",
|
||||
cmd_pos, type);
|
||||
}
|
||||
break;
|
||||
case 0x70 ... 0x7f: // wait
|
||||
wait_samples = (cmd & 0x0f) + 1;
|
||||
wait:
|
||||
vgm->sample_pos += wait_samples;
|
||||
samples = min(vgm->sample_pos, samples_per_frame);
|
||||
Pico.t.z80c_aim = samples * osc_cyc_per_smp / (15 * 256u);
|
||||
SekCycleCntS68k = s68k_base + (samples * scd_cyc_per_smp >> 8);
|
||||
break;
|
||||
case 0x80 ... 0x8f: // ym2612 dac write
|
||||
data = 0x80;
|
||||
if (vgm->blocks[0].pos < vgm->blocks[0].end) // always block 0?
|
||||
data = vgm->data[vgm->blocks[0].pos++];
|
||||
else
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: dac %x/%x",
|
||||
cmd_pos, vgm->blocks[0].pos, vgm->blocks[0].end);
|
||||
wait_samples = cmd & 0x0f;
|
||||
write_ym2612_dac:
|
||||
ym2612.OPN.ST.address = 0x2a;
|
||||
ym2612.addr_A1 = 0;
|
||||
PsndDoDAC(z80_cyclesDone());
|
||||
vgm->dacout_nonstream = ((int)data - 0x80) << DAC_SHIFT;
|
||||
ym2612.dacout = dacout_stream + vgm->dacout_nonstream;
|
||||
if (wait_samples)
|
||||
goto wait;
|
||||
break;
|
||||
case 0x90: // Stream Control
|
||||
id = *fdata++;
|
||||
type = *fdata++;
|
||||
param[0] = *fdata++;
|
||||
param[1] = *fdata++;
|
||||
if (!(stream = stream_get(vgm, cmd_pos, id)))
|
||||
break;
|
||||
stream->chip_type = type;
|
||||
stream->ioport = param[0];
|
||||
stream->reg = param[1];
|
||||
break;
|
||||
case 0x91: // Stream Data
|
||||
id = *fdata++;
|
||||
param[0] = *fdata++;
|
||||
param[1] = *fdata++;
|
||||
param[2] = *fdata++;
|
||||
if (!(stream = stream_get(vgm, cmd_pos, id)))
|
||||
break;
|
||||
if (param[0] >= ARRAY_SIZE(vgm->blocks)) {
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: bad block id %02x",
|
||||
cmd_pos, param[0]);
|
||||
break;
|
||||
}
|
||||
if (param[1] != 1 || param[2] != 0)
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: unhandled step %02x,%02x",
|
||||
cmd_pos, param[1], param[2]);
|
||||
stream->block_i = param[0];
|
||||
break;
|
||||
case 0x92: // Stream Frequency
|
||||
id = *fdata++;
|
||||
data32 = get32(&fdata);
|
||||
if (!(stream = stream_get(vgm, cmd_pos, id)))
|
||||
break;
|
||||
stream->pos_inc = ((u64)data32 << 24) / 44100u;
|
||||
break;
|
||||
case 0x93: // Start Stream
|
||||
id = *fdata++;
|
||||
offset32 = get32(&fdata);
|
||||
flags = *fdata++;
|
||||
length = get32(&fdata);
|
||||
if (!(stream = stream_get(vgm, cmd_pos, id)))
|
||||
break;
|
||||
if (!try_start_stream(vgm, stream, cmd_pos, stream->block_i, flags))
|
||||
break;
|
||||
if (offset32 != ~0) {
|
||||
struct vgm_block *block = &vgm->blocks[stream->block_i];
|
||||
if (offset32 >= block->end - block->start) {
|
||||
if (!stream->bad_start_warned) {
|
||||
elprintf(EL_VGM | EL_ANOMALY,
|
||||
"vgm: %06zx: stream start: bad offset %x", cmd_pos, offset32);
|
||||
stream->bad_start_warned = 1;
|
||||
}
|
||||
stream->playing = 0;
|
||||
break;
|
||||
}
|
||||
stream->pos = block->start + offset32;
|
||||
}
|
||||
stream->playing = 1;
|
||||
streams_playing = 1;
|
||||
break;
|
||||
case 0x94: // Stop Stream
|
||||
id = *fdata++;
|
||||
streams_playing = 0;
|
||||
if (id == 0xff) {
|
||||
for (i = 0; i < vgm->stream_count; i++)
|
||||
vgm->streams[i].playing = 0;
|
||||
break;
|
||||
}
|
||||
if (!(stream = stream_get(vgm, cmd_pos, id)))
|
||||
break;
|
||||
if (vgm->streams[id].playing)
|
||||
vgm->streams[id].playing = 0;
|
||||
for (i = 0; i < vgm->stream_count; i++) {
|
||||
stream = &vgm->streams[i];
|
||||
if (stream->playing) {
|
||||
// refresh since the current stream is done now
|
||||
stream->new_sample = 1;
|
||||
streams_playing = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x95: // Start Stream (fast call)
|
||||
id = *fdata++;
|
||||
data16 = get16(&fdata);
|
||||
flags = *fdata++;
|
||||
if (!(stream = stream_get(vgm, cmd_pos, id)))
|
||||
break;
|
||||
if (!try_start_stream(vgm, stream, cmd_pos, data16, flags))
|
||||
break;
|
||||
stream->playing = 1;
|
||||
streams_playing = 1;
|
||||
break;
|
||||
case 0xB1: // RF5C164, write
|
||||
addr = *fdata++;
|
||||
data = *fdata++;
|
||||
pcd_pcm_write(addr, data);
|
||||
break;
|
||||
case 0xe0: // seek to offset
|
||||
offset32 = get32(&fdata);
|
||||
if (offset32 < vgm->blocks[0].end - vgm->blocks[0].start) // always block 0?
|
||||
vgm->blocks[0].pos = vgm->blocks[0].start + offset32;
|
||||
else
|
||||
elprintf(EL_VGM | EL_ANOMALY, "vgm: %06zx: cmd E0 out of range: %x/%x",
|
||||
cmd_pos, offset32, vgm->blocks[0].end - vgm->blocks[0].start);
|
||||
break;
|
||||
default:
|
||||
elprintf(EL_VGM, "vgm: %06zx: skipping cmd %02x", cmd_pos, cmd);
|
||||
if (cmd >= 0xE0u)
|
||||
fdata += 4;
|
||||
else if (cmd >= 0xC0u)
|
||||
fdata += 3;
|
||||
else if (cmd >= 0xA0u)
|
||||
fdata += 2;
|
||||
else if ((cmd & 0xf0) == 0x40u)
|
||||
fdata += CPU_LE4(vgm->hdr.version) >= 0x160 ? 2 : 1;
|
||||
else if ((cmd & 0xf0) == 0x30u)
|
||||
fdata += 1;
|
||||
}
|
||||
vgm->data_pos = fdata - vgm->data;
|
||||
}
|
||||
if (!streams_playing) {
|
||||
sample_pos_stream = vgm->sample_pos;
|
||||
continue;
|
||||
}
|
||||
// stream to cmd catch-up
|
||||
{
|
||||
u32 stream_sample_to = min(vgm->sample_pos, samples_per_frame);
|
||||
int check_for_new_sample = 1;
|
||||
for (; sample_pos_stream < stream_sample_to; sample_pos_stream++)
|
||||
{
|
||||
if (check_for_new_sample) {
|
||||
int do_dac_write = 0;
|
||||
dacout_stream = 0;
|
||||
for (i = 0; i < vgm->stream_count; i++) {
|
||||
stream = &vgm->streams[i];
|
||||
if (!stream->playing)
|
||||
continue;
|
||||
do_dac_write |= vgm->streams[i].new_sample;
|
||||
vgm->streams[i].new_sample = 0;
|
||||
dacout_stream += ((int)vgm->data[stream->pos] - 0x80) << DAC_SHIFT;
|
||||
}
|
||||
if (do_dac_write) {
|
||||
Pico.t.z80c_aim = sample_pos_stream * osc_cyc_per_smp / (15 * 256u);
|
||||
ym2612.OPN.ST.address = 0x2a;
|
||||
ym2612.addr_A1 = 0;
|
||||
PsndDoDAC(z80_cyclesDone());
|
||||
ym2612.dacout = dacout_stream + vgm->dacout_nonstream;
|
||||
}
|
||||
check_for_new_sample = 0;
|
||||
}
|
||||
// advance all streams
|
||||
streams_playing = 0;
|
||||
for (i = 0; i < vgm->stream_count; i++) {
|
||||
u32 inc;
|
||||
stream = &vgm->streams[i];
|
||||
if (!stream->playing)
|
||||
continue;
|
||||
stream->pos_fraction += stream->pos_inc;
|
||||
inc = stream->pos_fraction >> 24;
|
||||
if (inc) {
|
||||
stream->pos_fraction &= 0xffffff;
|
||||
stream->pos += inc;
|
||||
if (stream->pos >= vgm->blocks[stream->block_i].end)
|
||||
stream->playing = 0;
|
||||
else
|
||||
check_for_new_sample = stream->new_sample = 1;
|
||||
}
|
||||
streams_playing |= stream->playing;
|
||||
}
|
||||
} // for sample_pos_stream
|
||||
}
|
||||
}
|
||||
// for a long wait, do only the current frame's worth of samples
|
||||
Pico.t.z80c_aim = samples_per_frame * osc_cyc_per_smp / (15 * 256u);
|
||||
SekCycleCntS68k = s68k_base + (samples_per_frame * scd_cyc_per_smp >> 8);
|
||||
vgm->sample_pos -= samples_per_frame;
|
||||
PsndDoSMSFM(Pico.t.z80c_aim);
|
||||
PsndGetSamples(0);
|
||||
Pico.m.frame_count++;
|
||||
}
|
||||
|
||||
void vgm_reset(void)
|
||||
{
|
||||
if (g_vgm)
|
||||
vgm_reset_p(g_vgm, NULL);
|
||||
}
|
||||
|
||||
void vgm_finish(void)
|
||||
{
|
||||
if (g_vgm) {
|
||||
free(g_vgm->streams);
|
||||
free(g_vgm);
|
||||
g_vgm = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=2:sw=2:expandtab
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
int vgm_load(const char *fname);
|
||||
void vgm_frame(void);
|
||||
void vgm_reset(void);
|
||||
void vgm_finish(void);
|
||||
|
||||
@@ -125,6 +125,7 @@ endif
|
||||
SRCS_COMMON += $(R)pico/sound/sound.c $(R)pico/sound/resampler.c
|
||||
SRCS_COMMON += $(R)pico/sound/sn76496.c $(R)pico/sound/ym2612.c
|
||||
SRCS_COMMON += $(R)pico/sound/ym2413.c
|
||||
SRCS_COMMON += $(R)pico/sound/vgm.c
|
||||
ifneq "$(ARCH)$(asm_mix)" "arm1"
|
||||
SRCS_COMMON += $(R)pico/sound/mix.c
|
||||
endif
|
||||
|
||||
@@ -41,6 +41,7 @@ static const char *rom_exts[] = {
|
||||
"iso", "cso", "cue", "chd",
|
||||
"32x",
|
||||
"sms", "gg", "sg", "sc",
|
||||
"vgm", "vgz",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
@@ -628,6 +628,8 @@ void lprintf(const char *fmt, ...)
|
||||
/* libretro */
|
||||
bool libretro_supports_bitmasks = false;
|
||||
|
||||
#define EXTENSIONS "bin|gen|smd|md|32x|cue|iso|chd|m3u|sms|gg|sg|sc|68k|sgd|pco|vgm|vgz"
|
||||
|
||||
void retro_set_environment(retro_environment_t cb)
|
||||
{
|
||||
bool option_categories_supported;
|
||||
@@ -637,7 +639,7 @@ void retro_set_environment(retro_environment_t cb)
|
||||
|
||||
static const struct retro_system_content_info_override content_overrides[] = {
|
||||
{
|
||||
"bin|gen|smd|md|32x|sms|gg|sg|sc|68k|sgd|pco", /* extensions */
|
||||
EXTENSIONS,
|
||||
#if defined(LOW_MEMORY)
|
||||
true, /* need_fullpath */
|
||||
#else
|
||||
@@ -683,7 +685,7 @@ void retro_get_system_info(struct retro_system_info *info)
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->library_name = "PicoDrive";
|
||||
info->library_version = VERSION;
|
||||
info->valid_extensions = "bin|gen|smd|md|32x|cue|iso|chd|sms|gg|sg|sc|m3u|68k|sgd|pco";
|
||||
info->valid_extensions = EXTENSIONS;
|
||||
info->need_fullpath = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ static void LoadROM(const char *cmdpath)
|
||||
rompath[sizeof(rompath) - 1] = 0;
|
||||
strncpy(rompath, rom_fname_loaded, sizeof(rompath) - 1);
|
||||
of.lStructSize = sizeof(of);
|
||||
of.lpstrFilter = "ROMs, CD images\0*.smd;*.bin;*.gen;*.zip;*.32x;*.sms;*.iso;*.cso;*.cue\0"
|
||||
of.lpstrFilter = "ROMs, CD images\0*.smd;*.bin;*.gen;*.zip;*.32x;*.sms;*.iso;*.cso;*.cue;*.vgm;*.vgz\0"
|
||||
"whatever\0*.*\0";
|
||||
of.lpstrFile = rompath;
|
||||
of.nMaxFile = MAX_PATH;
|
||||
|
||||
Reference in new issue
Block a user