From 0b352ecd9a7eda53467d6ae9779083dd50bec0cf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 13 Jan 2022 02:52:19 -0500 Subject: [PATCH] add AY-3-8910 platform! this paves the way for eventual AY-3-8930 platform... --- CMakeLists.txt | 2 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/ay.cpp | 357 ++++++ src/engine/platform/ay.h | 65 ++ src/engine/platform/sound/ay8910.cpp | 1543 ++++++++++++++++++++++++++ src/engine/platform/sound/ay8910.h | 371 +++++++ src/engine/playback.cpp | 25 + src/gui/gui.cpp | 7 +- src/main.cpp | 1 + 9 files changed, 2372 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/ay.cpp create mode 100644 src/engine/platform/ay.h create mode 100644 src/engine/platform/sound/ay8910.cpp create mode 100644 src/engine/platform/sound/ay8910.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bcb2a7487..cfa037384 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ src/log.cpp extern/Nuked-OPN2/ym3438.c extern/opm/opm.c src/engine/platform/sound/sn76496.cpp +src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/gb/apu.c src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp @@ -116,6 +117,7 @@ src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp +src/engine/platform/ay.cpp src/engine/platform/dummy.cpp) set(GUI_SOURCES diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index d5a820d36..21aa84517 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -9,6 +9,7 @@ #include "platform/arcade.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" +#include "platform/ay.h" #include "platform/dummy.h" #include "../ta-log.h" @@ -112,6 +113,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_YM2610_EXT: dispatch=new DivPlatformYM2610Ext; break; + case DIV_SYSTEM_AY8910: + dispatch=new DivPlatformAY8910; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp new file mode 100644 index 000000000..5383aef60 --- /dev/null +++ b/src/engine/platform/ay.cpp @@ -0,0 +1,357 @@ +#include "ay.h" +#include "../engine.h" +#include "sound/ay8910.h" +#include +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v);} + +#define PSG_FREQ_BASE 7640.0f + +void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (ayBufLenaddress_w(w.addr); + ay->data_w(w.val); + writes.pop(); + } + ay->sound_stream_update(ayBuf,len); + for (size_t i=0; iwrite(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + writes.pop(); + delay=4; + } + } + + fm->generate(&fmout); + + os[0]=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + }*/ +} + +void DivPlatformAY8910::tick() { + // PSG + for (int i=0; i<3; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=chan[i].std.vol-(15-chan[i].vol); + if (chan[i].outVol<0) chan[i].outVol=0; + if (isMuted[i]) { + rWrite(0x08+i,0); + } else { + rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)(chan[i].std.arp)/12.0f))); + } else { + chan[i].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)(chan[i].note+chan[i].std.arp-12)/12.0f))); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)(chan[i].note)/12.0f))); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + rWrite(0x06,31-chan[i].std.duty); + } + if (chan[i].std.hadWave) { + chan[i].psgMode&=4; + chan[i].psgMode|=(chan[i].std.wave+1)&3; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].keyOn) { + //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + } + if (chan[i].keyOff) { + rWrite(0x08+i,0); + } + rWrite((i)<<1,chan[i].freq&0xff); + rWrite(1+((i)<<1),chan[i].freq>>8); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } + + rWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4))); + + if (ayEnvSlide!=0) { + ayEnvSlideLow+=ayEnvSlide; + while (ayEnvSlideLow>7) { + ayEnvSlideLow-=8; + if (ayEnvPeriod<0xffff) { + ayEnvPeriod++; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + } + while (ayEnvSlideLow<-7) { + ayEnvSlideLow+=8; + if (ayEnvPeriod>0) { + ayEnvPeriod--; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + } + } + + for (int i=0; i<16; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } +} + +int DivPlatformAY8910::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + chan[c.chan].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)c.value/12.0f))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(ins); + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else { + rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].keyOff=true; + chan[c.chan].active=false; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else { + if (chan[c.chan].active) rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)c.value2/12.0f))); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)c.value/12.0f))); + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_STD_NOISE_FREQ: + rWrite(0x06,31-c.value); + break; + case DIV_CMD_AY_ENVELOPE_SET: + ayEnvMode=c.value>>4; + rWrite(0x0d,ayEnvMode); + if (c.value&15) { + chan[c.chan].psgMode|=4; + } else { + chan[c.chan].psgMode&=~4; + } + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else { + rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + case DIV_CMD_AY_ENVELOPE_LOW: + ayEnvPeriod&=0xff00; + ayEnvPeriod|=c.value; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + break; + case DIV_CMD_AY_ENVELOPE_HIGH: + ayEnvPeriod&=0xff; + ayEnvPeriod|=c.value<<8; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + break; + case DIV_CMD_AY_ENVELOPE_SLIDE: + ayEnvSlide=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_CMD_PRE_PORTA: + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformAY8910::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (isMuted[ch]) { + rWrite(0x08+ch,0); + } else { + rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); + } +} + +void DivPlatformAY8910::forceIns() { + for (int i=0; i<3; i++) { + chan[i].insChanged=true; + } + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + immWrite(0x0d,ayEnvMode); +} + +void DivPlatformAY8910::reset() { + while (!writes.empty()) writes.pop(); + ay->device_reset(); + for (int i=0; i<3; i++) { + chan[i]=DivPlatformAY8910::Channel(); + chan[i].vol=0x0f; + } + + for (int i=0; i<16; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + ayEnvPeriod=0; + ayEnvMode=0; + ayEnvSlide=0; + ayEnvSlideLow=0; + + delay=0; + + extMode=false; +} + +bool DivPlatformAY8910::isStereo() { + return false; +} + +bool DivPlatformAY8910::keyOffAffectsArp(int ch) { + return true; +} + +int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, bool pal) { + parent=p; + skipRegisterWrites=false; + for (int i=0; i<3; i++) { + isMuted[i]=false; + } + if (pal) { + rate=250000; + } else { + rate=250000; + } + ay=new ay8910_device(rate); + ay->set_psg_type(ay8910_device::PSG_TYPE_AY); + ay->device_start(); + ayBufLen=65536; + for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen]; + reset(); + return 3; +} + +void DivPlatformAY8910::quit() { + for (int i=0; i<3; i++) delete[] ayBuf[i]; + delete ay; +} \ No newline at end of file diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h new file mode 100644 index 000000000..4f98379eb --- /dev/null +++ b/src/engine/platform/ay.h @@ -0,0 +1,65 @@ +#ifndef _AY_H +#define _AY_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ay8910.h" + +class DivPlatformAY8910: public DivDispatch { + protected: + struct Channel { + unsigned char freqH, freqL; + int freq, baseFreq, pitch; + unsigned char ins, note, psgMode; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + int vol, outVol; + unsigned char pan; + DivMacroInt std; + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), note(0), psgMode(1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} + }; + Channel chan[3]; + bool isMuted[3]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ay8910_device* ay; + unsigned char lastBusy; + + bool dacMode; + int dacPeriod; + int dacRate; + int dacPos; + int dacSample; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[16]; + short pendingWrites[16]; + unsigned char ayEnvMode; + unsigned short ayEnvPeriod; + short ayEnvSlideLow; + short ayEnvSlide; + short* ayBuf[3]; + size_t ayBufLen; + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + int init(DivEngine* parent, int channels, int sugRate, bool pal); + void quit(); +}; +#endif diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp new file mode 100644 index 000000000..f36bbbb92 --- /dev/null +++ b/src/engine/platform/sound/ay8910.cpp @@ -0,0 +1,1543 @@ +// license:BSD-3-Clause +// copyright-holders:Couriersud +/* + * Couriersud, July 2014: + * + * This documents recent work on the AY8910. A YM2149 is now on it's way from + * Hong Kong as well. + * + * TODO: + * + * - Create a true sound device nAY8910 driver. + * - implement approach outlined below in this driver. + * + * For years I had a AY8910 in my drawer. Arduinos were around as well. + * Using the approach documented in this blog post + * http://www.986-studio.com/2014/05/18/another-ay-entry/#more-476 + * I measured the output voltages using a Extech 520. + * + * Measurement Setup + * + * Laptop <--> Arduino <---> AY8910 + * + * AY8910 Registers: + * 0x07: 3f + * 0x08: RV + * 0x09: RV + * 0x0A: RV + * + * Output was measured on Analog Output B with a resistor RD to + * ground. + * + * Measurement results: + * + * RD 983 9.830k 99.5k 1.001M open + * + * RV B B B B B + * 0 0.0000 0.0000 0.0001 0.0011 0.0616 + * 1 0.0106 0.0998 0.6680 1.8150 2.7260 + * 2 0.0150 0.1377 0.8320 1.9890 2.8120 + * 3 0.0222 0.1960 1.0260 2.1740 2.9000 + * 4 0.0320 0.2708 1.2320 2.3360 2.9760 + * 5 0.0466 0.3719 1.4530 2.4880 3.0440 + * 6 0.0665 0.4938 1.6680 2.6280 3.1130 + * 7 0.1039 0.6910 1.9500 2.7900 3.1860 + * 8 0.1237 0.7790 2.0500 2.8590 3.2340 + * 9 0.1986 1.0660 2.3320 3.0090 3.3090 + * 10 0.2803 1.3010 2.5050 3.0850 3.3380 + * 11 0.3548 1.4740 2.6170 3.1340 3.3590 + * 12 0.4702 1.6870 2.7340 3.1800 3.3730 + * 13 0.6030 1.8870 2.8410 3.2300 3.4050 + * 14 0.7530 2.0740 2.9280 3.2580 3.4170 + * 15 0.9250 2.2510 3.0040 3.2940 3.4380 + * + * Using an equivalent model approach with two resistors + * + * 5V + * | + * Z + * Z Resistor Value for RV + * Z + * | + * +---> Output signal + * | + * Z + * Z External RD + * Z + * | + * GND + * + * will NOT work out of the box since RV = RV(RD). + * + * The following approach will be used going forward based on die pictures + * of the AY8910 done by Dr. Stack van Hay: + * + * + * 5V + * _| D + * G | NMOS + * Vg ---|| Kn depends on volume selected + * |_ S Vs + * | + * | + * +---> VO Output signal + * | + * Z + * Z External RD + * Z + * | + * GND + * + * Whilst conducting, the FET operates in saturation mode: + * + * Id = Kn * (Vgs - Vth)^2 + * + * Using Id = Vs / RD + * + * Vs = Kn * RD * (Vg - Vs - Vth)^2 + * + * finally using Vg' = Vg - Vth + * + * Vs = Vg' + 1 / (2 * Kn * RD) - sqrt((Vg' + 1 / (2 * Kn * RD))^2 - Vg'^2) + * + * and finally + * + * VO = Vs + * + * and this can be used to re-Thenevin to 5V + * + * RVequiv = RD * ( 5V / VO - 1) + * + * The RV and Kn parameter are derived using least squares to match + * calculation results with measurements. + * + * FIXME: + * There is voltage of 60 mV measured with the EX520 (Ri ~ 10M). This may + * be induced by cutoff currents from the 15 FETs. + * + */ + + +/*************************************************************************** + + ay8910.cpp + + Emulation of the AY-3-8910 / YM2149 sound chip. + + Based on various code snippets by Ville Hallik, Michael Cuddy, + Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria. + + Mostly rewritten by couriersud in 2008 + + Public documentation: + + - http://privatfrickler.de/blick-auf-den-chip-soundchip-general-instruments-ay-3-8910/ + Die pictures of the AY8910 + + - US Patent 4933980 + + Games using ADSR: gyruss + + A list with more games using ADSR can be found here: + http://mametesters.org/view.php?id=3043 + + TODO: + * Measure volume / envelope parameters for AY8930 expanded mode + * YM2610 & YM2608 will need a separate flag in their config structures + to distinguish between legacy and discrete mode. + + The rewrite also introduces a generic model for the DAC. This model is + not perfect, but allows channel mixing based on a parametrized approach. + This model also allows to factor in different loads on individual channels. + If a better model is developped in the future or better measurements are + available, the driver should be easy to change. The model is described + later. + + In order to not break hundreds of existing drivers by default the flag + AY8910_LEGACY_OUTPUT is used by drivers not changed to take into account the + new model. All outputs are normalized to the old output range (i.e. 0 .. 7ffff). + In the case of channel mixing, output range is 0...3 * 7fff. + + The main difference between the AY-3-8910 and the YM2149 is, that the + AY-3-8910 datasheet mentions, that fixed volume level 0, which is set by + registers 8 to 10 is "channel off". The YM2149 mentions, that the generated + signal has a 2V DC component. This is confirmed by measurements. The approach + taken here is to assume the 2V DC offset for all outputs for the YM2149. + For the AY-3-8910, an offset is used if envelope is active for a channel. + This is backed by oscilloscope pictures from the datasheet. If a fixed volume + is set, i.e. envelope is disabled, the output voltage is set to 0V. Recordings + I found on the web for gyruss indicate, that the AY-3-8910 offset should + be around 0.2V. This will also make sound levels more compatible with + user observations for scramble. + + The Model: + 5V 5V + | | + / | + Volume Level x >---| Z + > Z Pullup Resistor RU + | Z + Z | + Rx Z | + Z | + | | + '-----+--------> >---+----> Output signal + | | + Z Z + Pulldown RD Z Z Load RL + Z Z + | | + GND GND + +Each Volume level x will select a different resistor Rx. Measurements from fpgaarcade.com +where used to calibrate channel mixing for the YM2149. This was done using +a least square approach using a fixed RL of 1K Ohm. + +For the AY measurements cited in e.g. openmsx as "Hacker Kay" for a single +channel were taken. These were normalized to 0 ... 65535 and consequently +adapted to an offset of 0.2V and a VPP of 1.3V. These measurements are in +line e.g. with the formula used by pcmenc for the volume: vol(i) = exp(i/2-7.5). + +The following is documentation from the code moved here and amended to reflect +the changes done: + +Careful studies of the chip output prove that the chip counts up from 0 +until the counter becomes greater or equal to the period. This is an +important difference when the program is rapidly changing the period to +modulate the sound. This is worthwhile noting, since the datasheets +say, that the chip counts down. +Also, note that period = 0 is the same as period = 1. This is mentioned +in the YM2203 data sheets. However, this does NOT apply to the Envelope +period. In that case, period = 0 is half as period = 1. + +Envelope shapes: + C AtAlH + 0 0 x x \___ + 0 1 x x /___ + 1 0 0 0 \\\\ + 1 0 0 1 \___ + 1 0 1 0 \/\/ + 1 0 1 1 \``` + 1 1 0 0 //// + 1 1 0 1 /``` + 1 1 1 0 /\/\ + 1 1 1 1 /___ + +The envelope counter on the AY-3-8910 has 16 steps. On the YM2149 it +has twice the steps, happening twice as fast. + +**************************************************************************** + + The bus control and chip selection signals of the AY PSGs and their + pin-compatible clones such as YM2149 are somewhat unconventional and + redundant, having been designed for compatibility with GI's CP1610 + series of microprocessors. Much of the redundancy can be finessed by + tying BC2 to Vcc; AY-3-8913 and AY8930 do this internally. + + /A9 A8 /CS BDIR BC2 BC1 + AY-3-8910 24 25 n/a 27 28 29 + AY-3-8912 n/a 17 n/a 18 19 20 + AY-3-8913 22 23 24 2 n/a 3 + ------------------------------------ + Inactive NACT 0 0 0 + Latch address ADAR 0 0 1 + Inactive IAB 0 1 0 + Read from PSG DTB 0 1 1 + Latch address BAR 1 0 0 + Inactive DW 1 0 1 + Write to PSG DWS 1 1 0 + Latch address INTAK 1 1 1 + +***************************************************************************/ + +/** +AY-3-8910(A)/8914/8916/8917/8930/YM2149 (others?): + _______ _______ + _| \__/ |_ + [4] VSS (GND) -- |_|1 * 40|_| -- VCC (+5v) + _| |_ + [5] NC |_|2 39|_| <- TEST 1 [1] + _| |_ +ANALOG CHANNEL B <- |_|3 38|_| -> ANALOG CHANNEL C + _| |_ +ANALOG CHANNEL A <- |_|4 37|_| <> DA0 + _| |_ + [5] NC |_|5 36|_| <> DA1 + _| |_ + IOB7 <> |_|6 35|_| <> DA2 + _| |_ + IOB6 <> |_|7 34|_| <> DA3 + _| /---\ |_ + IOB5 <> |_|8 \-/ | A 33|_| <> DA4 + _| . . Y |_ + IOB4 <> |_|9 |---| - S 32|_| <> DA5 + _| ' ' 3 O |_ + IOB3 <> |_|10 8 - U 31|_| <> DA6 + _| 3 8 N |_ + IOB2 <> |_|11 0 9 D 30|_| <> DA7 + _| 8 1 |_ + IOB1 <> |_|12 0 29|_| <- BC1 + _| P |_ + IOB0 <> |_|13 28|_| <- BC2 + _| |_ + IOA7 <> |_|14 27|_| <- BDIR + _| |_ Prelim. DS: YM2149/8930: + IOA6 <> |_|15 26|_| <- TEST 2 [2,3] CS2 /SEL + _| |_ + IOA5 <> |_|16 25|_| <- A8 [3] CS1 + _| |_ + IOA4 <> |_|17 24|_| <- /A9 [3] /CS0 + _| |_ + IOA3 <> |_|18 23|_| <- /RESET + _| |_ + IOA2 <> |_|19 22|_| == CLOCK + _| |_ + IOA1 <> |_|20 21|_| <> IOA0 + |__________________| + +[1] Based on the decap, TEST 1 connects to the Envelope Generator and/or the + frequency divider somehow. Is this an input or an output? +[2] The TEST 2 input connects to the same selector as A8 and /A9 do on the 8910 + and acts as just another active high enable like A8(pin 25). + The preliminary datasheet calls this pin CS2. + On the 8914, it performs the same above function but additionally ?disables? + the DA0-7 bus if pulled low/active. This additional function was removed + on the 8910. + This pin has an internal pullup. + On the AY8930 and YM2149, this pin is /SEL; if low, clock input is halved. +[3] These 3 pins are technically enables, and have pullups/pulldowns such that + if the pins are left floating, the chip remains enabled. +[4] On the AY-3-8910 the bond wire for the VSS pin goes to the substrate frame, + and then a separate bond wire connects it to a pad between pins 21 and 22. +[5] These pins lack internal bond wires entirely. + + +AY-3-8912(A): + _______ _______ + _| \__/ |_ +ANALOG CHANNEL C <- |_|1 * 28|_| <> DA0 + _| |_ + TEST 1 -> |_|2 27|_| <> DA1 + _| |_ + VCC (+5V) -- |_|3 26|_| <> DA2 + _| |_ +ANALOG CHANNEL B <- |_|4 25|_| <> DA3 + _| /---\ |_ +ANALOG CHANNEL A <- |_|5 \-/ | A 24|_| <> DA4 + _| . . Y |_ + VSS (GND) -- |_|6 |---| - S 23|_| <> DA5 + _| ' ' 3 O |_ + IOA7 <> |_|7 T 8 - U 22|_| <> DA6 + _| A 3 8 N |_ + IOA6 <> |_|8 I 1 9 D 21|_| <> DA7 + _| W 1 1 |_ + IOA5 <> |_|9 A C 2 20|_| <- BC1 + _| N D |_ + IOA4 <> |_|10 A 19|_| <- BC2 + _| |_ + IOA3 <> |_|11 18|_| <- BDIR + _| |_ + IOA2 <> |_|12 17|_| <- A8 + _| |_ + IOA1 <> |_|13 16|_| <- /RESET + _| |_ + IOA0 <> |_|14 15|_| == CLOCK + |__________________| + + +AY-3-8913: + _______ _______ + _| \__/ |_ + [1] VSS (GND) -- |_|1 * 24|_| <- /CHIP SELECT [2] + _| |_ + BDIR -> |_|2 23|_| <- A8 + _| |_ + BC1 -> |_|3 22|_| <- /A9 + _| /---\ |_ + DA7 <> |_|4 \-/ | A 21|_| <- /RESET + _| . . Y |_ + DA6 <> |_|5 |---| - 20|_| == CLOCK + _| ' ' 3 |_ + DA5 <> |_|6 T 8 - 19|_| -- VSS (GND) [1] + _| A 3 8 |_ + DA4 <> |_|7 I 3 9 18|_| -> ANALOG CHANNEL C + _| W 2 1 |_ + DA3 <> |_|8 A 3 17|_| -> ANALOG CHANNEL A + _| N C |_ + DA2 <> |_|9 - 16|_| NC(?) + _| A |_ + DA1 <> |_|10 15|_| -> ANALOG CHANNEL B + _| |_ + DA0 <> |_|11 14|_| ?? TEST IN [3] + _| |_ + [4] TEST OUT ?? |_|12 13|_| -- VCC (+5V) + |__________________| + +[1] Both of these are ground, they are probably connected together internally. Grounding either one should work. +[2] This is effectively another enable, much like TEST 2 is on the AY-3-8910 and 8914, but active low +[3] This is claimed to be equivalent to TEST 1 on the datasheet +[4] This is claimed to be equivalent to TEST 2 on the datasheet + + +GI AY-3-8910/A Programmable Sound Generator (PSG): 2 I/O ports + A7 thru A4 enable state for selecting a register can be changed with a + factory mask adjustment but default was 0000 for the "common" part shipped + (probably die "-100"). + Pins 24, 25, and 26 are /A9, A8, and TEST2, which are an active low, high + and high chip enable, respectively. + AY-3-8910: Unused bits in registers have unknown behavior. + AY-3-8910A: Unused bits in registers have unknown behavior. + I/O current source/sink behavior is unknown. + AY-3-8910 die is labeled "90-32033" with a 1979 copyright and a "-100" die + code. + AY-3-8910A die is labeled "90-32128" with a 1983 copyright. +GI AY-3-8912/A: 1 I/O port + /A9 pin doesn't exist and is considered pulled low. + TEST2 pin doesn't exist and is considered pulled high. + IOB pins do not exist and have unknown behavior if driven high/low and read + back. + A7 thru A4 enable state for selecting a register can be changed with a + factory mask adjustment but default was 0000 for the "common" part shipped + AY-3-8912: Unused bits in registers have unknown behavior. + AY-3-8912A: Unused bits in registers have unknown behavior. + I/O current source/sink behavior is unknown. + AY-3-8912 die is unknown. + AY-3-8912A or A/P die is unknown. +AY-3-8913: 0 I/O ports + BC2 pin doesn't exist and is considered pulled high. + IOA/B pins do not exist and have unknown behavior if driven high/low and read back. + A7 thru A4 enable state for selecting a register can be changed with a + factory mask adjustment but default was 0000 for the "common" part shipped + AY-3-8913: Unused bits in registers have unknown behavior. + AY-3-8913 die is unknown. +GI AY-3-8914/A: 2 I/O ports + A7 thru A4 enable state for selecting a register can be changed with a + factory mask adjustment but was 0000 for the part shipped with the + Intellivision. + Pins 24, 25, and 26 are /A9, A8, and TEST2, which are an active low, high + and high chip enable, respectively. + TEST2 additionally ?disables? the data bus if pulled low. + The register mapping is different from the AY-3-8910, the AY-3-8914 register + mapping matches the "preliminary" 1978 AY-3-8910 datasheet. + The Envelope/Volume control register is 6 bits wide instead of 5 bits, and + the additional bit combines with the M bit to form a bit pair C0 and C1, + which shift the volume output of the Envelope generator right by 0, 1 or 2 + bits on a given channel, or allow the low 4 bits to drive the channel + volume. + AY-3-8914: Unused bits in registers have unknown behavior. + AY-3-8914A: Unused bits in registers have unknown behavior. + I/O current source/sink behavior is unknown. + AY-3-8914 die is labeled "90-32022" with a 1978 copyright. + AY-3-8914A die is unknown. +GI AY-3-8916: 2 I/O ports + A7 thru A4 enable state for selecting a register can be changed with a + factory mask adjustment; its mask is unknown. This chip was shipped + with certain later Intellivision II systems. + Pins 24, 25, and 26 are /A9, /A8(!), and TEST2, which are an active low, + low(!) and high chip enable, respectively. + NOTE: the /A8 enable polarity may be mixed up with AY-3-8917 below. + AY-3-8916: Unused bits in registers have unknown behavior. + I/O current source/sink behavior is unknown. + AY-3-8916 die is unknown. +GI AY-3-8917: 2 I/O ports + A7 thru A4 enable state for selecting a register can be changed with a + factory mask adjustment but was 1111 for the part shipped with the + Intellivision ECS module. + Pins 24, 25, and 26 are /A9, A8, and TEST2, which are an active low, high + and high chip enable, respectively. + NOTE: the A8 enable polarity may be mixed up with AY-3-8916 above. + AY-3-8917: Unused bits in registers have unknown behavior. + I/O current source/sink behavior is unknown. + AY-3-8917 die is unknown. +Microchip AY8930 Enhanced Programmable Sound Generator (EPSG): 2 I/O ports + BC2 pin exists but is always considered pulled high. The pin might have no + bond wire at all. + Pins 2 and 5 might be additional test pins rather than being NC. + A7 thru A4 enable state for selecting a register are 0000 for all? parts + shipped. + Pins 24 and 25 are /A9, A8 which are an active low and high chip enable. + Pin 26 is /SELECT which if driven low divides the input clock by 2. + Writing 0xAn or 0xBn to register 0x0D turns on extended mode, which enables + an additional 16 registers (banked using 0x0D bit 0), and clears the + contents of all of the registers except the high 3 bits of register 0x0D + (according to the datasheet). + If the AY8930's extended mode is enabled, it gains higher resolution + frequency and volume control, separate volume per-channel, and the duty + cycle can be adjusted for the 3 channels. + If the mode is not enabled, it behaves almost exactly like an AY-3-8910(A?), + barring the BC2 and /SELECT differences. + AY8930: Unused bits in registers have unknown behavior, but the datasheet + explicitly states that unused bits always read as 0. + I/O current source/sink behavior is unknown. + AY8930 die is unknown. +Yamaha YM2149 Software-Controlled Sound Generator (SSG): 2 I/O ports + A7 thru A4 enable state for selecting a register are 0000 for all? parts + shipped. + Pins 24 and 25 are /A9, A8 which are an active low and high chip enable. + Pin 26 is /SEL which if driven low divides the input clock by 2. + The YM2149 envelope register has 5 bits of resolution internally, allowing + for smoother volume ramping, though the register for setting its direct + value remains 4 bits wide. + YM2149: Unused bits in registers have unknown behavior. + I/O current source/sink behavior is unknown. + YM2149 die is unknown; only one die revision, 'G', has been observed + from Yamaha chip/datecode silkscreen surface markings. +Yamaha YM2203: 2 I/O ports + The pinout of this chip is completely different from the AY-3-8910. + The entire way this chip is accessed is completely different from the usual + AY-3-8910 selection of chips, so there is a /CS and a /RD and a /WR and + an A0 pin; The chip status can be read back by reading the register + select address. + The chip has a 3-channel, 4-op FM synthesis sound core in it, not discussed + in this source file. + The first 16 registers are the same(?) as the YM2149. + YM2203: Unused bits in registers have unknown behavior. + I/O current source/sink behavior is unknown. + YM2203 die is unknown; three die revisions, 'D', 'F' and 'H', have been + observed from Yamaha chip/datecode silkscreen surface markings. It is + unknown what behavioral differences exist between these revisions. + The 'D' revision only appears during the first year of production, 1984, on chips marked 'YM2203B' + The 'F' revision exists from 1984?-1991, chips are marked 'YM2203C' + The 'H' revision exists from 1991 onward, chips are marked 'YM2203C' +Yamaha YM3439: limited info: CMOS version of YM2149? +Yamaha YMZ284: limited info: 0 I/O port, different clock divider + The chip selection logic is again simplified here: pin 1 is /WR, pin 2 is + /CS and pin 3 is A0. + D0-D7 are conveniently all on one side of the 16-pin package. + Pin 8 is /IC (initial clear), with an internal pullup. +Yamaha YMZ294: limited info: 0 I/O port + Pinout is identical to YMZ284 except for two additions: pin 8 selects + between 4MHz (H) and 6MHz (L), while pin 10 is /TEST. +OKI M5255, Winbond WF19054, JFC 95101, File KC89C72, Toshiba T7766A : differences to be listed + +AY8930 Expanded mode registers : + Bank Register Bits + A 0 xxxx xxxx Channel A Tone period fine tune + A 1 xxxx xxxx Channel A Tone period coarse tune + A 2 xxxx xxxx Channel B Tone period fine tune + A 3 xxxx xxxx Channel B Tone period coarse tune + A 4 xxxx xxxx Channel C Tone period fine tune + A 5 xxxx xxxx Channel C Tone period coarse tune + A 6 xxxx xxxx Noise period + A 7 x--- ---- I/O Port B input(0) / output(1) + -x-- ---- I/O Port A input(0) / output(1) + --x- ---- Channel C Noise enable(0) / disable(1) + ---x ---- Channel B Noise enable(0) / disable(1) + ---- x--- Channel A Noise enable(0) / disable(1) + ---- -x-- Channel C Tone enable(0) / disable(1) + ---- --x- Channel B Tone enable(0) / disable(1) + ---- ---x Channel A Tone enable(0) / disable(1) + A 8 --x- ---- Channel A Envelope mode + ---x xxxx Channel A Tone volume + A 9 --x- ---- Channel B Envelope mode + ---x xxxx Channel B Tone volume + A A --x- ---- Channel C Envelope mode + ---x xxxx Channel C Tone volume + A B xxxx xxxx Channel A Envelope period fine tune + A C xxxx xxxx Channel A Envelope period coarse tune + A D 101- ---- 101 = Expanded mode enable, other AY-3-8910A Compatiblity mode + ---0 ---- 0 for Register Bank A + ---- xxxx Channel A Envelope Shape/Cycle + A E xxxx xxxx 8 bit Parallel I/O on Port A + A F xxxx xxxx 8 bit Parallel I/O on Port B + + B 0 xxxx xxxx Channel B Envelope period fine tune + B 1 xxxx xxxx Channel B Envelope period coarse tune + B 2 xxxx xxxx Channel C Envelope period fine tune + B 3 xxxx xxxx Channel C Envelope period coarse tune + B 4 ---- xxxx Channel B Envelope Shape/Cycle + B 5 ---- xxxx Channel C Envelope Shape/Cycle + B 6 ---- xxxx Channel A Duty Cycle + B 7 ---- xxxx Channel B Duty Cycle + B 8 ---- xxxx Channel C Duty Cycle + B 9 xxxx xxxx Noise "And" Mask + B A xxxx xxxx Noise "Or" Mask + B B Reserved (Read as 0) + B C Reserved (Read as 0) + B D 101- ---- 101 = Expanded mode enable, other AY-3-8910A Compatiblity mode + ---1 ---- 1 for Register Bank B + ---- xxxx Channel A Envelope Shape + B E Reserved (Read as 0) + B F Test (Function unknown) + +Decaps: +AY-3-8914 - http://siliconpr0n.org/map/gi/ay-3-8914/mz_mit20x/ +AY-3-8910 - http://privatfrickler.de/blick-auf-den-chip-soundchip-general-instruments-ay-3-8910/ +AY-3-8910A - https://seanriddledecap.blogspot.com/2017/01/gi-ay-3-8910-ay-3-8910a-gi-8705-cba.html (TODO: update this link when it has its own page at seanriddle.com) + +Links: +AY-3-8910 'preliminary' datasheet (which actually describes the AY-3-8914) from 1978: + http://spatula-city.org/~im14u2c/intv/gi_micro_programmable_tv_games/page_7_100.png + http://spatula-city.org/~im14u2c/intv/gi_micro_programmable_tv_games/page_7_101.png + http://spatula-city.org/~im14u2c/intv/gi_micro_programmable_tv_games/page_7_102.png + http://spatula-city.org/~im14u2c/intv/gi_micro_programmable_tv_games/page_7_103.png + http://spatula-city.org/~im14u2c/intv/gi_micro_programmable_tv_games/page_7_104.png + http://spatula-city.org/~im14u2c/intv/gi_micro_programmable_tv_games/page_7_105.png +AY-3-8910/8912 Feb 1979 manual: https://web.archive.org/web/20140217224114/http://dev-docs.atariforge.org/files/GI_AY-3-8910_Feb-1979.pdf +AY-3-8910/8912/8913 post-1983 manual: http://map.grauw.nl/resources/sound/generalinstrument_ay-3-8910.pdf or http://www.ym2149.com/ay8910.pdf +AY-8930 datasheet: http://www.ym2149.com/ay8930.pdf +YM2149 datasheet: http://www.ym2149.com/ym2149.pdf +YM2203 English datasheet: http://www.appleii-box.de/APPLE2/JonasCard/YM2203%20datasheet.pdf +YM2203 Japanese datasheet contents, translated: http://www.larwe.com/technical/chip_ym2203.html +*/ + +#include "ay8910.h" +#include +#include +#include +#include + +/************************************* + * + * constants + * + *************************************/ + +#define ENABLE_REGISTER_TEST (0) /* Enable preprogrammed registers */ +#define LOG_IGNORED_WRITES (0) + +static constexpr float MAX_OUTPUT = 1.0; + +/************************************* + * + * Type definitions + * + *************************************/ + + +/************************************* + * + * Static + * + *************************************/ + +// duty cycle used for AY8930 expanded mode +static const unsigned int duty_cycle[9] = +{ + 0x80000000, // 3.125 % + 0xc0000000, // 6.25 % + 0xf0000000, // 12.50 % + 0xff000000, // 25.00 % + 0xffff0000, // 50.00 % + 0xffffff00, // 75.00 % + 0xfffffff0, // 87.50 % + 0xfffffffc, // 93.75 % + 0xfffffffe // 96.875 % +}; + +static const ay8910_device::ay_ym_param ym2149_param = +{ + 630, 801, + 16, + { 73770, 37586, 27458, 21451, 15864, 12371, 8922, 6796, + 4763, 3521, 2403, 1737, 1123, 762, 438, 251 }, +}; + +static const ay8910_device::ay_ym_param ym2149_param_env = +{ + 630, 801, + 32, + { 103350, 73770, 52657, 37586, 32125, 27458, 24269, 21451, + 18447, 15864, 14009, 12371, 10506, 8922, 7787, 6796, + 5689, 4763, 4095, 3521, 2909, 2403, 2043, 1737, + 1397, 1123, 925, 762, 578, 438, 332, 251 }, +}; + +#if 0 +/* RL = 1000, Hacker Kay normalized, 2.1V to 3.2V */ +static const ay8910_device::ay_ym_param ay8910_param = +{ + 664, 913, + 16, + { 85785, 34227, 26986, 20398, 14886, 10588, 7810, 4856, + 4120, 2512, 1737, 1335, 1005, 747, 586, 451 }, +}; + +/* + * RL = 3000, Hacker Kay normalized pattern, 1.5V to 2.8V + * These values correspond with guesses based on Gyruss schematics + * They work well with scramble as well. + */ +static const ay8910_device::ay_ym_param ay8910_param = +{ + 930, 454, + 16, + { 85066, 34179, 27027, 20603, 15046, 10724, 7922, 4935, + 4189, 2557, 1772, 1363, 1028, 766, 602, 464 }, +}; + +/* + * RL = 1000, Hacker Kay normalized pattern, 0.75V to 2.05V + * These values correspond with guesses based on Gyruss schematics + * They work well with scramble as well. + */ +static const ay8910_device::ay_ym_param ay8910_param = +{ + 1371, 313, + 16, + { 93399, 33289, 25808, 19285, 13940, 9846, 7237, 4493, + 3814, 2337, 1629, 1263, 962, 727, 580, 458 }, +}; + +/* + * RL = 1000, Hacker Kay normalized pattern, 0.2V to 1.5V + */ +static const ay8910_device::ay_ym_param ay8910_param = +{ + 5806, 300, + 16, + { 118996, 42698, 33105, 24770, 17925, 12678, 9331, 5807, + 4936, 3038, 2129, 1658, 1271, 969, 781, 623 } +}; +#endif + +/* + * RL = 2000, Based on Matthew Westcott's measurements from Dec 2001. + * ------------------------------------------------------------------- + * + * http://groups.google.com/group/comp.sys.sinclair/browse_thread/thread/fb3091da4c4caf26/d5959a800cda0b5e?lnk=gst&q=Matthew+Westcott#d5959a800cda0b5e + * After what Russell mentioned a couple of weeks back about the lack of + * publicised measurements of AY chip volumes - I've finally got round to + * making these readings, and I'm placing them in the public domain - so + * anyone's welcome to use them in emulators or anything else. + + * To make the readings, I set up the chip to produce a constant voltage on + * channel C (setting bits 2 and 5 of register 6), and varied the amplitude + * (the low 4 bits of register 10). The voltages were measured between the + * channel C output (pin 1) and ground (pin 6). + * + * Level Voltage + * 0 1.147 + * 1 1.162 + * 2 1.169 + * 3 1.178 + * 4 1.192 + * 5 1.213 + * 6 1.238 + * 7 1.299 + * 8 1.336 + * 9 1.457 + * 10 1.573 + * 11 1.707 + * 12 1.882 + * 13 2.06 + * 14 2.32 + * 15 2.58 + * ------------------------------------------------------------------- + * + * The ZX spectrum output circuit was modelled in SwitcherCAD and + * the resistor values below create the voltage levels above. + * RD was measured on a real chip to be 8m Ohm, RU was 0.8m Ohm. + */ + +static const ay8910_device::ay_ym_param ay8910_param = +{ + 800000, 8000000, + 16, + { 15950, 15350, 15090, 14760, 14275, 13620, 12890, 11370, + 10600, 8590, 7190, 5985, 4820, 3945, 3017, 2345 } +}; + +static const ay8910_device::mosfet_param ay8910_mosfet_param = +{ + 1.465385778, + 4.9, + 16, + { + 0.00076, + 0.80536, + 1.13106, + 1.65952, + 2.42261, + 3.60536, + 5.34893, + 8.96871, + 10.97202, + 19.32370, + 29.01935, + 38.82026, + 55.50539, + 78.44395, + 109.49257, + 153.72985, + } +}; + + + + +/************************************* + * + * Inline + * + *************************************/ + +static inline void build_3D_table(double rl, const ay8910_device::ay_ym_param *par, const ay8910_device::ay_ym_param *par_env, int normalize, double factor, int zero_is_off, short *tab) +{ + double min = 10.0, max = 0.0; + + std::vector temp(8*32*32*32, 0); + + for (int e = 0; e < 8; e++) + { + const ay8910_device::ay_ym_param *par_ch1 = (e & 0x01) ? par_env : par; + const ay8910_device::ay_ym_param *par_ch2 = (e & 0x02) ? par_env : par; + const ay8910_device::ay_ym_param *par_ch3 = (e & 0x04) ? par_env : par; + + for (int j1 = 0; j1 < par_ch1->res_count; j1++) + for (int j2 = 0; j2 < par_ch2->res_count; j2++) + for (int j3 = 0; j3 < par_ch3->res_count; j3++) + { + double n; + if (zero_is_off) + { + n = (j1 != 0 || (e & 0x01)) ? 1 : 0; + n += (j2 != 0 || (e & 0x02)) ? 1 : 0; + n += (j3 != 0 || (e & 0x04)) ? 1 : 0; + } + else + n = 3.0; + + double rt = n / par->r_up + 3.0 / par->r_down + 1.0 / rl; + double rw = n / par->r_up; + + rw += 1.0 / par_ch1->res[j1]; + rt += 1.0 / par_ch1->res[j1]; + rw += 1.0 / par_ch2->res[j2]; + rt += 1.0 / par_ch2->res[j2]; + rw += 1.0 / par_ch3->res[j3]; + rt += 1.0 / par_ch3->res[j3]; + + int indx = (e << 15) | (j3 << 10) | (j2 << 5) | j1; + temp[indx] = rw / rt; + if (temp[indx] < min) + min = temp[indx]; + if (temp[indx] > max) + max = temp[indx]; + } + } + + if (normalize) + { + for (int j = 0; j < 32*32*32*8; j++) + tab[j] = 16384 * MAX_OUTPUT * (((temp[j] - min)/(max-min))) * factor; + } + else + { + for (int j = 0; j < 32*32*32*8; j++) + tab[j] = 16384 * MAX_OUTPUT * temp[j]; + } + + /* for (e = 0;e<16;e++) printf("%d %d\n",e << 10, tab[e << 10]); */ +} + +static inline void build_single_table(double rl, const ay8910_device::ay_ym_param *par, int normalize, short *tab, int zero_is_off) +{ + double rt; + double rw; + double temp[32], min = 10.0, max = 0.0; + + for (int j = 0; j < par->res_count; j++) + { + rt = 1.0 / par->r_down + 1.0 / rl; + + rw = 1.0 / par->res[j]; + rt += 1.0 / par->res[j]; + + if (!(zero_is_off && j == 0)) + { + rw += 1.0 / par->r_up; + rt += 1.0 / par->r_up; + } + + temp[j] = rw / rt; + if (temp[j] < min) + min = temp[j]; + if (temp[j] > max) + max = temp[j]; + } + if (normalize) + { + for (int j = 0; j < par->res_count; j++) + tab[j] = 16384 * MAX_OUTPUT * (((temp[j] - min)/(max-min)) - 0.25) * 0.5; + } + else + { + for (int j = 0; j < par->res_count; j++) + tab[j] = 16384 * MAX_OUTPUT * temp[j]; + } + +} + +static inline void build_mosfet_resistor_table(const ay8910_device::mosfet_param &par, const double rd, short *tab) +{ + for (int j = 0; j < par.m_count; j++) + { + const double Vd = 5.0; + const double Vg = par.m_Vg - par.m_Vth; + const double kn = par.m_Kn[j] / 1.0e6; + const double p2 = 1.0 / (2.0 * kn * rd) + Vg; + const double Vs = p2 - sqrt(p2 * p2 - Vg * Vg); + + const double res = rd * (Vd / Vs - 1.0); + + tab[j] = res * 16384; + //printf("%d %f %10d\n", j, rd / (res + rd) * 5.0, tab[j]); + } +} + + +float ay8910_device::mix_3D() +{ + int indx = 0; + + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + tone_t *tone = &m_tone[chan]; + if (tone_envelope(tone) != 0) + { + envelope_t *envelope = &m_envelope[get_envelope_chan(chan)]; + unsigned int env_volume = envelope->volume; + unsigned int env_mask = (1 << (chan + 15)); + if (m_feature & PSG_HAS_EXPANDED_MODE) + { + if (!is_expanded_mode()) + { + env_volume >>= 1; + env_mask = 0; + } + } + if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field + { + indx |= env_mask | (m_vol_enabled[chan] ? ((env_volume >> (3-tone_envelope(tone))) << (chan*5)) : 0); + } + else + { + indx |= env_mask | (m_vol_enabled[chan] ? env_volume << (chan*5) : 0); + } + } + else + { + const unsigned int tone_mask = is_expanded_mode() ? (1 << (chan + 15)) : 0; + indx |= tone_mask | (m_vol_enabled[chan] ? tone_volume(tone) << (chan*5) : 0); + } + } + return m_vol3d_table[indx]; +} + +/************************************* + * + * Static functions + * + *************************************/ + +void ay8910_device::ay8910_write_reg(int r, int v) +{ + if ((r & 0xf) == AY_EASHAPE) // shared register + r &= 0xf; + + //if (r >= 11 && r <= 13) printf("%d %x %02x\n", PSG->index, r, v); + m_regs[r] = v; + unsigned char coarse; + + switch(r) + { + case AY_AFINE: + case AY_ACOARSE: + coarse = m_regs[AY_ACOARSE] & (is_expanded_mode() ? 0xff : 0xf); + m_tone[0].set_period(m_regs[AY_AFINE], coarse); + break; + case AY_BFINE: + case AY_BCOARSE: + coarse = m_regs[AY_BCOARSE] & (is_expanded_mode() ? 0xff : 0xf); + m_tone[1].set_period(m_regs[AY_BFINE], coarse); + break; + case AY_CFINE: + case AY_CCOARSE: + coarse = m_regs[AY_CCOARSE] & (is_expanded_mode() ? 0xff : 0xf); + m_tone[2].set_period(m_regs[AY_CFINE], coarse); + break; + case AY_NOISEPER: + /* No action required */ + break; + case AY_AVOL: + m_tone[0].set_volume(m_regs[AY_AVOL]); + break; + case AY_BVOL: + m_tone[1].set_volume(m_regs[AY_BVOL]); + break; + case AY_CVOL: + m_tone[2].set_volume(m_regs[AY_CVOL]); + break; + case AY_EACOARSE: + case AY_EAFINE: + m_envelope[0].set_period(m_regs[AY_EAFINE], m_regs[AY_EACOARSE]); + break; + case AY_ENABLE: + m_last_enable = m_regs[AY_ENABLE]; + break; + case AY_EASHAPE: + if (m_feature & PSG_HAS_EXPANDED_MODE) + { + const unsigned char old_mode = m_mode; + m_mode = (v >> 4) & 0xf; + if (old_mode != m_mode) + { + if (((old_mode & 0xe) == 0xa) ^ ((m_mode & 0xe) == 0xa)) // AY8930 expanded mode + { + for (int i = 0; i < AY_EASHAPE; i++) + { + ay8910_write_reg(i, 0); + ay8910_write_reg(i + 0x10, 0); + } + } + } + } + m_envelope[0].set_shape(m_regs[AY_EASHAPE], m_env_step_mask); + break; + case AY_EBFINE: + case AY_EBCOARSE: + m_envelope[1].set_period(m_regs[AY_EBFINE], m_regs[AY_EBCOARSE]); + break; + case AY_ECFINE: + case AY_ECCOARSE: + m_envelope[2].set_period(m_regs[AY_ECFINE], m_regs[AY_ECCOARSE]); + break; + case AY_EBSHAPE: + m_envelope[1].set_shape(m_regs[AY_EBSHAPE], m_env_step_mask); + break; + case AY_ECSHAPE: + m_envelope[2].set_shape(m_regs[AY_ECSHAPE], m_env_step_mask); + break; + case AY_ADUTY: + m_tone[0].set_duty(m_regs[AY_ADUTY]); + break; + case AY_BDUTY: + m_tone[1].set_duty(m_regs[AY_BDUTY]); + break; + case AY_CDUTY: + m_tone[2].set_duty(m_regs[AY_CDUTY]); + break; + case AY_NOISEAND: + case AY_NOISEOR: + // not implemented + break; + default: + m_regs[r] = 0; // reserved, set as 0 + break; + } +} + +//------------------------------------------------- +// sound_stream_update - handle a stream update +//------------------------------------------------- + +void ay8910_device::sound_stream_update(short** outputs, int outLen) +{ + tone_t *tone; + envelope_t *envelope; + + int samples = outLen; + + /* hack to prevent us from hanging when starting filtered outputs */ + if (!m_ready) + { + for (int chan = 0; chan < m_streams; chan++) + memset(outputs[chan],0,outLen*sizeof(short)); + } + + /* The 8910 has three outputs, each output is the mix of one of the three */ + /* tone generators and of the (single) noise generator. The two are mixed */ + /* BEFORE going into the DAC. The formula to mix each channel is: */ + /* (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). */ + /* Note that this means that if both tone and noise are disabled, the output */ + /* is 1, not 0, and can be modulated changing the volume. */ + + /* buffering loop */ + for (int sampindex = 0; sampindex < samples; sampindex++) + { + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + tone = &m_tone[chan]; + const int period = std::max(1,tone->period); + tone->count += is_expanded_mode() ? 16 : 1; + while (tone->count >= period) + { + tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f; + tone->output = is_expanded_mode() ? BIT(duty_cycle[tone_duty(tone)], tone->duty_cycle) : BIT(tone->duty_cycle, 0); + tone->count -= period; + } + } + + m_count_noise++; + if (m_count_noise >= noise_period()) + { + /* toggle the prescaler output. Noise is no different to + * channels. + */ + m_count_noise = 0; + m_prescale_noise ^= 1; + + if (!m_prescale_noise || is_expanded_mode()) // AY8930 noise generator rate is twice compares as compatibility mode + { + /* 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. */ + + // TODO : get actually algorithm for AY8930 + m_rng ^= (((m_rng & 1) ^ ((m_rng >> 3) & 1)) << 17); + m_rng >>= 1; + } + } + + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + tone = &m_tone[chan]; + m_vol_enabled[chan] = (tone->output | tone_enable(chan)) & (noise_output() | noise_enable(chan)); + } + + /* update envelope */ + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + envelope = &m_envelope[chan]; + if (envelope->holding == 0) + { + const int period = envelope->period * m_step; + envelope->count++; + if (envelope->count >= period) + { + envelope->count = 0; + envelope->step--; + + /* check envelope current position */ + if (envelope->step < 0) + { + if (envelope->hold) + { + if (envelope->alternate) + envelope->attack ^= m_env_step_mask; + envelope->holding = 1; + envelope->step = 0; + } + else + { + /* if CountEnv has looped an odd number of times (usually 1), */ + /* invert the output. */ + if (envelope->alternate && (envelope->step & (m_env_step_mask + 1))) + envelope->attack ^= m_env_step_mask; + + envelope->step &= m_env_step_mask; + } + } + + } + } + envelope->volume = (envelope->step ^ envelope->attack); + } + + if (m_streams == 3) + { + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + tone = &m_tone[chan]; + if (tone_envelope(tone) != 0) + { + envelope = &m_envelope[get_envelope_chan(chan)]; + unsigned int env_volume = envelope->volume; + if (m_feature & PSG_HAS_EXPANDED_MODE) + { + if (!is_expanded_mode()) + { + env_volume >>= 1; + if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field + outputs[chan][sampindex]=m_vol_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; + else + outputs[chan][sampindex]=m_vol_table[chan][m_vol_enabled[chan] ? env_volume : 0]; + } + else + { + if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field + outputs[chan][sampindex]=m_env_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; + else + outputs[chan][sampindex]=m_env_table[chan][m_vol_enabled[chan] ? env_volume : 0]; + } + } + else + { + if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field + outputs[chan][sampindex]=m_env_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; + else + outputs[chan][sampindex]=m_env_table[chan][m_vol_enabled[chan] ? env_volume : 0]; + } + } + else + { + if (is_expanded_mode()) + outputs[chan][sampindex]=m_env_table[chan][m_vol_enabled[chan] ? tone_volume(tone) : 0]; + else + outputs[chan][sampindex]=m_vol_table[chan][m_vol_enabled[chan] ? tone_volume(tone) : 0]; + } + } + } + else + { + outputs[0][sampindex]=mix_3D(); + } + } +} + +void ay8910_device::build_mixer_table() +{ + int normalize = 0; + + if ((m_flags & AY8910_LEGACY_OUTPUT) != 0) + { + normalize = 1; + } + + if ((m_flags & AY8910_RESISTOR_OUTPUT) != 0) + { + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + build_mosfet_resistor_table(ay8910_mosfet_param, m_res_load[chan], m_vol_table[chan]); + build_mosfet_resistor_table(ay8910_mosfet_param, m_res_load[chan], m_env_table[chan]); + } + } + else if (m_streams == NUM_CHANNELS) + { + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + build_single_table(m_res_load[chan], m_par, normalize, m_vol_table[chan], m_zero_is_off); + build_single_table(m_res_load[chan], m_par_env, normalize, m_env_table[chan], 0); + } + } + /* + * The previous implementation added all three channels up instead of averaging them. + * The factor of 3 will force the same levels if normalizing is used. + */ + else + { + build_3D_table(m_res_load[0], m_par, m_par_env, normalize, 3, m_zero_is_off, m_vol3d_table); + } +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void ay8910_device::device_start() +{ + if ((m_flags & AY8910_SINGLE_OUTPUT) != 0) + { + m_streams = 1; + } + + build_mixer_table(); +} + + +void ay8910_device::ay8910_reset_ym() +{ + m_active = false; + m_register_latch = 0; + m_rng = 1; + m_mode = 0; // ay-3-8910 compatible mode + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + m_tone[chan].reset(); + m_envelope[chan].reset(); + } + m_count_noise = 0; + m_prescale_noise = 0; + m_last_enable = -1; /* force a write */ + for (int i = 0; i < AY_PORTA; i++) + ay8910_write_reg(i,0); + m_ready = 1; +#if ENABLE_REGISTER_TEST + ay8910_write_reg(AY_AFINE, 0); + ay8910_write_reg(AY_ACOARSE, 1); + ay8910_write_reg(AY_BFINE, 0); + ay8910_write_reg(AY_BCOARSE, 2); + ay8910_write_reg(AY_CFINE, 0); + ay8910_write_reg(AY_CCOARSE, 4); + //#define AY_NOISEPER (6) + ay8910_write_reg(AY_ENABLE, ~7); + ay8910_write_reg(AY_AVOL, 10); + ay8910_write_reg(AY_BVOL, 10); + ay8910_write_reg(AY_CVOL, 10); + //#define AY_EAFINE (11) + //#define AY_EACOARSE (12) + //#define AY_EASHAPE (13) +#endif +} + +void ay8910_device::ay8910_write_ym(int addr, unsigned char data) +{ + if (addr & 1) + { + if (m_active) + { + const unsigned char register_latch = m_register_latch + get_register_bank(); + /* Data port */ + if (m_register_latch == AY_EASHAPE || m_regs[register_latch] != data) + { + /* update the output buffer before changing the register */ + } + + ay8910_write_reg(register_latch, data); + } + } + else + { + m_active = (data >> 4) == 0; // mask programmed 4-bit code + if (m_active) + { + /* Register port */ + m_register_latch = data & 0x0f; + } + } +} + +unsigned char ay8910_device::ay8910_read_ym() +{ + int r = m_register_latch + get_register_bank(); + + if (!m_active) return 0xff; // high impedance + + if ((r & 0xf) == AY_EASHAPE) // shared register + r &= 0xf; + + /* There are no state dependent register in the AY8910! */ + + /* Depending on chip type, unused bits in registers may or may not be accessible. + Untested chips are assumed to regard them as 'ram' + Tested and confirmed on hardware: + - AY-3-8910: inaccessible bits (see masks below) read back as 0 + - AY-3-8914: same as 8910 except regs B,C,D (8,9,A below due to 8910->8914 remapping) are 0x3f + - AY-3-8916/8917 (used on ECS INTV expansion): inaccessible bits mirror one of the i/o ports, needs further testing + - YM2149: no anomaly + */ + if (chip_type == AY8910) { + const unsigned char mask[0x10]={ + 0xff,0x0f,0xff,0x0f,0xff,0x0f,0x1f,0xff,0x1f,0x1f,0x1f,0xff,0xff,0x0f,0xff,0xff + }; + return m_regs[r] & mask[r]; + } + else if (chip_type == AY8914) { + const unsigned char mask[0x10]={ + 0xff,0x0f,0xff,0x0f,0xff,0x0f,0x1f,0xff,0x3f,0x3f,0x3f,0xff,0xff,0x0f,0xff,0xff + }; + return m_regs[r] & mask[r]; + } + else return m_regs[r]; +} + +/************************************* + * + * Sound Interface + * + *************************************/ + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void ay8910_device::device_reset() +{ + ay8910_reset_ym(); +} + +/************************************* + * + * Read/Write Handlers + * + *************************************/ + +void ay8910_device::address_w(unsigned char data) +{ +#if ENABLE_REGISTER_TEST + return; +#else + ay8910_write_ym(0, data); +#endif +} + +void ay8910_device::data_w(unsigned char data) +{ +#if ENABLE_REGISTER_TEST + return; +#else + ay8910_write_ym(1, data); +#endif +} + +// here, BC1 is hooked up to A0 on the host and BC2 is hooked up to A1 +void ay8910_device::write_bc1_bc2(int offset, unsigned char data) +{ + switch (offset & 3) + { + case 0: // latch address + address_w(data); + break; + case 1: // inactive + break; + case 2: // write to psg + data_w(data); + break; + case 3: // latch address + address_w(data); + break; + } +} + +static const unsigned char mapping8914to8910[16] = { 0, 2, 4, 11, 1, 3, 5, 12, 7, 6, 13, 8, 9, 10, 14, 15 }; + +unsigned char ay8914_device::read(int offset) +{ + unsigned char rv; + address_w(mapping8914to8910[offset & 0xf]); + rv = data_r(); + return rv; +} + +void ay8914_device::write(int offset, unsigned char data) +{ + address_w(mapping8914to8910[offset & 0xf]); + data_w(data & 0xff); +} + + + + + +ay8910_device::ay8910_device(unsigned int clock) + : ay8910_device(AY8910, clock, PSG_TYPE_AY, 3, 2) +{ +} + +ay8910_device::ay8910_device(device_type type, unsigned int clock, + psg_type_t psg_type, int streams, int ioports, int feature) + : chip_type(type), + m_type(psg_type), + m_streams(streams), + m_ioports(ioports), + m_ready(0), + m_active(false), + m_register_latch(0), + m_last_enable(0), + m_prescale_noise(0), + m_count_noise(0), + m_rng(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_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_feature(feature) +{ + memset(&m_regs,0,sizeof(m_regs)); + memset(&m_tone,0,sizeof(m_tone)); + memset(&m_envelope,0,sizeof(m_envelope)); + memset(&m_vol_enabled,0,sizeof(m_vol_enabled)); + memset(&m_vol_table,0,sizeof(m_vol_table)); + memset(&m_env_table,0,sizeof(m_env_table)); + 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); +} + +void ay8910_device::set_type(psg_type_t psg_type) +{ + m_type = psg_type; + if (psg_type == PSG_TYPE_AY) + { + m_env_step_mask = 0x0f; + m_step = 2; + m_zero_is_off = 1; + m_par = &ay8910_param; + m_par_env = &ay8910_param; + } + else + { + m_env_step_mask = 0x1f; + m_step = 1; + m_zero_is_off = 0; + m_par = &ym2149_param; + m_par_env = &ym2149_param_env; + } +} + + + +ay8912_device::ay8912_device(unsigned int clock) + : ay8910_device(AY8912, clock, PSG_TYPE_AY, 3, 1) +{ +} + + + + +ay8913_device::ay8913_device(unsigned int clock) + : ay8910_device(AY8913, clock, PSG_TYPE_AY, 3, 0) +{ +} + + + + +ay8914_device::ay8914_device(unsigned int clock) + : ay8910_device(AY8914, clock, PSG_TYPE_AY, 3, 2, PSG_EXTENDED_ENVELOPE) +{ +} + + + + +ay8930_device::ay8930_device(unsigned int clock) + : ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE) +{ +} + + + + +ym2149_device::ym2149_device(unsigned int clock) + : ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL) +{ +} + + + + +ym3439_device::ym3439_device(unsigned int clock) + : ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL) +{ +} + + + + +ymz284_device::ymz284_device(unsigned int clock) + : ay8910_device(YMZ284, clock, PSG_TYPE_YM, 1, 0) +{ +} + + + + +ymz294_device::ymz294_device(unsigned int clock) + : ay8910_device(YMZ294, clock, PSG_TYPE_YM, 1, 0) +{ +} + + + + +sunsoft_5b_sound_device::sunsoft_5b_sound_device(unsigned int clock) + : ay8910_device(SUNSOFT_5B_SOUND, clock, PSG_TYPE_YM, 1, 0, PSG_HAS_INTERNAL_DIVIDER) +{ +} diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h new file mode 100644 index 000000000..020081e5c --- /dev/null +++ b/src/engine/platform/sound/ay8910.h @@ -0,0 +1,371 @@ +// license:BSD-3-Clause +// copyright-holders:Couriersud +#ifndef MAME_SOUND_AY8910_H +#define MAME_SOUND_AY8910_H + +#define ALL_8910_CHANNELS -1 + +/* Internal resistance at Volume level 7. */ + +#define AY8910_INTERNAL_RESISTANCE (356) +#define YM2149_INTERNAL_RESISTANCE (353) + +/* + * The following is used by all drivers not reviewed yet. + * This will like the old behavior, output between + * 0 and 7FFF + */ +#define AY8910_LEGACY_OUTPUT (0x01) + +/* + * Specifying the next define will simulate the special + * cross channel mixing if outputs are tied together. + * The driver will only provide one stream in this case. + */ +#define AY8910_SINGLE_OUTPUT (0x02) + +/* + * The following define is the default behavior. + * Output level 0 is 0V and 7ffff corresponds to 5V. + * Use this to specify that a discrete mixing stage + * follows. + */ +#define AY8910_DISCRETE_OUTPUT (0x04) + +/* + * The following define causes the driver to output + * resistor values. Intended to be used for + * netlist interfacing. + */ + +#define AY8910_RESISTOR_OUTPUT (0x08) + +/* + * This define specifies the initial state of + * YM2149, YM3439, AY8930 pin 26 (SEL pin). + * By default it is set to high, + * compatible with AY8910. + */ +/* TODO: make it controllable while it's running (used by any hw???) */ +#define YM2149_PIN26_HIGH (0x00) /* or N/C */ +#define YM2149_PIN26_LOW (0x10) + +#define BIT(x,n) (((x)>>(n))&1) + +enum device_type { + AY8910, + AY8912, + AY8913, + AY8914, + AY8930, + YM2149, + YM3439, + YMZ284, + YMZ294, + SUNSOFT_5B_SOUND +}; + + +class ay8910_device +{ +public: + enum psg_type_t + { + PSG_TYPE_AY, + PSG_TYPE_YM + }; + + enum config_t + { + PSG_DEFAULT = 0x0, + PSG_PIN26_IS_CLKSEL = 0x1, + PSG_HAS_INTERNAL_DIVIDER = 0x2, + PSG_EXTENDED_ENVELOPE = 0x4, + PSG_HAS_EXPANDED_MODE = 0x8 + }; + + // construction/destruction + ay8910_device(unsigned int clock); + + // 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_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(); } + void address_w(unsigned char data); + void data_w(unsigned char data); + + // /RES + void reset_w(unsigned char data = 0) { ay8910_reset_ym(); } + + // 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 + + // use this when BC1 == !A0; here, BC1=0 selects 'latch address' and BC1=1 selects 'data' + void address_data_w(int offset, unsigned char data) { ay8910_write_ym(offset & 1, data); } + + // bc1=a0, bc2=a1 + void write_bc1_bc2(int offset, unsigned char data); + + struct ay_ym_param + { + double r_up; + double r_down; + int res_count; + double res[32]; + }; + + struct mosfet_param + { + double m_Vth; + double m_Vg; + int m_count; + double m_Kn[32]; + }; + + // 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); + + // device-level overrides + void device_start(); + void device_reset(); + + // sound stream update overrides + void sound_stream_update(short** outputs, int outLen); + + void ay8910_write_ym(int addr, unsigned char data); + unsigned char ay8910_read_ym(); + void ay8910_reset_ym(); + +private: + static constexpr int NUM_CHANNELS = 3; + device_type chip_type; + + /* register id's */ + enum + { + AY_AFINE = 0x00, + AY_ACOARSE = 0x01, + AY_BFINE = 0x02, + AY_BCOARSE = 0x03, + AY_CFINE = 0x04, + AY_CCOARSE = 0x05, + AY_NOISEPER = 0x06, + AY_ENABLE = 0x07, + AY_AVOL = 0x08, + AY_BVOL = 0x09, + AY_CVOL = 0x0a, + AY_EAFINE = 0x0b, + AY_EACOARSE = 0x0c, + AY_EASHAPE = 0x0d, + AY_PORTA = 0x0e, + AY_PORTB = 0x0f, + AY_EBFINE = 0x10, + AY_EBCOARSE = 0x11, + AY_ECFINE = 0x12, + AY_ECCOARSE = 0x13, + AY_EBSHAPE = 0x14, + AY_ECSHAPE = 0x15, + AY_ADUTY = 0x16, + AY_BDUTY = 0x17, + AY_CDUTY = 0x18, + AY_NOISEAND = 0x19, + AY_NOISEOR = 0x1a, + AY_TEST = 0x1f + }; + + // structs + struct tone_t + { + unsigned int period; + unsigned char volume; + unsigned char duty; + int count; + unsigned char duty_cycle; + unsigned char output; + + void reset() + { + period = 0; + volume = 0; + duty = 0; + count = 0; + duty_cycle = 0; + output = 0; + } + + void set_period(unsigned char fine, unsigned char coarse) + { + period = fine | (coarse << 8); + } + + void set_volume(unsigned char val) + { + volume = val; + } + + void set_duty(unsigned char val) + { + duty = val; + } + }; + + struct envelope_t + { + unsigned int period; + int count; + signed char step; + unsigned int volume; + unsigned char hold, alternate, attack, holding; + + void reset() + { + period = 0; + count = 0; + step = 0; + volume = 0; + hold = 0; + alternate = 0; + attack = 0; + holding = 0; + } + + void set_period(unsigned char fine, unsigned char coarse) + { + period = fine | (coarse << 8); + } + + void set_shape(unsigned char shape, unsigned char mask) + { + attack = (shape & 0x04) ? mask : 0x00; + if ((shape & 0x08) == 0) + { + /* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */ + hold = 1; + alternate = attack; + } + else + { + hold = shape & 0x01; + alternate = shape & 0x02; + } + step = mask; + holding = 0; + volume = (step ^ attack); + } + }; + + // 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); } + inline unsigned char tone_envelope(tone_t *tone) { return (tone->volume >> (is_expanded_mode() ? 5 : 4)) & ((m_feature & PSG_EXTENDED_ENVELOPE) ? 3 : 1); } + inline unsigned char tone_duty(tone_t *tone) { return is_expanded_mode() ? (tone->duty & 0x8 ? 0x8 : (tone->duty & 0xf)) : 0x4; } + 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 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; } + + // internal helpers + void set_type(psg_type_t psg_type); + inline float mix_3D(); + void ay8910_write_reg(int r, int v); + void build_mixer_table(); + + // internal state + psg_type_t m_type; + int m_streams; + int m_ioports; + int m_ready; + //sound_stream *m_channel; + bool m_active; + int 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 char m_mode; + unsigned char m_env_step_mask; + /* init parameters ... */ + int m_step; + int m_zero_is_off; + unsigned char m_vol_enabled[NUM_CHANNELS]; + const ay_ym_param *m_par; + const ay_ym_param *m_par_env; + short m_vol_table[NUM_CHANNELS][16]; + short m_env_table[NUM_CHANNELS][32]; + short m_vol3d_table[32*32*32*8]; + int m_flags; /* Flags */ + int m_feature; /* Chip specific features */ + int m_res_load[3]; /* Load on channel in ohms */ +}; + +class ay8912_device : public ay8910_device +{ +public: + ay8912_device(unsigned int clock); +}; + +class ay8913_device : public ay8910_device +{ +public: + ay8913_device(unsigned int clock); +}; + +class ay8914_device : public ay8910_device +{ +public: + ay8914_device(unsigned int clock); + + /* AY8914 handlers needed due to different register map */ + unsigned char read(int offset); + void write(int offset, unsigned char data); +}; + +class ay8930_device : public ay8910_device +{ +public: + ay8930_device(unsigned int clock); +}; + +class ym2149_device : public ay8910_device +{ +public: + ym2149_device(unsigned int clock); +}; + +class ym3439_device : public ay8910_device +{ +public: + ym3439_device(unsigned int clock); +}; + +class ymz284_device : public ay8910_device +{ +public: + ymz284_device(unsigned int clock); +}; + +class ymz294_device : public ay8910_device +{ +public: + ymz294_device(unsigned int clock); +}; + +class sunsoft_5b_sound_device : public ay8910_device +{ +public: + sunsoft_5b_sound_device(unsigned int clock); +}; + + +#endif // MAME_DEVICES_SOUND_AY8910_H diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 623a8e662..8ce34c32d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -327,6 +327,31 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char return false; } break; + case DIV_SYSTEM_AY8910: + switch (effect) { + case 0x20: // mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x21: // noise freq + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); + break; + case 0x22: // envelope enable + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); + break; + case 0x23: // envelope period low + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); + break; + case 0x24: // envelope period high + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); + break; + case 0x25: // envelope slide up + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); + break; + case 0x26: // envelope slide down + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); + break; + } + break; default: return false; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index de5e452fa..65f5a1261 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1532,7 +1532,7 @@ void FurnaceGUI::drawPattern() { ImGui::End(); } -const char* aboutLine[53]={ +const char* aboutLine[54]={ "tildearrow", "is proud to present", "", @@ -1555,6 +1555,7 @@ const char* aboutLine[53]={ "Nuked-OPM & Nuked-OPN2 by Nuke.YKT", "ymfm by Aaron Giles", "MAME SN76496 by Nicola Salmoria", + "MAME AY-3-8910 by Couriersud", "SameBoy by Lior Halphon", "Mednafen PCE", "puNES by FHorse", @@ -1637,7 +1638,7 @@ void FurnaceGUI::drawAbout() { } } - for (int i=0; i<53; i++) { + for (int i=0; i<54; i++) { double posX=(scrW*dpiScale/2.0)+(sin(double(i)*0.5+double(aboutScroll)/90.0)*120*dpiScale)-(ImGui::CalcTextSize(aboutLine[i]).x*0.5); double posY=(scrH-aboutScroll+42*i)*dpiScale; if (posY<-80*dpiScale || posY>scrH*dpiScale) continue; @@ -1660,7 +1661,7 @@ void FurnaceGUI::drawAbout() { ImGui::PopFont(); aboutScroll+=2; if (++aboutSin>=2400) aboutSin=0; - if (aboutScroll>(42*53+scrH)) aboutScroll=-20; + if (aboutScroll>(42*54+scrH)) aboutScroll=-20; } ImGui::End(); } diff --git a/src/main.cpp b/src/main.cpp index 34185f750..ed6c921a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -110,6 +110,7 @@ bool pVersion(String) { printf("- Nuked-OPN2 by Nuke.YKT (LGPLv2.1)\n"); printf("- ymfm by Aaron Giles (BSD 3-clause)\n"); printf("- MAME SN76496 emulation core by Nicola Salmoria (BSD 3-clause)\n"); + printf("- MAME AY-3-8910 emulation core by Couriersud (BSD 3-clause)\n"); printf("- SameBoy by Lior Halphon (MIT)\n"); printf("- Mednafen PCE by Mednafen Team (GPLv2)\n"); printf("- puNES by FHorse (GPLv2)\n");