Merge pull request #421 from cam900/ay_divider

Some AY-3-8910, AY8930 enhancements
This commit is contained in:
tildearrow 2022-05-15 02:14:50 -05:00 committed by GitHub
commit c5c612c354
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 254 additions and 151 deletions

1
.gitignore vendored
View file

@ -17,3 +17,4 @@ android/app/build/
android/app/.cxx/
.vs/
CMakeSettings.json
CMakePresets.json

View file

@ -27,7 +27,7 @@
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} }
#define CHIP_DIVIDER 8
#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8)
const char* regCheatSheetAY[]={
"FreqL_A", "0",
@ -589,6 +589,7 @@ void DivPlatformAY8910::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformAY8910::setFlags(unsigned int flags) {
clockSel=(flags>>7)&1;
switch (flags&15) {
case 1:
chipClock=COLOR_PAL*2.0/5.0;
@ -620,6 +621,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
case 10:
chipClock=2097152;
break;
case 11:
chipClock=COLOR_NTSC;
break;
case 12:
chipClock=3600000;
break;
default:
chipClock=COLOR_NTSC/2.0;
break;
@ -632,7 +639,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
if (ay!=NULL) delete ay;
switch ((flags>>4)&3) {
case 1:
ay=new ym2149_device(rate);
ay=new ym2149_device(rate,clockSel);
sunsoft=false;
intellivision=false;
break;
@ -654,7 +661,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
}
ay->device_start();
stereo=flags>>6;
stereo=(flags>>6)&1;
}
int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {

View file

@ -70,7 +70,7 @@ class DivPlatformAY8910: public DivDispatch {
int delay;
bool extMode;
bool stereo, sunsoft, intellivision;
bool stereo, sunsoft, intellivision, clockSel;
bool ioPortA, ioPortB;
unsigned char portAVal, portBVal;

View file

@ -27,7 +27,7 @@
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite2(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 4
#define CHIP_DIVIDER (clockSel?8:4)
const char* regCheatSheetAY8930[]={
"FreqL_A", "00",
@ -64,7 +64,7 @@ const char* regCheatSheetAY8930[]={
void DivPlatformAY8930::immWrite(unsigned char a, unsigned char v) {
if ((int)bank!=(a>>4)) {
bank=a>>4;
immWrite2(0x0d, 0xa0|(bank<<4)|ayEnvMode[0]);
immWrite2(0x0d, 0xa0|(bank<<4)|chan[0].envelope.mode);
}
if (a==0x0d) {
immWrite2(0x0d,0xa0|(bank<<4)|(v&15));
@ -258,8 +258,8 @@ void DivPlatformAY8930::tick(bool sysTick) {
rWrite(0x16+i,chan[i].std.ex1.val);
}
if (chan[i].std.ex2.had) {
ayEnvMode[i]=chan[i].std.ex2.val;
rWrite(regMode[i],ayEnvMode[i]);
chan[i].envelope.mode=chan[i].std.ex2.val;
rWrite(regMode[i],chan[i].envelope.mode);
}
if (chan[i].std.ex3.had) {
chan[i].autoEnvNum=chan[i].std.ex3.val;
@ -296,29 +296,29 @@ void DivPlatformAY8930::tick(bool sysTick) {
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
ayEnvPeriod[i]=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
chan[i].envelope.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4;
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
}
chan[i].freqChanged=false;
}
if (ayEnvSlide[i]!=0) {
ayEnvSlideLow[i]+=ayEnvSlide[i];
while (ayEnvSlideLow[i]>7) {
ayEnvSlideLow[i]-=8;
if (ayEnvPeriod[i]<0xffff) {
ayEnvPeriod[i]++;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
if (chan[i].envelope.slide!=0) {
chan[i].envelope.slideLow+=chan[i].envelope.slide;
while (chan[i].envelope.slideLow>7) {
chan[i].envelope.slideLow-=8;
if (chan[i].envelope.period<0xffff) {
chan[i].envelope.period++;
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
}
}
while (ayEnvSlideLow[i]<-7) {
ayEnvSlideLow[i]+=8;
if (ayEnvPeriod[i]>0) {
ayEnvPeriod[i]--;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
while (chan[i].envelope.slideLow<-7) {
chan[i].envelope.slideLow+=8;
if (chan[i].envelope.period>0) {
chan[i].envelope.period--;
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
}
}
}
@ -435,8 +435,8 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
rWrite(0x06,c.value);
break;
case DIV_CMD_AY_ENVELOPE_SET:
ayEnvMode[c.chan]=c.value>>4;
rWrite(regMode[c.chan],ayEnvMode[c.chan]);
chan[c.chan].envelope.mode=c.value>>4;
rWrite(regMode[c.chan],chan[c.chan].envelope.mode);
if (c.value&15) {
chan[c.chan].psgMode|=4;
} else {
@ -449,19 +449,19 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
}
break;
case DIV_CMD_AY_ENVELOPE_LOW:
ayEnvPeriod[c.chan]&=0xff00;
ayEnvPeriod[c.chan]|=c.value;
immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]);
immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8);
chan[c.chan].envelope.period&=0xff00;
chan[c.chan].envelope.period|=c.value;
immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period);
immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8);
break;
case DIV_CMD_AY_ENVELOPE_HIGH:
ayEnvPeriod[c.chan]&=0xff;
ayEnvPeriod[c.chan]|=c.value<<8;
immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]);
immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8);
chan[c.chan].envelope.period&=0xff;
chan[c.chan].envelope.period|=c.value<<8;
immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period);
immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8);
break;
case DIV_CMD_AY_ENVELOPE_SLIDE:
ayEnvSlide[c.chan]=c.value;
chan[c.chan].envelope.slide=c.value;
break;
case DIV_CMD_AY_NOISE_MASK_AND:
ayNoiseAnd=c.value;
@ -526,9 +526,9 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) {
void DivPlatformAY8930::forceIns() {
for (int i=0; i<3; i++) {
chan[i].insChanged=true;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
immWrite(regMode[i],ayEnvMode[i]);
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
immWrite(regMode[i],chan[i].envelope.mode);
}
}
@ -556,10 +556,10 @@ void DivPlatformAY8930::reset() {
chan[i]=DivPlatformAY8930::Channel();
chan[i].std.setEngine(parent);
chan[i].vol=31;
ayEnvPeriod[i]=0;
ayEnvMode[i]=0;
ayEnvSlide[i]=0;
ayEnvSlideLow[i]=0;
chan[i].envelope.period=0;
chan[i].envelope.mode=0;
chan[i].envelope.slide=0;
chan[i].envelope.slideLow=0;
}
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -610,6 +610,7 @@ void DivPlatformAY8930::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformAY8930::setFlags(unsigned int flags) {
clockSel=(flags>>7)&1;
switch (flags&15) {
case 1:
chipClock=COLOR_PAL*2.0/5.0;
@ -641,6 +642,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) {
case 10:
chipClock=2097152;
break;
case 11:
chipClock=COLOR_NTSC;
break;
case 12:
chipClock=3600000;
break;
default:
chipClock=COLOR_NTSC/2.0;
break;
@ -650,7 +657,7 @@ void DivPlatformAY8930::setFlags(unsigned int flags) {
oscBuf[i]->rate=rate;
}
stereo=flags>>6;
stereo=(flags>>6)&1;
}
int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
@ -662,7 +669,7 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
ay=new ay8930_device(rate);
ay=new ay8930_device(rate,clockSel);
ay->device_start();
ayBufLen=65536;
for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen];

View file

@ -27,6 +27,17 @@
class DivPlatformAY8930: public DivDispatch {
protected:
struct Channel {
struct Envelope {
unsigned char mode;
unsigned short period;
short slideLow;
short slide;
Envelope():
mode(0),
period(0),
slideLow(0),
slide(0) {}
} envelope;
unsigned char freqH, freqL;
int freq, baseFreq, note, pitch, pitch2;
int ins;
@ -59,16 +70,12 @@ class DivPlatformAY8930: public DivDispatch {
int delay;
bool extMode, stereo;
bool extMode, stereo, clockSel;
bool ioPortA, ioPortB;
unsigned char portAVal, portBVal;
short oldWrites[32];
short pendingWrites[32];
unsigned char ayEnvMode[3];
unsigned short ayEnvPeriod[3];
short ayEnvSlideLow[3];
short ayEnvSlide[3];
short* ayBuf[3];
size_t ayBufLen;

View file

@ -1021,10 +1021,8 @@ void ay8910_device::ay8910_write_reg(int r, int v)
m_tone[2].set_duty(m_regs[AY_CDUTY]);
break;
case AY_NOISEAND:
m_noise_and=m_regs[AY_NOISEAND];
break;
case AY_NOISEOR:
m_noise_or=m_regs[AY_NOISEOR];
// No action required
break;
default:
m_regs[r] = 0; // reserved, set as 0
@ -1047,7 +1045,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
if (!m_ready)
{
for (int chan = 0; chan < m_streams; chan++)
memset(outputs[chan],0,outLen*sizeof(short));
memset(outputs[chan],0,outLen*sizeof(short));
}
/* The 8910 has three outputs, each output is the mix of one of the three */
@ -1063,8 +1061,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
for (int chan = 0; chan < NUM_CHANNELS; chan++)
{
tone = &m_tone[chan];
const int period = std::max<int>(1,tone->period);
tone->count += is_expanded_mode() ? 16 : (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1;
const int period = tone->period * (m_step_mul << 1);
tone->count += is_expanded_mode() ? 32 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 1 : 2);
while (tone->count >= period)
{
tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f;
@ -1073,8 +1071,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
}
}
m_count_noise++;
if (m_count_noise >= noise_period())
const int period_noise = (int)(noise_period()) * m_step_mul;
if ((++m_count_noise) >= period_noise)
{
/* toggle the prescaler output. Noise is no different to
* channels.
@ -1082,39 +1080,27 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
m_count_noise = 0;
m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1);
if (!m_prescale_noise || is_expanded_mode()) // AY8930 noise generator rate is twice compares as compatibility mode
if (is_expanded_mode()) // AY8930 noise generator rate is twice? compares as compatibility mode
{
if (is_expanded_mode()) {
// This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR.
// Using AND/OR gates, specific periods can be "filtered" out.
// A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation.
// The period of the noise is determined by this value.
// The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask.
unsigned int noiseValuePeriod = ((m_rng & 0xFF & m_noise_and) | m_noise_or);
// Clock the noise value.
if (m_noise_value >= noiseValuePeriod) {
m_noise_value = 0;
// When everything is finally said and done, a 1bit latch is flipped.
// This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel.
m_noise_latch ^= 1;
// The 17-bit LFSR is updated, using an XOR across bits 0 and 2.
unsigned int feedback = (m_rng & 1) ^ ((m_rng >> 2) & 1);
m_rng >>= 1;
m_rng |= (feedback << 16);
}
m_noise_value++;
} else {
/* The Random Number Generator of the 8910 is a 17-bit shift */
/* register. The input to the shift register is bit0 XOR bit3 */
/* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */
m_rng ^= (((m_rng & 1) ^ ((m_rng >> 3) & 1)) << 17);
m_rng >>= 1;
}
// This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR.
// Using AND/OR gates, specific periods can be "filtered" out.
// A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation.
// The period of the noise is determined by this value.
// The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask.
if ((++m_noise_value) >= (((unsigned char)(m_rng) & noise_and()) | noise_or())) // Clock the noise value.
{
m_noise_value = 0;
// When everything is finally said and done, a 1bit latch is flipped.
// This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel.
m_noise_out ^= 1;
noise_rng_tick();
}
}
else if (!m_prescale_noise)
noise_rng_tick();
}
for (int chan = 0; chan < NUM_CHANNELS; chan++)
@ -1129,9 +1115,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
envelope = &m_envelope[chan];
if (envelope->holding == 0)
{
const int period = envelope->period * m_step;
envelope->count++;
if (envelope->count >= period)
const int period = envelope->period * m_env_step_mul;
if ((++envelope->count) >= period)
{
envelope->count = 0;
envelope->step--;
@ -1263,20 +1248,14 @@ void ay8910_device::device_start()
}
void ay8910_device::ay8910_reset_ym(bool ay8930)
void ay8910_device::ay8910_reset_ym()
{
m_active = false;
m_register_latch = 0;
if (ay8930) {
m_rng = 0x1ffff;
} else {
m_rng = 1;
}
m_rng = (m_feature & PSG_HAS_EXPANDED_MODE) ? 0x1ffff : 1;
m_mode = 0; // ay-3-8910 compatible mode
m_noise_and = 0xff;
m_noise_or = 0;
m_noise_value = 0;
m_noise_latch = 0;
m_noise_value = 0;
m_noise_out = 0;
for (int chan = 0; chan < NUM_CHANNELS; chan++)
{
m_tone[chan].reset();
@ -1335,7 +1314,7 @@ void ay8910_device::ay8910_write_ym(int addr, unsigned char data)
unsigned char ay8910_device::ay8910_read_ym()
{
int r = m_register_latch + get_register_bank();
unsigned char r = m_register_latch + get_register_bank();
if (!m_active) return 0xff; // high impedance
@ -1380,7 +1359,7 @@ unsigned char ay8910_device::ay8910_read_ym()
void ay8910_device::device_reset()
{
ay8910_reset_ym(chip_type == AY8930);
ay8910_reset_ym();
}
/*************************************
@ -1452,28 +1431,27 @@ ay8910_device::ay8910_device(unsigned int clock)
}
ay8910_device::ay8910_device(device_type type, unsigned int clock,
psg_type_t psg_type, int streams, int ioports, int feature)
psg_type_t psg_type, int streams, int ioports, int feature, bool clk_sel)
: chip_type(type),
m_type(psg_type),
m_type(psg_type),
m_streams(streams),
m_ready(0),
m_active(false),
m_register_latch(0),
m_last_enable(0),
m_prescale_noise(0),
m_noise_value(0),
m_count_noise(0),
m_rng(0),
m_noise_and(0),
m_noise_or(0),
m_noise_value(0),
m_noise_latch(0),
m_noise_out(0),
m_mode(0),
m_env_step_mask((!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 0x0f : 0x1f),
m_step( (feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY) ? 2 : 1),
m_step_mul( ((feature & PSG_HAS_INTERNAL_DIVIDER) || ((feature & PSG_PIN26_IS_CLKSEL) && clk_sel)) ? 2 : 1),
m_env_step_mul( ((feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY)) ? (m_step_mul << 1) : m_step_mul),
m_zero_is_off( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 1 : 0),
m_par( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param),
m_par_env( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param_env),
m_flags(AY8910_LEGACY_OUTPUT),
m_flags(AY8910_LEGACY_OUTPUT | (((feature & PSG_PIN26_IS_CLKSEL) && clk_sel) ? YM2149_PIN26_LOW : 0)),
m_feature(feature)
{
memset(&m_regs,0,sizeof(m_regs));
@ -1485,16 +1463,16 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock,
m_res_load[0] = m_res_load[1] = m_res_load[2] = 1000; //Default values for resistor loads
// TODO : measure ay8930 volume parameters (PSG_TYPE_YM for temporary 5 bit handling)
set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type);
set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type, clk_sel);
}
void ay8910_device::set_type(psg_type_t psg_type)
void ay8910_device::set_type(psg_type_t psg_type, bool clk_sel)
{
m_type = psg_type;
if (psg_type == PSG_TYPE_AY)
{
m_env_step_mask = 0x0f;
m_step = 2;
m_env_step_mul = is_clock_divided() ? 4 : 2;
m_zero_is_off = 1;
m_par = &ay8910_param;
m_par_env = &ay8910_param;
@ -1502,11 +1480,15 @@ void ay8910_device::set_type(psg_type_t psg_type)
else
{
m_env_step_mask = 0x1f;
m_step = (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1;
m_env_step_mul = is_clock_divided() ? 2 : 1;
m_zero_is_off = 0;
m_par = &ym2149_param;
m_par_env = &ym2149_param_env;
}
if (m_feature & PSG_HAS_EXPANDED_MODE)
m_env_step_mul <<= 1;
set_clock_sel(clk_sel);
}
@ -1535,24 +1517,24 @@ ay8914_device::ay8914_device(unsigned int clock)
ay8930_device::ay8930_device(unsigned int clock)
: ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE)
ay8930_device::ay8930_device(unsigned int clock, bool clk_sel)
: ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE, clk_sel)
{
}
ym2149_device::ym2149_device(unsigned int clock)
: ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL)
ym2149_device::ym2149_device(unsigned int clock, bool clk_sel)
: ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel)
{
}
ym3439_device::ym3439_device(unsigned int clock)
: ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL)
ym3439_device::ym3439_device(unsigned int clock, bool clk_sel)
: ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel)
{
}

View file

@ -3,6 +3,10 @@
#ifndef MAME_SOUND_AY8910_H
#define MAME_SOUND_AY8910_H
#pragma once
#include <algorithm>
#define ALL_8910_CHANNELS -1
/* Internal resistance at Volume level 7. */
@ -89,7 +93,8 @@ public:
// configuration helpers
void set_flags(int flags) { m_flags = flags; }
void set_psg_type(psg_type_t psg_type) { set_type(psg_type); }
void set_psg_type(psg_type_t psg_type) { set_type(psg_type, m_flags & YM2149_PIN26_LOW); }
void set_psg_type(psg_type_t psg_type, bool clk_sel) { set_type(psg_type, clk_sel); }
void set_resistors_load(int res_load0, int res_load1, int res_load2) { m_res_load[0] = res_load0; m_res_load[1] = res_load1; m_res_load[2] = res_load2; }
unsigned char data_r() { return ay8910_read_ym(); }
@ -97,7 +102,24 @@ public:
void data_w(unsigned char data);
// /RES
void reset_w(unsigned char data = 0) { ay8910_reset_ym(chip_type == AY8930); }
void reset_w(unsigned char data = 0) { ay8910_reset_ym(); }
// Clock select pin
void set_clock_sel(bool clk_sel)
{
if (m_feature & PSG_PIN26_IS_CLKSEL)
{
if (clk_sel)
m_flags |= YM2149_PIN26_LOW;
else
m_flags &= ~YM2149_PIN26_LOW;
m_step_mul = is_clock_divided() ? 2 : 1;
m_env_step_mul = (!(m_feature & PSG_HAS_EXPANDED_MODE)) && (m_type == PSG_TYPE_AY) ? (m_step_mul << 1) : m_step_mul;
if (m_feature & PSG_HAS_EXPANDED_MODE)
m_env_step_mul <<= 1;
}
}
// use this when BC1 == A0; here, BC1=0 selects 'data' and BC1=1 selects 'latch address'
void data_address_w(int offset, unsigned char data) { ay8910_write_ym(~offset & 1, data); } // note that directly connecting BC1 to A0 puts data on 0 and address on 1
@ -127,7 +149,7 @@ public:
// internal interface for PSG component of YM device
// FIXME: these should be private, but vector06 accesses them directly
ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT);
ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT, bool clk_sel = false);
// device-level overrides
void device_start();
@ -138,7 +160,7 @@ public:
void ay8910_write_ym(int addr, unsigned char data);
unsigned char ay8910_read_ym();
void ay8910_reset_ym(bool ay8930);
void ay8910_reset_ym();
private:
static constexpr int NUM_CHANNELS = 3;
@ -189,7 +211,7 @@ private:
void reset()
{
period = 0;
period = 1;
volume = 0;
duty = 0;
count = 0;
@ -199,7 +221,7 @@ private:
void set_period(unsigned char fine, unsigned char coarse)
{
period = fine | (coarse << 8);
period = std::max<unsigned int>(1, fine | (coarse << 8));
}
void set_volume(unsigned char val)
@ -223,7 +245,7 @@ private:
void reset()
{
period = 0;
period = 1;
count = 0;
step = 0;
volume = 0;
@ -235,7 +257,7 @@ private:
void set_period(unsigned char fine, unsigned char coarse)
{
period = fine | (coarse << 8);
period = std::max<unsigned int>(1, fine | (coarse << 8));
}
void set_shape(unsigned char shape, unsigned char mask)
@ -258,6 +280,18 @@ private:
}
};
inline void noise_rng_tick()
{
// The Random Number Generator of the 8910 is a 17-bit shift
// register. The input to the shift register is bit0 XOR bit3
// (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips.
if (m_feature & PSG_HAS_EXPANDED_MODE) // AY8930 LFSR algorithm is slightly different, verified from manual
m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 2)) << 16);
else
m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 3)) << 16);
}
// inlines
inline bool tone_enable(int chan) { return BIT(m_regs[AY_ENABLE], chan); }
inline unsigned char tone_volume(tone_t *tone) { return tone->volume & (is_expanded_mode() ? 0x1f : 0x0f); }
@ -266,14 +300,19 @@ private:
inline unsigned char get_envelope_chan(int chan) { return is_expanded_mode() ? chan : 0; }
inline bool noise_enable(int chan) { return BIT(m_regs[AY_ENABLE], 3 + chan); }
inline unsigned char noise_period() { return is_expanded_mode() ? m_regs[AY_NOISEPER] & 0xff : m_regs[AY_NOISEPER] & 0x1f; }
inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_latch & 1 : m_rng & 1; }
inline unsigned char noise_period() { return std::max<unsigned char>(1, is_expanded_mode() ? (m_regs[AY_NOISEPER] & 0xff) : (m_regs[AY_NOISEPER] & 0x1f)); }
inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_out & 1 : m_rng & 1; }
inline bool is_expanded_mode() { return ((m_feature & PSG_HAS_EXPANDED_MODE) && ((m_mode & 0xe) == 0xa)); }
inline unsigned char get_register_bank() { return is_expanded_mode() ? (m_mode & 0x1) << 4 : 0; }
inline unsigned char noise_and() { return m_regs[AY_NOISEAND] & 0xff; }
inline unsigned char noise_or() { return m_regs[AY_NOISEOR] & 0xff; }
inline bool is_clock_divided() { return ((m_feature & PSG_HAS_INTERNAL_DIVIDER) || ((m_feature & PSG_PIN26_IS_CLKSEL) && (m_flags & YM2149_PIN26_LOW))); }
// internal helpers
void set_type(psg_type_t psg_type);
void set_type(psg_type_t psg_type, bool clk_sel);
inline float mix_3D();
void ay8910_write_reg(int r, int v);
void build_mixer_table();
@ -284,22 +323,21 @@ private:
int m_ready;
//sound_stream *m_channel;
bool m_active;
int m_register_latch;
unsigned char m_register_latch;
unsigned char m_regs[16 * 2];
int m_last_enable;
tone_t m_tone[NUM_CHANNELS];
envelope_t m_envelope[NUM_CHANNELS];
unsigned char m_prescale_noise;
int m_count_noise;
int m_rng;
unsigned int m_noise_and;
unsigned int m_noise_or;
unsigned int m_noise_value;
unsigned int m_noise_latch;
signed short m_noise_value;
signed short m_count_noise;
unsigned int m_rng;
unsigned char m_noise_out;
unsigned char m_mode;
unsigned char m_env_step_mask;
/* init parameters ... */
int m_step;
int m_step_mul;
int m_env_step_mul;
int m_zero_is_off;
unsigned char m_vol_enabled[NUM_CHANNELS];
const ay_ym_param *m_par;
@ -337,19 +375,19 @@ public:
class ay8930_device : public ay8910_device
{
public:
ay8930_device(unsigned int clock);
ay8930_device(unsigned int clock, bool clk_sel = false);
};
class ym2149_device : public ay8910_device
{
public:
ym2149_device(unsigned int clock);
ym2149_device(unsigned int clock, bool clk_sel = false);
};
class ym3439_device : public ay8910_device
{
public:
ym3439_device(unsigned int clock);
ym3439_device(unsigned int clock, bool clk_sel = false);
};
class ymz284_device : public ay8910_device

View file

@ -262,13 +262,19 @@ struct DivSong {
// - 8: 0.83MHz (Sunsoft 5B on PAL)
// - 9: 1.10MHz (Gamate/VIC-20 PAL)
// - 10: 2.097152MHz (Game Boy)
// - 11: 3.58MHz (Darky)
// - 12: 3.6MHz (Darky)
// - bit 4-5: chip type (ignored on AY8930)
// - 0: AY-3-8910 or similar
// - 1: YM2149
// - 2: Sunsoft 5B
// - bit 6: stereo
// - 3: AY-3-8914
// - bit 6: stereo (ignored on Sunsoft 5B)
// - 0: mono
// - 1: stereo ABC
// - bit 7: clock divider pin (YM2149, AY8930)
// - 0: high (disable divider)
// - 1: low (internally divided to half)
// - SAA1099:
// - bit 0-1: clock rate
// - 0: 8MHz (SAM Coupé, Game Blaster)

View file

@ -906,11 +906,41 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
}
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
case DIV_SYSTEM_AY8930: {
if (!hasAY) {
bool hasClockDivider=false; // Configurable clock divider
bool hasStereo=true; // Stereo
hasAY=disCont[i].dispatch->chipClock;
ayConfig=(song.system[i]==DIV_SYSTEM_AY8930)?3:0;
ayFlags=1;
if (song.system[i]==DIV_SYSTEM_AY8930) { // AY8930
ayConfig=0x03;
hasClockDivider=true;
} else {
switch ((song.systemFlags[i]>>4)&3) {
default:
case 0: // AY8910
ayConfig=0x00;
break;
case 1: // YM2149
ayConfig=0x10;
hasClockDivider=true;
break;
case 2: // Sunsoft 5B
ayConfig=0x10;
ayFlags|=0x12; // Clock internally divided, Single sound output
hasStereo=false; // due to above, can't be per-channel stereo configurable
break;
case 3: // AY8914
ayConfig=0x04;
break;
}
}
if (hasClockDivider && ((song.systemFlags[i]>>7)&1)) {
ayFlags|=0x10;
}
if (hasStereo && ((song.systemFlags[i]>>6)&1)) {
ayFlags|=0x80;
}
willExport[i]=true;
} else if (!(hasAY&0x40000000)) {
isSecond[i]=true;
@ -919,6 +949,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
}
case DIV_SYSTEM_SAA1099:
if (!hasSAA) {
hasSAA=disCont[i].dispatch->chipClock;

View file

@ -461,7 +461,7 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef(
"NES with Sunsoft 5B", {
DIV_SYSTEM_NES, 64, 0, 0,
DIV_SYSTEM_AY8910, 64, 0, 38,
DIV_SYSTEM_AY8910, 64, 0, 32,
0
}
));
@ -643,6 +643,15 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + Darky", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_AY8930, 64, 0, 139, // 3.58MHz
DIV_SYSTEM_AY8930, 64, 0, 139, // 3.58MHz or 3.6MHz selectable via register
// per-channel mixer (soft panning, post processing) isn't emulated at all
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + SCC", {
DIV_SYSTEM_AY8910, 64, 0, 16,

View file

@ -200,7 +200,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
copyOfFlags=(flags&(~15))|5;
}
if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) {
if (ImGui::RadioButton("0.89MHz (Pre-divided Sunsoft 5B)",(flags&15)==6)) {
copyOfFlags=(flags&(~15))|6;
}
@ -208,7 +208,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
copyOfFlags=(flags&(~15))|7;
}
if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) {
if (ImGui::RadioButton("0.83MHz (Pre-divided Sunsoft 5B on PAL)",(flags&15)==8)) {
copyOfFlags=(flags&(~15))|8;
}
@ -219,6 +219,14 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) {
copyOfFlags=(flags&(~15))|10;
}
if (ImGui::RadioButton("3.58MHz (Darky)",(flags&15)==11)) {
copyOfFlags=(flags&(~15))|11;
}
if (ImGui::RadioButton("3.6MHz (Darky)",(flags&15)==12)) {
copyOfFlags=(flags&(~15))|12;
}
if (type==DIV_SYSTEM_AY8910) {
ImGui::Text("Chip type:");
@ -240,10 +248,17 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
}
}
bool stereo=flags&0x40;
ImGui::BeginDisabled((flags&0x30)==32);
ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)==32));
if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) {
copyOfFlags=(flags&(~0x40))|(stereo?0x40:0);
}
ImGui::EndDisabled();
bool clockSel=flags&0x80;
ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)!=16));
if (ImGui::Checkbox("Half Clock divider##_AY_CLKSEL",&clockSel)) {
copyOfFlags=(flags&(~0x80))|(clockSel?0x80:0);
}
ImGui::EndDisabled();
break;