📄 sound.cpp
字号:
// Stop calculating the sound.
if (bCalculateSound == TRUE)
bCalculateSound = FALSE;
}
// If the locked portion of the buffer wrapped around to the
// beginning of the buffer then we need to write to it.
if (dwBytesLocked2 > 0)
{
// Save a temp pointer to the second portions of the sound buffer.
pvDataSave = pvData2;
// Second portion of the buffer.
for (dwByteNum = 0; dwByteNum < dwBytesLocked2;
dwByteNum += OPTIONS_NUM_CHANNELS * (OPTIONS_NUM_BITSPERSAMPLE / 8))
{
// Clear the last volume outta there.
wTotalOutputVol = 0;
// Process the square channel 1.
wTotalOutputVol += (WORD)APU_DoSquare1();
// Write the data to the sound buffer and move the pointer
// to the buffer to the next data position.
if (OPTIONS_NUM_BITSPERSAMPLE == 8)
{
*((BYTE*)pvDataSave) = (BYTE)wTotalOutputVol;
pvDataSave = (BYTE*)pvDataSave + (OPTIONS_NUM_CHANNELS * 1);
}
else
{
*((WORD*)pvDataSave) = wTotalOutputVol;
pvDataSave = (BYTE*)pvDataSave + (OPTIONS_NUM_CHANNELS * 2);
}
}
}
// Unlock the buffer now that were done with it.
lpdsbSoundBuf->Unlock(pvData1, dwBytesLocked1, pvData2, dwBytesLocked2);
}
// Save the position of the last place we wrote to so
// we can continue the next time this function is called.
dwLastEndWritePos = (DWORD)(dwLastEndWritePos+dwBytesLocked1+dwBytesLocked2);
if (dwLastEndWritePos >= ((OPTIONS_NUM_BITSPERSAMPLE/8)*OPTIONS_NUM_CHANNELS*OPTIONS_NUM_SAMPLESPERSEC)*OPTIONS_NUM_SECONDSFORBUFFER)
{
dwLastEndWritePos -= ((OPTIONS_NUM_BITSPERSAMPLE/8)*OPTIONS_NUM_CHANNELS*OPTIONS_NUM_SAMPLESPERSEC)*OPTIONS_NUM_SECONDSFORBUFFER;
}
}
// Victory is ours!!!!
return TRUE;
} // end APU_DoFrame()
//------------------------------------------------------------------------------
// Name: APU_DoSquare1()
// Desc: Process the first square wave sound channel.
//------------------------------------------------------------------------------
WORD APU_DoSquare1()
{
static DWORD dwNumCyclesElapsed = 0; // Keeps track of where we are in the wave.
static DWORD dwDutyFlip = 0; // How many times the programmable timer must
// reload untill a duty flip.
static DWORD dwWaveLength = 0; // How many cycles till the programmable timer reloads.
static BYTE byOutput = 0x80; // The returned output volume.
static BOOL bDutyFlip = FALSE; // Has a duty flip happended?
static BOOL bCalcSquare = FALSE;
static BOOL bCalcOnNextFlag = FALSE;
if (bCalculateSound)
bCalcOnNextFlag = TRUE;
// If the calculate flag is set, then we need to calculate everything
// that is needed to return an output volume to the calling function.
// Otherwise we just return the precalculated data that was stored
// in the static variables.
if (bCalcSquare)
{
CalculateIt:
// First thing we need to do is start over by reseting the elapsed cycles.
dwNumCyclesElapsed = 0;
// Reset the duty toggle.
bDutyFlip = FALSE;
bCalcSquare = FALSE;
bCalcOnNextFlag = FALSE;
//---------------------------------------------------------------------
// Do the length counter part.
//---------------------------------------------------------------------
// If the length counter is enabled then we need to process it.
if (!(CPU.Memory[0x4000] & 0x20))
{
// If the length counter is not zero then decrement the value.
if (APU.sndchanSquare1.byLengthCtr)
APU.sndchanSquare1.byLengthCtr--;
}
//---------------------------------------------------------------------
// TODO: This is where the sweeping unit needs to be emulated.
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Emulate the programmable timer to get the wavelength.
//---------------------------------------------------------------------
// Take the 3 least significant bits from $4003 and
// use those as the bits 8-10 for our wavelength. Bits
// 0-7 come from $4002 to produce our 11-bit wavelength.
// The we need to add one to it.
dwWaveLength = ((((WORD)(CPU.Memory[0x4003]&0x7)) << 8) |
CPU.Memory[0x4002]) + 1;
//---------------------------------------------------------------------
// Emulate the duty flip part.
//---------------------------------------------------------------------
dwDutyFlip = abyDutyCycleTablePos[CPU.Memory[0x4000]>>6];
//---------------------------------------------------------------------
// Now finally send the signal through the volume/envelope decay unit.
//---------------------------------------------------------------------
// If the envelope decay bit is set, then the volume goes staight
// to the DAC...or in our case DirectSound. This means that
// the envelope decay is disabled.
if (!(CPU.Memory[0x4000] & 0x10))
{
// There are a few conditions when 0 is sent straight
// to the DAC for the volume. They are as follows:
// 1. If the length counter is 0
// 2. Something to do with the sweep unit.
// 3. On the negative portion of the output frequency
// signal coming from the duty cycle.
//
// Otherwise bits (0-3) of $4000 are sent straight the
// DAC for the volume.
if (APU.sndchanSquare1.byLengthCtr == 0) // TODO: implement other conditions.
byOutput = 0x80;
else
byOutput = ((CPU.Memory[0x4000] & 0x0F) << 3);
}
else
byOutput = ((CPU.Memory[0x4000] & 0x0F) << 3);
}
// If we are done with the wave we need to start over.
if (dwNumCyclesElapsed >= dwWaveLength)
{
// We need to flip the volume to negative if the amount of
// times for a duty flip has passed.
if ((dwDutyFlip--) == 0)
{
// If the duty flip is positive then we load the counter with the negative
// value. If the duty flip is negative, then we load the counter
// with the positive counter.
if (bDutyFlip)
{
dwDutyFlip = abyDutyCycleTablePos[CPU.Memory[0x4000]>>6];
if (bCalcOnNextFlag)
{
bCalcSquare = FALSE;
//return (WORD)byOutput;
goto CalculateIt;
}
}
else
{
dwDutyFlip = abyDutyCycleTableNeg[CPU.Memory[0x4000]>>6];
}
// Flip the output wave.
byOutput = -byOutput;
// Toggle the duty flip indicator.
bDutyFlip ^= TRUE;
}
dwNumCyclesElapsed -= dwWaveLength;
}
else
// Keep moving along the phase of the wave form.
dwNumCyclesElapsed += dwNumCyclesPerSample;
// Return the final value.
return (WORD)byOutput;
} // end APU_DoSquare1()
//------------------------------------------------------------------------------
// Name: APU_Read()
// Desc: Handles all the reads from the APU.
//------------------------------------------------------------------------------
BYTE APU_Read(WORD wReg)
{
BYTE byRetVal = 0;
switch (wReg)
{
case 0x4015:
// If the length counter is non-zero set bit 1.
if (APU.sndchanSquare1.byLengthCtr) byRetVal |= 0x01;
// Return the byte.
return byRetVal;
default:
return 0;
}
return 0;
} // end APU_Read()
//------------------------------------------------------------------------------
// Name: APU_Write()
// Desc: Handles all the writes to the APU.
//------------------------------------------------------------------------------
VOID APU_Write(WORD wReg, BYTE byData)
{
// The old length counters for the channel since writing 0 into
// $4015 sets the length counter to 0, but writing 1 sets the
// length counter back to its origional value.
static BYTE byOldLengthCounter = 0;
switch (wReg)
{
case 0x4003:
// Use a lookup table to set the length counter.
// Bits 3-7 are used for this but they must be
// right shifted to use the lookup table.
APU.sndchanSquare1.byLengthCtr = abyLengthTable[byData >> 3];
return;
case 0x4015:
// Deal with writes for the first square channel.
if (byData & 0x1)
APU.sndchanSquare1.byLengthCtr = byOldLengthCounter;
else
{
// If the bit is not set the length counter is forced to 0,
// but make sure to save the value for when a 1 gets written.
byOldLengthCounter = APU.sndchanSquare1.byLengthCtr;
APU.sndchanSquare1.byLengthCtr = 0;
}
return;
default:
return;
}
return;
} // end APU_Write()
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -