forked from KolibriOS/kolibrios
366 lines
7.7 KiB
C++
366 lines
7.7 KiB
C++
|
// DGen v1.29
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <errno.h>
|
||
|
#include "md.h"
|
||
|
#include "rc-vars.h"
|
||
|
|
||
|
// REMEMBER NOT TO USE ANY STATIC variables, because they
|
||
|
// will exist thoughout ALL megadrives!
|
||
|
int md::myfm_write(int a, int v, int md)
|
||
|
{
|
||
|
int sid = 0;
|
||
|
int pass = 1;
|
||
|
|
||
|
(void)md;
|
||
|
a &= 3;
|
||
|
sid = ((a & 0x02) >> 1);
|
||
|
if ((a & 0x01) == 0) {
|
||
|
fm_sel[sid] = v;
|
||
|
goto end;
|
||
|
}
|
||
|
#ifdef WITH_VGMDUMP
|
||
|
vgm_dump_ym2612(sid, fm_sel[sid], v);
|
||
|
#endif
|
||
|
if (fm_sel[sid] == 0x2a) {
|
||
|
dac_submit((uint8_t)v);
|
||
|
pass = 0;
|
||
|
}
|
||
|
if (fm_sel[sid] == 0x2b) {
|
||
|
dac_enable((uint8_t)v);
|
||
|
pass = 0;
|
||
|
}
|
||
|
if (fm_sel[sid] == 0x27) {
|
||
|
unsigned int now = frame_usecs();
|
||
|
|
||
|
if ((v & 0x01) && ((fm_reg[0][0x27] & 0x01) == 0)) {
|
||
|
// load timer A
|
||
|
fm_ticker[0] = 0;
|
||
|
fm_ticker[1] = now;
|
||
|
}
|
||
|
if ((v & 0x02) && ((fm_reg[0][0x27] & 0x02) == 0)) {
|
||
|
// load timer B
|
||
|
fm_ticker[2] = 0;
|
||
|
fm_ticker[3] = now;
|
||
|
}
|
||
|
// (v & 0x04) enable/disable timer A
|
||
|
// (v & 0x08) enable/disable timer B
|
||
|
if (v & 0x10) {
|
||
|
// reset overflow A
|
||
|
fm_tover &= ~0x01;
|
||
|
v &= ~0x10;
|
||
|
fm_reg[0][0x27] &= ~0x10;
|
||
|
}
|
||
|
if (v & 0x20) {
|
||
|
// reset overflow B
|
||
|
fm_tover &= ~0x02;
|
||
|
v &= ~0x20;
|
||
|
fm_reg[0][0x27] &= ~0x20;
|
||
|
}
|
||
|
}
|
||
|
// stash all values
|
||
|
fm_reg[sid][(fm_sel[sid])] = v;
|
||
|
end:
|
||
|
if (pass) {
|
||
|
YM2612Write(0, a, v);
|
||
|
if (dgen_mjazz) {
|
||
|
YM2612Write(1, a, v);
|
||
|
YM2612Write(2, a, v);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int md::myfm_read(int a)
|
||
|
{
|
||
|
fm_timer_callback();
|
||
|
return (fm_tover | (YM2612Read(0, (a & 3)) & ~0x03));
|
||
|
}
|
||
|
|
||
|
int md::mysn_write(int d)
|
||
|
{
|
||
|
#ifdef WITH_VGMDUMP
|
||
|
vgm_dump_sn76496(d);
|
||
|
#endif
|
||
|
SN76496Write(0, d);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int md::fm_timer_callback()
|
||
|
{
|
||
|
// periods in microseconds for timers A and B
|
||
|
int amax = (18 * (1024 -
|
||
|
(((fm_reg[0][0x24] << 2) |
|
||
|
(fm_reg[0][0x25] & 0x03)) & 0x3ff)));
|
||
|
int bmax = (288 * (256 - (fm_reg[0][0x26] & 0xff)));
|
||
|
unsigned int now = frame_usecs();
|
||
|
|
||
|
if ((fm_reg[0][0x27] & 0x01) && ((now - fm_ticker[1]) > 0)) {
|
||
|
fm_ticker[0] += (now - fm_ticker[1]);
|
||
|
fm_ticker[1] = now;
|
||
|
if (fm_ticker[0] >= amax) {
|
||
|
if (fm_reg[0][0x27] & 0x04)
|
||
|
fm_tover |= 0x01;
|
||
|
fm_ticker[0] -= amax;
|
||
|
}
|
||
|
}
|
||
|
if ((fm_reg[0][0x27] & 0x02) && ((now - fm_ticker[3]) > 0)) {
|
||
|
fm_ticker[2] += (now - fm_ticker[3]);
|
||
|
fm_ticker[3] = now;
|
||
|
if (fm_ticker[2] >= bmax) {
|
||
|
if (fm_reg[0][0x27] & 0x08)
|
||
|
fm_tover |= 0x02;
|
||
|
fm_ticker[2] -= bmax;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void md::fm_reset()
|
||
|
{
|
||
|
memset(fm_sel, 0, sizeof(fm_sel));
|
||
|
fm_tover = 0x00;
|
||
|
memset(fm_ticker, 0, sizeof(fm_ticker));
|
||
|
memset(fm_reg, 0, sizeof(fm_reg));
|
||
|
YM2612ResetChip(0);
|
||
|
if (dgen_mjazz) {
|
||
|
YM2612ResetChip(1);
|
||
|
YM2612ResetChip(2);
|
||
|
}
|
||
|
SN76496_init(0,
|
||
|
(((pal) ? PAL_MCLK : NTSC_MCLK) / 15),
|
||
|
dgen_soundrate, 16);
|
||
|
}
|
||
|
|
||
|
void md::dac_init()
|
||
|
{
|
||
|
dac_enabled = true;
|
||
|
dac_len = 0;
|
||
|
#ifndef NDEBUG
|
||
|
memset(dac_data, 0xff, sizeof(dac_data));
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static const struct {
|
||
|
unsigned int samples;
|
||
|
unsigned int usecs;
|
||
|
} per_frame[2] = {
|
||
|
{ (44100 / 60), (1000000 / 60) },
|
||
|
{ (44100 / 50), (1000000 / 50) },
|
||
|
};
|
||
|
|
||
|
void md::dac_submit(uint8_t d)
|
||
|
{
|
||
|
unsigned int usecs;
|
||
|
unsigned int index;
|
||
|
unsigned int i;
|
||
|
|
||
|
if (!dac_enabled)
|
||
|
return;
|
||
|
if (dac_len == elemof(dac_data))
|
||
|
return;
|
||
|
usecs = frame_usecs();
|
||
|
index = ((usecs << 10) /
|
||
|
((per_frame[pal].usecs << 10) /
|
||
|
elemof(dac_data)));
|
||
|
if (index >= elemof(dac_data))
|
||
|
return;
|
||
|
dac_data[index] = d;
|
||
|
if (dac_len)
|
||
|
d = dac_data[dac_len - 1];
|
||
|
for (i = dac_len; (i < index); ++i)
|
||
|
dac_data[i] = d;
|
||
|
dac_len = (index + 1);
|
||
|
}
|
||
|
|
||
|
void md::dac_enable(uint8_t d)
|
||
|
{
|
||
|
dac_enabled = ((d & 0x80) >> 7);
|
||
|
}
|
||
|
|
||
|
#ifdef WITH_VGMDUMP
|
||
|
|
||
|
void md::vgm_dump_ym2612(uint8_t a1, uint8_t reg, uint8_t data)
|
||
|
{
|
||
|
if (vgm_dump) {
|
||
|
uint8_t buf[] = { (uint8_t)(0x52 + a1), reg, data };
|
||
|
|
||
|
fwrite(buf, sizeof(buf), 1, vgm_dump_file);
|
||
|
if ((a1 == 0) && (reg == 0x2a)) {
|
||
|
unsigned int usecs = frame_usecs();
|
||
|
unsigned int samples;
|
||
|
unsigned int diff;
|
||
|
|
||
|
if (usecs > per_frame[pal].usecs)
|
||
|
usecs = per_frame[pal].usecs;
|
||
|
samples = ((usecs *
|
||
|
((per_frame[pal].samples << 20) /
|
||
|
per_frame[pal].usecs)) >> 20);
|
||
|
diff = (samples - vgm_dump_dac_samples);
|
||
|
if ((diff > 0) && (diff <= 16)) {
|
||
|
fputc((0x70 + (diff - 1)), vgm_dump_file);
|
||
|
vgm_dump_dac_wait += diff;
|
||
|
}
|
||
|
vgm_dump_dac_samples = samples;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void md::vgm_dump_sn76496(uint8_t data)
|
||
|
{
|
||
|
if (vgm_dump) {
|
||
|
uint8_t buf[] = { 0x50, data };
|
||
|
|
||
|
fwrite(buf, sizeof(buf), 1, vgm_dump_file);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void md::vgm_dump_frame()
|
||
|
{
|
||
|
unsigned int max = per_frame[pal].samples;
|
||
|
|
||
|
if (!vgm_dump)
|
||
|
return;
|
||
|
if (vgm_dump_dac_wait < max) {
|
||
|
uint8_t buf[] = { 0x61, 0x00, 0x00 };
|
||
|
uint16_t tmp = h2le16(max - vgm_dump_dac_wait);
|
||
|
|
||
|
memcpy(&buf[1], &tmp, sizeof(tmp));
|
||
|
fwrite(buf, sizeof(buf), 1, vgm_dump_file);
|
||
|
}
|
||
|
vgm_dump_samples_total += max;
|
||
|
vgm_dump_dac_wait = 0;
|
||
|
vgm_dump_dac_samples = 0;
|
||
|
}
|
||
|
|
||
|
// Generate VGM 1.70 header as defined by:
|
||
|
// http://www.smspower.org/uploads/Music/vgmspec170.txt
|
||
|
int md::vgm_dump_start(const char *name)
|
||
|
{
|
||
|
uint8_t ym2612_buf[0x200];
|
||
|
uint8_t buf[0x100] = { 0 };
|
||
|
union {
|
||
|
uint32_t u32;
|
||
|
uint16_t u16;
|
||
|
} tmp;
|
||
|
unsigned int i;
|
||
|
int err;
|
||
|
|
||
|
if (vgm_dump == true)
|
||
|
vgm_dump_stop();
|
||
|
vgm_dump_file = dgen_fopen("vgm", name, DGEN_WRITE);
|
||
|
if (vgm_dump_file == NULL)
|
||
|
return -1;
|
||
|
// 0x00: file identifier.
|
||
|
memcpy(&buf[0x00], "Vgm ", 4);
|
||
|
// 0x04: EoF offset. Not known yet.
|
||
|
// 0x08: version number (1.70).
|
||
|
tmp.u32 = 0x0170;
|
||
|
memcpy(&buf[0x08], &tmp.u32, 4);
|
||
|
// 0x0c: SN76489 (PSG) clock.
|
||
|
tmp.u32 = h2le32(clk0);
|
||
|
memcpy(&buf[0x0c], &tmp.u32, 4);
|
||
|
// 0x18: total # samples. Not known yet.
|
||
|
// 0x24: rate.
|
||
|
tmp.u32 = h2le32(vhz);
|
||
|
memcpy(&buf[0x24], &tmp.u32, 4);
|
||
|
// 0x28: SN76489 (PSG) feedback.
|
||
|
tmp.u16 = h2le16(0x0009);
|
||
|
memcpy(&buf[0x28], &tmp.u16, 2);
|
||
|
// 0x2a: SN76489 shift register width.
|
||
|
buf[0x2a] = 16;
|
||
|
// 0x2b: SN76489 flags.
|
||
|
buf[0x2b] = 0x00;
|
||
|
// 0x2c: YM2612 clock.
|
||
|
tmp.u32 = h2le32(clk1);
|
||
|
memcpy(&buf[0x2c], &tmp.u32, 4);
|
||
|
// 0x34: VGM data offset.
|
||
|
tmp.u32 = h2le32(sizeof(buf) - 0x34);
|
||
|
memcpy(&buf[0x34], &tmp.u32, 4);
|
||
|
// Dump VGM header.
|
||
|
if (fwrite(buf, sizeof(buf), 1, vgm_dump_file) != 1)
|
||
|
goto error;
|
||
|
// Dump YM2612 registers directly.
|
||
|
YM2612_dump(0, ym2612_buf);
|
||
|
// Timers.
|
||
|
{
|
||
|
uint8_t buf[] = {
|
||
|
0x52, 0x24, (uint8_t)fm_reg[0][0x24],
|
||
|
0x52, 0x25, (uint8_t)fm_reg[0][0x25],
|
||
|
0x52, 0x26, (uint8_t)fm_reg[0][0x26],
|
||
|
0x52, 0x27, (uint8_t)fm_reg[0][0x27],
|
||
|
};
|
||
|
|
||
|
if (fwrite(buf, sizeof(buf), 1, vgm_dump_file) != 1)
|
||
|
goto error;
|
||
|
}
|
||
|
// DAC.
|
||
|
{
|
||
|
uint8_t buf[] = { 0x52, 0x2b, (uint8_t)(dac_enabled << 7) };
|
||
|
|
||
|
if (fwrite(buf, sizeof(buf), 1, vgm_dump_file) != 1)
|
||
|
goto error;
|
||
|
}
|
||
|
// FM CH1-CH3.
|
||
|
for (i = 0x30; (i != 0x9e); ++i) {
|
||
|
uint8_t buf[] = {
|
||
|
0x52, (uint8_t)i, ym2612_buf[i],
|
||
|
0x53, (uint8_t)i, ym2612_buf[i | 0x100],
|
||
|
};
|
||
|
|
||
|
if (fwrite(buf, sizeof(buf), 1, vgm_dump_file) != 1)
|
||
|
goto error;
|
||
|
}
|
||
|
// FM CH4-CH6.
|
||
|
for (i = 0xb0; (i != 0xb6); ++i) {
|
||
|
uint8_t buf[] = {
|
||
|
0x52, (uint8_t)i, ym2612_buf[i],
|
||
|
0x53, (uint8_t)i, ym2612_buf[i | 0x100],
|
||
|
};
|
||
|
|
||
|
if (fwrite(buf, sizeof(buf), 1, vgm_dump_file) != 1)
|
||
|
goto error;
|
||
|
}
|
||
|
vgm_dump_samples_total = 0;
|
||
|
vgm_dump_dac_wait = 0;
|
||
|
vgm_dump_dac_samples = 0;
|
||
|
vgm_dump = true;
|
||
|
return 0;
|
||
|
error:
|
||
|
err = errno;
|
||
|
fclose(vgm_dump_file);
|
||
|
vgm_dump_file = NULL;
|
||
|
errno = err;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
void md::vgm_dump_stop()
|
||
|
{
|
||
|
long pos;
|
||
|
uint32_t tmp;
|
||
|
|
||
|
if (!vgm_dump)
|
||
|
return;
|
||
|
// Append end of sound data.
|
||
|
fputc(0x66, vgm_dump_file);
|
||
|
pos = ftell(vgm_dump_file);
|
||
|
// Fill EoF offset.
|
||
|
fseek(vgm_dump_file, 0x04, SEEK_SET);
|
||
|
tmp = h2le32(pos - 4);
|
||
|
fwrite(&tmp, sizeof(tmp), 1, vgm_dump_file);
|
||
|
// Fill total number of samples.
|
||
|
fseek(vgm_dump_file, 0x18, SEEK_SET);
|
||
|
tmp = h2le32(vgm_dump_samples_total);
|
||
|
fwrite(&tmp, sizeof(tmp), 1, vgm_dump_file);
|
||
|
fclose(vgm_dump_file);
|
||
|
vgm_dump_file = NULL;
|
||
|
vgm_dump_samples_total = 0;
|
||
|
vgm_dump_dac_wait = 0;
|
||
|
vgm_dump_dac_samples = 0;
|
||
|
vgm_dump = false;
|
||
|
}
|
||
|
|
||
|
#endif // WITH_VGMDUMP
|