mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-23 21:15:11 +00:00
Merge pull request #421 from cam900/ay_divider
Some AY-3-8910, AY8930 enhancements
This commit is contained in:
commit
c5c612c354
11 changed files with 254 additions and 151 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,3 +17,4 @@ android/app/build/
|
|||
android/app/.cxx/
|
||||
.vs/
|
||||
CMakeSettings.json
|
||||
CMakePresets.json
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue