From 090322a60aaff194727e0185c34b21e5f3e24e86 Mon Sep 17 00:00:00 2001 From: "ALIENJACK\\alien" Date: Fri, 2 Aug 2024 10:58:04 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E5=99=A8=E6=8F=90=E4=BA=A4=E8=BF=87=E6=9D=A5=E7=9A=84=E5=85=83?= =?UTF-8?q?=E7=B4=A0=E8=BD=AC=E6=8D=A2=E4=B8=BAUnity=20Texture2D=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets/Scene/EmuTest.unity | 117 +++--------------- .../Assets/Script/NesEmulator/NesEmulator.cs | 1 + .../New Render Texture.renderTexture | 38 ------ .../New Render Texture.renderTexture.meta | 8 -- .../Script/NesEmulator/VideoProvider.cs | 41 ++---- .../Assets/VirtualNes.Core/CPU.cs | 8 +- 6 files changed, 36 insertions(+), 177 deletions(-) delete mode 100644 AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture delete mode 100644 AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture.meta diff --git a/AxibugEmuOnline.Client/Assets/Scene/EmuTest.unity b/AxibugEmuOnline.Client/Assets/Scene/EmuTest.unity index e73c73e..dfb22f6 100644 --- a/AxibugEmuOnline.Client/Assets/Scene/EmuTest.unity +++ b/AxibugEmuOnline.Client/Assets/Scene/EmuTest.unity @@ -26,7 +26,7 @@ RenderSettings: m_AmbientIntensity: 1 m_AmbientMode: 0 m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} - m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_SkyboxMaterial: {fileID: 0} m_HaloStrength: 0.5 m_FlareStrength: 1 m_FlareFadeSpeed: 3 @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -123,99 +123,6 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} ---- !u!1 &708549044 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 708549046} - - component: {fileID: 708549045} - m_Layer: 0 - m_Name: Directional Light - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!108 &708549045 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 708549044} - m_Enabled: 1 - serializedVersion: 10 - m_Type: 1 - m_Shape: 0 - m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} - m_Intensity: 1 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.80208 - m_CookieSize: 10 - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 1 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 4 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_UseViewFrustumForShadowCasterCull: 1 - m_ShadowRadius: 0 - m_ShadowAngle: 0 ---- !u!4 &708549046 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 708549044} - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} - m_LocalPosition: {x: 0, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 0 - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} --- !u!1 &1232273651 GameObject: m_ObjectHideFlags: 0 @@ -252,7 +159,7 @@ Camera: m_Enabled: 1 serializedVersion: 2 m_ClearFlags: 1 - m_BackGroundColor: {r: 1, g: 1, b: 1, a: 0} + m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0} m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 @@ -297,7 +204,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 2 + m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1359344831 GameObject: @@ -363,7 +270,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 + m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1001 &4232056520998800727 PrefabInstance: @@ -374,7 +281,7 @@ PrefabInstance: m_Modifications: - target: {fileID: 4232056521131536011, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} propertyPath: m_RootOrder - value: 3 + value: 2 objectReference: {fileID: 0} - target: {fileID: 4232056521131536011, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} propertyPath: m_LocalPosition.x @@ -420,5 +327,17 @@ PrefabInstance: propertyPath: m_Name value: NesEmulator objectReference: {fileID: 0} + - target: {fileID: 4232056521759880274, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} + propertyPath: m_Enabled + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4232056521759880274, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} + propertyPath: m_Texture + value: + objectReference: {fileID: 0} + - target: {fileID: 4232056521759880276, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} + propertyPath: m_IsActive + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs index 308cd88..1a6f2ba 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs @@ -14,6 +14,7 @@ namespace AxibugEmuOnline.Client private void Start() { + Application.targetFrameRate = 60; StartGame("Kirby.nes"); } diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture deleted file mode 100644 index 6721306..0000000 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture +++ /dev/null @@ -1,38 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!84 &8400000 -RenderTexture: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: New Render Texture - m_ImageContentsHash: - serializedVersion: 2 - Hash: 00000000000000000000000000000000 - m_ForcedFallbackFormat: 4 - m_DownscaleFallback: 0 - m_IsAlphaChannelOptional: 0 - serializedVersion: 3 - m_Width: 272 - m_Height: 240 - m_AntiAliasing: 1 - m_MipCount: -1 - m_DepthFormat: 2 - m_ColorFormat: 8 - m_MipMap: 0 - m_GenerateMips: 1 - m_SRGB: 0 - m_UseDynamicScale: 0 - m_BindMS: 0 - m_EnableCompatibleFormat: 1 - m_TextureSettings: - serializedVersion: 2 - m_FilterMode: 1 - m_Aniso: 0 - m_MipBias: 0 - m_WrapU: 1 - m_WrapV: 1 - m_WrapW: 1 - m_Dimension: 2 - m_VolumeDepth: 1 diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture.meta b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture.meta deleted file mode 100644 index bfd7ec1..0000000 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/New Render Texture.renderTexture.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: ffe34aaf87e4b9942b4c2ac05943d444 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 8400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs index b4372e3..70abda6 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs @@ -1,13 +1,7 @@ using AxibugEmuOnline.Client.Assets.Script.NesEmulator; -using Codice.CM.Client.Differences; using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; using UnityEngine; -using UnityEngine.Playables; using UnityEngine.UI; using VirtualNes.Core; @@ -17,23 +11,29 @@ namespace AxibugEmuOnline.Client { public RawImage Image; - private Color32[] wrapTexBuffer; + private UInt32[] wrapTexBuffer; private IntPtr wrapTexBufferPointer; private Texture2D wrapTex; + private int TexBufferSize; + private uint[] pPal; public void SetDrawData(byte[] screenData, byte[] lineColorMode, int screenWidth, int screenHeight) { if (wrapTex == null) { wrapTex = new Texture2D(screenWidth, screenHeight, TextureFormat.BGRA32, false); - wrapTexBuffer = new Color32[screenWidth * screenHeight]; + wrapTexBuffer = new UInt32[screenWidth * screenHeight]; // 固定数组,防止垃圾回收器移动它 GCHandle handle = GCHandle.Alloc(wrapTexBuffer, GCHandleType.Pinned); // 获取数组的指针 wrapTexBufferPointer = handle.AddrOfPinnedObject(); + + Image.texture = wrapTex; + pPal = PaletteDefine.m_cnPalette[0]; + + TexBufferSize = wrapTexBuffer.Length * 4; } - uint[] pPal; int pScn = 0; int width; @@ -42,41 +42,26 @@ namespace AxibugEmuOnline.Client for (int line = 0; line < screenHeight; line++) { - if ((lineColorMode[line] & 0x80) == 0) - { - pPal = PaletteDefine.m_cnPalette[lineColorMode[line] & 0x07]; - } - else - { - pPal = PaletteDefine.m_mnPalette[lineColorMode[line] & 0x07]; - } - width = screenWidth; while (width > 0) { var edx = screenData[pScn + 8]; - byte index = (byte)(edx & 0xFF); + int index = edx & 0xFF; var colorData = pPal[index]; - var rawData = BitConverter.GetBytes(colorData); - Dst[pDst] = new Color32(rawData[0], rawData[1], rawData[2], 255); + Dst[pDst] = 0xFF000000 | colorData; pScn += 1; pDst += 1; width -= 1; } - pScn += PPU.SCREEN_WIDTH - screenWidth; + pScn += 16;// PPU.SCREEN_WIDTH - screenWidth; } - //wrapTex.SetPixels32(wrapTexBuffer); - wrapTex.LoadRawTextureData(wrapTexBufferPointer, screenWidth * screenHeight * 4); + wrapTex.LoadRawTextureData(wrapTexBufferPointer, TexBufferSize); wrapTex.Apply(); - - Graphics.Blit(wrapTex, Image.mainTexture as RenderTexture); } - - } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs index 5203e1a..835c758 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs @@ -45,7 +45,7 @@ namespace VirtualNes.Core private APU apu; internal R6502 R = new R6502(); private byte[] ZN_Table = new byte[256]; - private Memory STACK; + private ByteArrayRef STACK; public CPU(NES parent) { @@ -1361,12 +1361,12 @@ namespace VirtualNes.Core private byte POP() { - return STACK.Span[(++R.S) & 0xFF]; + return STACK[(++R.S) & 0xFF]; } private void PUSH(byte V) { - STACK.Span[(R.S--) & 0xFF] = V; + STACK[(R.S--) & 0xFF] = V; } private void SEI() @@ -2010,7 +2010,7 @@ namespace VirtualNes.Core DMA_cycles = 0; // STACK quick access - STACK = new Memory(MMU.RAM, 0x0100, MMU.RAM.Length - 0x100); + STACK = new ByteArrayRef(MMU.RAM, 0x0100, MMU.RAM.Length - 0x100); // Zero/Negative FLAG ZN_Table[0] = Z_FLAG; From 0a27237a178d5ba2794414145f42e51d13f5b46b Mon Sep 17 00:00:00 2001 From: "ALIENJACK\\alien" Date: Fri, 2 Aug 2024 16:50:16 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E9=9F=B3=E9=A2=91=20internal=20=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Script/NesEmulator/AudioProvider.cs | 47 ++ .../Script/NesEmulator/AudioProvider.cs.meta | 11 + .../Assets/Script/NesEmulator/NesEmulator.cs | 5 +- .../Script/NesEmulator/NesEmulator.prefab | 143 ++++ .../Assets/Script/RingBuffer.cs | 1 - .../Assets/Script/SoundBuffer.cs | 11 + .../Assets/Script/SoundBuffer.cs.meta | 11 + .../Assets/VirtualNes.Core/APU.cs | 334 +++++++- .../VirtualNes.Core/ApuEX/APU_INTERFACE.cs | 2 +- .../VirtualNes.Core/ApuEX/APU_INTERNAL.cs | 763 +++++++++++++++++- .../Assets/VirtualNes.Core/ApuEX/DPCM.cs | 2 +- .../Assets/VirtualNes.Core/ApuEX/NOISE.cs | 34 +- .../Assets/VirtualNes.Core/ApuEX/TRIANGLE.cs | 27 +- .../VirtualNes.Core/CoreLibs/ByteArrayRef.cs | 16 + .../CoreLibs/ISoundDataBuffer.cs | 13 + .../CoreLibs/ISoundDataBuffer.cs.meta | 11 + .../VirtualNes.Core/CoreLibs/MemoryUtility.cs | 4 + .../Supporter/EmulatorConfig/CfgSound.cs | 2 +- 18 files changed, 1422 insertions(+), 15 deletions(-) create mode 100644 AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs create mode 100644 AxibugEmuOnline.Client/Assets/Script/NesEmulator/AudioProvider.cs.meta create mode 100644 AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs create mode 100644 AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs.meta create mode 100644 AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs create mode 100644 AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ISoundDataBuffer.cs.meta 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 @@ +锘縰sing 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 @@ +锘縰sing 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 @@ -锘縰sing System; +锘縰sing 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) + { + //銉兗銉戙偣銉曘偅銉偪銉糡YPE 1(Simple) + output = (lowpass_filter[0] + output) / 2; + lowpass_filter[0] = output; + } + else if (nFilterType == 2) + { + //銉兗銉戙偣銉曘偅銉偪銉糡YPE 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) + { + //銉兗銉戙偣銉曘偅銉偪銉糡YPE 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) + { + //銉兗銉戙偣銉曘偅銉偪銉糡YPE 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銇疺irtuaNES鍥烘湁銉濄兗銉 + 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 ); // Reset時はincモードになる? + // Write( 0x4005, 0x08 ); // Reset時はincモードになる? + Write(0x4012, 0x00); + Write(0x4013, 0x00); + Write(0x4015, 0x00); + SyncWrite(0x4012, 0x00); + SyncWrite(0x4013, 0x00); + SyncWrite(0x4015, 0x00); + + // $4017は書き込みで初期化しない(初期モードが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)) + { + // 補間処理 + 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; + + // 更新無し + 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 @@ -锘縩amespace VirtualNes.Core +锘縰sing 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 @@ -锘縩amespace VirtualNes.Core +锘縰sing 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 @@ +锘縰sing 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;