add TIA platform
currently there is a desync bug which remains to be fixed...
This commit is contained in:
parent
8c7e58b3d5
commit
573ce69dc7
|
@ -89,6 +89,8 @@ src/engine/platform/sound/c64/wave8580_PST.cc
|
||||||
src/engine/platform/sound/c64/wave8580_P_T.cc
|
src/engine/platform/sound/c64/wave8580_P_T.cc
|
||||||
src/engine/platform/sound/c64/wave8580__ST.cc
|
src/engine/platform/sound/c64/wave8580__ST.cc
|
||||||
|
|
||||||
|
src/engine/platform/sound/tia/TIASnd.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
||||||
src/engine/platform/sound/ymfm/ymfm_opm.cpp
|
src/engine/platform/sound/ymfm/ymfm_opm.cpp
|
||||||
src/engine/platform/sound/ymfm/ymfm_opn.cpp
|
src/engine/platform/sound/ymfm/ymfm_opn.cpp
|
||||||
|
@ -119,6 +121,7 @@ src/engine/platform/ym2610.cpp
|
||||||
src/engine/platform/ym2610ext.cpp
|
src/engine/platform/ym2610ext.cpp
|
||||||
src/engine/platform/ay.cpp
|
src/engine/platform/ay.cpp
|
||||||
src/engine/platform/ay8930.cpp
|
src/engine/platform/ay8930.cpp
|
||||||
|
src/engine/platform/tia.cpp
|
||||||
src/engine/platform/dummy.cpp)
|
src/engine/platform/dummy.cpp)
|
||||||
|
|
||||||
set(GUI_SOURCES
|
set(GUI_SOURCES
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "platform/ym2610ext.h"
|
#include "platform/ym2610ext.h"
|
||||||
#include "platform/ay.h"
|
#include "platform/ay.h"
|
||||||
#include "platform/ay8930.h"
|
#include "platform/ay8930.h"
|
||||||
|
#include "platform/tia.h"
|
||||||
#include "platform/dummy.h"
|
#include "platform/dummy.h"
|
||||||
#include "../ta-log.h"
|
#include "../ta-log.h"
|
||||||
|
|
||||||
|
@ -120,6 +121,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
||||||
case DIV_SYSTEM_AY8930:
|
case DIV_SYSTEM_AY8930:
|
||||||
dispatch=new DivPlatformAY8930;
|
dispatch=new DivPlatformAY8930;
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_TIA:
|
||||||
|
dispatch=new DivPlatformTIA;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logW("this system is not supported yet! using dummy platform.\n");
|
logW("this system is not supported yet! using dummy platform.\n");
|
||||||
dispatch=new DivPlatformDummy;
|
dispatch=new DivPlatformDummy;
|
||||||
|
|
372
src/engine/platform/sound/tia/TIASnd.cpp
Normal file
372
src/engine/platform/sound/tia/TIASnd.cpp
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
samples--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
};
|
186
src/engine/platform/sound/tia/TIASnd.h
Normal file
186
src/engine/platform/sound/tia/TIASnd.h
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// 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.hxx 3239 2015-12-29 19:22:46Z stephena $
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#ifndef TIASOUND_HXX
|
||||||
|
#define TIASOUND_HXX
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
This class implements a fairly accurate emulation of the TIA sound
|
||||||
|
hardware. This class uses code/ideas from z26 and MESS.
|
||||||
|
|
||||||
|
Currently, the sound generation routines work at 31400Hz only.
|
||||||
|
Resampling can be done by passing in a different output frequency.
|
||||||
|
|
||||||
|
@author Bradford W. Mott, Stephen Anthony, z26 and MESS teams
|
||||||
|
@version $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $
|
||||||
|
*/
|
||||||
|
class TIASound
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
Create a new TIA Sound object using the specified output frequency
|
||||||
|
*/
|
||||||
|
TIASound(int outputFrequency = 31400);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
Reset the sound emulation to its power-on state
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set the frequency output samples should be generated at
|
||||||
|
*/
|
||||||
|
void outputFrequency(int freq);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Selects the number of audio channels per sample. There are two factors
|
||||||
|
to consider: hardware capability and desired mixing.
|
||||||
|
|
||||||
|
@param hardware The number of channels supported by the sound system
|
||||||
|
@param stereo Whether to output the internal sound signals into 1
|
||||||
|
or 2 channels
|
||||||
|
|
||||||
|
@return Status of the channel configuration used
|
||||||
|
*/
|
||||||
|
std::string channels(unsigned int hardware, bool stereo);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
Sets the specified sound register to the given value
|
||||||
|
|
||||||
|
@param address Register address
|
||||||
|
@param value Value to store in the register
|
||||||
|
*/
|
||||||
|
void set(unsigned short address, unsigned char value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets the specified sound register's value
|
||||||
|
|
||||||
|
@param address Register address
|
||||||
|
*/
|
||||||
|
unsigned char get(unsigned short address) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create sound samples based on the current sound register settings
|
||||||
|
in the specified buffer. NOTE: If channels is set to stereo then
|
||||||
|
the buffer will need to be twice as long as the number of samples.
|
||||||
|
|
||||||
|
@param buffer The location to store generated samples
|
||||||
|
@param samples The number of samples to generate
|
||||||
|
*/
|
||||||
|
void process(short* buffer, unsigned int samples);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set the volume of the samples created (0-100)
|
||||||
|
*/
|
||||||
|
void volume(unsigned int percent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void polyInit(unsigned char* poly, int size, int f0, int f1);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Definitions for AUDCx (15, 16)
|
||||||
|
enum AUDCxRegister
|
||||||
|
{
|
||||||
|
SET_TO_1 = 0x00, // 0000
|
||||||
|
POLY4 = 0x01, // 0001
|
||||||
|
DIV31_POLY4 = 0x02, // 0010
|
||||||
|
POLY5_POLY4 = 0x03, // 0011
|
||||||
|
PURE1 = 0x04, // 0100
|
||||||
|
PURE2 = 0x05, // 0101
|
||||||
|
DIV31_PURE = 0x06, // 0110
|
||||||
|
POLY5_2 = 0x07, // 0111
|
||||||
|
POLY9 = 0x08, // 1000
|
||||||
|
POLY5 = 0x09, // 1001
|
||||||
|
DIV31_POLY5 = 0x0a, // 1010
|
||||||
|
POLY5_POLY5 = 0x0b, // 1011
|
||||||
|
DIV3_PURE = 0x0c, // 1100
|
||||||
|
DIV3_PURE2 = 0x0d, // 1101
|
||||||
|
DIV93_PURE = 0x0e, // 1110
|
||||||
|
POLY5_DIV3 = 0x0f // 1111
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
POLY4_SIZE = 0x000f,
|
||||||
|
POLY5_SIZE = 0x001f,
|
||||||
|
POLY9_SIZE = 0x01ff,
|
||||||
|
DIV3_MASK = 0x0c,
|
||||||
|
AUDV_SHIFT = 10 // shift 2 positions for AUDV,
|
||||||
|
// then another 8 for 16-bit sound
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ChannelMode {
|
||||||
|
Hardware2Mono, // mono sampling with 2 hardware channels
|
||||||
|
Hardware2Stereo, // stereo sampling with 2 hardware channels
|
||||||
|
Hardware1 // mono/stereo sampling with only 1 hardware channel
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Structures to hold the 6 tia sound control bytes
|
||||||
|
unsigned char myAUDC[2]; // AUDCx (15, 16)
|
||||||
|
unsigned char myAUDF[2]; // AUDFx (17, 18)
|
||||||
|
short myAUDV[2]; // AUDVx (19, 1A)
|
||||||
|
|
||||||
|
short myVolume[2]; // Last output volume for each channel
|
||||||
|
|
||||||
|
unsigned char myP4[2]; // Position pointer for the 4-bit POLY array
|
||||||
|
unsigned char myP5[2]; // Position pointer for the 5-bit POLY array
|
||||||
|
unsigned short myP9[2]; // Position pointer for the 9-bit POLY array
|
||||||
|
|
||||||
|
unsigned char myDivNCnt[2]; // Divide by n counter. one for each channel
|
||||||
|
unsigned char myDivNMax[2]; // Divide by n maximum, one for each channel
|
||||||
|
unsigned char myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode
|
||||||
|
|
||||||
|
ChannelMode myChannelMode;
|
||||||
|
int myOutputFrequency;
|
||||||
|
int myOutputCounter;
|
||||||
|
unsigned int myVolumePercentage;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialize the bit patterns for the polynomials (at runtime).
|
||||||
|
|
||||||
|
The 4bit and 5bit patterns are the identical ones used in the tia chip.
|
||||||
|
Though the patterns could be packed with 8 bits per byte, using only a
|
||||||
|
single bit per byte keeps the math simple, which is important for
|
||||||
|
efficient processing.
|
||||||
|
*/
|
||||||
|
unsigned char Bit4[POLY4_SIZE];
|
||||||
|
unsigned char Bit5[POLY5_SIZE];
|
||||||
|
unsigned char Bit9[POLY9_SIZE];
|
||||||
|
|
||||||
|
/*
|
||||||
|
The 'Div by 31' counter is treated 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.
|
||||||
|
*/
|
||||||
|
static const unsigned char Div31[POLY5_SIZE];
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Following constructors and assignment operators not supported
|
||||||
|
TIASound(const TIASound&) = delete;
|
||||||
|
TIASound(TIASound&&) = delete;
|
||||||
|
TIASound& operator=(const TIASound&) = delete;
|
||||||
|
TIASound& operator=(TIASound&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
219
src/engine/platform/sound/tia/TIATables.h
Normal file
219
src/engine/platform/sound/tia/TIATables.h
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// 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: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#ifndef TIA_TABLES_HXX
|
||||||
|
#define TIA_TABLES_HXX
|
||||||
|
|
||||||
|
enum TIABit {
|
||||||
|
P0Bit = 0x01, // Bit for Player 0
|
||||||
|
M0Bit = 0x02, // Bit for Missle 0
|
||||||
|
P1Bit = 0x04, // Bit for Player 1
|
||||||
|
M1Bit = 0x08, // Bit for Missle 1
|
||||||
|
BLBit = 0x10, // Bit for Ball
|
||||||
|
PFBit = 0x20, // Bit for Playfield
|
||||||
|
ScoreBit = 0x40, // Bit for Playfield score mode
|
||||||
|
PriorityBit = 0x80 // Bit for Playfield priority
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TIAColor {
|
||||||
|
BKColor = 0, // Color index for Background
|
||||||
|
PFColor = 1, // Color index for Playfield
|
||||||
|
P0Color = 2, // Color index for Player 0
|
||||||
|
P1Color = 3, // Color index for Player 1
|
||||||
|
M0Color = 4, // Color index for Missle 0
|
||||||
|
M1Color = 5, // Color index for Missle 1
|
||||||
|
BLColor = 6, // Color index for Ball
|
||||||
|
HBLANKColor = 7 // Color index for HMove blank area
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CollisionBit
|
||||||
|
{
|
||||||
|
Cx_M0P1 = 1 << 0, // Missle0 - Player1 collision
|
||||||
|
Cx_M0P0 = 1 << 1, // Missle0 - Player0 collision
|
||||||
|
Cx_M1P0 = 1 << 2, // Missle1 - Player0 collision
|
||||||
|
Cx_M1P1 = 1 << 3, // Missle1 - Player1 collision
|
||||||
|
Cx_P0PF = 1 << 4, // Player0 - Playfield collision
|
||||||
|
Cx_P0BL = 1 << 5, // Player0 - Ball collision
|
||||||
|
Cx_P1PF = 1 << 6, // Player1 - Playfield collision
|
||||||
|
Cx_P1BL = 1 << 7, // Player1 - Ball collision
|
||||||
|
Cx_M0PF = 1 << 8, // Missle0 - Playfield collision
|
||||||
|
Cx_M0BL = 1 << 9, // Missle0 - Ball collision
|
||||||
|
Cx_M1PF = 1 << 10, // Missle1 - Playfield collision
|
||||||
|
Cx_M1BL = 1 << 11, // Missle1 - Ball collision
|
||||||
|
Cx_BLPF = 1 << 12, // Ball - Playfield collision
|
||||||
|
Cx_P0P1 = 1 << 13, // Player0 - Player1 collision
|
||||||
|
Cx_M0M1 = 1 << 14 // Missle0 - Missle1 collision
|
||||||
|
};
|
||||||
|
|
||||||
|
// TIA Write/Read register names
|
||||||
|
enum TIARegister {
|
||||||
|
VSYNC = 0x00, // Write: vertical sync set-clear (D1)
|
||||||
|
VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1)
|
||||||
|
WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe)
|
||||||
|
RSYNC = 0x03, // Write: reset hrz. sync counter (strobe)
|
||||||
|
NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0)
|
||||||
|
NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0)
|
||||||
|
COLUP0 = 0x06, // Write: color-lum player 0 (D7-1)
|
||||||
|
COLUP1 = 0x07, // Write: color-lum player 1 (D7-1)
|
||||||
|
COLUPF = 0x08, // Write: color-lum playfield (D7-1)
|
||||||
|
COLUBK = 0x09, // Write: color-lum background (D7-1)
|
||||||
|
CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0)
|
||||||
|
REFP0 = 0x0b, // Write: reflect player 0 (D3)
|
||||||
|
REFP1 = 0x0c, // Write: reflect player 1 (D3)
|
||||||
|
PF0 = 0x0d, // Write: playfield register byte 0 (D7-4)
|
||||||
|
PF1 = 0x0e, // Write: playfield register byte 1 (D7-0)
|
||||||
|
PF2 = 0x0f, // Write: playfield register byte 2 (D7-0)
|
||||||
|
RESP0 = 0x10, // Write: reset player 0 (strobe)
|
||||||
|
RESP1 = 0x11, // Write: reset player 1 (strobe)
|
||||||
|
RESM0 = 0x12, // Write: reset missle 0 (strobe)
|
||||||
|
RESM1 = 0x13, // Write: reset missle 1 (strobe)
|
||||||
|
RESBL = 0x14, // Write: reset ball (strobe)
|
||||||
|
AUDC0 = 0x15, // Write: audio control 0 (D3-0)
|
||||||
|
AUDC1 = 0x16, // Write: audio control 1 (D4-0)
|
||||||
|
AUDF0 = 0x17, // Write: audio frequency 0 (D4-0)
|
||||||
|
AUDF1 = 0x18, // Write: audio frequency 1 (D3-0)
|
||||||
|
AUDV0 = 0x19, // Write: audio volume 0 (D3-0)
|
||||||
|
AUDV1 = 0x1a, // Write: audio volume 1 (D3-0)
|
||||||
|
GRP0 = 0x1b, // Write: graphics player 0 (D7-0)
|
||||||
|
GRP1 = 0x1c, // Write: graphics player 1 (D7-0)
|
||||||
|
ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1)
|
||||||
|
ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1)
|
||||||
|
ENABL = 0x1f, // Write: graphics (enable) ball (D1)
|
||||||
|
HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4)
|
||||||
|
HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4)
|
||||||
|
HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4)
|
||||||
|
HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4)
|
||||||
|
HMBL = 0x24, // Write: horizontal motion ball (D7-4)
|
||||||
|
VDELP0 = 0x25, // Write: vertical delay player 0 (D0)
|
||||||
|
VDELP1 = 0x26, // Write: vertical delay player 1 (D0)
|
||||||
|
VDELBL = 0x27, // Write: vertical delay ball (D0)
|
||||||
|
RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1)
|
||||||
|
RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1)
|
||||||
|
HMOVE = 0x2a, // Write: apply horizontal motion (strobe)
|
||||||
|
HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe)
|
||||||
|
CXCLR = 0x2c, // Write: clear collision latches (strobe)
|
||||||
|
|
||||||
|
CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0)
|
||||||
|
CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1)
|
||||||
|
CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL)
|
||||||
|
CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL)
|
||||||
|
CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL)
|
||||||
|
CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL)
|
||||||
|
CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused)
|
||||||
|
CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1)
|
||||||
|
INPT0 = 0x08, // Read pot port: D7
|
||||||
|
INPT1 = 0x09, // Read pot port: D7
|
||||||
|
INPT2 = 0x0a, // Read pot port: D7
|
||||||
|
INPT3 = 0x0b, // Read pot port: D7
|
||||||
|
INPT4 = 0x0c, // Read P1 joystick trigger: D7
|
||||||
|
INPT5 = 0x0d // Read P2 joystick trigger: D7
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
The TIA class uses some static tables that aren't dependent on the actual
|
||||||
|
TIA state. For code organization, it's better to place that functionality
|
||||||
|
here.
|
||||||
|
|
||||||
|
@author Stephen Anthony
|
||||||
|
@version $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
|
||||||
|
*/
|
||||||
|
class TIATables
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
Compute all static tables used by the TIA
|
||||||
|
*/
|
||||||
|
static void computeAllTables();
|
||||||
|
|
||||||
|
// Player mask table
|
||||||
|
// [suppress mode][nusiz][pixel]
|
||||||
|
static unsigned char PxMask[2][8][320];
|
||||||
|
|
||||||
|
// Missle mask table (entries are true or false)
|
||||||
|
// [number][size][pixel]
|
||||||
|
// There are actually only 4 possible size combinations on a real system
|
||||||
|
// The fifth size is used for simulating the starfield effect in
|
||||||
|
// Cosmic Ark and Stay Frosty
|
||||||
|
static unsigned char MxMask[8][5][320];
|
||||||
|
|
||||||
|
// Ball mask table (entries are true or false)
|
||||||
|
// [size][pixel]
|
||||||
|
static unsigned char BLMask[4][320];
|
||||||
|
|
||||||
|
// Playfield mask table for reflected and non-reflected playfields
|
||||||
|
// [reflect, pixel]
|
||||||
|
static unsigned int PFMask[2][160];
|
||||||
|
|
||||||
|
// A mask table which can be used when an object is disabled
|
||||||
|
static unsigned char DisabledMask[640];
|
||||||
|
|
||||||
|
// Used to set the collision register to the correct value
|
||||||
|
static unsigned short CollisionMask[64];
|
||||||
|
|
||||||
|
// Indicates the update delay associated with poking at a TIA address
|
||||||
|
static const short PokeDelay[64];
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// Used to convert value written in a motion register into
|
||||||
|
// its internal representation
|
||||||
|
static const int CompleteMotion[76][16];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Indicates if HMOVE blanks should occur for the corresponding cycle
|
||||||
|
static const bool HMOVEBlankEnableCycles[76];
|
||||||
|
|
||||||
|
// Used to reflect a players graphics
|
||||||
|
static unsigned char GRPReflect[256];
|
||||||
|
|
||||||
|
// Indicates if player is being reset during delay, display or other times
|
||||||
|
// [nusiz][old pixel][new pixel]
|
||||||
|
static signed char PxPosResetWhen[8][160][160];
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Compute the collision decode table
|
||||||
|
static void buildCollisionMaskTable();
|
||||||
|
|
||||||
|
// Compute the player mask table
|
||||||
|
static void buildPxMaskTable();
|
||||||
|
|
||||||
|
// Compute the missle mask table
|
||||||
|
static void buildMxMaskTable();
|
||||||
|
|
||||||
|
// Compute the ball mask table
|
||||||
|
static void buildBLMaskTable();
|
||||||
|
|
||||||
|
// Compute playfield mask table
|
||||||
|
static void buildPFMaskTable();
|
||||||
|
|
||||||
|
// Compute the player reflect table
|
||||||
|
static void buildGRPReflectTable();
|
||||||
|
|
||||||
|
// Compute the player position reset when table
|
||||||
|
static void buildPxPosResetWhenTable();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Following constructors and assignment operators not supported
|
||||||
|
TIATables() = delete;
|
||||||
|
TIATables(const TIATables&) = delete;
|
||||||
|
TIATables(TIATables&&) = delete;
|
||||||
|
TIATables& operator=(const TIATables&) = delete;
|
||||||
|
TIATables& operator=(TIATables&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
260
src/engine/platform/tia.cpp
Normal file
260
src/engine/platform/tia.cpp
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
#include "tia.h"
|
||||||
|
#include "../engine.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v);}
|
||||||
|
|
||||||
|
void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
tia.process(bufL+start,len);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char dealWithFreq(unsigned char shape, int base, int pitch) {
|
||||||
|
int bp=base+pitch;
|
||||||
|
double mult=0.25*27.5*pow(2.0,double(768+bp)/(256.0*12.0));
|
||||||
|
switch (shape) {
|
||||||
|
case 1: // buzzy
|
||||||
|
return ceil(31400/(30.6*mult))-1;
|
||||||
|
break;
|
||||||
|
case 2: // low buzzy
|
||||||
|
return ceil(31400/(480*mult))-1;
|
||||||
|
break;
|
||||||
|
case 3: // flangy
|
||||||
|
return ceil(31400/(60*mult))-1;
|
||||||
|
break;
|
||||||
|
case 4: case 5: // square
|
||||||
|
return ceil(31400/(4.05*mult))-1;
|
||||||
|
break;
|
||||||
|
case 6: case 7: case 8: case 9: case 10: // pure buzzy/reedy/noise
|
||||||
|
return ceil(31400/(63*mult))-1;
|
||||||
|
break;
|
||||||
|
case 12: case 13: // low square
|
||||||
|
return round(31400/(4.0*3*mult))-1;
|
||||||
|
break;
|
||||||
|
case 14: case 15: // low pure buzzy/reedy
|
||||||
|
return ceil(31400/(3*63*mult))-1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformTIA::tick() {
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
chan[i].std.next();
|
||||||
|
if (chan[i].std.hadVol) {
|
||||||
|
chan[i].outVol=chan[i].std.vol-(15-chan[i].vol);
|
||||||
|
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||||
|
if (isMuted[i]) {
|
||||||
|
rWrite(0x19+i,0);
|
||||||
|
} else {
|
||||||
|
rWrite(0x19+i,chan[i].outVol&15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chan[i].std.hadArp) {
|
||||||
|
if (!chan[i].inPorta) {
|
||||||
|
if (chan[i].std.arpMode) {
|
||||||
|
chan[i].baseFreq=chan[i].std.arp<<8;
|
||||||
|
} else {
|
||||||
|
chan[i].baseFreq=(chan[i].note+chan[i].std.arp-12)<<8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
} else {
|
||||||
|
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||||
|
chan[i].baseFreq=chan[i].note<<8;
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chan[i].std.hadWave) {
|
||||||
|
chan[i].shape=chan[i].std.wave&15;
|
||||||
|
rWrite(0x15+i,chan[i].shape);
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
}
|
||||||
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
|
if (chan[i].insChanged) {
|
||||||
|
if (!chan[i].std.willWave) {
|
||||||
|
chan[i].shape=4;
|
||||||
|
rWrite(0x15+i,chan[i].shape);
|
||||||
|
}
|
||||||
|
chan[i].insChanged=false;
|
||||||
|
}
|
||||||
|
chan[i].freq=dealWithFreq(chan[i].shape,chan[i].baseFreq,chan[i].pitch);
|
||||||
|
if (chan[i].shape==4 || chan[i].shape==5) {
|
||||||
|
if (chan[i].baseFreq<39*256) {
|
||||||
|
rWrite(0x15+i,6);
|
||||||
|
chan[i].freq=dealWithFreq(6,chan[i].baseFreq,chan[i].pitch);
|
||||||
|
} else if (chan[i].baseFreq<59*256) {
|
||||||
|
rWrite(0x15+i,12);
|
||||||
|
chan[i].freq=dealWithFreq(12,chan[i].baseFreq,chan[i].pitch);
|
||||||
|
} else {
|
||||||
|
rWrite(0x15+i,chan[i].shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chan[i].freq>31) chan[i].freq=31;
|
||||||
|
if (chan[i].keyOff) {
|
||||||
|
rWrite(0x19+i,0);
|
||||||
|
}
|
||||||
|
rWrite(0x17+i,chan[i].freq);
|
||||||
|
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||||
|
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||||
|
chan[i].freqChanged=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformTIA::dispatch(DivCommand c) {
|
||||||
|
switch (c.cmd) {
|
||||||
|
case DIV_CMD_NOTE_ON: {
|
||||||
|
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||||
|
chan[c.chan].baseFreq=c.value<<8;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
chan[c.chan].note=c.value;
|
||||||
|
chan[c.chan].active=true;
|
||||||
|
chan[c.chan].keyOn=true;
|
||||||
|
rWrite(0x15+c.chan,chan[c.chan].shape);
|
||||||
|
chan[c.chan].std.init(ins);
|
||||||
|
if (isMuted[c.chan]) {
|
||||||
|
rWrite(0x19+c.chan,0);
|
||||||
|
} else {
|
||||||
|
rWrite(0x19+c.chan,chan[c.chan].vol&15);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_NOTE_OFF:
|
||||||
|
chan[c.chan].keyOff=true;
|
||||||
|
chan[c.chan].active=false;
|
||||||
|
chan[c.chan].std.init(NULL);
|
||||||
|
break;
|
||||||
|
case DIV_CMD_VOLUME: {
|
||||||
|
chan[c.chan].vol=c.value;
|
||||||
|
if (!chan[c.chan].std.hasVol) {
|
||||||
|
chan[c.chan].outVol=c.value;
|
||||||
|
}
|
||||||
|
if (isMuted[c.chan]) {
|
||||||
|
rWrite(0x19+c.chan,0);
|
||||||
|
} else {
|
||||||
|
rWrite(0x19+c.chan,chan[c.chan].vol&15);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_GET_VOLUME: {
|
||||||
|
return chan[c.chan].vol;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_INSTRUMENT:
|
||||||
|
if (chan[c.chan].ins!=c.value) {
|
||||||
|
chan[c.chan].insChanged=true;
|
||||||
|
}
|
||||||
|
chan[c.chan].ins=c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PITCH: {
|
||||||
|
chan[c.chan].pitch=c.value;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
|
int destFreq=c.value2<<8;
|
||||||
|
bool return2=false;
|
||||||
|
if (destFreq>chan[c.chan].baseFreq) {
|
||||||
|
chan[c.chan].baseFreq+=c.value;
|
||||||
|
if (chan[c.chan].baseFreq>=destFreq) {
|
||||||
|
chan[c.chan].baseFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chan[c.chan].baseFreq-=c.value;
|
||||||
|
if (chan[c.chan].baseFreq<=destFreq) {
|
||||||
|
chan[c.chan].baseFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
if (return2) {
|
||||||
|
chan[c.chan].inPorta=false;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_LEGATO: {
|
||||||
|
chan[c.chan].baseFreq=c.value<<8;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_ALWAYS_SET_VOLUME:
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_GET_VOLMAX:
|
||||||
|
return 15;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PRE_PORTA:
|
||||||
|
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||||
|
chan[c.chan].inPorta=c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PRE_NOTE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//printf("WARNING: unimplemented command %d\n",c.cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformTIA::muteChannel(int ch, bool mute) {
|
||||||
|
isMuted[ch]=mute;
|
||||||
|
if (isMuted[ch]) {
|
||||||
|
rWrite(0x19+ch,0);
|
||||||
|
} else {
|
||||||
|
rWrite(0x19+ch,chan[ch].outVol&15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformTIA::forceIns() {
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
chan[i].insChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformTIA::reset() {
|
||||||
|
tia.reset();
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
chan[i]=DivPlatformTIA::Channel();
|
||||||
|
chan[i].vol=0x0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformTIA::isStereo() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformTIA::notifyInsDeletion(void* ins) {
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformTIA::setPAL(bool pal) {
|
||||||
|
if (pal) {
|
||||||
|
rate=31250;
|
||||||
|
} else {
|
||||||
|
rate=31400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, bool pal) {
|
||||||
|
parent=p;
|
||||||
|
skipRegisterWrites=false;
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
isMuted[i]=false;
|
||||||
|
}
|
||||||
|
tia.channels(1,false);
|
||||||
|
setPAL(pal);
|
||||||
|
reset();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformTIA::quit() {
|
||||||
|
}
|
37
src/engine/platform/tia.h
Normal file
37
src/engine/platform/tia.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef _TIA_H
|
||||||
|
#define _TIA_H
|
||||||
|
#include "../dispatch.h"
|
||||||
|
#include "../macroInt.h"
|
||||||
|
#include <queue>
|
||||||
|
#include "sound/tia/TIASnd.h"
|
||||||
|
|
||||||
|
class DivPlatformTIA: public DivDispatch {
|
||||||
|
protected:
|
||||||
|
struct Channel {
|
||||||
|
int freq, baseFreq, pitch;
|
||||||
|
unsigned char ins, note, shape;
|
||||||
|
signed char konCycles;
|
||||||
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||||
|
int vol, outVol;
|
||||||
|
DivMacroInt std;
|
||||||
|
Channel(): freq(0), baseFreq(0), pitch(0), ins(-1), note(0), shape(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15) {}
|
||||||
|
};
|
||||||
|
Channel chan[2];
|
||||||
|
bool isMuted[2];
|
||||||
|
TIASound tia;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
int dispatch(DivCommand c);
|
||||||
|
void reset();
|
||||||
|
void forceIns();
|
||||||
|
void tick();
|
||||||
|
void muteChannel(int ch, bool mute);
|
||||||
|
void setPAL(bool pal);
|
||||||
|
bool isStereo();
|
||||||
|
bool keyOffAffectsArp(int ch);
|
||||||
|
void notifyInsDeletion(void* ins);
|
||||||
|
int init(DivEngine* parent, int channels, int sugRate, bool pal);
|
||||||
|
void quit();
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -933,7 +933,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
||||||
// 2. check whether we gonna tick
|
// 2. check whether we gonna tick
|
||||||
if (cycles<=0) {
|
if (cycles<=0) {
|
||||||
// we have to tick
|
// we have to tick
|
||||||
unsigned int realPos=(runPos[0]*size)/runtotal[0];
|
unsigned int realPos=size-(runLeftG>>8);
|
||||||
if (realPos>=size) realPos=size-1;
|
if (realPos>=size) realPos=size-1;
|
||||||
if (song.hilightA>0) {
|
if (song.hilightA>0) {
|
||||||
if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1;
|
if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1;
|
||||||
|
@ -951,7 +951,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
||||||
// 3. tick the clock and fill buffers as needed
|
// 3. tick the clock and fill buffers as needed
|
||||||
if (cycles<runLeftG) {
|
if (cycles<runLeftG) {
|
||||||
for (int i=0; i<song.systemLen; i++) {
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
int total=cycles*runtotal[i]/(size<<8);
|
int total=(cycles*runtotal[i])/(size<<8);
|
||||||
disCont[i].acquire(runPos[i],total);
|
disCont[i].acquire(runPos[i],total);
|
||||||
runLeft[i]-=total;
|
runLeft[i]-=total;
|
||||||
runPos[i]+=total;
|
runPos[i]+=total;
|
||||||
|
|
|
@ -1543,7 +1543,7 @@ void FurnaceGUI::drawPattern() {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* aboutLine[54]={
|
const char* aboutLine[55]={
|
||||||
"tildearrow",
|
"tildearrow",
|
||||||
"is proud to present",
|
"is proud to present",
|
||||||
"",
|
"",
|
||||||
|
@ -1571,6 +1571,7 @@ const char* aboutLine[54]={
|
||||||
"Mednafen PCE",
|
"Mednafen PCE",
|
||||||
"puNES by FHorse",
|
"puNES by FHorse",
|
||||||
"reSID by Dag Lem",
|
"reSID by Dag Lem",
|
||||||
|
"Stella by Stella Team",
|
||||||
"",
|
"",
|
||||||
"greetings to:",
|
"greetings to:",
|
||||||
"Delek",
|
"Delek",
|
||||||
|
@ -1649,7 +1650,7 @@ void FurnaceGUI::drawAbout() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<54; i++) {
|
for (int i=0; i<55; i++) {
|
||||||
double posX=(scrW*dpiScale/2.0)+(sin(double(i)*0.5+double(aboutScroll)/90.0)*120*dpiScale)-(ImGui::CalcTextSize(aboutLine[i]).x*0.5);
|
double posX=(scrW*dpiScale/2.0)+(sin(double(i)*0.5+double(aboutScroll)/90.0)*120*dpiScale)-(ImGui::CalcTextSize(aboutLine[i]).x*0.5);
|
||||||
double posY=(scrH-aboutScroll+42*i)*dpiScale;
|
double posY=(scrH-aboutScroll+42*i)*dpiScale;
|
||||||
if (posY<-80*dpiScale || posY>scrH*dpiScale) continue;
|
if (posY<-80*dpiScale || posY>scrH*dpiScale) continue;
|
||||||
|
@ -1672,7 +1673,7 @@ void FurnaceGUI::drawAbout() {
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
aboutScroll+=2;
|
aboutScroll+=2;
|
||||||
if (++aboutSin>=2400) aboutSin=0;
|
if (++aboutSin>=2400) aboutSin=0;
|
||||||
if (aboutScroll>(42*54+scrH)) aboutScroll=-20;
|
if (aboutScroll>(42*55+scrH)) aboutScroll=-20;
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,7 @@ bool pVersion(String) {
|
||||||
printf("- Mednafen PCE by Mednafen Team (GPLv2)\n");
|
printf("- Mednafen PCE by Mednafen Team (GPLv2)\n");
|
||||||
printf("- puNES by FHorse (GPLv2)\n");
|
printf("- puNES by FHorse (GPLv2)\n");
|
||||||
printf("- reSID by Dag Lem (GPLv2)\n");
|
printf("- reSID by Dag Lem (GPLv2)\n");
|
||||||
|
printf("- Stella by Stella Team (GPLv2)\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue