NINTV-DS/arm9/source/videoaud.cpp
2024-03-06 10:08:34 -05:00

395 lines
14 KiB
C++

// =====================================================================================
// Copyright (c) 2021-2024 Dave Bernazzani (wavemotion-dave)
//
// Copying and distribution of this emulator, its source code and associated
// readme files, with or without modification, are permitted in any medium without
// royalty provided the this copyright notice is used and wavemotion-dave (NINTV-DS)
// and Kyle Davis (BLISS) are thanked profusely.
//
// The NINTV-DS emulator is offered as-is, without any warranty.
// =====================================================================================
#include <nds.h>
#include <nds/fifomessages.h>
#include "nintv-ds.h"
#include "videoaud.h"
#include "config.h"
#include "Emulator.h"
#include "AudioMixer.h"
// ---------------------------------------------------------------------------
// This is called very frequently (14040 times per second) to fill the
// pipeline of sound values from the Audio Mixer into the Nintendo DS sound
// buffer which will be processed in the background by the ARM 7 processor.
// ---------------------------------------------------------------------------
UINT32 audio_arm7_xfer_buffer __attribute__ ((aligned (4))) = 0;
UINT32* aptr __attribute__((section(".dtcm"))) = (UINT32*) (&audio_arm7_xfer_buffer + 0xA000000/4);
UINT16 myCurrentSampleIdx16 __attribute__((section(".dtcm"))) = 0;
ITCM_CODE void VsoundHandler(void)
{
UINT16 sample[2];
// If there is a fresh sample...
if (myCurrentSampleIdx16 != currentSampleIdx16)
{
sample[0] = audio_mixer_buffer[myCurrentSampleIdx16++];
sample[1] = sample[0];
*aptr = *((UINT32*)&sample);
if (myCurrentSampleIdx16 == SOUND_SIZE) myCurrentSampleIdx16=0;
}
}
// -----------------------------------------------------------------------
// Gentle (but fast) ramp down of the audio to prevent pops and clicks
// when transitioning in and out of the game running to the menu
// -----------------------------------------------------------------------
void audioRampDown(void)
{
// Stop the background processing of audio - we'll manually control it below
irqDisable(IRQ_TIMER2);
UINT16 rampDownAudio = (*aptr) & 0xFFF0;
while(rampDownAudio)
{
*aptr = (UINT32)rampDownAudio | ((UINT32)rampDownAudio << 16);
if (rampDownAudio > 0x800) rampDownAudio -= 0x400; // Ramp a little slower to avoid pops
else rampDownAudio = rampDownAudio>>1; // Ramp faster to get to zero quickly
swiWaitForVBlank(); // Wait for 1 vertical blank (1/60th of a sec)
}
}
// -----------------------------------------------------------------------------------------------------------------
// This starts the sound engine... the ARM7 is told about the 1-sample buffer and runs at 2x the normal processing
// frequency so that it samples fast enough that no sounds are dropped (Nyquist would have something to say here).
// The internal VSoundHandler() is setup to run at the frequency at which we want to place samples into the buffer.
// -----------------------------------------------------------------------------------------------------------------
void dsInstallSoundEmuFIFO(void)
{
// Clear out the sound buffers...
currentSampleIdx16 = 0;
// -----------------------------------------------------------------------------
// We are going to move out the memory access to the non-cached mirros since
// this audio buffer is shared between ARM7 and ARM9 and we want to ensure
// that the data is never cached as we want the ARM7 to have updated values.
// -----------------------------------------------------------------------------
if (isDSiMode())
{
b_dsi_mode = true;
aptr = (UINT32*) (&audio_arm7_xfer_buffer + 0xA000000/4);
}
else
{
b_dsi_mode = false;
aptr = (UINT32*) (&audio_arm7_xfer_buffer + 0x00400000/4);
}
// Stop the background sound handler and ramp it down to prevent 'pops'
audioRampDown();
fifoSendValue32(FIFO_USER_01,(1<<16) | SOUND_KILL);
swiWaitForVBlank();swiWaitForVBlank(); // Wait 2 vertical blanks... that's enough for the ARM7 to stop...
*aptr = 0x00000000;
memset(audio_mixer_buffer, 0x00, 256 * sizeof(UINT16));
FifoMessage msg;
msg.SoundPlay.data = &audio_arm7_xfer_buffer;
msg.SoundPlay.freq = mySoundFrequency*2;
msg.SoundPlay.volume = 127;
msg.SoundPlay.pan = 64;
msg.SoundPlay.loop = 1;
msg.SoundPlay.format = ((1)<<4) | SoundFormat_16Bit;
msg.SoundPlay.loopPoint = 0;
msg.SoundPlay.dataSize = (4 >> 2); // Just 1 32-bit value
msg.type = EMUARM7_PLAY_SND;
fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg);
swiWaitForVBlank();swiWaitForVBlank(); // Wait 2 vertical blanks... that's enough for the ARM7 to start chugging...
// Now setup to feed the audio mixer buffer into the ARM7 core via shared memory
UINT16 tFreq = mySoundFrequency;
if (target_frames[myConfig.target_fps] != 999) // If not running MAX, adjust sound to match speed emulator is running at.
{
tFreq = (UINT16)(((UINT32)mySoundFrequency * (UINT32)target_frames[myConfig.target_fps]) / (UINT32)60) + 2;
}
TIMER2_DATA = TIMER_FREQ(tFreq);
TIMER2_CR = TIMER_DIV_1 | TIMER_IRQ_REQ | TIMER_ENABLE;
irqSet(IRQ_TIMER2, VsoundHandler);
irqEnable(IRQ_TIMER2);
}
// ------------------------------------------------------------------
// Game palette arrays... we support 3 built-in palettes and one
// custom palette that can be read from NINTV-DS.PAL
// ------------------------------------------------------------------
const UINT32 muted_gamePalette[32] =
{
0x000000, 0x0021AD, 0xE03904, 0xCECE94,
0x1E4912, 0x01812E, 0xF7E64A, 0xFFFFFF,
0xA5ADA5, 0x51B7E5, 0xEF9C00, 0x424A08,
0xFF3173, 0x9A8AEF, 0x4AA552, 0x950457,
0x000000, 0x0021AD, 0xE03904, 0xCECE94,
0x1E4912, 0x01812E, 0xF7E64A, 0xFFFFFF,
0xA5ADA5, 0x51B7E5, 0xEF9C00, 0x424A08,
0xFF3173, 0x9A8AEF, 0x4AA552, 0x950457,
};
const UINT32 bright_gamePalette[32] =
{
0x000000, 0x1438F7, 0xE35B0E, 0xCBF168,
0x009428, 0x07C200, 0xFFFF01, 0xFFFFFF,
0xC8C8C8, 0x23CEC3, 0xFD9918, 0x3A8A00,
0xF0463C, 0xD383FF, 0x7CED00, 0xB81178,
0x000000, 0x1438F7, 0xE35B0E, 0xCBF168,
0x009428, 0x07C200, 0xFFFF01, 0xFFFFFF,
0xC8C8C8, 0x23CEC3, 0xFD9918, 0x3A8A00,
0xF0463C, 0xD383FF, 0x7CED00, 0xB81178,
};
const UINT32 pal_gamePalette[32] =
{
0x000000, 0x0075FF, 0xFF4C39, 0xD1B951,
0x09B900, 0x30DF10, 0xFFE501, 0xFFFFFF,
0x8C8C8C, 0x28E5C0, 0xFFA02E, 0x646700,
0xFF29FF, 0x8C8FFF, 0x7CED00, 0xC42BFC,
0x000000, 0x0075FF, 0xFF4C39, 0xD1B951,
0x09B900, 0x30DF10, 0xFFE501, 0xFFFFFF,
0x8C8C8C, 0x28E5C0, 0xFFA02E, 0x646700,
0xFF29FF, 0x8C8FFF, 0x7CED00, 0xC42BFC,
};
UINT32 default_Palette[32] =
{
// Optmized
0x000000, 0x002DFF, 0xFF3D10, 0xC8D271,
0x386B3F, 0x00A756, 0xFAEA50, 0xFFFFFF,
0x9B9DA1, 0x24B8FF, 0xFFB41F, 0x3D5A02,
0xFF4E57, 0xA496FF, 0x75CC80, 0xB51A58,
0x000000, 0x002DFF, 0xFF3D10, 0xC8D271,
0x386B3F, 0x00A756, 0xFAEA50, 0xFFFFFF,
0x9B9DA1, 0x24B8FF, 0xFFB41F, 0x3D5A02,
0xFF4E57, 0xA496FF, 0x75CC80, 0xB51A58,
};
UINT32 custom_Palette[32] =
{
// Optmized
0x000000, 0x002DFF, 0xFF3D10, 0xC8D271,
0x386B3F, 0x00A756, 0xFAEA50, 0xFFFFFF,
0x9B9DA1, 0x24B8FF, 0xFFB41F, 0x3D5A02,
0xFF4E57, 0xA496FF, 0x75CC80, 0xB51A58,
0x000000, 0x002DFF, 0xFF3D10, 0xC8D271,
0x386B3F, 0x00A756, 0xFAEA50, 0xFFFFFF,
0x9B9DA1, 0x24B8FF, 0xFFB41F, 0x3D5A02,
0xFF4E57, 0xA496FF, 0x75CC80, 0xB51A58,
};
// ---------------------------------------------------------------------------------------
// To save battery and/or if the user simply wants to de-ephasize the bottom "overlay"
// screen while playing on the top screen, we support dimming that bottom screen. This
// is easy since the NDS has hardware support for this...
// ---------------------------------------------------------------------------------------
const INT8 brightness[] = {0, -3, -6, -9};
void dsInitPalette(void)
{
extern char szName[];
static UINT8 bFirstTime = TRUE;
if (bFirstTime)
{
// Load Custom Palette (if it exists)
FILE *fp;
fp = fopen("/data/NINTV-DS.PAL", "r");
if (fp != NULL)
{
UINT8 pal_idx=0;
while (!feof(fp))
{
fgets(szName, 255, fp);
if (!feof(fp))
{
if (strstr(szName, "#") != NULL)
{
char *ptr = szName;
while (*ptr == ' ' || *ptr == '#') ptr++;
custom_Palette[pal_idx] = strtoul(ptr, &ptr, 16);
custom_Palette[pal_idx+16] = custom_Palette[pal_idx];
if (pal_idx < 16) pal_idx++;
}
}
}
fclose(fp);
}
bFirstTime = FALSE;
}
if (!bGameLoaded) return;
// Init DS Specific palette for the Intellivision (16 colors...)
for(int i = 0; i < 256; i++)
{
unsigned char r, g, b;
switch (myConfig.palette)
{
case 1: // MUTED
r = (unsigned char) ((muted_gamePalette[i%32] & 0x00ff0000) >> 19);
g = (unsigned char) ((muted_gamePalette[i%32] & 0x0000ff00) >> 11);
b = (unsigned char) ((muted_gamePalette[i%32] & 0x000000ff) >> 3);
break;
case 2: // BRIGHT
r = (unsigned char) ((bright_gamePalette[i%32] & 0x00ff0000) >> 19);
g = (unsigned char) ((bright_gamePalette[i%32] & 0x0000ff00) >> 11);
b = (unsigned char) ((bright_gamePalette[i%32] & 0x000000ff) >> 3);
break;
case 3: // PAL
r = (unsigned char) ((pal_gamePalette[i%32] & 0x00ff0000) >> 19);
g = (unsigned char) ((pal_gamePalette[i%32] & 0x0000ff00) >> 11);
b = (unsigned char) ((pal_gamePalette[i%32] & 0x000000ff) >> 3);
break;
case 4: // CUSTOM
r = (unsigned char) ((custom_Palette[i%32] & 0x00ff0000) >> 19);
g = (unsigned char) ((custom_Palette[i%32] & 0x0000ff00) >> 11);
b = (unsigned char) ((custom_Palette[i%32] & 0x000000ff) >> 3);
break;
default:
r = (unsigned char) ((default_Palette[i%32] & 0x00ff0000) >> 19);
g = (unsigned char) ((default_Palette[i%32] & 0x0000ff00) >> 11);
b = (unsigned char) ((default_Palette[i%32] & 0x000000ff) >> 3);
break;
}
BG_PALETTE[i]=RGB15(r,g,b);
}
setBrightness(2, brightness[myGlobalConfig.brightness]); // Subscreen Brightness
}
AudioMixerDS::AudioMixerDS()
: outputBuffer(NULL),
outputBufferSize(0),
outputBufferWritePosition(0)
{
this->outputBufferWritePosition = 0;
this->outputBufferSize = SOUND_SIZE;
}
void AudioMixerDS::resetProcessor()
{
if (outputBuffer)
{
outputBufferWritePosition = 0;
}
audioRampDown();
fifoSendValue32(FIFO_USER_01,(1<<16) | SOUND_KILL);
// clears the emulator side of the audio mixer
AudioMixer::resetProcessor();
// restarts the ARM7 side of the audio...
bStartSoundFifo = true;
// Set the emulation back to the start...
reset_emu_frames();
}
void AudioMixerDS::init(UINT32 sampleRate)
{
release();
outputBufferWritePosition = 0;
AudioMixer::init(sampleRate);
}
void AudioMixerDS::release()
{
AudioMixer::release();
}
void AudioMixerDS::flushAudio()
{
AudioMixer::flushAudio();
}
// ---------------------------------------------------------------
// Our main frameskip boolean table... a 1 in this table means
// that we will render the full display on that frame.
// ---------------------------------------------------------------
UINT8 renderz[4][4] __attribute__((section(".dtcm"))) =
{
{1,1,1,1}, // Frameskip Off
{1,0,1,0}, // Frameskip Odd
{0,1,0,1}, // Frameskip Even
{1,0,0,0} // Frameskip Agressive
};
VideoBusDS::VideoBusDS() { }
void VideoBusDS::init(UINT32 width, UINT32 height)
{
release();
// initialize the pixelBuffer
VideoBus::init(width, height);
}
void VideoBusDS::release()
{
VideoBus::release();
}
// -----------------------------------------------------------------------------
// Here we take the fully rendered Intellivision 'pixelBuffer' which is 160x192
// and copy it over to the NDS video memory. We use DMA copy as it's found to
// be slightly more efficient. We are utilizing two DMA channels because there
// isn't anything else using it and we gain a bit of speed...
// -----------------------------------------------------------------------------
ITCM_CODE void VideoBusDS::render()
{
frames_per_sec_calc++;
global_frames++;
VideoBus::render();
// ------------------------------------------------------
// Check if we are skipping rendering this frame...
// ------------------------------------------------------
if (renderz[myConfig.frame_skip][global_frames&3])
{
UINT8 chan = 0;
UINT32 *ds_video=(UINT32*)0x06000000;
UINT32 *source_video = (UINT32*)pixelBuffer;
for (int j=0; j<192; j++)
{
while (dmaBusy(chan)) asm("nop");
dmaCopyWordsAsynch (chan, source_video, ds_video, 160);
source_video += 40;
ds_video += 64;
chan++; chan = 2+(chan&1); // Use channels 2 and 3 for the copy...
}
}
}
// End of Line