add AY-3-8910 platform!
this paves the way for eventual AY-3-8930 platform...
This commit is contained in:
parent
2fcb474544
commit
0b352ecd9a
|
@ -65,6 +65,7 @@ src/log.cpp
|
||||||
extern/Nuked-OPN2/ym3438.c
|
extern/Nuked-OPN2/ym3438.c
|
||||||
extern/opm/opm.c
|
extern/opm/opm.c
|
||||||
src/engine/platform/sound/sn76496.cpp
|
src/engine/platform/sound/sn76496.cpp
|
||||||
|
src/engine/platform/sound/ay8910.cpp
|
||||||
src/engine/platform/sound/gb/apu.c
|
src/engine/platform/sound/gb/apu.c
|
||||||
src/engine/platform/sound/gb/timing.c
|
src/engine/platform/sound/gb/timing.c
|
||||||
src/engine/platform/sound/pce_psg.cpp
|
src/engine/platform/sound/pce_psg.cpp
|
||||||
|
@ -116,6 +117,7 @@ src/engine/platform/c64.cpp
|
||||||
src/engine/platform/arcade.cpp
|
src/engine/platform/arcade.cpp
|
||||||
src/engine/platform/ym2610.cpp
|
src/engine/platform/ym2610.cpp
|
||||||
src/engine/platform/ym2610ext.cpp
|
src/engine/platform/ym2610ext.cpp
|
||||||
|
src/engine/platform/ay.cpp
|
||||||
src/engine/platform/dummy.cpp)
|
src/engine/platform/dummy.cpp)
|
||||||
|
|
||||||
set(GUI_SOURCES
|
set(GUI_SOURCES
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "platform/arcade.h"
|
#include "platform/arcade.h"
|
||||||
#include "platform/ym2610.h"
|
#include "platform/ym2610.h"
|
||||||
#include "platform/ym2610ext.h"
|
#include "platform/ym2610ext.h"
|
||||||
|
#include "platform/ay.h"
|
||||||
#include "platform/dummy.h"
|
#include "platform/dummy.h"
|
||||||
#include "../ta-log.h"
|
#include "../ta-log.h"
|
||||||
|
|
||||||
|
@ -112,6 +113,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
||||||
case DIV_SYSTEM_YM2610_EXT:
|
case DIV_SYSTEM_YM2610_EXT:
|
||||||
dispatch=new DivPlatformYM2610Ext;
|
dispatch=new DivPlatformYM2610Ext;
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_AY8910:
|
||||||
|
dispatch=new DivPlatformAY8910;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logW("this system is not supported yet! using dummy platform.\n");
|
logW("this system is not supported yet! using dummy platform.\n");
|
||||||
dispatch=new DivPlatformDummy;
|
dispatch=new DivPlatformDummy;
|
||||||
|
|
357
src/engine/platform/ay.cpp
Normal file
357
src/engine/platform/ay.cpp
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
#include "ay.h"
|
||||||
|
#include "../engine.h"
|
||||||
|
#include "sound/ay8910.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#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 (ayBufLen<len) {
|
||||||
|
ayBufLen=len;
|
||||||
|
for (int i=0; i<3; i++) {
|
||||||
|
delete[] ayBuf[i];
|
||||||
|
ayBuf[i]=new short[ayBufLen];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!writes.empty()) {
|
||||||
|
QueuedWrite w=writes.front();
|
||||||
|
ay->address_w(w.addr);
|
||||||
|
ay->data_w(w.val);
|
||||||
|
writes.pop();
|
||||||
|
}
|
||||||
|
ay->sound_stream_update(ayBuf,len);
|
||||||
|
for (size_t i=0; i<len; i++) {
|
||||||
|
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i]+ayBuf[2][i];
|
||||||
|
bufR[i+start]=ayBuf[0][i]+ayBuf[1][i]+ayBuf[2][i];
|
||||||
|
}
|
||||||
|
//static int os[2];
|
||||||
|
|
||||||
|
/*for (size_t h=start; h<start+len; h++) {
|
||||||
|
os[0]=0; os[1]=0;
|
||||||
|
if (!writes.empty()) {
|
||||||
|
if (--delay<1) {
|
||||||
|
QueuedWrite& w=writes.front();
|
||||||
|
fm->write(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;
|
||||||
|
}
|
65
src/engine/platform/ay.h
Normal file
65
src/engine/platform/ay.h
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#ifndef _AY_H
|
||||||
|
#define _AY_H
|
||||||
|
#include "../dispatch.h"
|
||||||
|
#include "../macroInt.h"
|
||||||
|
#include <queue>
|
||||||
|
#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<QueuedWrite> 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
|
1543
src/engine/platform/sound/ay8910.cpp
Normal file
1543
src/engine/platform/sound/ay8910.cpp
Normal file
File diff suppressed because it is too large
Load diff
371
src/engine/platform/sound/ay8910.h
Normal file
371
src/engine/platform/sound/ay8910.h
Normal file
|
@ -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
|
|
@ -327,6 +327,31 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1532,7 +1532,7 @@ void FurnaceGUI::drawPattern() {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* aboutLine[53]={
|
const char* aboutLine[54]={
|
||||||
"tildearrow",
|
"tildearrow",
|
||||||
"is proud to present",
|
"is proud to present",
|
||||||
"",
|
"",
|
||||||
|
@ -1555,6 +1555,7 @@ const char* aboutLine[53]={
|
||||||
"Nuked-OPM & Nuked-OPN2 by Nuke.YKT",
|
"Nuked-OPM & Nuked-OPN2 by Nuke.YKT",
|
||||||
"ymfm by Aaron Giles",
|
"ymfm by Aaron Giles",
|
||||||
"MAME SN76496 by Nicola Salmoria",
|
"MAME SN76496 by Nicola Salmoria",
|
||||||
|
"MAME AY-3-8910 by Couriersud",
|
||||||
"SameBoy by Lior Halphon",
|
"SameBoy by Lior Halphon",
|
||||||
"Mednafen PCE",
|
"Mednafen PCE",
|
||||||
"puNES by FHorse",
|
"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 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;
|
double posY=(scrH-aboutScroll+42*i)*dpiScale;
|
||||||
if (posY<-80*dpiScale || posY>scrH*dpiScale) continue;
|
if (posY<-80*dpiScale || posY>scrH*dpiScale) continue;
|
||||||
|
@ -1660,7 +1661,7 @@ void FurnaceGUI::drawAbout() {
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
aboutScroll+=2;
|
aboutScroll+=2;
|
||||||
if (++aboutSin>=2400) aboutSin=0;
|
if (++aboutSin>=2400) aboutSin=0;
|
||||||
if (aboutScroll>(42*53+scrH)) aboutScroll=-20;
|
if (aboutScroll>(42*54+scrH)) aboutScroll=-20;
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,7 @@ bool pVersion(String) {
|
||||||
printf("- Nuked-OPN2 by Nuke.YKT (LGPLv2.1)\n");
|
printf("- Nuked-OPN2 by Nuke.YKT (LGPLv2.1)\n");
|
||||||
printf("- ymfm by Aaron Giles (BSD 3-clause)\n");
|
printf("- ymfm by Aaron Giles (BSD 3-clause)\n");
|
||||||
printf("- MAME SN76496 emulation core by Nicola Salmoria (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("- SameBoy by Lior Halphon (MIT)\n");
|
||||||
printf("- Mednafen PCE by Mednafen Team (GPLv2)\n");
|
printf("- Mednafen PCE by Mednafen Team (GPLv2)\n");
|
||||||
printf("- puNES by FHorse (GPLv2)\n");
|
printf("- puNES by FHorse (GPLv2)\n");
|
||||||
|
|
Loading…
Reference in a new issue