dev_4VirtualNes #23
@ -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));
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6a09b6a4cf4c2d4f994a13fd7e89d6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
public class RingBuffer<T>
|
||||
|
11
AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs
Normal file
11
AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs
Normal 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);
|
||||
}
|
||||
}
|
11
AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs.meta
Normal file
11
AxibugEmuOnline.Client/Assets/Script/SoundBuffer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5f491c577bed63478340426f7698563
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,4 +1,9 @@
|
||||
using System;
|
||||
using Codice.CM.Client.Differences;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
using UnityEngine;
|
||||
using VirtualNes.Core;
|
||||
using VirtualNes.Core.Debug;
|
||||
|
||||
namespace VirtualNes.Core
|
||||
@ -7,6 +12,20 @@ namespace VirtualNes.Core
|
||||
{
|
||||
public const uint QUEUE_LENGTH = 8192;
|
||||
|
||||
// Volume adjust
|
||||
// Internal sounds
|
||||
public const uint RECTANGLE_VOL = 0x0F0;
|
||||
public const uint TRIANGLE_VOL = 0x130;
|
||||
public const uint NOISE_VOL = 0x0C0;
|
||||
public const uint DPCM_VOL = 0x0F0;
|
||||
// Extra sounds
|
||||
public const uint VRC6_VOL = 0x0F0;
|
||||
public const uint VRC7_VOL = 0x130;
|
||||
public const uint FDS_VOL = 0x0F0;
|
||||
public const uint MMC5_VOL = 0x0F0;
|
||||
public const uint N106_VOL = 0x088;
|
||||
public const uint FME7_VOL = 0x130;
|
||||
|
||||
private NES nes;
|
||||
private byte exsound_select;
|
||||
private APU_INTERNAL @internal = new APU_INTERNAL();
|
||||
@ -41,8 +60,319 @@ namespace VirtualNes.Core
|
||||
m_bMute[i] = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void Dispose() { }
|
||||
|
||||
private int[] vol = new int[24];
|
||||
static double cutofftemp = (2.0 * 3.141592653579 * 40.0);
|
||||
static double tmp = 0.0;
|
||||
|
||||
public void Process(ISoundDataBuffer lpBuffer, uint dwSize)
|
||||
{
|
||||
int nBits = Supporter.Config.sound.nBits;
|
||||
uint dwLength = (uint)(dwSize / (nBits / 8));
|
||||
int output;
|
||||
QUEUEDATA q = default;
|
||||
uint writetime;
|
||||
|
||||
var pSoundBuf = m_SoundBuffer;
|
||||
int nCcount = 0;
|
||||
|
||||
int nFilterType = Supporter.Config.sound.nFilterType;
|
||||
|
||||
if (!Supporter.Config.sound.bEnable)
|
||||
{
|
||||
byte empty = (byte)(Supporter.Config.sound.nRate == 8 ? 128 : 0);
|
||||
for (int i = 0; i < dwSize; i++)
|
||||
lpBuffer.WriteByte(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
// Volume setup
|
||||
// 0:Master
|
||||
// 1:Rectangle 1
|
||||
// 2:Rectangle 2
|
||||
// 3:Triangle
|
||||
// 4:Noise
|
||||
// 5:DPCM
|
||||
// 6:VRC6
|
||||
// 7:VRC7
|
||||
// 8:FDS
|
||||
// 9:MMC5
|
||||
// 10:N106
|
||||
// 11:FME7
|
||||
MemoryUtility.ZEROMEMORY(vol, vol.Length);
|
||||
|
||||
var bMute = m_bMute;
|
||||
var nVolume = Supporter.Config.sound.nVolume;
|
||||
|
||||
int nMasterVolume = bMute[0] ? nVolume[0] : 0;
|
||||
|
||||
// Internal
|
||||
vol[0] = (int)(bMute[1] ? (RECTANGLE_VOL * nVolume[1] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[1] = (int)(bMute[2] ? (RECTANGLE_VOL * nVolume[2] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[2] = (int)(bMute[3] ? (TRIANGLE_VOL * nVolume[3] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[3] = (int)(bMute[4] ? (NOISE_VOL * nVolume[4] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[4] = (int)(bMute[5] ? (DPCM_VOL * nVolume[5] * nMasterVolume) / (100 * 100) : 0);
|
||||
|
||||
// VRC6
|
||||
vol[5] = (int)(bMute[6] ? (VRC6_VOL * nVolume[6] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[6] = (int)(bMute[7] ? (VRC6_VOL * nVolume[6] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[7] = (int)(bMute[8] ? (VRC6_VOL * nVolume[6] * nMasterVolume) / (100 * 100) : 0);
|
||||
|
||||
// VRC7
|
||||
vol[8] = (int)(bMute[6] ? (VRC7_VOL * nVolume[7] * nMasterVolume) / (100 * 100) : 0);
|
||||
|
||||
// FDS
|
||||
vol[9] = (int)(bMute[6] ? (FDS_VOL * nVolume[8] * nMasterVolume) / (100 * 100) : 0);
|
||||
|
||||
// MMC5
|
||||
vol[10] = (int)(bMute[6] ? (MMC5_VOL * nVolume[9] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[11] = (int)(bMute[7] ? (MMC5_VOL * nVolume[9] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[12] = (int)(bMute[8] ? (MMC5_VOL * nVolume[9] * nMasterVolume) / (100 * 100) : 0);
|
||||
|
||||
// N106
|
||||
vol[13] = (int)(bMute[6] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[14] = (int)(bMute[7] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[15] = (int)(bMute[8] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[16] = (int)(bMute[9] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[17] = (int)(bMute[10] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[18] = (int)(bMute[11] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[19] = (int)(bMute[12] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[20] = (int)(bMute[13] ? (N106_VOL * nVolume[10] * nMasterVolume) / (100 * 100) : 0);
|
||||
|
||||
// FME7
|
||||
vol[21] = (int)(bMute[6] ? (FME7_VOL * nVolume[11] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[22] = (int)(bMute[7] ? (FME7_VOL * nVolume[11] * nMasterVolume) / (100 * 100) : 0);
|
||||
vol[23] = (int)(bMute[8] ? (FME7_VOL * nVolume[11] * nMasterVolume) / (100 * 100) : 0);
|
||||
|
||||
// double cycle_rate = ((double)FRAME_CYCLES*60.0/12.0)/(double)Config.sound.nRate;
|
||||
double cycle_rate = (nes.nescfg.FrameCycles * 60.0 / 12.0) / Supporter.Config.sound.nRate;
|
||||
|
||||
// CPUサイクル数がループしてしまった時の対策処理
|
||||
if (elapsed_time > nes.cpu.GetTotalCycles())
|
||||
{
|
||||
QueueFlush();
|
||||
}
|
||||
|
||||
while ((dwLength--) != 0)
|
||||
{
|
||||
writetime = (uint)elapsed_time;
|
||||
|
||||
while (GetQueue((int)writetime, ref q))
|
||||
{
|
||||
WriteProcess(q.addr, q.data);
|
||||
}
|
||||
|
||||
while (GetExQueue((int)writetime, ref q))
|
||||
{
|
||||
WriteExProcess(q.addr, q.data);
|
||||
}
|
||||
|
||||
// 0-4:internal 5-7:VRC6 8:VRC7 9:FDS 10-12:MMC5 13-20:N106 21-23:FME7
|
||||
output = 0;
|
||||
output += @internal.Process(0) * vol[0];
|
||||
output += @internal.Process(1) * vol[1];
|
||||
output += @internal.Process(2) * vol[2];
|
||||
output += @internal.Process(3) * vol[3];
|
||||
output += @internal.Process(4) * vol[4];
|
||||
|
||||
if ((exsound_select & 0x01) != 0)
|
||||
{
|
||||
output += vrc6.Process(0) * vol[5];
|
||||
output += vrc6.Process(1) * vol[6];
|
||||
output += vrc6.Process(2) * vol[7];
|
||||
}
|
||||
if ((exsound_select & 0x02) != 0)
|
||||
{
|
||||
output += vrc7.Process(0) * vol[8];
|
||||
}
|
||||
if ((exsound_select & 0x04) != 0)
|
||||
{
|
||||
output += fds.Process(0) * vol[9];
|
||||
}
|
||||
if ((exsound_select & 0x08) != 0)
|
||||
{
|
||||
output += mmc5.Process(0) * vol[10];
|
||||
output += mmc5.Process(1) * vol[11];
|
||||
output += mmc5.Process(2) * vol[12];
|
||||
}
|
||||
if ((exsound_select & 0x10) != 0)
|
||||
{
|
||||
output += n106.Process(0) * vol[13];
|
||||
output += n106.Process(1) * vol[14];
|
||||
output += n106.Process(2) * vol[15];
|
||||
output += n106.Process(3) * vol[16];
|
||||
output += n106.Process(4) * vol[17];
|
||||
output += n106.Process(5) * vol[18];
|
||||
output += n106.Process(6) * vol[19];
|
||||
output += n106.Process(7) * vol[20];
|
||||
}
|
||||
if ((exsound_select & 0x20) != 0)
|
||||
{
|
||||
fme7.Process(3); // Envelope & Noise
|
||||
output += fme7.Process(0) * vol[21];
|
||||
output += fme7.Process(1) * vol[22];
|
||||
output += fme7.Process(2) * vol[23];
|
||||
}
|
||||
|
||||
output >>= 8;
|
||||
|
||||
if (nFilterType == 1)
|
||||
{
|
||||
//ローパスフィルターTYPE 1(Simple)
|
||||
output = (lowpass_filter[0] + output) / 2;
|
||||
lowpass_filter[0] = output;
|
||||
}
|
||||
else if (nFilterType == 2)
|
||||
{
|
||||
//ローパスフィルターTYPE 2(Weighted type 1)
|
||||
output = (lowpass_filter[1] + lowpass_filter[0] + output) / 3;
|
||||
lowpass_filter[1] = lowpass_filter[0];
|
||||
lowpass_filter[0] = output;
|
||||
}
|
||||
else if (nFilterType == 3)
|
||||
{
|
||||
//ローパスフィルターTYPE 3(Weighted type 2)
|
||||
output = (lowpass_filter[2] + lowpass_filter[1] + lowpass_filter[0] + output) / 4;
|
||||
lowpass_filter[2] = lowpass_filter[1];
|
||||
lowpass_filter[1] = lowpass_filter[0];
|
||||
lowpass_filter[0] = output;
|
||||
}
|
||||
else if (nFilterType == 4)
|
||||
{
|
||||
//ローパスフィルターTYPE 4(Weighted type 3)
|
||||
output = (lowpass_filter[1] + lowpass_filter[0] * 2 + output) / 4;
|
||||
lowpass_filter[1] = lowpass_filter[0];
|
||||
lowpass_filter[0] = output;
|
||||
}
|
||||
// DC成分のカット(HPF TEST)
|
||||
{
|
||||
// static double cutoff = (2.0*3.141592653579*40.0/44100.0);
|
||||
double cutoff = cutofftemp / Supporter.Config.sound.nRate;
|
||||
double @in, @out;
|
||||
|
||||
@in = output;
|
||||
@out = (@in - tmp);
|
||||
tmp = tmp + cutoff * @out;
|
||||
|
||||
output = (int)@out;
|
||||
}
|
||||
|
||||
// Limit
|
||||
if (output > 0x7FFF)
|
||||
{
|
||||
output = 0x7FFF;
|
||||
}
|
||||
else if (output < -0x8000)
|
||||
{
|
||||
output = -0x8000;
|
||||
}
|
||||
|
||||
if (nBits != 8)
|
||||
{
|
||||
byte highByte = (byte)(output >> 8); // 获取高8位
|
||||
byte lowByte = (byte)(output & 0xFF); // 获取低8位
|
||||
lpBuffer.WriteByte(highByte);
|
||||
lpBuffer.WriteByte(lowByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
lpBuffer.WriteByte((byte)((output >> 8) ^ 0x80));
|
||||
}
|
||||
|
||||
if (nCcount < 0x0100)
|
||||
pSoundBuf[nCcount++] = (short)output;
|
||||
|
||||
// elapsedtime += cycle_rate;
|
||||
elapsed_time += cycle_rate;
|
||||
}
|
||||
|
||||
|
||||
if (elapsed_time > ((nes.nescfg.FrameCycles / 24) + nes.cpu.GetTotalCycles()))
|
||||
{
|
||||
elapsed_time = nes.cpu.GetTotalCycles();
|
||||
}
|
||||
if ((elapsed_time + (nes.nescfg.FrameCycles / 6)) < nes.cpu.GetTotalCycles())
|
||||
{
|
||||
elapsed_time = nes.cpu.GetTotalCycles();
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetExQueue(int writetime, ref QUEUEDATA ret)
|
||||
{
|
||||
if (exqueue.wrptr == exqueue.rdptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (exqueue.data[exqueue.rdptr].time <= writetime)
|
||||
{
|
||||
ret = exqueue.data[exqueue.rdptr];
|
||||
exqueue.rdptr++;
|
||||
exqueue.rdptr = (int)(exqueue.rdptr & (QUEUE_LENGTH - 1));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void QueueFlush()
|
||||
{
|
||||
while (queue.wrptr != queue.rdptr)
|
||||
{
|
||||
WriteProcess(queue.data[queue.rdptr].addr, queue.data[queue.rdptr].data);
|
||||
queue.rdptr++;
|
||||
queue.rdptr = (int)(queue.rdptr & (QUEUE_LENGTH - 1));
|
||||
}
|
||||
|
||||
while (exqueue.wrptr != exqueue.rdptr)
|
||||
{
|
||||
WriteExProcess(exqueue.data[exqueue.rdptr].addr, exqueue.data[exqueue.rdptr].data);
|
||||
exqueue.rdptr++;
|
||||
exqueue.rdptr = (int)(exqueue.rdptr & (QUEUE_LENGTH - 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteExProcess(ushort addr, byte data)
|
||||
{
|
||||
if ((exsound_select & 0x01) != 0)
|
||||
{
|
||||
vrc6.Write(addr, data);
|
||||
}
|
||||
if ((exsound_select & 0x02) != 0)
|
||||
{
|
||||
vrc7.Write(addr, data);
|
||||
}
|
||||
if ((exsound_select & 0x04) != 0)
|
||||
{
|
||||
fds.Write(addr, data);
|
||||
}
|
||||
if ((exsound_select & 0x08) != 0)
|
||||
{
|
||||
mmc5.Write(addr, data);
|
||||
}
|
||||
if ((exsound_select & 0x10) != 0)
|
||||
{
|
||||
if (addr == 0x0000)
|
||||
{
|
||||
byte dummy = n106.Read(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
n106.Write(addr, data);
|
||||
}
|
||||
}
|
||||
if ((exsound_select & 0x20) != 0)
|
||||
{
|
||||
fme7.Write(addr, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteProcess(ushort addr, byte data)
|
||||
{
|
||||
// $4018はVirtuaNES固有ポート
|
||||
if (addr >= 0x4000 && addr <= 0x401F)
|
||||
{
|
||||
@internal.Write(addr, data);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SyncDPCM(int cycles)
|
||||
|
@ -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))
|
||||
{
|
||||
// 娗嶲<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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace VirtualNes.Core
|
||||
using System;
|
||||
|
||||
namespace VirtualNes.Core
|
||||
{
|
||||
public class NOISE
|
||||
{
|
||||
@ -32,5 +34,35 @@
|
||||
public byte sync_holdnote;
|
||||
public byte dummy1;
|
||||
public int sync_len_count;
|
||||
|
||||
internal void ZeroMemory()
|
||||
{
|
||||
Array.Clear(reg, 0, reg.Length);
|
||||
|
||||
enable = 0;
|
||||
holdnote = 0;
|
||||
volume = 0;
|
||||
xor_tap = 0;
|
||||
shift_reg = 0;
|
||||
|
||||
phaseacc = 0;
|
||||
freq = 0;
|
||||
len_count = 0;
|
||||
nowvolume = 0;
|
||||
output = 0;
|
||||
|
||||
env_fixed = 0;
|
||||
env_decay = 0;
|
||||
env_count = 0;
|
||||
dummy0 = 0;
|
||||
env_vol = 0;
|
||||
|
||||
Array.Clear(sync_reg, 0, sync_reg.Length);
|
||||
sync_output_enable = 0;
|
||||
sync_enable = 0;
|
||||
sync_holdnote = 0;
|
||||
dummy1 = 0;
|
||||
sync_len_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace VirtualNes.Core
|
||||
using System;
|
||||
|
||||
namespace VirtualNes.Core
|
||||
{
|
||||
public class TRIANGLE
|
||||
{
|
||||
@ -25,5 +27,28 @@
|
||||
// public byte dummy1;
|
||||
public int sync_len_count;
|
||||
public int sync_lin_count;
|
||||
|
||||
internal void ZeroMemory()
|
||||
{
|
||||
Array.Clear(reg, 0, reg.Length);
|
||||
|
||||
enable = 0;
|
||||
holdnote = 0;
|
||||
counter_start = 0;
|
||||
dummy0 = 0;
|
||||
phaseacc = 0;
|
||||
freq = 0;
|
||||
len_count = 0;
|
||||
lin_count = 0;
|
||||
adder = 0;
|
||||
nowvolume = 0;
|
||||
Array.Clear(sync_reg, 0, sync_reg.Length);
|
||||
sync_enable = 0;
|
||||
sync_holdnote = 0;
|
||||
sync_counter_start = 0;
|
||||
|
||||
sync_len_count = 0;
|
||||
sync_lin_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4585a754599519b48bfe50294600818f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user