diff --git a/CMakeLists.txt b/CMakeLists.txt index cfa037384..b1324e27c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,7 @@ src/engine/platform/arcade.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ay.cpp +src/engine/platform/ay8930.cpp src/engine/platform/dummy.cpp) set(GUI_SOURCES diff --git a/README.md b/README.md index 224488359..44039693a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Yamaha YM2151 (plus PCM) - Neo Geo - AY-3-8910 (ZX Spectrum, Atari ST, etc.) + - Microchip AY8930 - multiple sound chips in a single song! - clean-room design (zero reverse-engineered code and zero decompilation; using official DMF specs, guesswork and ABX tests only) - bug/quirk implementation for increased playback accuracy diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 21aa84517..ef59ae63d 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -10,6 +10,7 @@ #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ay.h" +#include "platform/ay8930.h" #include "platform/dummy.h" #include "../ta-log.h" @@ -116,6 +117,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_AY8910: dispatch=new DivPlatformAY8910; break; + case DIV_SYSTEM_AY8930: + dispatch=new DivPlatformAY8930; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e77c172d4..ee67920f9 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -80,6 +80,8 @@ DivSystem systemFromFile(unsigned char val) { return DIV_SYSTEM_TIA; case 0x97: return DIV_SYSTEM_SAA1099; + case 0x9a: + return DIV_SYSTEM_AY8930; } return DIV_SYSTEM_NULL; } @@ -123,6 +125,8 @@ unsigned char systemToFile(DivSystem val) { return 0x84; case DIV_SYSTEM_SAA1099: return 0x97; + case DIV_SYSTEM_AY8930: + return 0x9a; case DIV_SYSTEM_NULL: return 0; @@ -156,6 +160,7 @@ int DivEngine::getChannelCount(DivSystem sys) { return 16; // Furnace-specific systems case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: return 3; case DIV_SYSTEM_AMIGA: return 4; @@ -216,6 +221,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Atari 2600"; case DIV_SYSTEM_SAA1099: return "Philips SAA1099"; + case DIV_SYSTEM_AY8930: + return "Microchip AY8930"; } return "Unknown"; } @@ -250,7 +257,7 @@ const char* chanNames[17][17]={ {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "Sample 1", "Sample 2", "Sample 3", "Sample 4", "Sample 5"}, // Arcade {"FM 1", "FM 2", "FM 3", "FM 4", "Square 1", "Square 2", "Square 3", "Sample 1", "Sample 2", "Sample 3", "Sample 4", "Sample 5", "Sample 6"}, // YM2610 {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "Square 1", "Square 2", "Square 3", "Sample 1", "Sample 2", "Sample 3", "Sample 4", "Sample 5", "Sample 6"}, // YM2610 (extended channel 2) - {"Square 1", "Square 2", "Square 3"}, // AY-3-8910 + {"Square 1", "Square 2", "Square 3"}, // AY-3-8910/AY8930 {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, // YM2151 {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, // YM2612 @@ -270,7 +277,7 @@ const char* chanShortNames[17][17]={ {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "P1", "P2", "P3", "P4", "P5"}, // Arcade {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, // YM2610 {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, // YM2610 (extended channel 2) - {"S1", "S2", "S3"}, // AY-3-8910 + {"S1", "S2", "S3"}, // AY-3-8910/AY8930 {"CH1", "CH2", "CH3", "CH4"}, // Amiga {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, // YM2151 {"F1", "F2", "F3", "F4", "F5", "F6"}, // YM2612 @@ -290,7 +297,7 @@ const int chanTypes[17][17]={ {0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4}, // Arcade {0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4}, // YM2610 {0, 5, 5, 5, 5, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4}, // YM2610 (extended channel 2) - {1, 1, 1}, // AY-3-8910 + {1, 1, 1}, // AY-3-8910/AY8930 {4, 4, 4, 4}, // Amiga {0, 0, 0, 0, 0, 0, 0, 0}, // YM2151 {0, 0, 0, 0, 0, 0}, // YM2612 @@ -335,6 +342,7 @@ const char* DivEngine::getChannelName(int chan) { return chanNames[10][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: return chanNames[11][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_AMIGA: @@ -393,6 +401,7 @@ const char* DivEngine::getChannelShortName(int chan) { return chanShortNames[10][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: return chanShortNames[11][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_AMIGA: @@ -450,6 +459,7 @@ int DivEngine::getChannelType(int chan) { return chanTypes[10][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: return chanTypes[11][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_AMIGA: diff --git a/src/engine/engine.h b/src/engine/engine.h index 4334504b2..413abab93 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -9,8 +9,8 @@ #include #include -#define DIV_VERSION "0.4pre1" -#define DIV_ENGINE_VERSION 17 +#define DIV_VERSION "0.4pre2" +#define DIV_ENGINE_VERSION 18 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 1cfd929d2..bc0ca5a80 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -9,7 +9,8 @@ enum DivInstrumentType { DIV_INS_C64=3, DIV_INS_AMIGA=4, DIV_INS_PCE=5, - DIV_INS_AY=6 + DIV_INS_AY=6, + DIV_INS_AY8930=7 }; struct DivInstrumentFM { diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp new file mode 100644 index 000000000..6a974d91a --- /dev/null +++ b/src/engine/platform/ay8930.cpp @@ -0,0 +1,372 @@ +#include "ay8930.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 DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (ayBufLen>4)) { + bank=w.addr>>4; + ay->address_w(0x0d); + ay->data_w(0xa0|(bank<<4)); + } + ay->address_w(w.addr&15); + if (w.addr==0x0d) { + ay->data_w(0xa0|(bank<<4)|(w.val&15)); + } else { + ay->data_w(w.val); + } + writes.pop(); + } + ay->sound_stream_update(ayBuf,len); + for (size_t i=0; icalcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].keyOn) { + if (chan[i].insChanged) { + if (!chan[i].std.willEx1) immWrite(0x16+i,chan[i].duty); + chan[i].insChanged=false; + } + } + 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; + } + + if (ayEnvSlide[i]!=0) { + ayEnvSlideLow[i]+=ayEnvSlide[i]; + while (ayEnvSlideLow[i]>7) { + ayEnvSlideLow[i]-=8; + if (ayEnvPeriod[i]<0xffff) { + ayEnvPeriod[i]++; + immWrite(regPeriodL[i],ayEnvPeriod[i]); + immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + } + } + while (ayEnvSlideLow[i]<-7) { + ayEnvSlideLow[i]+=8; + if (ayEnvPeriod[i]>0) { + ayEnvPeriod[i]--; + immWrite(regPeriodL[i],ayEnvPeriod[i]); + immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + } + } + } + } + + 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))); + + for (int i=0; i<32; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } +} + +int DivPlatformAY8930::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&31)|((chan[c.chan].psgMode&4)<<3)); + } + 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&31)|((chan[c.chan].psgMode&4)<<3)); + } + 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_MODE: + if (c.value<0x10) { + // TODO: channel mode + } else { + chan[c.chan].duty=c.value&15; + immWrite(0x16,chan[c.chan].duty); + } + break; + case DIV_CMD_STD_NOISE_FREQ: + rWrite(0x06,c.value); + break; + case DIV_CMD_AY_ENVELOPE_SET: + ayEnvMode[c.chan]=c.value>>4; + rWrite(regMode[c.chan],ayEnvMode[c.chan]); + 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&31)|((chan[c.chan].psgMode&4)<<3)); + } + break; + case DIV_CMD_AY_ENVELOPE_LOW: + ayEnvPeriod[c.chan]&=0xff00; + ayEnvPeriod[c.chan]|=c.value; + immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]); + immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8); + break; + case DIV_CMD_AY_ENVELOPE_HIGH: + ayEnvPeriod[c.chan]&=0xff; + ayEnvPeriod[c.chan]|=c.value<<8; + immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]); + immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8); + break; + case DIV_CMD_AY_ENVELOPE_SLIDE: + ayEnvSlide[c.chan]=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + return 31; + 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 DivPlatformAY8930::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (isMuted[ch]) { + rWrite(0x08+ch,0); + } else { + rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode&4)<<3)); + } +} + +void DivPlatformAY8930::forceIns() { + for (int i=0; i<3; i++) { + chan[i].insChanged=true; + immWrite(regPeriodL[i],ayEnvPeriod[i]); + immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + immWrite(regMode[i],ayEnvMode[i]); + } +} + +void DivPlatformAY8930::reset() { + while (!writes.empty()) writes.pop(); + ay->device_reset(); + for (int i=0; i<3; i++) { + chan[i]=DivPlatformAY8930::Channel(); + chan[i].vol=31; + ayEnvPeriod[i]=0; + ayEnvMode[i]=0; + ayEnvSlide[i]=0; + ayEnvSlideLow[i]=0; + } + + for (int i=0; i<32; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + + delay=0; + + extMode=false; + bank=false; + + immWrite(0x0d,0xa0); +} + +bool DivPlatformAY8930::isStereo() { + return false; +} + +bool DivPlatformAY8930::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformAY8930::notifyInsDeletion(void* ins) { + for (int i=0; i<3; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +int DivPlatformAY8930::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 ay8930_device(rate); + ay->device_start(); + ayBufLen=65536; + for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen]; + reset(); + return 3; +} + +void DivPlatformAY8930::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/ay8930.h b/src/engine/platform/ay8930.h new file mode 100644 index 000000000..1220b2cd0 --- /dev/null +++ b/src/engine/platform/ay8930.h @@ -0,0 +1,67 @@ +#ifndef _AY8930_H +#define _AY8930_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ay8910.h" + +class DivPlatformAY8930: public DivDispatch { + protected: + struct Channel { + unsigned char freqH, freqL; + int freq, baseFreq, pitch; + unsigned char ins, note, psgMode, duty; + 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), duty(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(31), 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; + ay8930_device* ay; + unsigned char lastBusy; + + bool dacMode; + int dacPeriod; + int dacRate; + int dacPos; + int dacSample; + unsigned char sampleBank; + bool bank; + + int delay; + + bool extMode; + + short oldWrites[32]; + short pendingWrites[32]; + unsigned char ayEnvMode[3]; + unsigned short ayEnvPeriod[3]; + short ayEnvSlideLow[3]; + short ayEnvSlide[3]; + short* ayBuf[3]; + size_t ayBufLen; + + 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); + void notifyInsDeletion(void* ins); + int init(DivEngine* parent, int channels, int sugRate, bool pal); + void quit(); +}; +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 0ae7fe6ed..e497b2aab 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -328,9 +328,13 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char } break; case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: switch (effect) { + case 0x12: // duty on 8930 + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,0x10+effectVal)); + break; case 0x20: // mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal&15)); break; case 0x21: // noise freq dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); diff --git a/src/engine/song.h b/src/engine/song.h index 853a1c9b4..c3d8e6a5b 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -30,7 +30,8 @@ enum DivSystem { DIV_SYSTEM_YM2151, DIV_SYSTEM_YM2612, DIV_SYSTEM_TIA, - DIV_SYSTEM_SAA1099 + DIV_SYSTEM_SAA1099, + DIV_SYSTEM_AY8930 }; struct DivSong { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fefb0d085..1c6ab3e7c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -31,7 +31,6 @@ const int _ZERO=0; const int _ONE=1; const int _THREE=3; -const int _SIX=6; const int _SEVEN=7; const int _TEN=10; const int _FIFTEEN=15; @@ -559,6 +558,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); name=fmt::sprintf(ICON_FA_BAR_CHART " %.2x: %s##_INS%d\n",i,ins->name,i); break; + case DIV_INS_AY8930: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2x: %s##_INS%d\n",i,ins->name,i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION " %.2x: %s##_INS%d\n",i,ins->name,i); @@ -583,8 +586,8 @@ int detuneTable[8]={ 0, 1, 2, 3, 0, -3, -2, -1 }; -const char* insTypes[7]={ - "Standard", "FM", "Game Boy", "C64", "Amiga", "PC Engine", "AY/SSG" +const char* insTypes[8]={ + "Standard", "FM", "Game Boy", "C64", "Amiga", "PC Engine", "AY-3-8910/SSG", "AY8930" }; const char* ssgEnvTypes[8]={ @@ -599,8 +602,8 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>6) ins->type=DIV_INS_FM; - if (ImGui::SliderScalar("Type",ImGuiDataType_U8,&ins->type,&_ZERO,&_SIX,insTypes[ins->type])) { + if (ins->type<0 || ins->type>7) ins->type=DIV_INS_FM; + if (ImGui::SliderScalar("Type",ImGuiDataType_U8,&ins->type,&_ZERO,&_SEVEN,insTypes[ins->type])) { ins->mode=(ins->type==DIV_INS_FM); } @@ -775,7 +778,7 @@ void FurnaceGUI::drawInsEdit() { loopIndicator[i]=(ins->std.volMacroLoop!=-1 && i>=ins->std.volMacroLoop); } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - int volMax=(ins->type==DIV_INS_PCE)?31:15; + int volMax=(ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)?31:15; int volMin=0; if (ins->type==DIV_INS_C64) { if (ins->c64.volIsCutoff) { @@ -855,7 +858,7 @@ void FurnaceGUI::drawInsEdit() { } // duty macro - int dutyMax=(ins->type==DIV_INS_AY)?31:3; + int dutyMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?31:3; if (ins->type==DIV_INS_C64) { if (ins->c64.dutyIsAbs) { dutyMax=4095; @@ -863,6 +866,9 @@ void FurnaceGUI::drawInsEdit() { dutyMax=24; } } + if (ins->type==DIV_INS_AY8930) { + dutyMax=255; + } if (dutyMax>0) { ImGui::Separator(); if (ins->type==DIV_INS_C64) { @@ -872,7 +878,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Relative Duty Macro"); } } else { - if (ins->type==DIV_INS_AY) { + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { ImGui::Text("Noise Frequency Macro"); } else { ImGui::Text("Duty/Noise Mode Macro"); @@ -912,7 +918,7 @@ void FurnaceGUI::drawInsEdit() { } // wave macro - int waveMax=(ins->type==DIV_INS_AY)?7:63; + int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?7:63; if (ins->type==DIV_INS_C64) waveMax=8; if (waveMax>0) { ImGui::Separator(); @@ -948,6 +954,48 @@ void FurnaceGUI::drawInsEdit() { if (ins->std.waveMacroLen>127) ins->std.waveMacroLen=127; } } + + // extra 1 macro + int ex1Max=(ins->type==DIV_INS_AY8930)?15:0; + if (ex1Max>0) { + ImGui::Separator(); + if (ins->type==DIV_INS_AY8930) { + ImGui::Text("Duty Macro"); + } else { + ImGui::Text("Extra 1 Macro"); + } + for (int i=0; istd.ex1MacroLen; i++) { + asFloat[i]=ins->std.ex1Macro[i]; + loopIndicator[i]=(ins->std.ex1MacroLoop!=-1 && i>=ins->std.ex1MacroLoop); + } + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + + ImGui::PlotHistogram("##IEx1Macro",asFloat,ins->std.ex1MacroLen,0,NULL,0,ex1Max,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroDragStart=ImGui::GetItemRectMin(); + macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); + macroDragMin=0; + macroDragMax=ex1Max; + macroDragLen=ins->std.ex1MacroLen; + macroDragActive=true; + macroDragTarget=ins->std.ex1Macro; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + ImGui::PlotHistogram("##IEx1MacroLoop",loopIndicator,ins->std.ex1MacroLen,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroLoopDragStart=ImGui::GetItemRectMin(); + macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); + macroLoopDragLen=ins->std.ex1MacroLen; + macroLoopDragTarget=&ins->std.ex1MacroLoop; + macroLoopDragActive=true; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + ImGui::PopStyleVar(); + if (ImGui::InputScalar("Length##IEx1MacroL",ImGuiDataType_U8,&ins->std.ex1MacroLen,&_ONE,&_THREE)) { + if (ins->std.ex1MacroLen>127) ins->std.ex1MacroLen=127; + } + } + ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -2863,6 +2911,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_YM2612); sysAddOption(DIV_SYSTEM_TIA); sysAddOption(DIV_SYSTEM_SAA1099); + sysAddOption(DIV_SYSTEM_AY8930); ImGui::EndMenu(); } if (ImGui::BeginMenu("change platform...")) { @@ -2885,6 +2934,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_YM2612); sysChangeOption(i,DIV_SYSTEM_TIA); sysChangeOption(i,DIV_SYSTEM_SAA1099); + sysChangeOption(i,DIV_SYSTEM_AY8930); ImGui::EndMenu(); } } @@ -3325,6 +3375,7 @@ FurnaceGUI::FurnaceGUI(): uiColors[GUI_COLOR_INSTR_AMIGA]=ImVec4(1.0f,0.5f,0.5f,1.0f); uiColors[GUI_COLOR_INSTR_PCE]=ImVec4(1.0f,0.8f,0.5f,1.0f); uiColors[GUI_COLOR_INSTR_AY]=ImVec4(1.0f,0.5f,1.0f,1.0f); + uiColors[GUI_COLOR_INSTR_AY8930]=ImVec4(0.7f,0.5f,1.0f,1.0f); uiColors[GUI_COLOR_INSTR_UNKNOWN]=ImVec4(0.3f,0.3f,0.3f,1.0f); uiColors[GUI_COLOR_CHANNEL_FM]=ImVec4(0.2f,0.8f,1.0f,1.0f); uiColors[GUI_COLOR_CHANNEL_PULSE]=ImVec4(0.4f,1.0f,0.2f,1.0f); diff --git a/src/gui/gui.h b/src/gui/gui.h index 2f01738eb..08310f92a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -20,6 +20,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_AMIGA, GUI_COLOR_INSTR_PCE, GUI_COLOR_INSTR_AY, + GUI_COLOR_INSTR_AY8930, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE,