sound: add vgm playback support

This commit is contained in:
notaz authored and irixxxx committed 2025-07-30 08:07:57 +02:00
1 parent 150c28559e
commit 9e304a66eb
13 files changed
+711 -17

No files matched your search

+2
View File
@@ -11,6 +11,8 @@
*.a
tags
cscope.out
compile_commands.json
.gdb_history
config.mak
config.log
cpu/musashi/m68kmake
+5 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+2
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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;
+644
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
int vgm_load(const char *fname);
void vgm_frame(void);
void vgm_reset(void);
void vgm_finish(void);
+1
View File
@@ -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
+1
View File
@@ -41,6 +41,7 @@ static const char *rom_exts[] = {
"iso", "cso", "cue", "chd",
"32x",
"sms", "gg", "sg", "sc",
"vgm", "vgz",
NULL
};
+4 -2
View File
@@ -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;
}
+1 -1
View File
@@ -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;