OSDN Git Service

[FMGEN] Initial implementation of OPN2 (YM2612) from OPNA.
authorK.Ohta <whatisthis.sowhat@gmail.com>
Mon, 24 Sep 2018 17:29:20 +0000 (02:29 +0900)
committerK.Ohta <whatisthis.sowhat@gmail.com>
Mon, 24 Sep 2018 17:29:20 +0000 (02:29 +0900)
source/src/vm/fmgen/CMakeLists.txt
source/src/vm/fmgen/opna.cpp
source/src/vm/fmgen/opna.h

index 4b71399..1eb0f0a 100644 (file)
@@ -1,7 +1,7 @@
 cmake_minimum_required (VERSION 2.6)
 
 message("* vm/fmgen")
-SET(THIS_LIB_VERSION 1.3.0)
+SET(THIS_LIB_VERSION 1.4.0)
 add_definitions(-D__LIBFMGEN_VERSION=\"libCSPfmgen.${THIS_LIB_VERSION}\")
 
 SET(s_vm_fmgen_srcs
index ee9b47d..00628c7 100644 (file)
@@ -18,6 +18,7 @@
 #define BUILD_OPN
 #define BUILD_OPNA
 #define BUILD_OPNB
+#define BUILD_OPN2
 
 
 //     TOFIX:
@@ -41,7 +42,7 @@ namespace FM
 // ---------------------------------------------------------------------------
 //     OPNBase
 
-#if defined(BUILD_OPN) || defined(BUILD_OPNA) || defined (BUILD_OPNB)
+#if defined(BUILD_OPN) || defined(BUILD_OPNA) || defined (BUILD_OPNB) || defined(BUILD_OPN2)
 
 uint32 OPNBase::lfotable[8];                   // OPNA/B \97p
 
@@ -272,7 +273,7 @@ bool OPNBase::LoadState(void *f)
        return true;
 }
 
-#endif // defined(BUILD_OPN) || defined(BUILD_OPNA) || defined (BUILD_OPNB)
+#endif // defined(BUILD_OPN) || defined(BUILD_OPNA) || defined (BUILD_OPNB) || defined(BUILD_OPN2)
 
 // ---------------------------------------------------------------------------
 //     YM2203
@@ -2430,4 +2431,561 @@ void OPNB::Mix(Sample* buffer, int nsamples)
 
 #endif // BUILD_OPNB
 
+// ---------------------------------------------------------------------------
+//     YM2612 common part
+// ---------------------------------------------------------------------------
+
+#if defined(BUILD_OPN2)
+
+int OPN2Base::amtable[FM_LFOENTS] = { -1, };
+int OPN2Base::pmtable[FM_LFOENTS];
+
+int32 OPN2Base::tltable[FM_TLENTS+FM_TLPOS];
+bool OPN2Base::tablehasmade = false;
+
+OPN2Base::OPN2Base()
+{
+
+       MakeTable2();
+       BuildLFOTable();
+       for (int i=0; i<6; i++)
+       {
+               ch[i].SetChip(&chip);
+               ch[i].SetType(typeN);
+       }
+}
+
+OPN2Base::~OPN2Base()
+{
+}
+
+// ---------------------------------------------------------------------------
+//     \8f\89\8aú\89»
+//
+bool OPN2Base::Init(uint c, uint r, bool)
+{
+       RebuildTimeTable();
+       
+       Reset();
+
+       SetVolumeFM(0, 0);
+       SetVolumePSG(0, 0);
+       SetChannelMask(0);
+       return true;
+}
+
+// ---------------------------------------------------------------------------
+//     \83e\81[\83u\83\8b\8dì\90¬
+//
+void OPN2Base::MakeTable2()
+{
+       if (!tablehasmade)
+       {
+               for (int i=-FM_TLPOS; i<FM_TLENTS; i++)
+               {
+                       tltable[i+FM_TLPOS] = uint(65536. * pow(2.0, i * -16. / FM_TLENTS))-1;
+               }
+
+               tablehasmade = true;
+       }
+}
+
+// ---------------------------------------------------------------------------
+//     \83\8a\83Z\83b\83g
+//
+void OPN2Base::Reset()
+{
+       int i;
+       
+       OPNBase::Reset();
+       for (i=0x20; i<0x28; i++) SetReg(i, 0);
+       for (i=0x30; i<0xc0; i++) SetReg(i, 0);
+       for (i=0x130; i<0x1c0; i++) SetReg(i, 0);
+       for (i=0x100; i<0x110; i++) SetReg(i, 0);
+       for (i=0x10; i<0x20; i++) SetReg(i, 0);
+       for (i=0; i<6; i++)
+       {
+               pan[i] = 3;
+               ch[i].Reset();
+       }
+       
+       stmask = ~0x1c;
+       statusnext = 0;
+       lfocount = 0;
+       status = 0;
+       UpdateStatus();
+}
+
+// ---------------------------------------------------------------------------
+//     \83T\83\93\83v\83\8a\83\93\83O\83\8c\81[\83g\95Ï\8dX
+//
+bool OPN2Base::SetRate(uint c, uint r, bool)
+{
+       c /= 2;         // \8f]\97\88\94Å\82Æ\82Ì\8cÝ\8a·\90«\82ð\8fd\8e\8b\82µ\82½\82¯\82è\82á\83R\83\81\83\93\83g\83A\83E\83g\82µ\82æ\82¤
+       
+       OPNBase::Init(c, r);
+
+       RebuildTimeTable();
+
+       lfodcount = reg22 & 0x08 ? lfotable[reg22 & 7] : 0;
+       return true;
+}
+
+
+// ---------------------------------------------------------------------------
+//     \83`\83\83\83\93\83l\83\8b\83}\83X\83N\82Ì\90Ý\92è
+//
+void OPN2Base::SetChannelMask(uint mask)
+{
+       for (int i=0; i<6; i++)
+               ch[i].Mute(!!(mask & (1 << i)));
+}
+
+// ---------------------------------------------------------------------------
+//     \83\8c\83W\83X\83^\83A\83\8c\83C\82É\83f\81[\83^\82ð\90Ý\92è
+//
+void OPN2Base::SetReg(uint addr, uint data)
+{
+       int     c = addr & 3;
+       switch (addr)
+       {
+               uint modified;
+
+       // Timer -----------------------------------------------------------------
+               case 0x24: case 0x25:
+                       SetTimerA(addr, data);
+                       break;
+
+               case 0x26:
+                       SetTimerB(data);
+                       break;
+
+               case 0x27:
+                       SetTimerControl(data);
+                       break;
+
+       // Misc ------------------------------------------------------------------
+       case 0x28:              // Key On/Off
+               if ((data & 3) < 3)
+               {
+                       c = (data & 3) + (data & 4 ? 3 : 0);
+                       ch[c].KeyControl(data >> 4);
+               }
+               break;
+
+       // Status Mask -----------------------------------------------------------
+       case 0x29:
+               reg29 = data;
+//             UpdateStatus(); //?
+               break;
+       
+       // Prescaler -------------------------------------------------------------
+       case 0x2d: case 0x2e: case 0x2f:
+               SetPrescaler(addr-0x2d);
+               break;
+       
+       // F-Number --------------------------------------------------------------
+       case 0x1a0:     case 0x1a1: case 0x1a2:
+               c += 3;
+       case 0xa0:      case 0xa1: case 0xa2:
+               fnum[c] = data + fnum2[c] * 0x100;
+               ch[c].SetFNum(fnum[c]);
+               break;
+
+       case 0x1a4:     case 0x1a5: case 0x1a6:
+               c += 3;
+       case 0xa4 : case 0xa5: case 0xa6:
+               fnum2[c] = uint8(data);
+               break;
+
+       case 0xa8:      case 0xa9: case 0xaa:
+               fnum3[c] = data + fnum2[c+6] * 0x100;
+               break;
+
+       case 0xac : case 0xad: case 0xae:
+               fnum2[c+6] = uint8(data);
+               break;
+               
+       // Algorithm -------------------------------------------------------------
+       
+       case 0x1b0:     case 0x1b1:  case 0x1b2:
+               c += 3;
+       case 0xb0:      case 0xb1:  case 0xb2:
+               ch[c].SetFB((data >> 3) & 7);
+               ch[c].SetAlgorithm(data & 7);
+               break;
+       
+       case 0x1b4: case 0x1b5: case 0x1b6:
+               c += 3;
+       case 0xb4: case 0xb5: case 0xb6:
+               pan[c] = (data >> 6) & 3;
+               ch[c].SetMS(data);
+               break;
+
+       // LFO -------------------------------------------------------------------
+       case 0x22:
+               modified = reg22 ^ data;
+               reg22 = data;
+               if (modified & 0x8)
+                       lfocount = 0;
+               lfodcount = reg22 & 8 ? lfotable[reg22 & 7] : 0;
+               break;
+
+       // PSG -------------------------------------------------------------------
+       case  0: case  1: case  2: case  3: case  4: case  5: case  6: case  7:
+       case  8: case  9: case 10: case 11: case 12: case 13: case 14: case 15:
+               break;
+
+       // \89¹\90F ------------------------------------------------------------------
+       default:
+               if (c < 3)
+               {
+                       if (addr & 0x100)
+                               c += 3;
+                       OPNBase::SetParameter(&ch[c], addr, data);
+               }
+               break;
+       }
+}
+
+// ---------------------------------------------------------------------------
+//     \83X\83e\81[\83^\83X\83t\83\89\83O\90Ý\92è
+//
+void OPN2Base::SetStatus(uint bits)
+{
+       if (!(status & bits))
+       {
+//             LOG2("SetStatus(%.2x %.2x)\n", bits, stmask);
+               status |= bits & stmask;
+               UpdateStatus();
+       }
+//     else
+//             LOG1("SetStatus(%.2x) - ignored\n", bits);
+}
+
+void OPN2Base::ResetStatus(uint bits)
+{
+       status &= ~bits;
+//     LOG1("ResetStatus(%.2x)\n", bits);
+       UpdateStatus();
+}
+
+inline void OPN2Base::UpdateStatus()
+{
+//     LOG2("%d:INT = %d\n", Diag::GetCPUTick(), (status & stmask & reg29) != 0);
+       Intr((status & stmask & reg29) != 0);
+}
+
+// ---------------------------------------------------------------------------
+//     \8d\87\90¬
+//     in:             buffer          \8d\87\90¬\90æ
+//                     nsamples        \8d\87\90¬\83T\83\93\83v\83\8b\90\94
+//
+void OPN2Base::FMMix(Sample* buffer, int nsamples)
+{
+       if (fmvolume_l > 0 || fmvolume_r > 0)
+       {
+               // \8f\80\94õ
+               // Set F-Number
+               if (!(regtc & 0xc0))
+                       csmch->SetFNum(fnum[csmch-ch]);
+               else
+               {
+                       // \8cø\89Ê\89¹\83\82\81[\83h
+                       csmch->op[0].SetFNum(fnum3[1]); csmch->op[1].SetFNum(fnum3[2]);
+                       csmch->op[2].SetFNum(fnum3[0]); csmch->op[3].SetFNum(fnum[2]);
+               }
+               
+               int act = (((ch[2].Prepare() << 2) | ch[1].Prepare()) << 2) | ch[0].Prepare();
+               if (reg29 & 0x80)
+                       act |= (ch[3].Prepare() | ((ch[4].Prepare() | (ch[5].Prepare() << 2)) << 2)) << 6;
+               if (!(reg22 & 0x08))
+                       act &= 0x555;
+
+               if (act & 0x555)
+               {
+                       Mix6(buffer, nsamples, act);
+               }
+       }
+}
+
+// ---------------------------------------------------------------------------
+
+void OPN2Base::MixSubSL(int activech, ISample** dest)
+{
+       if (activech & 0x001) (*dest[0]  = ch[0].CalcL());
+       if (activech & 0x004) (*dest[1] += ch[1].CalcL());
+       if (activech & 0x010) (*dest[2] += ch[2].CalcL());
+       if (activech & 0x040) (*dest[3] += ch[3].CalcL());
+       if (activech & 0x100) (*dest[4] += ch[4].CalcL());
+       if (activech & 0x400) (*dest[5] += ch[5].CalcL());
+}
+
+inline void OPN2Base::MixSubS(int activech, ISample** dest)
+{
+       if (activech & 0x001) (*dest[0]  = ch[0].Calc());
+       if (activech & 0x004) (*dest[1] += ch[1].Calc());
+       if (activech & 0x010) (*dest[2] += ch[2].Calc());
+       if (activech & 0x040) (*dest[3] += ch[3].Calc());
+       if (activech & 0x100) (*dest[4] += ch[4].Calc());
+       if (activech & 0x400) (*dest[5] += ch[5].Calc());
+}
+
+// ---------------------------------------------------------------------------
+
+void OPN2Base::BuildLFOTable()
+{
+       if (amtable[0] == -1)
+       {
+               for (int c=0; c<256; c++)
+               {
+                       int v;
+                       if (c < 0x40)           v = c * 2 + 0x80;
+                       else if (c < 0xc0)      v = 0x7f - (c - 0x40) * 2 + 0x80;
+                       else                            v = (c - 0xc0) * 2;
+                       pmtable[c] = v;
+
+                       if (c < 0x80)           v = 0xff - c * 2;
+                       else                            v = (c - 0x80) * 2;
+                       amtable[c] = v & ~3;
+               }
+       }
+}
+
+// ---------------------------------------------------------------------------
+
+inline void OPN2Base::LFO()
+{
+//     LOG3("%4d - %8d, %8d\n", c, lfocount, lfodcount);
+
+//     Operator::SetPML(pmtable[(lfocount >> (FM_LFOCBITS+1)) & 0xff]);
+//     Operator::SetAML(amtable[(lfocount >> (FM_LFOCBITS+1)) & 0xff]);
+       chip.SetPML(pmtable[(lfocount >> (FM_LFOCBITS+1)) & 0xff]);
+       chip.SetAML(amtable[(lfocount >> (FM_LFOCBITS+1)) & 0xff]);
+       lfocount += lfodcount;
+}
+
+// ---------------------------------------------------------------------------
+//     \8d\87\90¬
+//
+//#define IStoSampleL(s)       ((Limit(s, 0x7fff, -0x8000) * fmvolume_l) >> 14)
+//#define IStoSampleR(s)       ((Limit(s, 0x7fff, -0x8000) * fmvolume_r) >> 14)
+
+void OPN2Base::Mix6(Sample* buffer, int nsamples, int activech)
+{
+       // Mix
+       ISample ibuf[4];
+       ISample* idest[6];
+       idest[0] = &ibuf[pan[0]];
+       idest[1] = &ibuf[pan[1]];
+       idest[2] = &ibuf[pan[2]];
+       idest[3] = &ibuf[pan[3]];
+       idest[4] = &ibuf[pan[4]];
+       idest[5] = &ibuf[pan[5]];
+
+       Sample* limit = buffer + nsamples * 2;
+       for (Sample* dest = buffer; dest < limit; dest+=2)
+       {
+               ibuf[1] = ibuf[2] = ibuf[3] = 0;
+               if (activech & 0xaaa)
+                       LFO(), MixSubSL(activech, idest);
+               else
+                       MixSubS(activech, idest);
+               StoreSample(dest[0], IStoSampleL(ibuf[2] + ibuf[3]));
+               StoreSample(dest[1], IStoSampleR(ibuf[1] + ibuf[3]));
+       }
+}
+// ---------------------------------------------------------------------------
+//     \83X\83e\81[\83g\83Z\81[\83u
+//
+#define OPN2_BASE_STATE_VERSION        1
+
+void OPN2Base::DeclState(void *f)
+{
+
+       OPNBase::DeclState(f);
+       
+       DECL_STATE_ENTRY_1D_ARRAY(pan, sizeof(pan));
+       DECL_STATE_ENTRY_1D_ARRAY(fnum2, sizeof(fnum2));
+       DECL_STATE_ENTRY_UINT8(reg22);
+       DECL_STATE_ENTRY_UINT32(reg29);
+       DECL_STATE_ENTRY_UINT32(stmask);
+       DECL_STATE_ENTRY_UINT32(statusnext);
+       DECL_STATE_ENTRY_UINT32(lfocount);
+       DECL_STATE_ENTRY_UINT32(lfodcount);
+       //state_fio->Fwrite(fnum, sizeof(fnum), 1);
+       //state_fio->Fwrite(fnum3, sizeof(fnum3), 1);
+       for(int i = 0; i < 6; i++) {
+               DECL_STATE_ENTRY_UINT32_MEMBER((fnum[i]), i);
+       }               
+       for(int i = 0; i < 3; i++) {
+               DECL_STATE_ENTRY_UINT32_MEMBER((fnum3[i]), i);
+       }               
+       for(int i = 0; i < 6; i++) {
+               ch[i].DeclState(f);
+       }
+}
+
+void OPN2Base::SaveState(void *f)
+{
+       FILEIO *state_fio = (FILEIO *)f;
+       if(state_entry != NULL) {
+               state_entry->save_state(state_fio);
+       }
+       chip.SaveState(f);
+       for(int i = 0; i < 6; i++) {
+               ch[i].SaveState(f);
+       }
+}
+
+bool OPN2Base::LoadState(void *f)
+{
+       FILEIO *state_fio = (FILEIO *)f;
+       
+       bool mb = false;
+       if(state_entry != NULL) {
+               mb = state_entry->load_state(state_fio);
+       }
+       if(!mb) return false;
+       {
+               // Make force-restore around prescaler and timers. 20180625 K.O
+               uint bak = prescale;
+               prescale = 10;
+               SetPrescaler(bak);
+       }
+       if(!chip.LoadState(f)) {
+               return false;
+       }
+       for(int i = 0; i < 6; i++) {
+               if(!ch[i].LoadState(f)) {
+                       return false;
+               }
+       }
+       return true;
+}
+#endif // defined(BUILD_OPN2)
+
+// ---------------------------------------------------------------------------
+//     YM2612(OPN2)
+// ---------------------------------------------------------------------------
+
+#ifdef BUILD_OPN2
+
+// ---------------------------------------------------------------------------
+//     \83\8c\83W\83X\83^\8eæ\93¾
+//
+uint OPN2::GetReg(uint addr)
+{
+       return 0;
+}
+
+// ---------------------------------------------------------------------------
+//     \8d\\92z
+//
+OPN2::OPN2()
+{
+       csmch = &ch[2]; // ToDo: Check register.
+}
+// ---------------------------------------------------------------------------
+
+OPN2::~OPN2()
+{
+}
+
+// ---------------------------------------------------------------------------
+//     \8f\89\8aú\89»
+//
+bool OPN2::Init(uint c, uint r, bool ipflag, const _TCHAR* path)
+{
+       rate = 8000;
+       if (!SetRate(c, r, ipflag))
+               return false;
+       if (!OPN2Base::Init(c, r, ipflag))
+               return false;
+       
+       Reset();
+       return true;
+}
+// ---------------------------------------------------------------------------
+//     \83\8a\83Z\83b\83g
+//
+void OPN2::Reset()
+{
+       reg29 = 0x1f;
+       OPN2Base::Reset();
+}
+// ---------------------------------------------------------------------------
+//     \83T\83\93\83v\83\8a\83\93\83O\83\8c\81[\83g\95Ï\8dX
+//
+bool OPN2::SetRate(uint c, uint r, bool ipflag)
+{
+       if (!OPN2Base::SetRate(c, r, ipflag))
+               return false;
+       return true;
+}
+
+// ---------------------------------------------------------------------------
+//     \83\8c\83W\83X\83^\83A\83\8c\83C\82É\83f\81[\83^\82ð\90Ý\92è
+//
+void OPN2::SetReg(uint addr, uint data)
+{
+       addr &= 0x1ff;
+
+       switch (addr)
+       {
+       case 0x29:
+               reg29 = data;
+//             UpdateStatus(); //?
+               break;
+       
+       default:
+               OPN2Base::SetReg(addr, data);
+               break;
+       }
+}
+
+
+
+// ---------------------------------------------------------------------------
+//     \8d\87\90¬
+//     in:             buffer          \8d\87\90¬\90æ
+//                     nsamples        \8d\87\90¬\83T\83\93\83v\83\8b\90\94
+//
+void OPN2::Mix(Sample* buffer, int nsamples)
+{
+       FMMix(buffer, nsamples);
+}
+
+// ---------------------------------------------------------------------------
+//     \83X\83e\81[\83g\83Z\81[\83u
+//
+#define OPN2_STATE_VERSION     1
+
+void OPN2::DeclState(void *f)
+{
+       p_logger = (CSP_Logger *)f;
+       state_entry = new csp_state_utils(OPN2_STATE_VERSION, chip_num, _T("FMGEN::OPN2::"), p_logger);
+
+       OPN2Base::DeclState(f);
+}
+
+void OPN2::SaveState(void *f)
+{
+       FILEIO *state_fio = (FILEIO *)f;
+       OPN2Base::SaveState(f);
+}
+
+bool OPN2::LoadState(void *f)
+{
+       FILEIO *state_fio = (FILEIO *)f;
+       
+       bool mb = false;
+       mb = OPN2Base::LoadState(f);
+       if(!mb) return false;
+
+       return true;
+}
+
+#endif // BUILD_OPN2
+
 }      // namespace FM
index 44fdf9b..b8d54b9 100644 (file)
@@ -137,7 +137,7 @@ namespace FM
                PSG             psg;
        };
 
-       //      OPN2 Base ------------------------------------------------------
+       //      OPNA Base ------------------------------------------------------
        class DLL_PREFIX OPNABase : public OPNBase
        {
        public:
@@ -246,6 +246,68 @@ namespace FM
                static bool     tablehasmade;
        };
 
+       //      OPN2 Base ------------------------------------------------------
+       class DLL_PREFIX OPN2Base : public OPNBase
+       {
+       public:
+               OPN2Base();
+               ~OPN2Base();
+               
+               uint    ReadStatus() { return status & 0x03; }
+               uint    ReadStatusEx();
+               void    SetChannelMask(uint mask);
+       
+       private:
+               void    MakeTable2();
+       
+       protected:
+               bool    Init(uint c, uint r, bool);
+               bool    SetRate(uint c, uint r, bool);
+
+               void    Reset();
+               void    SetReg(uint addr, uint data);
+               uint    GetReg(uint addr);      
+               
+               void DeclState(void *f);
+               void SaveState(void *f);
+               bool LoadState(void *f);
+       
+       protected:
+               void    FMMix(Sample* buffer, int nsamples);
+               void    Mix6(Sample* buffer, int nsamples, int activech);
+               
+               void    MixSubS(int activech, ISample**);
+               void    MixSubSL(int activech, ISample**);
+
+               void    SetStatus(uint bit);
+               void    ResetStatus(uint bit);
+               void    UpdateStatus();
+               void    LFO();
+
+       // FM \89¹\8c¹\8aÖ\8cW
+               uint8   pan[6];
+               uint8   fnum2[9];
+               
+               uint8   reg22;
+               uint    reg29;          // OPNA only?
+               
+               uint    stmask;
+               uint    statusnext;
+
+               uint32  lfocount;
+               uint32  lfodcount;
+               
+               uint    fnum[6];
+               uint    fnum3[3];
+               
+               Channel4 ch[6];
+
+               static void     BuildLFOTable();
+               static int amtable[FM_LFOENTS];
+               static int pmtable[FM_LFOENTS];
+               static int32 tltable[FM_TLENTS+FM_TLPOS];
+               static bool     tablehasmade;
+       };
        //      YM2203(OPN) ----------------------------------------------------
        class DLL_PREFIX OPN : public OPNBase
        {
@@ -402,7 +464,7 @@ namespace FM
        };
 
        //      YM2612/3438(OPN2) ----------------------------------------------------
-       class OPN2 : public OPNBase
+       class OPN2 : public OPN2Base
        {
        public:
                OPN2();
@@ -417,21 +479,16 @@ namespace FM
                uint    GetReg(uint addr);
                uint    ReadStatus() { return status & 0x03; }
                uint    ReadStatusEx() { return 0xff; }
-               
-               void    SetChannelMask(uint mask);
-               
+               void DeclState(void *f);
+               void SaveState(void *f);
+               bool LoadState(void *f);
+
        private:
                void    SetStatus(uint bit);
                void    ResetStatus(uint bit);
-               
-               uint    fnum[3];
-               uint    fnum3[3];
-               uint8   fnum2[6];
-               
        // \90ü\8c`\95â\8aÔ\97p\83\8f\81[\83N
                int32   mixc, mixc1;
                
-               Channel4 ch[3];
        };
 }