378 lines
10 KiB
C++
378 lines
10 KiB
C++
//============================================================================
|
|
//
|
|
// SSSS tt lll lll
|
|
// SS SS tt ll ll
|
|
// SS tttttt eeee ll ll aaaa
|
|
// SSSS tt ee ee ll ll aa
|
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
// SS SS tt ee ll ll aa aa
|
|
// SSSS ttt eeeee llll llll aaaaa
|
|
//
|
|
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
|
|
// and the Stella Team
|
|
//
|
|
// See the file "License.txt" for information on usage and redistribution of
|
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
//
|
|
// $Id: TIASnd.cxx 3239 2015-12-29 19:22:46Z stephena $
|
|
//============================================================================
|
|
|
|
#include "TIATables.h"
|
|
#include "TIASnd.h"
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
TIASound::TIASound(int outputFrequency)
|
|
: myChannelMode(Hardware2Stereo),
|
|
myOutputFrequency(outputFrequency),
|
|
myVolumePercentage(100)
|
|
{
|
|
reset();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void TIASound::reset()
|
|
{
|
|
// Fill the polynomials
|
|
polyInit(Bit4, 4, 4, 3);
|
|
polyInit(Bit5, 5, 5, 3);
|
|
polyInit(Bit9, 9, 9, 5);
|
|
|
|
// Initialize instance variables
|
|
for(int chan = 0; chan <= 1; ++chan)
|
|
{
|
|
myVolume[chan] = 0;
|
|
myDivNCnt[chan] = 0;
|
|
myDivNMax[chan] = 0;
|
|
myDiv3Cnt[chan] = 3;
|
|
myAUDC[chan] = 0;
|
|
myAUDF[chan] = 0;
|
|
myAUDV[chan] = 0;
|
|
myP4[chan] = 0;
|
|
myP5[chan] = 0;
|
|
myP9[chan] = 0;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void TIASound::outputFrequency(int freq)
|
|
{
|
|
myOutputFrequency = freq;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
std::string TIASound::channels(unsigned int hardware, bool stereo)
|
|
{
|
|
if(hardware == 1)
|
|
myChannelMode = Hardware1;
|
|
else
|
|
myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono;
|
|
|
|
switch(myChannelMode)
|
|
{
|
|
case Hardware1: return "Hardware1";
|
|
case Hardware2Mono: return "Hardware2Mono";
|
|
case Hardware2Stereo: return "Hardware2Stereo";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void TIASound::set(unsigned short address, unsigned char value)
|
|
{
|
|
int chan = ~address & 0x1;
|
|
switch(address)
|
|
{
|
|
case TIARegister::AUDC0:
|
|
case TIARegister::AUDC1:
|
|
myAUDC[chan] = value & 0x0f;
|
|
break;
|
|
|
|
case TIARegister::AUDF0:
|
|
case TIARegister::AUDF1:
|
|
myAUDF[chan] = value & 0x1f;
|
|
break;
|
|
|
|
case TIARegister::AUDV0:
|
|
case TIARegister::AUDV1:
|
|
myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
unsigned short newVal = 0;
|
|
|
|
// An AUDC value of 0 is a special case
|
|
if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5)
|
|
{
|
|
// Indicate the clock is zero so no processing will occur,
|
|
// and set the output to the selected volume
|
|
newVal = 0;
|
|
myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise calculate the 'divide by N' value
|
|
newVal = myAUDF[chan] + 1;
|
|
|
|
// If bits 2 & 3 are set, then multiply the 'div by n' count by 3
|
|
if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3)
|
|
newVal *= 3;
|
|
}
|
|
|
|
// Only reset those channels that have changed
|
|
if(newVal != myDivNMax[chan])
|
|
{
|
|
// Reset the divide by n counters
|
|
myDivNMax[chan] = newVal;
|
|
|
|
// If the channel is now volume only or was volume only,
|
|
// reset the counter (otherwise let it complete the previous)
|
|
if ((myDivNCnt[chan] == 0) || (newVal == 0))
|
|
myDivNCnt[chan] = newVal;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
unsigned char TIASound::get(unsigned short address) const
|
|
{
|
|
switch(address)
|
|
{
|
|
case TIARegister::AUDC0: return myAUDC[0];
|
|
case TIARegister::AUDC1: return myAUDC[1];
|
|
case TIARegister::AUDF0: return myAUDF[0];
|
|
case TIARegister::AUDF1: return myAUDF[1];
|
|
case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT;
|
|
case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void TIASound::volume(unsigned int percent)
|
|
{
|
|
if(percent <= 100)
|
|
myVolumePercentage = percent;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf)
|
|
{
|
|
// Make temporary local copy
|
|
unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1];
|
|
unsigned char p5_0 = myP5[0], p5_1 = myP5[1];
|
|
unsigned char div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1];
|
|
short v0 = myVolume[0], v1 = myVolume[1];
|
|
|
|
// Take external volume into account
|
|
short audv0 = (myAUDV[0] * myVolumePercentage) / 100,
|
|
audv1 = (myAUDV[1] * myVolumePercentage) / 100;
|
|
|
|
// Loop until the sample buffer is full
|
|
while(samples > 0)
|
|
{
|
|
// Process channel 0
|
|
if (div_n_cnt0 > 1)
|
|
{
|
|
div_n_cnt0--;
|
|
}
|
|
else if (div_n_cnt0 == 1)
|
|
{
|
|
int prev_bit5 = Bit5[p5_0];
|
|
div_n_cnt0 = myDivNMax[0];
|
|
|
|
// The P5 counter has multiple uses, so we increment it here
|
|
p5_0++;
|
|
if (p5_0 == POLY5_SIZE)
|
|
p5_0 = 0;
|
|
|
|
// Check clock modifier for clock tick
|
|
if ((audc0 & 0x02) == 0 ||
|
|
((audc0 & 0x01) == 0 && Div31[p5_0]) ||
|
|
((audc0 & 0x01) == 1 && Bit5[p5_0]) ||
|
|
((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5))
|
|
{
|
|
if (audc0 & 0x04) // Pure modified clock selected
|
|
{
|
|
if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
|
|
{
|
|
if ( Bit5[p5_0] != prev_bit5 )
|
|
{
|
|
myDiv3Cnt[0]--;
|
|
if ( !myDiv3Cnt[0] )
|
|
{
|
|
myDiv3Cnt[0] = 3;
|
|
v0 = v0 ? 0 : audv0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the output was set turn it off, else turn it on
|
|
v0 = v0 ? 0 : audv0;
|
|
}
|
|
}
|
|
else if (audc0 & 0x08) // Check for p5/p9
|
|
{
|
|
if (audc0 == POLY9) // Check for poly9
|
|
{
|
|
// Increase the poly9 counter
|
|
myP9[0]++;
|
|
if (myP9[0] == POLY9_SIZE)
|
|
myP9[0] = 0;
|
|
|
|
v0 = Bit9[myP9[0]] ? audv0 : 0;
|
|
}
|
|
else if ( audc0 & 0x02 )
|
|
{
|
|
v0 = (v0 || audc0 & 0x01) ? 0 : audv0;
|
|
}
|
|
else // Must be poly5
|
|
{
|
|
v0 = Bit5[p5_0] ? audv0 : 0;
|
|
}
|
|
}
|
|
else // Poly4 is the only remaining option
|
|
{
|
|
// Increase the poly4 counter
|
|
myP4[0]++;
|
|
if (myP4[0] == POLY4_SIZE)
|
|
myP4[0] = 0;
|
|
|
|
v0 = Bit4[myP4[0]] ? audv0 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process channel 1
|
|
if (div_n_cnt1 > 1)
|
|
{
|
|
div_n_cnt1--;
|
|
}
|
|
else if (div_n_cnt1 == 1)
|
|
{
|
|
int prev_bit5 = Bit5[p5_1];
|
|
|
|
div_n_cnt1 = myDivNMax[1];
|
|
|
|
// The P5 counter has multiple uses, so we increment it here
|
|
p5_1++;
|
|
if (p5_1 == POLY5_SIZE)
|
|
p5_1 = 0;
|
|
|
|
// Check clock modifier for clock tick
|
|
if ((audc1 & 0x02) == 0 ||
|
|
((audc1 & 0x01) == 0 && Div31[p5_1]) ||
|
|
((audc1 & 0x01) == 1 && Bit5[p5_1]) ||
|
|
((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5))
|
|
{
|
|
if (audc1 & 0x04) // Pure modified clock selected
|
|
{
|
|
if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
|
|
{
|
|
if ( Bit5[p5_1] != prev_bit5 )
|
|
{
|
|
myDiv3Cnt[1]--;
|
|
if ( ! myDiv3Cnt[1] )
|
|
{
|
|
myDiv3Cnt[1] = 3;
|
|
v1 = v1 ? 0 : audv1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the output was set turn it off, else turn it on
|
|
v1 = v1 ? 0 : audv1;
|
|
}
|
|
}
|
|
else if (audc1 & 0x08) // Check for p5/p9
|
|
{
|
|
if (audc1 == POLY9) // Check for poly9
|
|
{
|
|
// Increase the poly9 counter
|
|
myP9[1]++;
|
|
if (myP9[1] == POLY9_SIZE)
|
|
myP9[1] = 0;
|
|
|
|
v1 = Bit9[myP9[1]] ? audv1 : 0;
|
|
}
|
|
else if ( audc1 & 0x02 )
|
|
{
|
|
v1 = (v1 || audc1 & 0x01) ? 0 : audv1;
|
|
}
|
|
else // Must be poly5
|
|
{
|
|
v1 = Bit5[p5_1] ? audv1 : 0;
|
|
}
|
|
}
|
|
else // Poly4 is the only remaining option
|
|
{
|
|
// Increase the poly4 counter
|
|
myP4[1]++;
|
|
if (myP4[1] == POLY4_SIZE)
|
|
myP4[1] = 0;
|
|
|
|
v1 = Bit4[myP4[1]] ? audv1 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
short byte = v0 + v1;
|
|
switch(myChannelMode)
|
|
{
|
|
case Hardware2Mono: // mono sampling with 2 hardware channels
|
|
*(buffer++) = byte;
|
|
*(buffer++) = byte;
|
|
samples--;
|
|
break;
|
|
|
|
case Hardware2Stereo: // stereo sampling with 2 hardware channels
|
|
*(buffer++) = v0;
|
|
*(buffer++) = v1;
|
|
samples--;
|
|
break;
|
|
|
|
case Hardware1: // mono/stereo sampling with only 1 hardware channel
|
|
*(buffer++) = (v0 + v1) >> 1;
|
|
samples--;
|
|
break;
|
|
}
|
|
|
|
if (oscBuf!=NULL) {
|
|
oscBuf[0]->data[oscBuf[0]->needle++]=v0;
|
|
oscBuf[1]->data[oscBuf[1]->needle++]=v1;
|
|
}
|
|
}
|
|
|
|
// Save for next round
|
|
myP5[0] = p5_0;
|
|
myP5[1] = p5_1;
|
|
myVolume[0] = v0;
|
|
myVolume[1] = v1;
|
|
myDivNCnt[0] = div_n_cnt0;
|
|
myDivNCnt[1] = div_n_cnt1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void TIASound::polyInit(unsigned char* poly, int size, int f0, int f1)
|
|
{
|
|
int mask = (1 << size) - 1, x = mask;
|
|
|
|
for(int i = 0; i < mask; i++)
|
|
{
|
|
int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01;
|
|
int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01;
|
|
poly[i] = x & 1;
|
|
// calculate next bit
|
|
x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) );
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
const unsigned char TIASound::Div31[POLY5_SIZE] = {
|
|
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
|
|
};
|