552 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			552 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * Furnace Tracker - multi-system chiptune tracker
 | |
|  * Copyright (C) 2021-2022 tildearrow and contributors
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License as published by
 | |
|  * the Free Software Foundation; either version 2 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along
 | |
|  * with this program; if not, write to the Free Software Foundation, Inc.,
 | |
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | |
|  */
 | |
| 
 | |
| #include "ym2610ext.h"
 | |
| #include <math.h>
 | |
| 
 | |
| int DivPlatformYM2610Ext::dispatch(DivCommand c) {
 | |
|   if (c.chan<extChanOffs) {
 | |
|     return DivPlatformYM2610::dispatch(c);
 | |
|   }
 | |
|   if (c.chan>(extChanOffs+3)) {
 | |
|     c.chan-=3;
 | |
|     return DivPlatformYM2610::dispatch(c);
 | |
|   }
 | |
|   int ch=c.chan-extChanOffs;
 | |
|   int ordch=orderedOps[ch];
 | |
|   if (!extMode) {
 | |
|     c.chan=extChanOffs;
 | |
|     return DivPlatformYM2610::dispatch(c);
 | |
|   }
 | |
|   switch (c.cmd) {
 | |
|     case DIV_CMD_NOTE_ON: {
 | |
|       DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
 | |
|       
 | |
|       unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
 | |
|       DivInstrumentFM::Operator op=ins->fm.op[ordch];
 | |
|       // TODO: how does this work?!
 | |
|       if (isOpMuted[ch]) {
 | |
|         rWrite(baseAddr+0x40,127);
 | |
|       } else {
 | |
|         if (opChan[ch].insChanged) {
 | |
|           rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch].vol&0x7f,127));
 | |
|         }
 | |
|       }
 | |
|       if (opChan[ch].insChanged) {
 | |
|         rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
 | |
|         rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
 | |
|         rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
 | |
|         rWrite(baseAddr+0x70,op.d2r&31);
 | |
|         rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
 | |
|         rWrite(baseAddr+0x90,op.ssgEnv&15);
 | |
|         opChan[ch].mask=op.enable;
 | |
|       }
 | |
|       if (opChan[ch].insChanged) { // TODO how does this work?
 | |
|         rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
 | |
|         rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
 | |
|       }
 | |
|       opChan[ch].insChanged=false;
 | |
| 
 | |
|       if (c.value!=DIV_NOTE_NULL) {
 | |
|         opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
 | |
|         opChan[ch].portaPause=false;
 | |
|         opChan[ch].freqChanged=true;
 | |
|       }
 | |
|       opChan[ch].keyOn=true;
 | |
|       opChan[ch].active=true;
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_NOTE_OFF:
 | |
|       opChan[ch].keyOff=true;
 | |
|       opChan[ch].keyOn=false;
 | |
|       opChan[ch].active=false;
 | |
|       break;
 | |
|     case DIV_CMD_VOLUME: {
 | |
|       opChan[ch].vol=c.value;
 | |
|       DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
 | |
|       unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
 | |
|       DivInstrumentFM::Operator op=ins->fm.op[ordch];
 | |
|       if (isOpMuted[ch]) {
 | |
|         rWrite(baseAddr+0x40,127);
 | |
|       } else {
 | |
|         rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch].vol&0x7f,127));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_GET_VOLUME: {
 | |
|       return opChan[ch].vol;
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_INSTRUMENT:
 | |
|       if (opChan[ch].ins!=c.value || c.value2==1) {
 | |
|         opChan[ch].insChanged=true;
 | |
|       }
 | |
|       opChan[ch].ins=c.value;
 | |
|       break;
 | |
|     case DIV_CMD_PANNING: {
 | |
|       if (c.value==0 && c.value2==0) {
 | |
|         opChan[ch].pan=3;
 | |
|       } else {
 | |
|         opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
 | |
|       }
 | |
|       DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
 | |
|       if (parent->song.sharedExtStat) {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           if (ch==i) continue;
 | |
|           opChan[i].pan=opChan[ch].pan;
 | |
|         }
 | |
|       }
 | |
|       rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_PITCH: {
 | |
|       opChan[ch].pitch=c.value;
 | |
|       opChan[ch].freqChanged=true;
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_NOTE_PORTA: {
 | |
|       if (parent->song.linearPitch==2) {
 | |
|         int destFreq=NOTE_FREQUENCY(c.value2);
 | |
|         bool return2=false;
 | |
|         if (destFreq>opChan[ch].baseFreq) {
 | |
|           opChan[ch].baseFreq+=c.value;
 | |
|           if (opChan[ch].baseFreq>=destFreq) {
 | |
|             opChan[ch].baseFreq=destFreq;
 | |
|             return2=true;
 | |
|           }
 | |
|         } else {
 | |
|           opChan[ch].baseFreq-=c.value;
 | |
|           if (opChan[ch].baseFreq<=destFreq) {
 | |
|             opChan[ch].baseFreq=destFreq;
 | |
|             return2=true;
 | |
|           }
 | |
|         }
 | |
|         opChan[ch].freqChanged=true;
 | |
|         if (return2) {
 | |
|           //opChan[ch].inPorta=false;
 | |
|           return 2;
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       PLEASE_HELP_ME(opChan[ch]);
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_LEGATO: {
 | |
|       opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
 | |
|       opChan[ch].freqChanged=true;
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_EXTCH: {
 | |
|       extMode=c.value;
 | |
|       immWrite(0x27,extMode?0x40:0);
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_LFO: {
 | |
|       rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_FB: {
 | |
|       chan[extChanOffs].state.fb=c.value&7;
 | |
|       rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3));
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_MULT: { // TODO
 | |
|       unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|       DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
 | |
|       DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
 | |
|       rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_TL: { // TODO
 | |
|       unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|       DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
 | |
|       if (isOutput[ins->fm.alg][c.value]) {
 | |
|         rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
 | |
|       } else {
 | |
|         rWrite(baseAddr+0x40,c.value2);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_AR: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.ar=c.value2&31;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
 | |
|         }
 | |
|       } else {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.ar=c.value2&31;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_RS: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.rs=c.value2&3;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.rs=c.value2&3;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_AM: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.am=c.value2&1;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.am=c.value2&1;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_DR: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.dr=c.value2&31;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.dr=c.value2&31;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_SL: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.sl=c.value2&15;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.sl=c.value2&15;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_RR: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.rr=c.value2&15;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.rr=c.value2&15;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_D2R: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.d2r=c.value2&31;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.d2r=c.value2&31;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_DT: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.dt=c.value&7;
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.dt=c.value2&7;
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_FM_SSG: {
 | |
|       if (c.value<0)  {
 | |
|         for (int i=0; i<4; i++) {
 | |
|           DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i];
 | |
|           op.ssgEnv=8^(c.value2&15);
 | |
|           unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i];
 | |
|           rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
 | |
|         }
 | |
|       } else if (c.value<4) {
 | |
|         DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
 | |
|         op.ssgEnv=8^(c.value2&15);
 | |
|         unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
 | |
|         rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case DIV_CMD_GET_VOLMAX:
 | |
|       return 127;
 | |
|       break;
 | |
|     case DIV_ALWAYS_SET_VOLUME:
 | |
|       return 0;
 | |
|       break;
 | |
|     case DIV_CMD_PRE_PORTA:
 | |
|       break;
 | |
|     default:
 | |
|       //printf("WARNING: unimplemented command %d\n",c.cmd);
 | |
|       break;
 | |
|   }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| static int opChanOffsL[4]={
 | |
|   0xa9, 0xaa, 0xa8, 0xa2
 | |
| };
 | |
| 
 | |
| static int opChanOffsH[4]={
 | |
|   0xad, 0xae, 0xac, 0xa6
 | |
| };
 | |
| 
 | |
| void DivPlatformYM2610Ext::tick(bool sysTick) {
 | |
|   if (extMode) {
 | |
|     bool writeSomething=false;
 | |
|     unsigned char writeMask=2;
 | |
|     for (int i=0; i<4; i++) {
 | |
|       writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
 | |
|       if (opChan[i].keyOn || opChan[i].keyOff) {
 | |
|         writeSomething=true;
 | |
|         writeMask&=~(1<<(4+i));
 | |
|         opChan[i].keyOff=false;
 | |
|       }
 | |
|     }
 | |
|     if (writeSomething) {
 | |
|       immWrite(0x28,writeMask);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DivPlatformYM2610::tick(sysTick);
 | |
| 
 | |
|   bool writeNoteOn=false;
 | |
|   unsigned char writeMask=2;
 | |
|   if (extMode) for (int i=0; i<4; i++) {
 | |
|     if (opChan[i].freqChanged) {
 | |
|       if (parent->song.linearPitch==2) {
 | |
|         opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
 | |
|       } else {
 | |
|         int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
 | |
|         int block=(opChan[i].baseFreq&0xf800)>>11;
 | |
|         if (fNum<0) fNum=0;
 | |
|         if (fNum>2047) {
 | |
|           while (block<7) {
 | |
|             fNum>>=1;
 | |
|             block++;
 | |
|           }
 | |
|           if (fNum>2047) fNum=2047;
 | |
|         }
 | |
|         opChan[i].freq=(block<<11)|fNum;
 | |
|       }
 | |
|       if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
 | |
|       immWrite(opChanOffsH[i],opChan[i].freq>>8);
 | |
|       immWrite(opChanOffsL[i],opChan[i].freq&0xff);
 | |
|     }
 | |
|     writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
 | |
|     if (opChan[i].keyOn) {
 | |
|       writeNoteOn=true;
 | |
|       if (opChan[i].mask) {
 | |
|         writeMask|=1<<(4+i);
 | |
|       }
 | |
|       opChan[i].keyOn=false;
 | |
|     }
 | |
|   }
 | |
|   if (writeNoteOn) {
 | |
|     immWrite(0x28,writeMask);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) {
 | |
|   if (ch<extChanOffs) {
 | |
|     DivPlatformYM2610::muteChannel(ch,mute);
 | |
|     return;
 | |
|   }
 | |
|   if (ch>(extChanOffs+3)) {
 | |
|     DivPlatformYM2610::muteChannel(ch-3,mute);
 | |
|     return;
 | |
|   }
 | |
|   isOpMuted[ch-extChanOffs]=mute;
 | |
|   
 | |
|   int ordch=orderedOps[ch-extChanOffs];
 | |
|   DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
 | |
|   unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
 | |
|   DivInstrumentFM::Operator op=ins->fm.op[ordch];
 | |
|   if (isOpMuted[ch]) {
 | |
|     rWrite(baseAddr+0x40,127);
 | |
|   } else if (isOutput[ins->fm.alg][ordch]) {
 | |
|     rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch-extChanOffs].vol&0x7f,127));
 | |
|   } else {
 | |
|     rWrite(baseAddr+0x40,op.tl);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void DivPlatformYM2610Ext::forceIns() {
 | |
|   for (int i=0; i<psgChanOffs; i++) {
 | |
|     for (int j=0; j<4; j++) {
 | |
|       unsigned short baseAddr=chanOffs[i]|opOffs[j];
 | |
|       DivInstrumentFM::Operator& op=chan[i].state.op[j];
 | |
|       if (i==1 && extMode) { // extended channel
 | |
|         if (isOpMuted[j]) {
 | |
|           rWrite(baseAddr+0x40,127);
 | |
|         } else if (KVS(i,j)) {
 | |
|           rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[j].vol&0x7f,127));
 | |
|         } else {
 | |
|           rWrite(baseAddr+0x40,op.tl);
 | |
|         }
 | |
|       } else {
 | |
|         if (isMuted[i]) {
 | |
|           rWrite(baseAddr+ADDR_TL,127);
 | |
|         } else {
 | |
|           if (KVS(i,j)) {
 | |
|             rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127));
 | |
|           } else {
 | |
|             rWrite(baseAddr+ADDR_TL,op.tl);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
 | |
|       rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
 | |
|       rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
 | |
|       rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
 | |
|       rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
 | |
|       rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
 | |
|     }
 | |
|     rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
 | |
|     rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
 | |
|     if (chan[i].active) {
 | |
|       chan[i].keyOn=true;
 | |
|       chan[i].freqChanged=true;
 | |
|     }
 | |
|   }
 | |
|   for (int i=adpcmAChanOffs; i<=adpcmBChanOffs; i++) {
 | |
|     chan[i].insChanged=true;
 | |
|   }
 | |
|   ay->forceIns();
 | |
|   ay->flushWrites();
 | |
|   for (DivRegWrite& i: ay->getRegisterWrites()) {
 | |
|     if (i.addr>15) continue;
 | |
|     immWrite(i.addr&15,i.val);
 | |
|   }
 | |
|   ay->getRegisterWrites().clear();
 | |
|   for (int i=0; i<4; i++) {
 | |
|     opChan[i].insChanged=true;
 | |
|     if (opChan[i].active) {
 | |
|       opChan[i].keyOn=true;
 | |
|       opChan[i].freqChanged=true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void* DivPlatformYM2610Ext::getChanState(int ch) {
 | |
|   if (ch>=(extChanOffs+4)) return &chan[ch-3];
 | |
|   if (ch>=extChanOffs) return &opChan[ch-extChanOffs];
 | |
|   return &chan[ch];
 | |
| }
 | |
| 
 | |
| DivMacroInt* DivPlatformYM2610Ext::getChanMacroInt(int ch) {
 | |
|   if (ch>=(psgChanOffs+3) && ch<(adpcmAChanOffs+3)) return ay->getChanMacroInt(ch-psgChanOffs-3);
 | |
|   if (ch>=(extChanOffs+4)) return &chan[ch-3].std;
 | |
|   if (ch>=extChanOffs) return NULL; // currently not implemented
 | |
|   return &chan[ch].std;
 | |
| }
 | |
| 
 | |
| DivDispatchOscBuffer* DivPlatformYM2610Ext::getOscBuffer(int ch) {
 | |
|   if (ch>=(extChanOffs+4)) return oscBuf[ch-3];
 | |
|   if (ch<(extChanOffs+1)) return oscBuf[ch];
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| void DivPlatformYM2610Ext::reset() {
 | |
|   DivPlatformYM2610::reset();
 | |
| 
 | |
|   for (int i=0; i<4; i++) {
 | |
|     opChan[i]=DivPlatformYM2610Ext::OpChannel();
 | |
|     opChan[i].vol=127;
 | |
|   }
 | |
| 
 | |
|   // channel 2 mode
 | |
|   immWrite(0x27,0x40);
 | |
|   extMode=true;
 | |
| }
 | |
| 
 | |
| bool DivPlatformYM2610Ext::keyOffAffectsArp(int ch) {
 | |
|   return (ch>=(psgChanOffs+3));
 | |
| }
 | |
| 
 | |
| void DivPlatformYM2610Ext::notifyInsChange(int ins) {
 | |
|   DivPlatformYM2610::notifyInsChange(ins);
 | |
|   for (int i=0; i<4; i++) {
 | |
|     if (opChan[i].ins==ins) {
 | |
|       opChan[i].insChanged=true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| int DivPlatformYM2610Ext::init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) {
 | |
|   DivPlatformYM2610::init(parent,channels,sugRate,flags);
 | |
|   for (int i=0; i<4; i++) {
 | |
|     isOpMuted[i]=false;
 | |
|   }
 | |
|   extSys=true;
 | |
| 
 | |
|   reset();
 | |
|   return 17;
 | |
| }
 | |
| 
 | |
| void DivPlatformYM2610Ext::quit() {
 | |
|   DivPlatformYM2610::quit();
 | |
| }
 | |
| 
 | |
| DivPlatformYM2610Ext::~DivPlatformYM2610Ext() {
 | |
| }
 | 
