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 +