Some AY-3-8910, AY8930 enhancements

Fix AY PSG clock divider function when exists, Update emulation core (MAME AY PSG and compatibles - includes noise routine updates)
Fix configuration flag, Add MSX Darky sound expander to preset

Clock divider isn't affected to expanded mode's tone and noise generator?
This commit is contained in:
cam900 2022-05-04 23:04:20 +09:00
parent 28e7b86728
commit 4f1ebc3b6e
8 changed files with 178 additions and 122 deletions

View file

@ -27,7 +27,7 @@
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #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 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[]={ const char* regCheatSheetAY[]={
"FreqL_A", "0", "FreqL_A", "0",
@ -589,6 +589,7 @@ void DivPlatformAY8910::poke(std::vector<DivRegWrite>& wlist) {
} }
void DivPlatformAY8910::setFlags(unsigned int flags) { void DivPlatformAY8910::setFlags(unsigned int flags) {
clockSel=(flags>>7)&1;
switch (flags&15) { switch (flags&15) {
case 1: case 1:
chipClock=COLOR_PAL*2.0/5.0; chipClock=COLOR_PAL*2.0/5.0;
@ -620,6 +621,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
case 10: case 10:
chipClock=2097152; chipClock=2097152;
break; break;
case 11:
chipClock=COLOR_NTSC;
break;
case 12:
chipClock=3600000;
break;
default: default:
chipClock=COLOR_NTSC/2.0; chipClock=COLOR_NTSC/2.0;
break; break;
@ -653,8 +660,9 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
break; break;
} }
ay->device_start(); ay->device_start();
ay->set_clock_sel(clockSel);
stereo=flags>>6; stereo=(flags>>6)&1;
} }
int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {

View file

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

View file

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

View file

@ -27,6 +27,17 @@
class DivPlatformAY8930: public DivDispatch { class DivPlatformAY8930: public DivDispatch {
protected: protected:
struct Channel { 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; unsigned char freqH, freqL;
int freq, baseFreq, note, pitch, pitch2; int freq, baseFreq, note, pitch, pitch2;
int ins; int ins;
@ -59,16 +70,12 @@ class DivPlatformAY8930: public DivDispatch {
int delay; int delay;
bool extMode, stereo; bool extMode, stereo, clockSel;
bool ioPortA, ioPortB; bool ioPortA, ioPortB;
unsigned char portAVal, portBVal; unsigned char portAVal, portBVal;
short oldWrites[32]; short oldWrites[32];
short pendingWrites[32]; short pendingWrites[32];
unsigned char ayEnvMode[3];
unsigned short ayEnvPeriod[3];
short ayEnvSlideLow[3];
short ayEnvSlide[3];
short* ayBuf[3]; short* ayBuf[3];
size_t ayBufLen; 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]); m_tone[2].set_duty(m_regs[AY_CDUTY]);
break; break;
case AY_NOISEAND: case AY_NOISEAND:
m_noise_and=m_regs[AY_NOISEAND];
break;
case AY_NOISEOR: case AY_NOISEOR:
m_noise_or=m_regs[AY_NOISEOR]; // No action required
break; break;
default: default:
m_regs[r] = 0; // reserved, set as 0 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) if (!m_ready)
{ {
for (int chan = 0; chan < m_streams; chan++) 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 */ /* 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++) for (int chan = 0; chan < NUM_CHANNELS; chan++)
{ {
tone = &m_tone[chan]; tone = &m_tone[chan];
const int period = std::max<int>(1,tone->period); const int period = std::max<int>(1,tone->period) << 1;
tone->count += is_expanded_mode() ? 16 : (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; tone->count += is_expanded_mode() ? 32 : (is_clock_divided() ? 1 : 2);
while (tone->count >= period) while (tone->count >= period)
{ {
tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f; tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f;
@ -1073,48 +1071,35 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
} }
} }
m_count_noise++; if ((++m_count_noise) >= noise_period())
if (m_count_noise >= noise_period())
{ {
/* toggle the prescaler output. Noise is no different to /* toggle the prescaler output. Noise is no different to
* channels. * channels.
*/ */
m_count_noise = 0; m_count_noise = 0;
m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1); m_prescale_noise = (m_prescale_noise + 1) & (is_clock_divided() ? 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.
// 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.
// 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.
// 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 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.
// 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.
unsigned int noiseValuePeriod = ((m_rng & 0xFF & m_noise_and) | m_noise_or); {
m_noise_value = 0;
// Clock the noise value.
if (m_noise_value >= noiseValuePeriod) { // When everything is finally said and done, a 1bit latch is flipped.
m_noise_value = 0; // This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel.
m_noise_out ^= 1;
// 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. noise_rng_tick();
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;
}
} }
else if (!m_prescale_noise)
noise_rng_tick();
} }
for (int chan = 0; chan < NUM_CHANNELS; chan++) for (int chan = 0; chan < NUM_CHANNELS; chan++)
@ -1130,8 +1115,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
if (envelope->holding == 0) if (envelope->holding == 0)
{ {
const int period = envelope->period * m_step; const int period = envelope->period * m_step;
envelope->count++; if ((++envelope->count) >= period)
if (envelope->count >= period)
{ {
envelope->count = 0; envelope->count = 0;
envelope->step--; envelope->step--;
@ -1263,20 +1247,14 @@ void ay8910_device::device_start()
} }
void ay8910_device::ay8910_reset_ym(bool ay8930) void ay8910_device::ay8910_reset_ym()
{ {
m_active = false; m_active = false;
m_register_latch = 0; m_register_latch = 0;
if (ay8930) { m_rng = (m_feature & PSG_HAS_EXPANDED_MODE) ? 0x1ffff : 1;
m_rng = 0x1ffff;
} else {
m_rng = 1;
}
m_mode = 0; // ay-3-8910 compatible mode m_mode = 0; // ay-3-8910 compatible mode
m_noise_and = 0xff; m_noise_value = 0;
m_noise_or = 0; m_noise_out = 0;
m_noise_value = 0;
m_noise_latch = 0;
for (int chan = 0; chan < NUM_CHANNELS; chan++) for (int chan = 0; chan < NUM_CHANNELS; chan++)
{ {
m_tone[chan].reset(); m_tone[chan].reset();
@ -1335,7 +1313,7 @@ void ay8910_device::ay8910_write_ym(int addr, unsigned char data)
unsigned char ay8910_device::ay8910_read_ym() 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 if (!m_active) return 0xff; // high impedance
@ -1380,7 +1358,7 @@ unsigned char ay8910_device::ay8910_read_ym()
void ay8910_device::device_reset() void ay8910_device::device_reset()
{ {
ay8910_reset_ym(chip_type == AY8930); ay8910_reset_ym();
} }
/************************************* /*************************************
@ -1454,22 +1432,20 @@ ay8910_device::ay8910_device(unsigned int clock)
ay8910_device::ay8910_device(device_type type, 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)
: chip_type(type), : chip_type(type),
m_type(psg_type), m_type(psg_type),
m_streams(streams), m_streams(streams),
m_ready(0), m_ready(0),
m_active(false), m_active(false),
m_register_latch(0), m_register_latch(0),
m_last_enable(0), m_last_enable(0),
m_prescale_noise(0), m_prescale_noise(0),
m_noise_value(0),
m_count_noise(0), m_count_noise(0),
m_rng(0), m_rng(0),
m_noise_and(0), m_noise_out(0),
m_noise_or(0),
m_noise_value(0),
m_noise_latch(0),
m_mode(0), m_mode(0),
m_env_step_mask((!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 0x0f : 0x1f), 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( (feature & PSG_HAS_INTERNAL_DIVIDER) || (psg_type == PSG_TYPE_AY) ? 2 : 1),
m_zero_is_off( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 1 : 0), 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( (!(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_par_env( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param_env),
@ -1494,7 +1470,7 @@ void ay8910_device::set_type(psg_type_t psg_type)
if (psg_type == PSG_TYPE_AY) if (psg_type == PSG_TYPE_AY)
{ {
m_env_step_mask = 0x0f; m_env_step_mask = 0x0f;
m_step = 2; m_step = is_clock_divided() ? 4 : 2;
m_zero_is_off = 1; m_zero_is_off = 1;
m_par = &ay8910_param; m_par = &ay8910_param;
m_par_env = &ay8910_param; m_par_env = &ay8910_param;
@ -1502,7 +1478,7 @@ void ay8910_device::set_type(psg_type_t psg_type)
else else
{ {
m_env_step_mask = 0x1f; m_env_step_mask = 0x1f;
m_step = (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; m_step = is_clock_divided() ? 2 : 1;
m_zero_is_off = 0; m_zero_is_off = 0;
m_par = &ym2149_param; m_par = &ym2149_param;
m_par_env = &ym2149_param_env; m_par_env = &ym2149_param_env;

View file

@ -97,7 +97,25 @@ public:
void data_w(unsigned char data); void data_w(unsigned char data);
// /RES // /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;
set_type(m_type);
}
else
{
m_flags &= ~YM2149_PIN26_LOW;
set_type(m_type);
}
}
}
// use this when BC1 == A0; here, BC1=0 selects 'data' and BC1=1 selects 'latch address' // 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 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
@ -138,7 +156,7 @@ public:
void ay8910_write_ym(int addr, unsigned char data); void ay8910_write_ym(int addr, unsigned char data);
unsigned char ay8910_read_ym(); unsigned char ay8910_read_ym();
void ay8910_reset_ym(bool ay8930); void ay8910_reset_ym();
private: private:
static constexpr int NUM_CHANNELS = 3; static constexpr int NUM_CHANNELS = 3;
@ -258,6 +276,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 // inlines
inline bool tone_enable(int chan) { return BIT(m_regs[AY_ENABLE], chan); } 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); } inline unsigned char tone_volume(tone_t *tone) { return tone->volume & (is_expanded_mode() ? 0x1f : 0x0f); }
@ -267,11 +297,16 @@ private:
inline bool noise_enable(int chan) { return BIT(m_regs[AY_ENABLE], 3 + chan); } 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_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_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 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 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 // internal helpers
void set_type(psg_type_t psg_type); void set_type(psg_type_t psg_type);
inline float mix_3D(); inline float mix_3D();
@ -284,18 +319,16 @@ private:
int m_ready; int m_ready;
//sound_stream *m_channel; //sound_stream *m_channel;
bool m_active; bool m_active;
int m_register_latch; unsigned char m_register_latch;
unsigned char m_regs[16 * 2]; unsigned char m_regs[16 * 2];
int m_last_enable; int m_last_enable;
tone_t m_tone[NUM_CHANNELS]; tone_t m_tone[NUM_CHANNELS];
envelope_t m_envelope[NUM_CHANNELS]; envelope_t m_envelope[NUM_CHANNELS];
unsigned char m_prescale_noise; unsigned char m_prescale_noise;
int m_count_noise; signed short m_noise_value;
int m_rng; signed short m_count_noise;
unsigned int m_noise_and; unsigned int m_rng;
unsigned int m_noise_or; unsigned char m_noise_out;
unsigned int m_noise_value;
unsigned int m_noise_latch;
unsigned char m_mode; unsigned char m_mode;
unsigned char m_env_step_mask; unsigned char m_env_step_mask;
/* init parameters ... */ /* init parameters ... */

View file

@ -425,6 +425,15 @@ void FurnaceGUI::initSystemPresets() {
0 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, 140, // 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( cat.systems.push_back(FurnaceGUISysDef(
"ZX Spectrum (48K)", { "ZX Spectrum (48K)", {
DIV_SYSTEM_AY8910, 64, 0, 2, DIV_SYSTEM_AY8910, 64, 0, 2,

View file

@ -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)) { if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) {
copyOfFlags=(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) { if (type==DIV_SYSTEM_AY8910) {
ImGui::Text("Chip type:"); ImGui::Text("Chip type:");
@ -240,10 +248,17 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
} }
} }
bool stereo=flags&0x40; 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)) { if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) {
copyOfFlags=(flags&(~0x40))|(stereo?0x40:0); 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(); ImGui::EndDisabled();
break; break;