Delete TIASound.cs
This commit is contained in:
parent
e98fb0c058
commit
444f90cde0
|
@ -1,361 +0,0 @@
|
||||||
/*
|
|
||||||
* TIASound.cs
|
|
||||||
*
|
|
||||||
* Sound emulation for the 2600. Based upon TIASound © 1997 by Ron Fries.
|
|
||||||
*
|
|
||||||
* Copyright © 2003, 2004 Mike Murphy
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
|
||||||
/* */
|
|
||||||
/* License Information and Copyright Notice */
|
|
||||||
/* ======================================== */
|
|
||||||
/* */
|
|
||||||
/* TiaSound is Copyright(c) 1997 by Ron Fries */
|
|
||||||
/* */
|
|
||||||
/* This library is free software; you can redistribute it and/or modify it */
|
|
||||||
/* under the terms of version 2 of the GNU Library General Public License */
|
|
||||||
/* as published by the Free Software Foundation. */
|
|
||||||
/* */
|
|
||||||
/* This library is distributed in the hope that it will be useful, but */
|
|
||||||
/* WITHOUT ANY WARRANTY; without even the implied warranty of */
|
|
||||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library */
|
|
||||||
/* General Public License for more details. */
|
|
||||||
/* To obtain a copy of the GNU Library General Public License, write to the */
|
|
||||||
/* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
|
||||||
/* */
|
|
||||||
/* Any permitted reproduction of these routines, in whole or in part, must */
|
|
||||||
/* bear this legend. */
|
|
||||||
/* */
|
|
||||||
/*****************************************************************************/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace EMU7800.Core
|
|
||||||
{
|
|
||||||
public sealed class TIASound
|
|
||||||
{
|
|
||||||
#region Constants and Tables
|
|
||||||
|
|
||||||
// Clock Source Clock Modifier Source Pattern
|
|
||||||
const int
|
|
||||||
SET_TO_1 = 0x00, // 0 0 0 0 3.58 Mhz/114 none (pure) none
|
|
||||||
//POLY4 = 0x01, // 0 0 0 1 3.58 Mhz/114 none (pure) 4-bit poly
|
|
||||||
//DIV31_POLY4 = 0x02, // 0 0 1 0 3.58 Mhz/114 divide by 31 4-bit poly
|
|
||||||
//POLY5_POLY4 = 0x03, // 0 0 1 1 3.58 Mhz/114 5-bit poly 4-bit poly
|
|
||||||
//PURE = 0x04, // 0 1 0 0 3.58 Mhz/114 none (pure) pure (~Q)
|
|
||||||
//PURE2 = 0x05, // 0 1 0 1 3.58 Mhz/114 none (pure) pure (~Q)
|
|
||||||
//DIV31_PURE = 0x06, // 0 1 1 0 3.58 Mhz/114 divide by 31 pure (~Q)
|
|
||||||
//POLY5_2 = 0x07, // 0 1 1 1 3.58 Mhz/114 5-bit poly pure (~Q)
|
|
||||||
POLY9 = 0x08; // 1 0 0 0 3.58 Mhz/114 none (pure) 9-bit poly
|
|
||||||
//POLY5 = 0x09, // 1 0 0 1 3.58 Mhz/114 none (pure) 5-bit poly
|
|
||||||
//DIV31_POLY5 = 0x0a, // 1 0 1 0 3.58 Mhz/114 divide by 31 5-bit poly
|
|
||||||
//POLY5_POLY5 = 0x0b, // 1 0 1 1 3.58 Mhz/114 5-bit poly 5-bit poly
|
|
||||||
//DIV3_PURE = 0x0c, // 1 1 0 0 1.19 Mhz/114 none (pure) pure (~Q)
|
|
||||||
//DIV3_PURE2 = 0x0d, // 1 1 0 1 1.19 Mhz/114 none (pure) pure (~Q)
|
|
||||||
//DIV93_PURE = 0x0e, // 1 1 1 0 1.19 Mhz/114 divide by 31 pure (~Q)
|
|
||||||
//DIV3_POLY5 = 0x0f; // 1 1 1 1 1.19 Mhz/114 5-bit poly pure (~Q)
|
|
||||||
|
|
||||||
const int
|
|
||||||
AUDC0 = 0x15, // audio control 0 (D3-0)
|
|
||||||
AUDC1 = 0x16, // audio control 1 (D4-0)
|
|
||||||
AUDF0 = 0x17, // audio frequency 0 (D4-0)
|
|
||||||
AUDF1 = 0x18, // audio frequency 1 (D3-0)
|
|
||||||
AUDV0 = 0x19, // audio volume 0 (D3-0)
|
|
||||||
AUDV1 = 0x1a; // audio volume 1 (D3-0)
|
|
||||||
|
|
||||||
// The 4bit and 5bit patterns are the identical ones used in the tia chip.
|
|
||||||
readonly byte[] Bit4 = new byte[] { 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0 }; // 2^4 - 1 = 15
|
|
||||||
readonly byte[] Bit5 = new byte[] { 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1 }; // 2^5 - 1 = 31
|
|
||||||
|
|
||||||
// [Ron] treated the 'Div by 31' counter as another polynomial because of
|
|
||||||
// the way it operates. It does not have a 50% duty cycle, but instead
|
|
||||||
// has a 13:18 ratio (of course, 13+18 = 31). This could also be
|
|
||||||
// implemented by using counters.
|
|
||||||
readonly byte[] Div31 = new byte[] { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
||||||
|
|
||||||
// Rather than have a table with 511 entries, I use a random number
|
|
||||||
readonly byte[] Bit9 = new byte[511]; // 2^9 - 1 = 511
|
|
||||||
|
|
||||||
readonly int[] P4 = new int[2]; // Position counter for the 4-bit POLY array
|
|
||||||
readonly int[] P5 = new int[2]; // Position counter for the 5-bit POLY array
|
|
||||||
readonly int[] P9 = new int[2]; // Position counter for the 9-bit POLY array
|
|
||||||
|
|
||||||
readonly int[] DivByNCounter = new int[2]; // Divide by n counter, one for each channel
|
|
||||||
readonly int[] DivByNMaximum = new int[2]; // Divide by n maximum, one for each channel
|
|
||||||
|
|
||||||
readonly int _cpuClocksPerSample;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Object State
|
|
||||||
|
|
||||||
readonly MachineBase M;
|
|
||||||
|
|
||||||
// The TIA Sound registers
|
|
||||||
readonly byte[] AUDC = new byte[2];
|
|
||||||
readonly byte[] AUDF = new byte[2];
|
|
||||||
readonly byte[] AUDV = new byte[2];
|
|
||||||
|
|
||||||
// The last output volume for each channel
|
|
||||||
readonly byte[] OutputVol = new byte[2];
|
|
||||||
|
|
||||||
// Used to determine how much sound to render
|
|
||||||
ulong LastUpdateCPUClock;
|
|
||||||
|
|
||||||
int BufferIndex;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Members
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
for (var chan = 0; chan < 2; chan++)
|
|
||||||
{
|
|
||||||
OutputVol[chan] = 0;
|
|
||||||
DivByNCounter[chan] = 0;
|
|
||||||
DivByNMaximum[chan] = 0;
|
|
||||||
AUDC[chan] = 0;
|
|
||||||
AUDF[chan] = 0;
|
|
||||||
AUDV[chan] = 0;
|
|
||||||
P4[chan] = 0;
|
|
||||||
P5[chan] = 0;
|
|
||||||
P9[chan] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartFrame()
|
|
||||||
{
|
|
||||||
LastUpdateCPUClock = M.CPU.Clock;
|
|
||||||
BufferIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndFrame()
|
|
||||||
{
|
|
||||||
RenderSamples(M.FrameBuffer.SoundBufferByteLength - BufferIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(ushort addr, byte data)
|
|
||||||
{
|
|
||||||
if (M.CPU.Clock > LastUpdateCPUClock)
|
|
||||||
{
|
|
||||||
var updCPUClocks = (int)(M.CPU.Clock - LastUpdateCPUClock);
|
|
||||||
var samples = updCPUClocks / _cpuClocksPerSample;
|
|
||||||
RenderSamples(samples);
|
|
||||||
LastUpdateCPUClock += (ulong)(samples * _cpuClocksPerSample);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte chan;
|
|
||||||
|
|
||||||
switch (addr)
|
|
||||||
{
|
|
||||||
case AUDC0:
|
|
||||||
AUDC[0] = (byte)(data & 0x0f);
|
|
||||||
chan = 0;
|
|
||||||
break;
|
|
||||||
case AUDC1:
|
|
||||||
AUDC[1] = (byte)(data & 0x0f);
|
|
||||||
chan = 1;
|
|
||||||
break;
|
|
||||||
case AUDF0:
|
|
||||||
AUDF[0] = (byte)(data & 0x1f);
|
|
||||||
chan = 0;
|
|
||||||
break;
|
|
||||||
case AUDF1:
|
|
||||||
AUDF[1] = (byte)(data & 0x1f);
|
|
||||||
chan = 1;
|
|
||||||
break;
|
|
||||||
case AUDV0:
|
|
||||||
AUDV[0] = (byte)(data & 0x0f);
|
|
||||||
chan = 0;
|
|
||||||
break;
|
|
||||||
case AUDV1:
|
|
||||||
AUDV[1] = (byte)(data & 0x0f);
|
|
||||||
chan = 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte new_divn_max;
|
|
||||||
|
|
||||||
if (AUDC[chan] == SET_TO_1)
|
|
||||||
{
|
|
||||||
// indicate the clock is zero so no process will occur
|
|
||||||
new_divn_max = 0;
|
|
||||||
// and set the output to the selected volume
|
|
||||||
OutputVol[chan] = AUDV[chan];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// otherwise calculate the 'divide by N' value
|
|
||||||
new_divn_max = (byte)(AUDF[chan] + 1);
|
|
||||||
// if bits D2 & D3 are set, then multiply the 'div by n' count by 3
|
|
||||||
if ((AUDC[chan] & 0x0c) == 0x0c)
|
|
||||||
{
|
|
||||||
new_divn_max *= 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only reset those channels that have changed
|
|
||||||
if (new_divn_max != DivByNMaximum[chan])
|
|
||||||
{
|
|
||||||
DivByNMaximum[chan] = new_divn_max;
|
|
||||||
|
|
||||||
// if the channel is now volume only or was volume only...
|
|
||||||
if (DivByNCounter[chan] == 0 || new_divn_max == 0)
|
|
||||||
{
|
|
||||||
// reset the counter (otherwise let it complete the previous)
|
|
||||||
DivByNCounter[chan] = new_divn_max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
private TIASound()
|
|
||||||
{
|
|
||||||
var r = new Random();
|
|
||||||
r.NextBytes(Bit9);
|
|
||||||
for (var i = 0; i < Bit9.Length; i++)
|
|
||||||
{
|
|
||||||
Bit9[i] &= 0x01;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TIASound(MachineBase m, int cpuClocksPerSample) : this()
|
|
||||||
{
|
|
||||||
if (m == null)
|
|
||||||
throw new ArgumentNullException("m");
|
|
||||||
if (cpuClocksPerSample <= 0)
|
|
||||||
throw new ArgumentException("cpuClocksPerSample must be positive.");
|
|
||||||
|
|
||||||
M = m;
|
|
||||||
_cpuClocksPerSample = cpuClocksPerSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Serialization Members
|
|
||||||
|
|
||||||
public TIASound(DeserializationContext input, MachineBase m, int cpuClocksPerSample) : this(m, cpuClocksPerSample)
|
|
||||||
{
|
|
||||||
if (input == null)
|
|
||||||
throw new ArgumentNullException("input");
|
|
||||||
|
|
||||||
input.CheckVersion(1);
|
|
||||||
Bit9 = input.ReadExpectedBytes(511);
|
|
||||||
P4 = input.ReadIntegers(2);
|
|
||||||
P5 = input.ReadIntegers(2);
|
|
||||||
P9 = input.ReadIntegers(2);
|
|
||||||
DivByNCounter = input.ReadIntegers(2);
|
|
||||||
DivByNMaximum = input.ReadIntegers(2);
|
|
||||||
AUDC = input.ReadExpectedBytes(2);
|
|
||||||
AUDF = input.ReadExpectedBytes(2);
|
|
||||||
AUDV = input.ReadExpectedBytes(2);
|
|
||||||
OutputVol = input.ReadExpectedBytes(2);
|
|
||||||
LastUpdateCPUClock = input.ReadUInt64();
|
|
||||||
BufferIndex = input.ReadInt32();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GetObjectData(SerializationContext output)
|
|
||||||
{
|
|
||||||
if (output == null)
|
|
||||||
throw new ArgumentNullException("output");
|
|
||||||
|
|
||||||
output.WriteVersion(1);
|
|
||||||
output.Write(Bit9);
|
|
||||||
output.Write(P4);
|
|
||||||
output.Write(P5);
|
|
||||||
output.Write(P9);
|
|
||||||
output.Write(DivByNCounter);
|
|
||||||
output.Write(DivByNMaximum);
|
|
||||||
output.Write(AUDC);
|
|
||||||
output.Write(AUDF);
|
|
||||||
output.Write(AUDV);
|
|
||||||
output.Write(OutputVol);
|
|
||||||
output.Write(LastUpdateCPUClock);
|
|
||||||
output.Write(BufferIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Helpers
|
|
||||||
|
|
||||||
void RenderSamples(int count)
|
|
||||||
{
|
|
||||||
for (; BufferIndex < M.FrameBuffer.SoundBufferByteLength && count-- > 0; BufferIndex++)
|
|
||||||
{
|
|
||||||
if (DivByNCounter[0] > 1)
|
|
||||||
{
|
|
||||||
DivByNCounter[0]--;
|
|
||||||
}
|
|
||||||
else if (DivByNCounter[0] == 1)
|
|
||||||
{
|
|
||||||
DivByNCounter[0] = DivByNMaximum[0];
|
|
||||||
ProcessChannel(0);
|
|
||||||
}
|
|
||||||
if (DivByNCounter[1] > 1)
|
|
||||||
{
|
|
||||||
DivByNCounter[1]--;
|
|
||||||
}
|
|
||||||
else if (DivByNCounter[1] == 1)
|
|
||||||
{
|
|
||||||
DivByNCounter[1] = DivByNMaximum[1];
|
|
||||||
ProcessChannel(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
M.FrameBuffer.SoundBuffer[BufferIndex] += (byte)(OutputVol[0] + OutputVol[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessChannel(int chan)
|
|
||||||
{
|
|
||||||
// the P5 counter has multiple uses, so we inc it here
|
|
||||||
if (++P5[chan] >= 31)
|
|
||||||
{ // POLY5 size: 2^5 - 1 = 31
|
|
||||||
P5[chan] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check clock modifier for clock tick
|
|
||||||
if ((AUDC[chan] & 0x02) == 0 ||
|
|
||||||
((AUDC[chan] & 0x01) == 0 && Div31[P5[chan]] == 1) ||
|
|
||||||
((AUDC[chan] & 0x01) == 1 && Bit5[P5[chan]] == 1))
|
|
||||||
{
|
|
||||||
if ((AUDC[chan] & 0x04) != 0)
|
|
||||||
{ // pure modified clock selected
|
|
||||||
OutputVol[chan] = (OutputVol[chan] != 0) ? (byte)0 : AUDV[chan];
|
|
||||||
}
|
|
||||||
else if ((AUDC[chan] & 0x08) != 0)
|
|
||||||
{ // check for poly5/poly9
|
|
||||||
if (AUDC[chan] == POLY9)
|
|
||||||
{ // check for poly9
|
|
||||||
if (++P9[chan] >= 511)
|
|
||||||
{ // poly9 size: 2^9 - 1 = 511
|
|
||||||
P9[chan] = 0;
|
|
||||||
}
|
|
||||||
OutputVol[chan] = (Bit9[P9[chan]] == 1) ? AUDV[chan] : (byte)0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // must be poly5
|
|
||||||
OutputVol[chan] = (Bit5[P5[chan]] == 1) ? AUDV[chan] : (byte)0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // poly4 is the only remaining possibility
|
|
||||||
if (++P4[chan] >= 15)
|
|
||||||
{ // POLY4 size: 2^4 - 1 = 15
|
|
||||||
P4[chan] = 0;
|
|
||||||
}
|
|
||||||
OutputVol[chan] = (Bit4[P4[chan]] == 1) ? AUDV[chan] : (byte)0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue