优化模拟器提交过来的元素转换为Unity Texture2D数据的代码 #20

Merged
sin365 merged 2 commits from Alienjack/AxibugEmuOnline:dev_4VirtualNes into dev_4VirtualNes 2024-08-02 16:55:28 +08:00
23 changed files with 1458 additions and 192 deletions

View File

@ -26,7 +26,7 @@ RenderSettings:
m_AmbientIntensity: 1 m_AmbientIntensity: 1
m_AmbientMode: 0 m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 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_HaloStrength: 0.5
m_FlareStrength: 1 m_FlareStrength: 1
m_FlareFadeSpeed: 3 m_FlareFadeSpeed: 3
@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1 m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0} m_CustomReflection: {fileID: 0}
m_Sun: {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 m_UseRadianceAmbientProbe: 0
--- !u!157 &3 --- !u!157 &3
LightmapSettings: LightmapSettings:
@ -123,99 +123,6 @@ NavMeshSettings:
debug: debug:
m_Flags: 0 m_Flags: 0
m_NavMeshData: {fileID: 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 --- !u!1 &1232273651
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -252,7 +159,7 @@ Camera:
m_Enabled: 1 m_Enabled: 1
serializedVersion: 2 serializedVersion: 2
m_ClearFlags: 1 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_projectionMatrixMode: 1
m_GateFitMode: 2 m_GateFitMode: 2
m_FOVAxisMode: 0 m_FOVAxisMode: 0
@ -297,7 +204,7 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 2 m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1359344831 --- !u!1 &1359344831
GameObject: GameObject:
@ -363,7 +270,7 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 1 m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &4232056520998800727 --- !u!1001 &4232056520998800727
PrefabInstance: PrefabInstance:
@ -374,7 +281,7 @@ PrefabInstance:
m_Modifications: m_Modifications:
- target: {fileID: 4232056521131536011, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} - target: {fileID: 4232056521131536011, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3}
propertyPath: m_RootOrder propertyPath: m_RootOrder
value: 3 value: 2
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 4232056521131536011, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} - target: {fileID: 4232056521131536011, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3}
propertyPath: m_LocalPosition.x propertyPath: m_LocalPosition.x
@ -420,5 +327,17 @@ PrefabInstance:
propertyPath: m_Name propertyPath: m_Name
value: NesEmulator value: NesEmulator
objectReference: {fileID: 0} 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_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3} m_SourcePrefab: {fileID: 100100000, guid: f8bea3f8aa351bb46ada33b2274729ea, type: 3}

View File

@ -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));
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a6a09b6a4cf4c2d4f994a13fd7e89d6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,4 @@
using System; using System;
using System.IO;
using UnityEngine; using UnityEngine;
using VirtualNes.Core; using VirtualNes.Core;
using VirtualNes.Core.Debug; using VirtualNes.Core.Debug;
@ -11,9 +10,11 @@ namespace AxibugEmuOnline.Client
private NES m_nesIns; private NES m_nesIns;
public VideoProvider VideoProvider; public VideoProvider VideoProvider;
public AudioProvider AudioProvider;
private void Start() private void Start()
{ {
Application.targetFrameRate = 60;
StartGame("Kirby.nes"); StartGame("Kirby.nes");
} }
@ -50,8 +51,9 @@ namespace AxibugEmuOnline.Client
var screenBuffer = m_nesIns.ppu.GetScreenPtr(); var screenBuffer = m_nesIns.ppu.GetScreenPtr();
var lineColorMode = m_nesIns.ppu.GetLineColorMode(); var lineColorMode = m_nesIns.ppu.GetLineColorMode();
VideoProvider.SetDrawData(screenBuffer, lineColorMode, 256, 240); VideoProvider.SetDrawData(screenBuffer, lineColorMode, 256, 240);
AudioProvider.ProcessSound(m_nesIns);
} }
} }
} }

View File

@ -1,5 +1,146 @@
%YAML 1.1 %YAML 1.1
%TAG !u! tag:unity3d.com,2011: %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 --- !u!1 &4232056520112715746
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -174,6 +315,7 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: m_Children:
- {fileID: 4232056520112715745} - {fileID: 4232056520112715745}
- {fileID: 4785916497946256520}
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 0 m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@ -190,6 +332,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
VideoProvider: {fileID: 4232056520112715744} VideoProvider: {fileID: 4232056520112715744}
AudioProvider: {fileID: 9003897287163669553}
--- !u!1 &4232056521759880276 --- !u!1 &4232056521759880276
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@ -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

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ffe34aaf87e4b9942b4c2ac05943d444
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 8400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +1,7 @@
using AxibugEmuOnline.Client.Assets.Script.NesEmulator; using AxibugEmuOnline.Client.Assets.Script.NesEmulator;
using Codice.CM.Client.Differences;
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using UnityEngine; using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.UI; using UnityEngine.UI;
using VirtualNes.Core; using VirtualNes.Core;
@ -17,23 +11,29 @@ namespace AxibugEmuOnline.Client
{ {
public RawImage Image; public RawImage Image;
private Color32[] wrapTexBuffer; private UInt32[] wrapTexBuffer;
private IntPtr wrapTexBufferPointer; private IntPtr wrapTexBufferPointer;
private Texture2D wrapTex; private Texture2D wrapTex;
private int TexBufferSize;
private uint[] pPal;
public void SetDrawData(byte[] screenData, byte[] lineColorMode, int screenWidth, int screenHeight) public void SetDrawData(byte[] screenData, byte[] lineColorMode, int screenWidth, int screenHeight)
{ {
if (wrapTex == null) if (wrapTex == null)
{ {
wrapTex = new Texture2D(screenWidth, screenHeight, TextureFormat.BGRA32, false); 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); GCHandle handle = GCHandle.Alloc(wrapTexBuffer, GCHandleType.Pinned);
// 获取数组的指针 // 获取数组的指针
wrapTexBufferPointer = handle.AddrOfPinnedObject(); wrapTexBufferPointer = handle.AddrOfPinnedObject();
Image.texture = wrapTex;
pPal = PaletteDefine.m_cnPalette[0];
TexBufferSize = wrapTexBuffer.Length * 4;
} }
uint[] pPal;
int pScn = 0; int pScn = 0;
int width; int width;
@ -42,41 +42,26 @@ namespace AxibugEmuOnline.Client
for (int line = 0; line < screenHeight; line++) 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; width = screenWidth;
while (width > 0) while (width > 0)
{ {
var edx = screenData[pScn + 8]; var edx = screenData[pScn + 8];
byte index = (byte)(edx & 0xFF); int index = edx & 0xFF;
var colorData = pPal[index]; var colorData = pPal[index];
var rawData = BitConverter.GetBytes(colorData); Dst[pDst] = 0xFF000000 | colorData;
Dst[pDst] = new Color32(rawData[0], rawData[1], rawData[2], 255);
pScn += 1; pScn += 1;
pDst += 1; pDst += 1;
width -= 1; width -= 1;
} }
pScn += PPU.SCREEN_WIDTH - screenWidth; pScn += 16;// PPU.SCREEN_WIDTH - screenWidth;
} }
//wrapTex.SetPixels32(wrapTexBuffer); wrapTex.LoadRawTextureData(wrapTexBufferPointer, TexBufferSize);
wrapTex.LoadRawTextureData(wrapTexBufferPointer, screenWidth * screenHeight * 4);
wrapTex.Apply(); wrapTex.Apply();
Graphics.Blit(wrapTex, Image.mainTexture as RenderTexture);
} }
} }
} }

View File

@ -1,4 +1,3 @@
using System;
using System.Threading; using System.Threading;
public class RingBuffer<T> public class RingBuffer<T>

View File

@ -0,0 +1,11 @@
using VirtualNes.Core;
public class SoundBuffer : RingBuffer<byte>, ISoundDataBuffer
{
public SoundBuffer(int capacity) : base(capacity) { }
public void WriteByte(byte value)
{
Write(value);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c5f491c577bed63478340426f7698563
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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; using VirtualNes.Core.Debug;
namespace VirtualNes.Core namespace VirtualNes.Core
@ -7,6 +12,20 @@ namespace VirtualNes.Core
{ {
public const uint QUEUE_LENGTH = 8192; 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 NES nes;
private byte exsound_select; private byte exsound_select;
private APU_INTERNAL @internal = new APU_INTERNAL(); private APU_INTERNAL @internal = new APU_INTERNAL();
@ -41,8 +60,319 @@ namespace VirtualNes.Core
m_bMute[i] = true; 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) internal void SyncDPCM(int cycles)

View File

@ -1,7 +1,32 @@
using Codice.CM.Client.Differences;
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace VirtualNes.Core namespace VirtualNes.Core
{ {
public class APU_INTERNAL : APU_INTERFACE 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] private static int[] vbl_length = new int[32]
{ {
5, 127, 10, 1, 19, 2, 40, 3, 5, 127, 10, 1, 19, 2, 40, 3,
@ -37,9 +62,23 @@ namespace VirtualNes.Core
private NOISE ch3 = new NOISE(); private NOISE ch3 = new NOISE();
private DPCM ch4 = new DPCM(); private DPCM ch4 = new DPCM();
// Sound
private float cpu_clock;
private int sampling_rate;
private int cycle_rate;
// $4015 Reg // $4015 Reg
private byte reg4015, sync_reg4015; 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) public void SetParent(NES parent)
{ {
nes = parent; nes = parent;
@ -127,24 +166,738 @@ namespace VirtualNes.Core
public override void Reset(float fClock, int nRate) 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) 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) 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) public override int Process(int channel)
{ {
//todo : 实现 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; 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))
{
// 娗嶲<E5A897>
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) internal byte SyncRead(ushort addr)
{ {

View File

@ -12,7 +12,7 @@
public int phaseacc; public int phaseacc;
public int output; public int output;
ushort address, cache_addr; public ushort address, cache_addr;
public int dmalength, cache_dmalength; public int dmalength, cache_dmalength;
public int dpcm_output_real, dpcm_output_fake, dpcm_output_old, dpcm_output_offset; public int dpcm_output_real, dpcm_output_fake, dpcm_output_old, dpcm_output_offset;

View File

@ -1,4 +1,6 @@
namespace VirtualNes.Core using System;
namespace VirtualNes.Core
{ {
public class NOISE public class NOISE
{ {
@ -32,5 +34,35 @@
public byte sync_holdnote; public byte sync_holdnote;
public byte dummy1; public byte dummy1;
public int sync_len_count; 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;
}
} }
} }

View File

@ -1,4 +1,6 @@
namespace VirtualNes.Core using System;
namespace VirtualNes.Core
{ {
public class TRIANGLE public class TRIANGLE
{ {
@ -25,5 +27,28 @@
// public byte dummy1; // public byte dummy1;
public int sync_len_count; public int sync_len_count;
public int sync_lin_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;
}
} }
} }

View File

@ -45,7 +45,7 @@ namespace VirtualNes.Core
private APU apu; private APU apu;
internal R6502 R = new R6502(); internal R6502 R = new R6502();
private byte[] ZN_Table = new byte[256]; private byte[] ZN_Table = new byte[256];
private Memory<byte> STACK; private ByteArrayRef STACK;
public CPU(NES parent) public CPU(NES parent)
{ {
@ -1361,12 +1361,12 @@ namespace VirtualNes.Core
private byte POP() private byte POP()
{ {
return STACK.Span[(++R.S) & 0xFF]; return STACK[(++R.S) & 0xFF];
} }
private void PUSH(byte V) private void PUSH(byte V)
{ {
STACK.Span[(R.S--) & 0xFF] = V; STACK[(R.S--) & 0xFF] = V;
} }
private void SEI() private void SEI()
@ -2010,7 +2010,7 @@ namespace VirtualNes.Core
DMA_cycles = 0; DMA_cycles = 0;
// STACK quick access // STACK quick access
STACK = new Memory<byte>(MMU.RAM, 0x0100, MMU.RAM.Length - 0x100); STACK = new ByteArrayRef(MMU.RAM, 0x0100, MMU.RAM.Length - 0x100);
// Zero/Negative FLAG // Zero/Negative FLAG
ZN_Table[0] = Z_FLAG; ZN_Table[0] = Z_FLAG;

View File

@ -6,7 +6,23 @@
private int m_offset; private int m_offset;
private int m_length; 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) 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_rawArray = array;
m_offset = offset; m_offset = offset;

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4585a754599519b48bfe50294600818f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -9,6 +9,10 @@ namespace VirtualNes.Core
{ {
Array.Clear(array, 0, array.Length); 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) public static void memset(byte[] array, byte value, int length)
{ {

View File

@ -3,7 +3,7 @@
public class CfgSound public class CfgSound
{ {
public bool bEnable { get; set; } = true; 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 nBits { get; set; } = 8;
public int nBufferSize { get; set; } = 4; public int nBufferSize { get; set; } = 4;
public int nFilterType { get; set; } = 0; public int nFilterType { get; set; } = 0;