mirror of
https://github.com/wavemotion-dave/NINTV-DS.git
synced 2025-06-18 13:55:33 -04:00
395 lines
14 KiB
C++
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
|
|
|
|
|