diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs new file mode 100644 index 0000000..d637b2c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; +using UnityEngine; +using VirtualNes.Core; + +public class AudioProvider : MonoBehaviour +{ + [SerializeField] + private AudioSource m_as; + + private SoundBuffer _buffer = new SoundBuffer(4096); + + public void Initialize() + { + var dummy = AudioClip.Create("dummy", 1, 1, AudioSettings.outputSampleRate, false); + + dummy.SetData(new float[] { 1 }, 0); + m_as.clip = dummy; //just to let unity play the audiosource + m_as.loop = true; + m_as.spatialBlend = 1; + m_as.Play(); + } + + void OnAudioFilterRead(float[] data, int channels) + { + int step = channels; + + var bufferCount = _buffer.Available(); + + for (int i = 0; i < data.Length; i += step) + { + float rawFloat = 0; + if (_buffer.TryRead(out byte rawData)) + rawFloat = rawData / 255f; + + data[i] = rawFloat; + for (int fill = 1; fill < step; fill++) + data[i + fill] = rawFloat; + + } + } + + public void ProcessSound(NES nes) + { + nes.apu.Process(_buffer, (uint)(Supporter.Config.sound.nRate * Time.deltaTime)); + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs.meta b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs.meta new file mode 100644 index 0000000..2ec068d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6a09b6a4cf4c2d4f994a13fd7e89d6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs index 1a6f2ba..4b882ab 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using UnityEngine; using VirtualNes.Core; using VirtualNes.Core.Debug; @@ -11,6 +10,7 @@ namespace AxibugEmuOnline.Client private NES m_nesIns; public VideoProvider VideoProvider; + public AudioProvider AudioProvider; private void Start() { @@ -51,8 +51,9 @@ namespace AxibugEmuOnline.Client var screenBuffer = m_nesIns.ppu.GetScreenPtr(); var lineColorMode = m_nesIns.ppu.GetLineColorMode(); - VideoProvider.SetDrawData(screenBuffer, lineColorMode, 256, 240); + + AudioProvider.ProcessSound(m_nesIns); } } } diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.prefab b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.prefab index d695d77..841a651 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.prefab +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.prefab @@ -1,5 +1,146 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1 &9760340517325694 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4785916497946256520} + - component: {fileID: 9003897287163669553} + - component: {fileID: 7558824780418593440} + m_Layer: 0 + m_Name: AudioProvider + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4785916497946256520 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9760340517325694} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 4232056521131536011} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &9003897287163669553 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9760340517325694} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a6a09b6a4cf4c2d4f994a13fd7e89d6f, type: 3} + m_Name: + m_EditorClassIdentifier: + m_as: {fileID: 7558824780418593440} +--- !u!82 &7558824780418593440 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9760340517325694} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 --- !u!1 &4232056520112715746 GameObject: m_ObjectHideFlags: 0 @@ -174,6 +315,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: - {fileID: 4232056520112715745} + - {fileID: 4785916497946256520} m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -190,6 +332,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: VideoProvider: {fileID: 4232056520112715744} + AudioProvider: {fileID: 9003897287163669553} --- !u!1 &4232056521759880276 GameObject: m_ObjectHideFlags: 0 diff --git a/AxibugEmuOnline.Client/Assets/Script/RingBuffer.cs b/AxibugEmuOnline.Client/Assets/Script/RingBuffer.cs index 20f5613..2c92e49 100644 --- a/AxibugEmuOnline.Client/Assets/Script/RingBuffer.cs +++ b/AxibugEmuOnline.Client/Assets/Script/RingBuffer.cs @@ -1,4 +1,3 @@ -using System; using System.Threading; public class RingBuffer diff --git a/AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs b/AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs new file mode 100644 index 0000000..b647384 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs @@ -0,0 +1,11 @@ +using VirtualNes.Core; + +public class SoundBuffer : RingBuffer, ISoundDataBuffer +{ + public SoundBuffer(int capacity) : base(capacity) { } + + public void WriteByte(byte value) + { + Write(value); + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs.meta b/AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs.meta new file mode 100644 index 0000000..128c170 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5f491c577bed63478340426f7698563 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs index 1cb299e..63bcb2a 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs @@ -1,4 +1,9 @@ -using System; +using Codice.CM.Client.Differences; +using System; +using System.IO; +using System.Security.Principal; +using UnityEngine; +using VirtualNes.Core; using VirtualNes.Core.Debug; namespace VirtualNes.Core @@ -7,6 +12,20 @@ namespace VirtualNes.Core { public const uint QUEUE_LENGTH = 8192; + // Volume adjust + // Internal sounds + public const uint RECTANGLE_VOL = 0x0F0; + public const uint TRIANGLE_VOL = 0x130; + public const uint NOISE_VOL = 0x0C0; + public const uint DPCM_VOL = 0x0F0; + // Extra sounds + public const uint VRC6_VOL = 0x0F0; + public const uint VRC7_VOL = 0x130; + public const uint FDS_VOL = 0x0F0; + public const uint MMC5_VOL = 0x0F0; + public const uint N106_VOL = 0x088; + public const uint FME7_VOL = 0x130; + private NES nes; private byte exsound_select; private APU_INTERNAL @internal = new APU_INTERNAL(); @@ -41,8 +60,319 @@ namespace VirtualNes.Core m_bMute[i] = true; } - public void Dispose() + public void Dispose() { } + + private int[] vol = new int[24]; + static double cutofftemp = (2.0 * 3.141592653579 * 40.0); + static double tmp = 0.0; + + public void Process(ISoundDataBuffer lpBuffer, uint dwSize) { + int nBits = Supporter.Config.sound.nBits; + uint dwLength = (uint)(dwSize / (nBits / 8)); + int output; + QUEUEDATA q = default; + uint writetime; + + var pSoundBuf = m_SoundBuffer; + int nCcount = 0; + + int nFilterType = Supporter.Config.sound.nFilterType; + + if (!Supporter.Config.sound.bEnable) + { + byte empty = (byte)(Supporter.Config.sound.nRate == 8 ? 128 : 0); + for (int i = 0; i < dwSize; i++) + lpBuffer.WriteByte(empty); + return; + } + + // Volume setup + // 0:Master + // 1:Rectangle 1 + // 2:Rectangle 2 + // 3:Triangle + // 4:Noise + // 5:DPCM + // 6:VRC6 + // 7:VRC7 + // 8:FDS + // 9:MMC5 + // 10:N106 + // 11:FME7 + MemoryUtility.ZEROMEMORY(vol, vol.Length); + + var bMute = m_bMute; + var nVolume = Supporter.Config.sound.nVolume; + + int nMasterVolume = bMute[0] ? nVolume[0] : 0; + + // Internal + vol[0] = (int)(bMute[1] ? (RECTANGLE_VOL * nVolume[1] * nMasterVolume) / (100 * 100) : 0); + vol[1] = (int)(bMute[2] ? (RECTANGLE_VOL * nVolume[2] * nMasterVolume) / (100 * 100) : 0); + vol[2] = (int)(bMute[3] ? (TRIANGLE_VOL * nVolume[3] * nMasterVolume) / (100 * 100) : 0); + vol[3] = (int)(bMute[4] ? (NOISE_VOL * nVolume[4] * nMasterVolume) / (100 * 100) : 0); + vol[4] = (int)(bMute[5] ? (DPCM_VOL * nVolume[5] * nMasterVolume) / (100 * 100) : 0); + + // VRC6 + vol[5] = (int)(bMute[6] ? (VRC6_VOL * nVolume[6] * nMasterVolume) / (100 * 100) : 0); + vol[6] = (int)(bMute[7] ? (VRC6_VOL * nVolume[6] * nMasterVolume) / (100 * 100) : 0); + vol[7] = (int)(bMute[8] ? (VRC6_VOL * nVolume[6] * nMasterVolume) / (100 * 100) : 0); + + // VRC7 + vol[8] = (int)(bMute[6] ? (VRC7_VOL * nVolume[7] * nMasterVolume) / (100 * 100) : 0); + + // FDS + vol[9] = (int)(bMute[6] ? (FDS_VOL * nVolume[8] * nMasterVolume) / (100 * 100) : 0); + + // MMC5 + vol[10] = (int)(bMute[6] ? (MMC5_VOL * nVolume[9] * nMasterVolume) / (100 * 100) : 0); + vol[11] = (int)(bMute[7] ? (MMC5_VOL * nVolume[9] * nMasterVolume) / (100 * 100) : 0); + vol[12] = (int)(bMute[8] ? (MMC5_VOL * nVolume[9] * nMasterVolume) / (100 * 100) : 0); + + // N106 + vol[13] = (int)(bMute[6] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + vol[14] = (int)(bMute[7] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + vol[15] = (int)(bMute[8] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + vol[16] = (int)(bMute[9] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + vol[17] = (int)(bMute[10] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + vol[18] = (int)(bMute[11] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + vol[19] = (int)(bMute[12] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + vol[20] = (int)(bMute[13] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0); + + // FME7 + vol[21] = (int)(bMute[6] ? (FME7_VOL * nVolume[11] * nMasterVolume) / (100 * 100) : 0); + vol[22] = (int)(bMute[7] ? (FME7_VOL * nVolume[11] * nMasterVolume) / (100 * 100) : 0); + vol[23] = (int)(bMute[8] ? (FME7_VOL * nVolume[11] * nMasterVolume) / (100 * 100) : 0); + + // double cycle_rate = ((double)FRAME_CYCLES*60.0/12.0)/(double)Config.sound.nRate; + double cycle_rate = (nes.nescfg.FrameCycles * 60.0 / 12.0) / Supporter.Config.sound.nRate; + + // CPUサイクル数がループしてしまった時の対策処理 + if (elapsed_time > nes.cpu.GetTotalCycles()) + { + QueueFlush(); + } + + while ((dwLength--) != 0) + { + writetime = (uint)elapsed_time; + + while (GetQueue((int)writetime, ref q)) + { + WriteProcess(q.addr, q.data); + } + + while (GetExQueue((int)writetime, ref q)) + { + WriteExProcess(q.addr, q.data); + } + + // 0-4:internal 5-7:VRC6 8:VRC7 9:FDS 10-12:MMC5 13-20:N106 21-23:FME7 + output = 0; + output += @internal.Process(0) * vol[0]; + output += @internal.Process(1) * vol[1]; + output += @internal.Process(2) * vol[2]; + output += @internal.Process(3) * vol[3]; + output += @internal.Process(4) * vol[4]; + + if ((exsound_select & 0x01) != 0) + { + output += vrc6.Process(0) * vol[5]; + output += vrc6.Process(1) * vol[6]; + output += vrc6.Process(2) * vol[7]; + } + if ((exsound_select & 0x02) != 0) + { + output += vrc7.Process(0) * vol[8]; + } + if ((exsound_select & 0x04) != 0) + { + output += fds.Process(0) * vol[9]; + } + if ((exsound_select & 0x08) != 0) + { + output += mmc5.Process(0) * vol[10]; + output += mmc5.Process(1) * vol[11]; + output += mmc5.Process(2) * vol[12]; + } + if ((exsound_select & 0x10) != 0) + { + output += n106.Process(0) * vol[13]; + output += n106.Process(1) * vol[14]; + output += n106.Process(2) * vol[15]; + output += n106.Process(3) * vol[16]; + output += n106.Process(4) * vol[17]; + output += n106.Process(5) * vol[18]; + output += n106.Process(6) * vol[19]; + output += n106.Process(7) * vol[20]; + } + if ((exsound_select & 0x20) != 0) + { + fme7.Process(3); // Envelope & Noise + output += fme7.Process(0) * vol[21]; + output += fme7.Process(1) * vol[22]; + output += fme7.Process(2) * vol[23]; + } + + output >>= 8; + + if (nFilterType == 1) + { + //ローパスフィルターTYPE 1(Simple) + output = (lowpass_filter[0] + output) / 2; + lowpass_filter[0] = output; + } + else if (nFilterType == 2) + { + //ローパスフィルターTYPE 2(Weighted type 1) + output = (lowpass_filter[1] + lowpass_filter[0] + output) / 3; + lowpass_filter[1] = lowpass_filter[0]; + lowpass_filter[0] = output; + } + else if (nFilterType == 3) + { + //ローパスフィルターTYPE 3(Weighted type 2) + output = (lowpass_filter[2] + lowpass_filter[1] + lowpass_filter[0] + output) / 4; + lowpass_filter[2] = lowpass_filter[1]; + lowpass_filter[1] = lowpass_filter[0]; + lowpass_filter[0] = output; + } + else if (nFilterType == 4) + { + //ローパスフィルターTYPE 4(Weighted type 3) + output = (lowpass_filter[1] + lowpass_filter[0] * 2 + output) / 4; + lowpass_filter[1] = lowpass_filter[0]; + lowpass_filter[0] = output; + } + // DC成分のカット(HPF TEST) + { + // static double cutoff = (2.0*3.141592653579*40.0/44100.0); + double cutoff = cutofftemp / Supporter.Config.sound.nRate; + double @in, @out; + + @in = output; + @out = (@in - tmp); + tmp = tmp + cutoff * @out; + + output = (int)@out; + } + + // Limit + if (output > 0x7FFF) + { + output = 0x7FFF; + } + else if (output < -0x8000) + { + output = -0x8000; + } + + if (nBits != 8) + { + byte highByte = (byte)(output >> 8); // 获取高8位 + byte lowByte = (byte)(output & 0xFF); // 获取低8位 + lpBuffer.WriteByte(highByte); + lpBuffer.WriteByte(lowByte); + } + else + { + lpBuffer.WriteByte((byte)((output >> 8) ^ 0x80)); + } + + if (nCcount < 0x0100) + pSoundBuf[nCcount++] = (short)output; + + // elapsedtime += cycle_rate; + elapsed_time += cycle_rate; + } + + + if (elapsed_time > ((nes.nescfg.FrameCycles / 24) + nes.cpu.GetTotalCycles())) + { + elapsed_time = nes.cpu.GetTotalCycles(); + } + if ((elapsed_time + (nes.nescfg.FrameCycles / 6)) < nes.cpu.GetTotalCycles()) + { + elapsed_time = nes.cpu.GetTotalCycles(); + } + } + + private bool GetExQueue(int writetime, ref QUEUEDATA ret) + { + if (exqueue.wrptr == exqueue.rdptr) + { + return false; + } + if (exqueue.data[exqueue.rdptr].time <= writetime) + { + ret = exqueue.data[exqueue.rdptr]; + exqueue.rdptr++; + exqueue.rdptr = (int)(exqueue.rdptr & (QUEUE_LENGTH - 1)); + return true; + } + return false; + } + + private void QueueFlush() + { + while (queue.wrptr != queue.rdptr) + { + WriteProcess(queue.data[queue.rdptr].addr, queue.data[queue.rdptr].data); + queue.rdptr++; + queue.rdptr = (int)(queue.rdptr & (QUEUE_LENGTH - 1)); + } + + while (exqueue.wrptr != exqueue.rdptr) + { + WriteExProcess(exqueue.data[exqueue.rdptr].addr, exqueue.data[exqueue.rdptr].data); + exqueue.rdptr++; + exqueue.rdptr = (int)(exqueue.rdptr & (QUEUE_LENGTH - 1)); + } + } + + private void WriteExProcess(ushort addr, byte data) + { + if ((exsound_select & 0x01) != 0) + { + vrc6.Write(addr, data); + } + if ((exsound_select & 0x02) != 0) + { + vrc7.Write(addr, data); + } + if ((exsound_select & 0x04) != 0) + { + fds.Write(addr, data); + } + if ((exsound_select & 0x08) != 0) + { + mmc5.Write(addr, data); + } + if ((exsound_select & 0x10) != 0) + { + if (addr == 0x0000) + { + byte dummy = n106.Read(addr); + } + else + { + n106.Write(addr, data); + } + } + if ((exsound_select & 0x20) != 0) + { + fme7.Write(addr, data); + } + } + + private void WriteProcess(ushort addr, byte data) + { + // $4018はVirtuaNES固有ポート + if (addr >= 0x4000 && addr <= 0x401F) + { + @internal.Write(addr, data); + } } internal void SyncDPCM(int cycles) diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERFACE.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERFACE.cs index 391406c..b4ff65f 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERFACE.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERFACE.cs @@ -22,7 +22,7 @@ public virtual int GetStateSize() { return 0; } public virtual void SaveState(byte[] p) { } public virtual void LoadState(byte[] p) { } - + public static int INT2FIX(int x) { return x << 16; diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs index 1b88755..49ad681 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs @@ -1,7 +1,32 @@ +using Codice.CM.Client.Differences; +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + namespace VirtualNes.Core { public class APU_INTERNAL : APU_INTERFACE { + // Volume shift + public const int RECTANGLE_VOL_SHIFT = 8; + public const int TRIANGLE_VOL_SHIFT = 9; + public const int NOISE_VOL_SHIFT = 8; + public const int DPCM_VOL_SHIFT = 8; + + // Tables + static public int[] freq_limit = new int[8] + { + 0x03FF, 0x0555, 0x0666, 0x071C, 0x0787, 0x07C1, 0x07E0, 0x07F0 + }; + static public int[] duty_lut = new int[4] + { + 2, 4, 8, 12 + }; + static public int[] noise_freq = new int[16]{ + 4, 8, 16, 32, 64, 96, 128, 160, + 202, 254, 380, 508, 762, 1016, 2034, 4068 + }; + private static int[] vbl_length = new int[32] { 5, 127, 10, 1, 19, 2, 40, 3, @@ -37,9 +62,23 @@ namespace VirtualNes.Core private NOISE ch3 = new NOISE(); private DPCM ch4 = new DPCM(); + // Sound + private float cpu_clock; + private int sampling_rate; + private int cycle_rate; + // $4015 Reg private byte reg4015, sync_reg4015; + private const int TONEDATA_MAX = 16; + private const int TONEDATA_LEN = 32; + private const int CHANNEL_MAX = 3; + private const int TONE_MAX = 4; + + bool[] bToneTableEnable = new bool[TONEDATA_MAX]; + int[,] ToneTable = new int[TONEDATA_MAX, TONEDATA_LEN]; + int[,] ChannelTone = new int[CHANNEL_MAX, TONE_MAX]; + public void SetParent(NES parent) { nes = parent; @@ -127,23 +166,737 @@ namespace VirtualNes.Core public override void Reset(float fClock, int nRate) { - //todo : ʵ + ch0.ZeroMemory(); + ch1.ZeroMemory(); + ch2.ZeroMemory(); + ch3.ZeroMemory(); + + Array.Clear(bToneTableEnable, 0, bToneTableEnable.Length); + Array.Clear(ToneTable, 0, ToneTable.Length); + Array.Clear(ChannelTone, 0, ChannelTone.Length); + + reg4015 = sync_reg4015 = 0; + + // Sweep complement + ch0.complement = 0x00; + ch1.complement = 0xFF; + + // Noise shift register + ch3.shift_reg = 0x4000; + + Setup(fClock, nRate); + + // $4011ϳڻʤ + ushort addr; + for (addr = 0x4000; addr <= 0x4010; addr++) + { + Write(addr, 0x00); + SyncWrite(addr, 0x00); + } + // Write( 0x4001, 0x08 ); // Resetrinc`ɤˤʤ? + // Write( 0x4005, 0x08 ); // Resetrinc`ɤˤʤ? + Write(0x4012, 0x00); + Write(0x4013, 0x00); + Write(0x4015, 0x00); + SyncWrite(0x4012, 0x00); + SyncWrite(0x4013, 0x00); + SyncWrite(0x4015, 0x00); + + // $4017ϕzߤdzڻʤ(ڥ`ɤ0ǤΤڴեȤ) + FrameIRQ = 0xC0; + FrameCycle = 0; + FrameIRQoccur = 0; + FrameCount = 0; + FrameType = 0; } public override void Setup(float fClock, int nRate) { - //todo : ʵ + cpu_clock = fClock; + sampling_rate = nRate; + + cycle_rate = (int)(fClock * 65536.0f / nRate); } public override void Write(ushort addr, byte data) { - //todo : ʵ + switch (addr) + { + // CH0,1 rectangle + case 0x4000: + case 0x4001: + case 0x4002: + case 0x4003: + case 0x4004: + case 0x4005: + case 0x4006: + case 0x4007: + WriteRectangle((addr < 0x4004) ? 0 : 1, addr, data); + break; + + // CH2 triangle + case 0x4008: + case 0x4009: + case 0x400A: + case 0x400B: + WriteTriangle(addr, data); + break; + + // CH3 noise + case 0x400C: + case 0x400D: + case 0x400E: + case 0x400F: + WriteNoise(addr, data); + break; + + // CH4 DPCM + case 0x4010: + case 0x4011: + case 0x4012: + case 0x4013: + WriteDPCM(addr, data); + break; + + case 0x4015: + reg4015 = data; + + if ((data & (1 << 0)) == 0) + { + ch0.enable = 0; + ch0.len_count = 0; + } + if ((data & (1 << 1)) == 0) + { + ch1.enable = 0; + ch1.len_count = 0; + } + if ((data & (1 << 2)) == 0) + { + ch2.enable = 0; + ch2.len_count = 0; + ch2.lin_count = 0; + ch2.counter_start = 0; + } + if ((data & (1 << 3)) == 0) + { + ch3.enable = 0; + ch3.len_count = 0; + } + if ((data & (1 << 4)) == 0) + { + ch4.enable = 0; + ch4.dmalength = 0; + } + else + { + ch4.enable = 0xFF; + if (ch4.dmalength == 0) + { + ch4.address = ch4.cache_addr; + ch4.dmalength = ch4.cache_dmalength; + ch4.phaseacc = 0; + } + } + break; + + case 0x4017: + break; + + // VirtuaNESХݩ` + case 0x4018: + UpdateRectangle(ch0, data); + UpdateRectangle(ch1, data); + UpdateTriangle(data); + UpdateNoise(data); + break; + + default: + break; + } + } + + private void UpdateNoise(int type) + { + if (ch3.enable == 0 || ch3.len_count <= 0) + return; + + // Update Length + if (ch3.holdnote == 0) + { + // Holdnote + if ((type & 1) == 0 && ch3.len_count != 0) + { + ch3.len_count--; + } + } + + // Update Envelope + if (ch3.env_count != 0) + { + ch3.env_count--; + } + if (ch3.env_count == 0) + { + ch3.env_count = ch3.env_decay; + + // Holdnote + if (ch3.holdnote != 0) + { + ch3.env_vol = (ch3.env_vol - 1) & 0x0F; + } + else if (ch3.env_vol != 0) + { + ch3.env_vol--; + } + } + + if (ch3.env_fixed == 0) + { + ch3.nowvolume = ch3.env_vol << RECTANGLE_VOL_SHIFT; + } + } + + private void UpdateTriangle(int type) + { + if (ch2.enable == 0) + return; + + if ((type & 1) == 0 && ch2.holdnote == 0) + { + if (ch2.len_count != 0) + { + ch2.len_count--; + } + } + + // if( !ch2.len_count ) { + // ch2.lin_count = 0; + // } + + // Update Length/Linear + if (ch2.counter_start != 0) + { + ch2.lin_count = ch2.reg[0] & 0x7F; + } + else if (ch2.lin_count != 0) + { + ch2.lin_count--; + } + if (ch2.holdnote == 0 && ch2.lin_count != 0) + { + ch2.counter_start = 0; + } + } + + private void UpdateRectangle(RECTANGLE ch, int type) + { + if (ch.enable == 0 || ch.len_count <= 0) + return; + + // Update Length/Sweep + if ((type & 1) == 0) + { + // Update Length + if (ch.len_count != 0 && ch.holdnote == 0) + { + // Holdnote + if (ch.len_count != 0) + { + ch.len_count--; + } + } + + // Update Sweep + if (ch.swp_on != 0 && ch.swp_shift != 0) + { + if (ch.swp_count != 0) + { + ch.swp_count--; + } + if (ch.swp_count == 0) + { + ch.swp_count = ch.swp_decay; + if (ch.swp_inc != 0) + { + // Sweep increment(to higher frequency) + if (ch.complement == 0) + ch.freq += ~(ch.freq >> ch.swp_shift); // CH 0 + else + ch.freq -= (ch.freq >> ch.swp_shift); // CH 1 + } + else + { + // Sweep decrement(to lower frequency) + ch.freq += (ch.freq >> ch.swp_shift); + } + } + } + } + + // Update Envelope + if (ch.env_count != 0) + { + ch.env_count--; + } + if (ch.env_count == 0) + { + ch.env_count = ch.env_decay; + + // Holdnote + if (ch.holdnote != 0) + { + ch.env_vol = (ch.env_vol - 1) & 0x0F; + } + else if (ch.env_vol != 0) + { + ch.env_vol--; + } + } + + if (ch.env_fixed == 0) + { + ch.nowvolume = ch.env_vol << RECTANGLE_VOL_SHIFT; + } + } + + private void WriteDPCM(ushort addr, byte data) + { + ch4.reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch4.freq = INT2FIX(nes.GetVideoMode() ? dpcm_cycles_pal[data & 0x0F] : dpcm_cycles[data & 0x0F]); + // ch4.freq = INT2FIX( dpcm_cycles[data&0x0F] ); + //// ch4.freq = INT2FIX( (dpcm_cycles[data&0x0F]-((data&0x0F)^0x0F)*2-2) ); + ch4.looping = (byte)(data & 0x40); + break; + case 1: + ch4.dpcm_value = (byte)((data & 0x7F) >> 1); + break; + case 2: + ch4.cache_addr = (ushort)(0xC000 + (ushort)(data << 6)); + break; + case 3: + ch4.cache_dmalength = ((data << 4) + 1) << 3; + break; + } + } + + private void WriteNoise(ushort addr, byte data) + { + ch3.reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch3.holdnote = (byte)(data & 0x20); + ch3.volume = (byte)(data & 0x0F); + ch3.env_fixed = (byte)(data & 0x10); + ch3.env_decay = (byte)((data & 0x0F) + 1); + break; + case 1: // Unused + break; + case 2: + ch3.freq = INT2FIX(noise_freq[data & 0x0F]); + ch3.xor_tap = (byte)((data & 0x80) != 0 ? 0x40 : 0x02); + break; + case 3: // Master + ch3.len_count = vbl_length[data >> 3] * 2; + ch3.env_vol = 0x0F; + ch3.env_count = (byte)(ch3.env_decay + 1); + + if ((reg4015 & (1 << 3)) != 0) + ch3.enable = 0xFF; + break; + } + } + + private void WriteTriangle(ushort addr, byte data) + { + ch2.reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch2.holdnote = (byte)(data & 0x80); + break; + case 1: // Unused + break; + case 2: + ch2.freq = INT2FIX(((ch2.reg[3] & 0x07) << 8) + data + 1); + break; + case 3: // Master + ch2.freq = INT2FIX((((data & 0x07) << 8) + ch2.reg[2] + 1)); + ch2.len_count = vbl_length[data >> 3] * 2; + ch2.counter_start = 0x80; + + if ((reg4015 & (1 << 2)) != 0) + ch2.enable = 0xFF; + break; + } + } + + private void WriteRectangle(int no, ushort addr, byte data) + { + RECTANGLE ch = (no == 0) ? ch0 : ch1; + + ch.reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch.holdnote = (byte)(data & 0x20); + ch.volume = (byte)(data & 0x0F); + ch.env_fixed = (byte)(data & 0x10); + ch.env_decay = (byte)((data & 0x0F) + 1); + ch.duty = duty_lut[data >> 6]; + break; + case 1: + ch.swp_on = (byte)(data & 0x80); + ch.swp_inc = (byte)(data & 0x08); + ch.swp_shift = (byte)(data & 0x07); + ch.swp_decay = (byte)(((data >> 4) & 0x07) + 1); + ch.freqlimit = freq_limit[data & 0x07]; + break; + case 2: + ch.freq = (ch.freq & (~0xFF)) + data; + break; + case 3: // Master + ch.freq = ((data & 0x07) << 8) + (ch.freq & 0xFF); + ch.len_count = vbl_length[data >> 3] * 2; + ch.env_vol = 0x0F; + ch.env_count = (byte)(ch.env_decay + 1); + ch.adder = 0; + + if ((reg4015 & (1 << no)) != 0) + ch.enable = 0xFF; + break; + } } public override int Process(int channel) { - //todo : ʵ - return 0; + switch (channel) + { + case 0: + return RenderRectangle(ch0); + case 1: + return RenderRectangle(ch1); + case 2: + return RenderTriangle(); + case 3: + return RenderNoise(); + case 4: + return RenderDPCM(); + default: + return 0; + } + } + + private int RenderDPCM() + { + if (ch4.dmalength != 0) + { + ch4.phaseacc -= cycle_rate; + + while (ch4.phaseacc < 0) + { + ch4.phaseacc += ch4.freq; + if ((ch4.dmalength & 7) == 0) + { + ch4.cur_byte = nes.Read(ch4.address); + if (0xFFFF == ch4.address) + ch4.address = 0x8000; + else + ch4.address++; + } + + if ((--ch4.dmalength) == 0) + { + if (ch4.looping != 0) + { + ch4.address = ch4.cache_addr; + ch4.dmalength = ch4.cache_dmalength; + } + else + { + ch4.enable = 0; + break; + } + } + // positive delta + if ((ch4.cur_byte & (1 << ((ch4.dmalength & 7) ^ 7))) != 0) + { + if (ch4.dpcm_value < 0x3F) + ch4.dpcm_value += 1; + } + else + { + // negative delta + if (ch4.dpcm_value > 1) + ch4.dpcm_value -= 1; + } + } + } + + // ץΥå(TEST) + ch4.dpcm_output_real = ((ch4.reg[1] & 0x01) + ch4.dpcm_value * 2) - 0x40; + if (Math.Abs(ch4.dpcm_output_real - ch4.dpcm_output_fake) <= 8) + { + ch4.dpcm_output_fake = ch4.dpcm_output_real; + ch4.output = ch4.dpcm_output_real << DPCM_VOL_SHIFT; + } + else + { + if (ch4.dpcm_output_real > ch4.dpcm_output_fake) + ch4.dpcm_output_fake += 8; + else + ch4.dpcm_output_fake -= 8; + ch4.output = ch4.dpcm_output_fake << DPCM_VOL_SHIFT; + } + return ch4.output; + } + + private int RenderNoise() + { + if (ch3.enable == 0 || ch3.len_count <= 0) + return 0; + + if (ch3.env_fixed != 0) + { + ch3.nowvolume = ch3.volume << RECTANGLE_VOL_SHIFT; + } + + int vol = 256 - ((ch4.reg[1] & 0x01) + ch4.dpcm_value * 2); + + ch3.phaseacc -= cycle_rate; + if (ch3.phaseacc >= 0) + return ch3.output * vol / 256; + + if (ch3.freq > cycle_rate) + { + ch3.phaseacc += ch3.freq; + if (NoiseShiftreg(ch3.xor_tap)) + ch3.output = ch3.nowvolume; + else + ch3.output = -ch3.nowvolume; + + return ch3.output * vol / 256; + } + + int num_times, total; + num_times = total = 0; + while (ch3.phaseacc < 0) + { + ch3.phaseacc += ch3.freq; + if (NoiseShiftreg(ch3.xor_tap)) + ch3.output = ch3.nowvolume; + else + ch3.output = -ch3.nowvolume; + + total += ch3.output; + num_times++; + } + + return (total / num_times) * vol / 256; + } + + private bool NoiseShiftreg(byte xor_tap) + { + int bit0, bit14; + + bit0 = ch3.shift_reg & 1; + if ((ch3.shift_reg & xor_tap) != 0) bit14 = bit0 ^ 1; + else bit14 = bit0 ^ 0; + ch3.shift_reg >>= 1; + ch3.shift_reg |= (bit14 << 14); + return (bit0 ^ 1) != 0; + } + + private int RenderTriangle() + { + int vol; + if (Supporter.Config.sound.bDisableVolumeEffect) + { + vol = 256; + } + else + { + vol = 256 - ((ch4.reg[1] & 0x01) + ch4.dpcm_value * 2); + } + + if (ch2.enable == 0 || (ch2.len_count <= 0) || (ch2.lin_count <= 0)) + { + return ch2.nowvolume * vol / 256; + } + + if (ch2.freq < INT2FIX(8)) + { + return ch2.nowvolume * vol / 256; + } + + if (!(Supporter.Config.sound.bChangeTone && ChannelTone[2, 0] != 0)) + { + ch2.phaseacc -= cycle_rate; + if (ch2.phaseacc >= 0) + { + return ch2.nowvolume * vol / 256; + } + + if (ch2.freq > cycle_rate) + { + ch2.phaseacc += ch2.freq; + ch2.adder = (ch2.adder + 1) & 0x1F; + + if (ch2.adder < 0x10) + { + ch2.nowvolume = (ch2.adder & 0x0F) << TRIANGLE_VOL_SHIFT; + } + else + { + ch2.nowvolume = (0x0F - (ch2.adder & 0x0F)) << TRIANGLE_VOL_SHIFT; + } + + return ch2.nowvolume * vol / 256; + } + + // ƽ + int num_times, total; + num_times = total = 0; + while (ch2.phaseacc < 0) + { + ch2.phaseacc += ch2.freq; + ch2.adder = (ch2.adder + 1) & 0x1F; + + if (ch2.adder < 0x10) + { + ch2.nowvolume = (ch2.adder & 0x0F) << TRIANGLE_VOL_SHIFT; + } + else + { + ch2.nowvolume = (0x0F - (ch2.adder & 0x0F)) << TRIANGLE_VOL_SHIFT; + } + + total += ch2.nowvolume; + num_times++; + } + + return (total / num_times) * vol / 256; + } + else + { + int x = ChannelTone[2, 0] - 1; + int pTone = 0; + + ch2.phaseacc -= cycle_rate; + if (ch2.phaseacc >= 0) + { + return ch2.nowvolume * vol / 256; + } + + if (ch2.freq > cycle_rate) + { + ch2.phaseacc += ch2.freq; + ch2.adder = (ch2.adder + 1) & 0x1F; + var temp = ToneTable[x, pTone + (ch2.adder & 0x1F)]; + ch2.nowvolume = temp * 0x0F; + return ch2.nowvolume * vol / 256; + } + + // ƽ + int num_times, total; + num_times = total = 0; + while (ch2.phaseacc < 0) + { + ch2.phaseacc += ch2.freq; + ch2.adder = (ch2.adder + 1) & 0x1F; + var temp = ToneTable[x, pTone + (ch2.adder & 0x1F)]; + total += temp * 0x0F; + num_times++; + } + + return (total / num_times) * vol / 256; + } + } + + private int RenderRectangle(RECTANGLE ch) + { + if (ch.enable == 0 || ch.len_count <= 0) + return 0; + + // Channel disable? + if ((ch.freq < 8) || (ch.swp_inc == 0 && ch.freq > ch.freqlimit)) + { + return 0; + } + + if (ch.env_fixed != 0) + { + ch.nowvolume = ch.volume << RECTANGLE_VOL_SHIFT; + } + int volume = ch.nowvolume; + + if (!(Supporter.Config.sound.bChangeTone && (ChannelTone[(ch.complement == 0) ? 0 : 1, ch.reg[0] >> 6]) != 0)) + { + // agI + double total; + double sample_weight = ch.phaseacc; + if (sample_weight > cycle_rate) + { + sample_weight = cycle_rate; + } + total = (ch.adder < ch.duty) ? sample_weight : -sample_weight; + + int freq = INT2FIX(ch.freq + 1); + ch.phaseacc -= cycle_rate; + while (ch.phaseacc < 0) + { + ch.phaseacc += freq; + ch.adder = (ch.adder + 1) & 0x0F; + + sample_weight = freq; + if (ch.phaseacc > 0) + { + sample_weight -= ch.phaseacc; + } + total += (ch.adder < ch.duty) ? sample_weight : -sample_weight; + } + return (int)(volume * total / cycle_rate + 0.5); + } + else + { + int x = ChannelTone[(ch.complement == 0) ? 0 : 1, ch.reg[0] >> 6] - 1; + int pTone = 0; + + // Ÿo + ch.phaseacc -= cycle_rate * 2; + if (ch.phaseacc >= 0) + { + var temp = ToneTable[x, pTone + (ch.adder & 0x1F)]; + return temp * volume / ((1 << RECTANGLE_VOL_SHIFT) / 2); + } + + // 1ƥåפ + int freq = INT2FIX(ch.freq + 1); + if (freq > cycle_rate * 2) + { + ch.phaseacc += freq; + ch.adder = (ch.adder + 1) & 0x1F; + var temp = ToneTable[x, pTone + (ch.adder & 0x1F)]; + return temp * volume / ((1 << RECTANGLE_VOL_SHIFT) / 2); + } + + // ƽ + int num_times, total; + num_times = total = 0; + while (ch.phaseacc < 0) + { + ch.phaseacc += freq; + ch.adder = (ch.adder + 1) & 0x1F; + var temp = ToneTable[x, pTone + (ch.adder & 0x1F)]; + total += temp * volume / ((1 << RECTANGLE_VOL_SHIFT) / 2); + num_times++; + } + return total / num_times; + } } internal byte SyncRead(ushort addr) diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/DPCM.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/DPCM.cs index 54246a2..35835b3 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/DPCM.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/DPCM.cs @@ -12,7 +12,7 @@ public int phaseacc; public int output; - ushort address, cache_addr; + public ushort address, cache_addr; public int dmalength, cache_dmalength; public int dpcm_output_real, dpcm_output_fake, dpcm_output_old, dpcm_output_offset; diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/NOISE.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/NOISE.cs index 8b2dc0e..0fa41a4 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/NOISE.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/NOISE.cs @@ -1,4 +1,6 @@ -namespace VirtualNes.Core +using System; + +namespace VirtualNes.Core { public class NOISE { @@ -32,5 +34,35 @@ public byte sync_holdnote; public byte dummy1; public int sync_len_count; + + internal void ZeroMemory() + { + Array.Clear(reg, 0, reg.Length); + + enable = 0; + holdnote = 0; + volume = 0; + xor_tap = 0; + shift_reg = 0; + + phaseacc = 0; + freq = 0; + len_count = 0; + nowvolume = 0; + output = 0; + + env_fixed = 0; + env_decay = 0; + env_count = 0; + dummy0 = 0; + env_vol = 0; + + Array.Clear(sync_reg, 0, sync_reg.Length); + sync_output_enable = 0; + sync_enable = 0; + sync_holdnote = 0; + dummy1 = 0; + sync_len_count = 0; + } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/TRIANGLE.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/TRIANGLE.cs index 80f4c4a..191a619 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/TRIANGLE.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/TRIANGLE.cs @@ -1,4 +1,6 @@ -namespace VirtualNes.Core +using System; + +namespace VirtualNes.Core { public class TRIANGLE { @@ -25,5 +27,28 @@ // public byte dummy1; public int sync_len_count; public int sync_lin_count; + + internal void ZeroMemory() + { + Array.Clear(reg, 0, reg.Length); + + enable = 0; + holdnote = 0; + counter_start = 0; + dummy0 = 0; + phaseacc = 0; + freq = 0; + len_count = 0; + lin_count = 0; + adder = 0; + nowvolume = 0; + Array.Clear(sync_reg, 0, sync_reg.Length); + sync_enable = 0; + sync_holdnote = 0; + sync_counter_start = 0; + + sync_len_count = 0; + sync_lin_count = 0; + } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs index 4966557..16ea18b 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs @@ -6,7 +6,23 @@ private int m_offset; private int m_length; + public int Offset + { + get => m_offset; + set + { + var gap = value - m_offset; + m_length -= gap; + } + } + + public ByteArrayRef() { } public ByteArrayRef(byte[] array, int offset, int length) + { + SetArray(array, offset, length); + } + + public void SetArray(byte[] array, int offset, int length) { m_rawArray = array; m_offset = offset; diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs new file mode 100644 index 0000000..7994134 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VirtualNes.Core +{ + public interface ISoundDataBuffer + { + void WriteByte(byte value); + } +} diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs.meta new file mode 100644 index 0000000..71ba7b0 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4585a754599519b48bfe50294600818f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/MemoryUtility.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/MemoryUtility.cs index b6a45c3..691ec13 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/MemoryUtility.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/MemoryUtility.cs @@ -9,6 +9,10 @@ namespace VirtualNes.Core { Array.Clear(array, 0, array.Length); } + public static void ZEROMEMORY(int[] array, int length) + { + Array.Clear(array, 0, array.Length); + } public static void memset(byte[] array, byte value, int length) { diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/CfgSound.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/CfgSound.cs index 2db18fb..186b36b 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/CfgSound.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/CfgSound.cs @@ -3,7 +3,7 @@ public class CfgSound { public bool bEnable { get; set; } = true; - public int nRate { get; set; } = 22050; + public int nRate { get; set; } = 48000; public int nBits { get; set; } = 8; public int nBufferSize { get; set; } = 4; public int nFilterType { get; set; } = 0;