diff --git a/MDPlayer/MDPlayer/Driver/NSF/chip/emu2149.cs b/MDPlayer/MDPlayer/Driver/NSF/chip/emu2149.cs
new file mode 100644
index 00000000..70c537c9
--- /dev/null
+++ b/MDPlayer/MDPlayer/Driver/NSF/chip/emu2149.cs
@@ -0,0 +1,394 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MDPlayer.NSF
+{
+ public class emu2149
+ {
+ private const Int32 EMU2149_VOL_DEFAULT = 1;
+ private const Int32 EMU2149_VOL_YM2149 = 0;
+ private const Int32 EMU2149_VOL_AY_3_8910 = 1;
+
+ private Int32 PSG_MASK_CH(Int32 x) {
+ return (1 << (x));
+ }
+
+ public class PSG
+ {
+ /* Volume Table */
+ public UInt32[] voltbl; //*
+
+ public byte[] reg = new byte[0x20];
+ public Int32 _out;
+ public Int32[] cout = new Int32[3];
+
+ public UInt32 clk, rate, base_incr, quality;
+
+ public UInt32[] count = new UInt32[3];
+ public UInt32[] volume = new UInt32[3];
+ public UInt32[] freq = new UInt32[3];
+ public UInt32[] edge = new UInt32[3];
+ public UInt32[] tmask = new UInt32[3];
+ public UInt32[] nmask = new UInt32[3];
+ public UInt32 mask;
+
+ public UInt32 base_count;
+
+ public UInt32 env_volume;
+ public UInt32 env_ptr;
+ public UInt32 env_face;
+
+ public UInt32 env_continue;
+ public UInt32 env_attack;
+ public UInt32 env_alternate;
+ public UInt32 env_hold;
+ public UInt32 env_pause;
+ public UInt32 env_reset;
+
+ public UInt32 env_freq;
+ public UInt32 env_count;
+
+ public UInt32 noise_seed;
+ public UInt32 noise_count;
+ public UInt32 noise_freq;
+
+ /* rate converter */
+ public UInt32 realstep;
+ public UInt32 psgtime;
+ public UInt32 psgstep;
+
+ /* I/O Ctrl */
+ public UInt32 adr;
+
+ }
+
+ private static UInt32[][] voltbl = new UInt32[2][] {
+ new UInt32[32]{
+ 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0B, 0x0D, 0x0F, 0x12
+ , 0x16, 0x1A, 0x1F, 0x25, 0x2D, 0x35, 0x3F, 0x4C, 0x5A, 0x6A, 0x7F, 0x97, 0xB4, 0xD6, 0xEB, 0xFF
+ },
+ new UInt32[32]{
+ 0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x05, 0x05, 0x07, 0x07, 0x0B, 0x0B, 0x0F, 0x0F
+ , 0x16, 0x16, 0x1F, 0x1F, 0x2D, 0x2D, 0x3F, 0x3F, 0x5A, 0x5A, 0x7F, 0x7F, 0xB4, 0xB4, 0xFF, 0xFF
+ }
+ };
+
+
+ private const Int32 GETA_BITS = 24;
+
+ private void internal_refresh(PSG psg)
+ {
+ if (psg.quality != 0)
+ {
+ psg.base_incr = 1 << GETA_BITS;
+ psg.realstep = (UInt32)((1 << 31) / psg.rate);
+ psg.psgstep = (UInt32)((1 << 31) / (psg.clk / 16));
+ psg.psgtime = 0;
+ }
+ else
+ {
+ psg.base_incr =
+ (UInt32)((double)psg.clk * (1 << GETA_BITS) / (16 * psg.rate));
+ }
+ }
+
+ public void PSG_set_rate(PSG psg, UInt32 r)
+ {
+ psg.rate = r!=0 ? r : 44100;
+ internal_refresh(psg);
+ }
+
+ public void PSG_set_quality(PSG psg, UInt32 q)
+ {
+ psg.quality = q;
+ internal_refresh(psg);
+ }
+
+ public PSG PSG_new(UInt32 c, UInt32 r)
+ {
+ PSG psg;
+
+ psg = new PSG();
+ if (psg == null)
+ return null;
+
+ PSG_setVolumeMode(psg, EMU2149_VOL_DEFAULT);
+ psg.clk = c;
+ psg.rate = r != 0 ? r : 44100;
+ PSG_set_quality(psg, 0);
+
+ return psg;
+ }
+
+ public void PSG_setVolumeMode(PSG psg, Int32 type)
+ {
+ switch (type)
+ {
+ case 1:
+ psg.voltbl = voltbl[EMU2149_VOL_YM2149];
+ break;
+ case 2:
+ psg.voltbl = voltbl[EMU2149_VOL_AY_3_8910];
+ break;
+ default:
+ psg.voltbl = voltbl[EMU2149_VOL_DEFAULT];
+ break;
+ }
+ }
+
+ public UInt32 PSG_setMask(PSG psg, UInt32 mask)
+ {
+ UInt32 ret = 0;
+ if (psg != null)
+ {
+ ret = psg.mask;
+ psg.mask = mask;
+ }
+ return ret;
+ }
+
+ public UInt32 PSG_toggleMask(PSG psg, UInt32 mask)
+ {
+ UInt32 ret = 0;
+ if (psg!=null)
+ {
+ ret = psg.mask;
+ psg.mask ^= mask;
+ }
+ return ret;
+ }
+
+ public void PSG_reset(PSG psg)
+ {
+ int i;
+
+ psg.base_count = 0;
+
+ for (i = 0; i < 3; i++)
+ {
+ psg.cout[i] = 0;
+ psg.count[i] = 0x1000;
+ psg.freq[i] = 0;
+ psg.edge[i] = 0;
+ psg.volume[i] = 0;
+ }
+
+ psg.mask = 0;
+
+ for (i = 0; i < 16; i++)
+ psg.reg[i] = 0;
+ psg.adr = 0;
+
+ psg.noise_seed = 0xffff;
+ psg.noise_count = 0x40;
+ psg.noise_freq = 0;
+
+ psg.env_volume = 0;
+ psg.env_ptr = 0;
+ psg.env_freq = 0;
+ psg.env_count = 0;
+ psg.env_pause = 1;
+
+ psg._out = 0;
+ }
+
+ public void PSG_delete(PSG psg)
+ {
+ psg = null;
+ }
+
+ public byte PSG_readIO(PSG psg)
+ {
+ return (byte)(psg.reg[psg.adr]);
+ }
+
+ public byte PSG_readReg(PSG psg, UInt32 reg)
+ {
+ return (byte)(psg.reg[reg & 0x1f]);
+
+ }
+
+ public void PSG_writeIO(PSG psg, UInt32 adr, UInt32 val)
+ {
+ if ((adr & 1)!=0)
+ PSG_writeReg(psg, psg.adr, val);
+ else
+ psg.adr = val & 0x1f;
+ }
+
+ public Int16 calc(PSG psg)
+ {
+
+ Int32 i, noise;
+ UInt32 incr;
+ Int32 mix = 0;
+
+ psg.base_count += psg.base_incr;
+ incr = (psg.base_count >> GETA_BITS);
+ psg.base_count &= (1 << GETA_BITS) - 1;
+
+ /* Envelope */
+ psg.env_count += incr;
+ while (psg.env_count >= 0x10000 && psg.env_freq != 0)
+ {
+ if (psg.env_pause == 0)
+ {
+ if (psg.env_face != 0)
+ psg.env_ptr = (psg.env_ptr + 1) & 0x3f;
+ else
+ psg.env_ptr = (psg.env_ptr + 0x3f) & 0x3f;
+ }
+
+ if ((psg.env_ptr & 0x20) != 0) /* if carry or borrow */
+ {
+ if (psg.env_continue != 0)
+ {
+ if ((psg.env_alternate ^ psg.env_hold) != 0) psg.env_face ^= 1;
+ if (psg.env_hold != 0) psg.env_pause = 1;
+ psg.env_ptr = (UInt32)((psg.env_face != 0) ? 0 : 0x1f);
+ }
+ else
+ {
+ psg.env_pause = 1;
+ psg.env_ptr = 0;
+ }
+ }
+
+ psg.env_count -= psg.env_freq;
+ }
+
+ /* Noise */
+ psg.noise_count += incr;
+ if ((psg.noise_count & 0x40)!=0)
+ {
+ if ((psg.noise_seed & 1)!=0)
+ psg.noise_seed ^= 0x24000;
+ psg.noise_seed >>= 1;
+ psg.noise_count -= psg.noise_freq;
+ }
+ noise = (Int32)(psg.noise_seed & 1);
+
+ /* Tone */
+ for (i = 0; i < 3; i++)
+ {
+ psg.count[i] += incr;
+ if( (psg.count[i] & 0x1000)!=0)
+ {
+ if (psg.freq[i] > 1)
+ {
+ psg.edge[i] = (~psg.edge[i]) & 1;
+ psg.count[i] -= psg.freq[i];
+ }
+ else
+ {
+ psg.edge[i] = 1;
+ }
+ }
+
+ psg.cout[i] = 0; // maintaining cout for stereo mix
+
+ if ((psg.mask & PSG_MASK_CH(i))!=0)
+ continue;
+
+ if ((psg.tmask[i]!=0 || psg.edge[i]!=0) && (psg.nmask[i]!=0 || noise!=0))
+ {
+ if ((psg.volume[i] & 32)==0)
+ psg.cout[i] = (Int32)psg.voltbl[psg.volume[i] & 31];
+ else
+ psg.cout[i] = (Int32)psg.voltbl[psg.env_ptr];
+
+ mix += psg.cout[i];
+ }
+ }
+
+ return (Int16)mix;
+ }
+
+ public Int16 PSG_calc(PSG psg)
+ {
+ if (psg.quality == 0)
+ return (Int16)(calc(psg) << 4);
+
+ /* Simple rate converter */
+ while (psg.realstep > psg.psgtime)
+ {
+ psg.psgtime += psg.psgstep;
+ psg._out += calc(psg);
+ psg._out >>= 1;
+ }
+
+ psg.psgtime = psg.psgtime - psg.realstep;
+
+ return (Int16)(psg._out << 4);
+ }
+
+ public void PSG_writeReg(PSG psg, UInt32 reg, UInt32 val)
+ {
+ int c;
+
+ if (reg > 15) return;
+
+ psg.reg[reg] = (byte)(val & 0xff);
+ switch (reg)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ c = (Int32)(reg >> 1);
+ psg.freq[c] = (UInt32)(((psg.reg[c * 2 + 1] & 15) << 8) + psg.reg[c * 2]);
+ break;
+
+ case 6:
+ psg.noise_freq = (val == 0) ? 1 : ((val & 31) << 1);
+ break;
+
+ case 7:
+ psg.tmask[0] = (val & 1);
+ psg.tmask[1] = (val & 2);
+ psg.tmask[2] = (val & 4);
+ psg.nmask[0] = (val & 8);
+ psg.nmask[1] = (val & 16);
+ psg.nmask[2] = (val & 32);
+ break;
+
+ case 8:
+ case 9:
+ case 10:
+ psg.volume[reg - 8] = val << 1;
+
+ break;
+
+ case 11:
+ case 12:
+ psg.env_freq = (UInt32)((psg.reg[12] << 8) + psg.reg[11]);
+ break;
+
+ case 13:
+ psg.env_continue = (val >> 3) & 1;
+ psg.env_attack = (val >> 2) & 1;
+ psg.env_alternate = (val >> 1) & 1;
+ psg.env_hold = val & 1;
+ psg.env_face = psg.env_attack;
+ psg.env_pause = 0;
+ psg.env_count = 0x10000 - psg.env_freq;
+ psg.env_ptr = (UInt32)(psg.env_face!=0 ? 0 : 0x1f);
+ break;
+
+ case 14:
+ case 15:
+ default:
+ break;
+ }
+
+ return;
+ }
+
+
+ }
+}
diff --git a/MDPlayer/MDPlayer/Driver/NSF/chip/nes_fme7.cs b/MDPlayer/MDPlayer/Driver/NSF/chip/nes_fme7.cs
new file mode 100644
index 00000000..8faef4e8
--- /dev/null
+++ b/MDPlayer/MDPlayer/Driver/NSF/chip/nes_fme7.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MDSound.np;
+
+namespace MDPlayer.NSF
+{
+ public class nes_fme7 : ISoundChip
+ {
+ protected Int32[][] sm=new Int32[2][]{new Int32[3],new Int32[3] }; // stereo mix
+ protected Int16[] buf = new Int16[2];
+ protected emu2149.PSG psg;
+ protected emu2149 emu2149;
+ protected Int32 divider; // clock divider
+ protected double clock, rate;
+ protected TrackInfoBasic[] trkinfo = new TrackInfoBasic[5];
+ protected const Int32 DIVIDER = 8*2;
+ public const double DEFAULT_CLOCK = 1789772.0;
+ public const int DEFAULT_RATE = 44100;
+
+ public nes_fme7()
+ {
+ emu2149 = new emu2149();
+ psg = emu2149.PSG_new((UInt32)DEFAULT_CLOCK, DEFAULT_RATE);
+
+ for (int c = 0; c < 2; ++c)
+ for (int t = 0; t < 3; ++t)
+ sm[c][t] = 128;
+ }
+
+ ~nes_fme7()
+ {
+ if (psg != null)
+ psg = null;
+ }
+
+ public override void SetClock(double c)
+ {
+ this.clock = c * 2.0;
+ }
+
+ public override void SetRate(double r)
+ {
+ //rate = r ? r : DEFAULT_RATE;
+ rate = DEFAULT_CLOCK / (double)DIVIDER; // TODO rewrite PSG to integrate with clock
+ if (psg!=null)
+ emu2149.PSG_set_rate(psg, (UInt32)rate);
+ }
+
+ public override void Reset()
+ {
+ for (Int32 i = 0; i < 16; ++i) // blank all registers
+ {
+ Write(0xC000, (UInt32)i);
+ Write(0xE000, 0);
+ }
+ Write(0xC000, 0x07); // disable all tones
+ Write(0xE000, 0x3F);
+
+ divider = 0;
+ if (psg != null)
+ emu2149.PSG_reset(psg);
+ }
+
+ public override bool Write(UInt32 adr, UInt32 val, UInt32 id=0)
+ {
+ if (adr == 0xC000)
+ {
+ if (psg != null)
+ emu2149.PSG_writeIO(psg, 0, val);
+ return true;
+ }
+ if (adr == 0xE000)
+ {
+ if (psg != null)
+ emu2149.PSG_writeIO(psg, 1, val);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ public override bool Read(uint adr, ref uint val, uint id = 0)
+ {
+ return false;
+ }
+
+ public override void Tick(UInt32 clocks)
+ {
+ divider += (Int32)clocks;
+ while (divider >= DIVIDER)
+ {
+ divider -= DIVIDER;
+ if (psg!=null) emu2149.PSG_calc(psg);
+ }
+ }
+
+ public override UInt32 Render(Int32[] b)//b[2])
+ {
+ b[0] = b[1] = 0;
+
+ for (int i = 0; i < 3; ++i)
+ {
+ // note negative polarity
+ b[0] -= psg.cout[i] * sm[0][i];
+ b[1] -= psg.cout[i] * sm[1][i];
+ }
+ b[0] >>= (7 - 4);
+ b[1] >>= (7 - 4);
+
+ // master volume adjustment
+ const Int32 MASTER = (Int32)(0.64 * 256.0);
+ b[0] = (b[0] * MASTER) >> 5;// 8;
+ b[1] = (b[1] * MASTER) >> 5;// 8;
+
+ return 2;
+ }
+
+ public override void SetStereoMix(Int32 trk, Int16 mixl, Int16 mixr)
+ {
+ if (trk < 0) return;
+ if (trk > 2) return;
+ sm[0][trk] = mixl;
+ sm[1][trk] = mixr;
+ }
+
+ public ITrackInfo GetTrackInfo(Int32 trk)
+ {
+ //assert(trk < 5);
+
+ if (psg!=null)
+ {
+ if (trk < 3)
+ {
+ trkinfo[trk]._freq = psg.freq[trk];
+ if (psg.freq[trk]!=0)
+ trkinfo[trk].freq = psg.clk / 32.0 / psg.freq[trk];
+ else
+ trkinfo[trk].freq = 0;
+
+ trkinfo[trk].output = psg.cout[trk];
+ trkinfo[trk].max_volume = 15;
+ trkinfo[trk].volume = (Int32)(psg.volume[trk] >> 1);
+ //trkinfo[trk].key = (psg.cout[trk]>0)?true:false;
+ trkinfo[trk].key = ((~(psg.tmask[trk])) & 1) != 0;
+ trkinfo[trk].tone = (psg.tmask[trk]!=0 ? 2 : 0) + (psg.nmask[trk]!=0 ? 1 : 0);
+ }
+ else if (trk == 3) // envelope
+ {
+ trkinfo[trk]._freq = psg.env_freq;
+ if (psg.env_freq!=0)
+ trkinfo[trk].freq = psg.clk / 512.0 / psg.env_freq;
+ else
+ trkinfo[trk].freq = 0;
+
+ if (psg.env_continue!=0 && psg.env_alternate!=0 && psg.env_hold==0) // triangle wave
+ {
+ trkinfo[trk].freq *= 0.5f; // sounds an octave down
+ }
+
+ trkinfo[trk].output = (Int32)psg.voltbl[psg.env_ptr];
+ trkinfo[trk].max_volume = 0;
+ trkinfo[trk].volume = 0;
+ trkinfo[trk].key = (((psg.volume[0] | psg.volume[1] | psg.volume[2]) & 32) != 0);
+ trkinfo[trk].tone =
+ (psg.env_continue!=0 ? 8 : 0) |
+ (psg.env_attack!=0 ? 4 : 0) |
+ (psg.env_alternate!=0 ? 2 : 0) |
+ (psg.env_hold!=0 ? 1 : 0);
+ }
+ else if (trk == 4) // noise
+ {
+ trkinfo[trk]._freq = psg.noise_freq >> 1;
+ if (trkinfo[trk]._freq > 0)
+ trkinfo[trk].freq = psg.clk / 16.0 / psg.noise_freq;
+ else
+ trkinfo[trk].freq = 0;
+
+ trkinfo[trk].output = (Int32)(psg.noise_seed & 1);
+ trkinfo[trk].max_volume = 0;
+ trkinfo[trk].volume = 0;
+ //trkinfo[trk].key = ((psg->nmask[0]&psg->nmask[1]&psg->nmask[2]) == 0);
+ trkinfo[trk].key = false;
+ trkinfo[trk].tone = 0;
+ }
+ }
+ return trkinfo[trk];
+ }
+
+ public override void SetMask(int mask)
+ {
+ if (psg!=null) emu2149.PSG_setMask(psg, (UInt32)mask);
+ }
+
+ public override void SetOption(int id, int val)
+ {
+ throw new NotImplementedException();
+ }
+
+ }
+}
diff --git a/MDPlayer/MDPlayer/Driver/nsf.cs b/MDPlayer/MDPlayer/Driver/nsf.cs
index 3feab104..bf124ec6 100644
--- a/MDPlayer/MDPlayer/Driver/nsf.cs
+++ b/MDPlayer/MDPlayer/Driver/nsf.cs
@@ -81,12 +81,12 @@ public override GD3 getGD3Info(byte[] buf, uint vgmGd3)
soundchip = buf[0x7b];
- use_vrc6 = (soundchip & 1) != 0 ? true : false;
- use_vrc7 = (soundchip & 2) != 0 ? true : false;
- use_fds = (soundchip & 4) != 0 ? true : false;
- use_mmc5 = (soundchip & 8) != 0 ? true : false;
- use_n106 = (soundchip & 16) != 0 ? true : false;
- use_fme7 = (soundchip & 32) != 0 ? true : false;
+ use_vrc6 = (soundchip & 1) != 0;
+ use_vrc7 = (soundchip & 2) != 0;
+ use_fds = (soundchip & 4) != 0;
+ use_mmc5 = (soundchip & 8) != 0;
+ use_n106 = (soundchip & 16) != 0;
+ use_fme7 = (soundchip & 32) != 0;
//memcpy(extra, image + 0x7c, 4);
for (int i = 0; i < 4; i++) extra[i] = buf[0x7c + i];
@@ -235,6 +235,7 @@ public class NSFE_Entry
private nes_n106 nes_n106 = null;
private nes_vrc6 nes_vrc6 = null;
private nes_mmc5 nes_mmc5 = null;
+ private nes_fme7 nes_fme7 = null;
private NESDetector ld = null;
// private NESDetectorEx ld = null;
@@ -257,6 +258,7 @@ private void nsfInit()
nes_n106 = new nes_n106();
nes_vrc6 = new nes_vrc6();
nes_mmc5 = new nes_mmc5();
+ nes_fme7 = new nes_fme7();
nes_apu.chip = nes_apu.apu.NES_APU_np_Create(common.NsfClock, common.SampleRate);
nes_apu.Reset();
@@ -274,6 +276,9 @@ private void nsfInit()
nes_mmc5.SetRate(common.SampleRate);
nes_mmc5.Reset();
nes_mmc5.SetCPU(nes_cpu);
+ nes_fme7.SetClock(common.NsfClock);
+ nes_fme7.SetRate(common.SampleRate);
+ nes_fme7.Reset();
nes_dmc.dmc.nes_apu = nes_apu.apu;
nes_dmc.dmc.NES_DMC_np_SetAPU(nes_dmc.chip, nes_apu.chip);
@@ -335,6 +340,10 @@ private void nsfInit()
{
apu_bus.Attach(nes_mmc5);
}
+ if (use_fme7)
+ {
+ apu_bus.Attach(nes_fme7);
+ }
if (bmax > 0) layer.Attach(nes_bank);
layer.Attach(nes_mem);
@@ -509,6 +518,14 @@ public UInt32 Render(Int16[] b, UInt32 length,Int32 offset)
_out[1] += buf[1];
}
+ if (use_fme7)
+ {
+ nes_fme7.Tick((UInt32)apu_clocks);
+ nes_fme7.Render(buf);
+ _out[0] += buf[0];
+ _out[1] += buf[1];
+ }
+
outm = (_out[0] + _out[1]);// >> 1; // mono mix
if (outm == last_out) silent_length++;
else silent_length = 0;
diff --git a/MDPlayer/MDPlayer/MDPlayer.csproj b/MDPlayer/MDPlayer/MDPlayer.csproj
index 8988d793..511145cb 100644
--- a/MDPlayer/MDPlayer/MDPlayer.csproj
+++ b/MDPlayer/MDPlayer/MDPlayer.csproj
@@ -121,10 +121,12 @@
+
+
@@ -546,7 +548,14 @@
- copy "$(ProjectDir)lib"\*.* "$(TargetDir)"
+ copy "$(ProjectDir)lib"\*.* "$(TargetDir)"
+copy "$(TargetDir)"*.dll c:\bin\$(ConfigurationName)
+copy "$(TargetDir)"MDPlayer.exe c:\bin\$(ConfigurationName)
+copy "$(TargetDir)"scciconfig.exe c:\bin\$(ConfigurationName)
+copy "$(TargetDir)"scci.ini c:\bin\$(ConfigurationName)
+del c:\bin\bin$(ConfigurationName).zip
+powershell compress-archive c:\bin\$(ConfigurationName)\*.* c:\bin\bin$(ConfigurationName).zip
+