Files
atari2600/tia.cpp
T
raimon@prometeo c819ab5f16 Mañanerous commit!
- [WIP] [tia] implementat el pintat, pero ahir no me deixaren tranquil i està tot empastrat
2026-09-17 06:45:33 +02:00

371 lines
14 KiB
C++

#include "tia.h"
#include "tia-audio.h"
namespace tia
{
// Standard Stella NTSC Palette (ARGB format: 0xFFRRGGBB)
// Indexed by: (COLUx >> 1) & 0x7F
const uint32_t NTSC_PALETTE[128] = {
// Hue $0 (Grayscale / Monochromatic)
0xFF000000, 0xFF282828, 0xFF444444, 0xFF5C5C5C, 0xFF707070, 0xFF848484, 0xFF989898, 0xFFACACAC,
// Hue $1 (Gold / Yellow-Orange)
0xFF4C2C00, 0xFF684400, 0xFF845C00, 0xFF9C7400, 0xFFB48C00, 0xFFCCA414, 0xFFE4BC34, 0xFFFCD454,
// Hue $2 (Orange / Red-Orange)
0xFF541800, 0xFF742C00, 0xFF904400, 0xFFA85C00, 0xFFC07400, 0xFFD88C00, 0xFFF0A420, 0xFFFFBC40,
// Hue $3 (Red-Orange)
0xFF580C00, 0xFF781C00, 0xFF943000, 0xFFB04800, 0xFFC86000, 0xFFE07808, 0xFFF89028, 0xFFFFA848,
// Hue $4 (Magenta / Red-Violet)
0xFF540420, 0xFF74103C, 0xFF902058, 0xFFAC3474, 0xFFC4488C, 0xFFDC5CA8, 0xFFF474C0, 0xFFFF8CD8,
// Hue $5 (Purple / Violet)
0xFF440454, 0xFF601074, 0xFF7C2090, 0xFF9434AC, 0xFFAC48C4, 0xFFC45CDC, 0xFFDC74F4, 0xFFF48CFF,
// Hue $6 (Blue-Violet / Indigo)
0xFF2C0874, 0xFF441494, 0xFF5C24B0, 0xFF7438CC, 0xFF8C4CE4, 0xFFA460FC, 0xFFBC78FF, 0xFFD490FF,
// Hue $7 (Blue)
0xFF101480, 0xFF2424A0, 0xFF3C38BC, 0xFF504CD4, 0xFF6860EC, 0xFF8078FF, 0xFF9890FF, 0xFFB0A8FF,
// Hue $8 (Blue-Cyan)
0xFF002074, 0xFF003494, 0xFF0C48B0, 0xFF2060CC, 0xFF3474E4, 0xFF4C8CFC, 0xFF64A4FF, 0xFF7CBCFF,
// Hue $9 (Cyan / Blue-Green)
0xFF002854, 0xFF004074, 0xFF005490, 0xFF006CAC, 0xFF1880C4, 0xFF3098DC, 0xFF48B0F4, 0xFF60C8FF,
// Hue $A (Teal / Green-Cyan)
0xFF00302C, 0xFF004844, 0xFF00605C, 0xFF007874, 0xFF00908C, 0xFF1CA8A4, 0xFF38C0BC, 0xFF54D8D4,
// Hue $B (Green)
0xFF003408, 0xFF004C18, 0xFF00642C, 0xFF007C40, 0xFF009454, 0xFF14AC6C, 0xFF30C484, 0xFF4CDC9C,
// Hue $C (Yellow-Green)
0xFF0C3400, 0xFF204C00, 0xFF346400, 0xFF487C00, 0xFF5C9400, 0xFF74AC10, 0xFF8CC428, 0xFFA4DC44,
// Hue $D (Olive Green)
0xFF243000, 0xFF3C4800, 0xFF546000, 0xFF6C7800, 0xFF849000, 0xFF9CA800, 0xFFB4C018, 0xFFCCC434,
// Hue $E (Brown / Olive-Yellow)
0xFF3C2C00, 0xFF584000, 0xFF745800, 0xFF8C6C00, 0xFFA48400, 0xFFBC9C00, 0xFFD4B420, 0xFFECC83C,
// Hue $F (Orange-Brown)
0xFF482400, 0xFF643800, 0xFF805000, 0xFF986400, 0xFFB07C00, 0xFFC89400, 0xFFE0AC28, 0xFFF8C444
};
uint8_t collisions[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
float paddle_pos[4] { 0.5f, 0.5f, 0.5f, 0.5f };
uint32_t paddle_charge_cycles[4] { 0, 0, 0, 0 };
bool joy0_button_pressed = false;
bool joy1_button_pressed = false;
uint8_t player0_size = 1;
uint8_t player0_num_copies = 1;
uint8_t player0_copies_distance = 0;
uint32_t player0_color = 0x00000000;
bool player0_reflect = false;
uint8_t player0_pos = 0;
uint8_t player0_bits = 0;
int8_t player0_hmotion = 0;
bool player0_vert_delay = false;
uint8_t player0_pixel_counter = 0;
uint8_t player0_pixel_position = 0;
uint8_t player1_size = 1;
uint8_t player1_num_copies = 1;
uint8_t player1_copies_distance = 0;
uint32_t player1_color = 0x00000000;
bool player1_reflect = false;
uint8_t player1_pos = 0;
uint8_t player1_bits = 0;
int8_t player1_hmotion = 0;
bool player1_vert_delay = false;
uint8_t player1_pixel_counter = 0;
uint8_t player1_pixel_position = 0;
bool missile0_enabled = false;
uint8_t missile0_size = 1;
uint8_t missile0_pos = 0;
int8_t missile0_hmotion = 0;
bool missile0_reset = false;
uint8_t missile0_pixel_counter = 0;
bool missile1_enabled = false;
uint8_t missile1_size = 1;
uint8_t missile1_pos = 0;
int8_t missile1_hmotion = 0;
bool missile1_reset = false;
uint8_t missile1_pixel_counter = 0;
bool ball_enabled = false;
uint8_t ball_size = 1;
uint8_t ball_pos = 0;
int8_t ball_hmotion = 0;
bool ball_vert_delay = false;
uint8_t ball_pixel_counter = 0;
uint32_t playfield_color = 0x00000000;
bool playfield_reflected = false;
bool playfield_score = false;
bool playfield_priority = false;
uint32_t playfield_bits = 0x00000000;
uint8_t playfield_shifts[2][40] = {
// Repeat
{16,17,18,19, 15,14,13,12,11,10,9,8, 0,1,2,3,4,5,6,7, 16,17,18,19, 15,14,13,12,11,10,9,8, 0,1,2,3,4,5,6,7},
// Reflect
{16,17,18,19, 15,14,13,12,11,10,9,8, 0,1,2,3,4,5,6,7, 7,6,5,4,3,2,1,0, 8,9,10,11,12,13,14,15, 19,18,17,16}
};
uint32_t background_color = 0x00000000;
uint32_t current_frame_cycles = 0;
bool hmove = false;
bool vsync_set = false;
bool vblank_set = false;
bool vblank_latch = false;
bool vblank_dump = false;
uint16_t color_clock = 0;
uint16_t current_scanline = 0;
bool rdy_low = false;
void reset()
{
}
void tick()
{
if (!vblank_dump) current_frame_cycles++;
color_clock = (color_clock + 1) % 228;
if (color_clock == 0) {
rdy_low = false;
hmove = false;
current_scanline++;
}
if (missile0_reset) missile0_pos = player0_pos + (4*player0_size);
if (missile1_reset) missile1_pos = player1_pos + (4*player1_size);
audio::tick();
// [TODO] Pintar pixels
if ( (!vsync_set) && (!vblank_set) && (color_clock >= 68) && (current_scanline >= 37) && (current_scanline < 229) && (!hmove || color_clock >= 92) ) {
const uint8_t pixel_clock = color_clock - 68;
if (player0_pos == pixel_clock) {
player0_pixel_counter = player0_size;
player0_pixel_position = 7;
}
if (player1_pos == pixel_clock) {
player1_pixel_counter = player1_size;
player1_pixel_position = 7;
}
if ( (missile0_enabled) && (missile0_pos == pixel_clock) ) {
missile0_pixel_counter = missile0_size;
}
if ( (missile1_enabled) && (missile1_pos == pixel_clock) ) {
missile1_pixel_counter = missile1_size;
}
if ( (ball_enabled) && (ball_pos == pixel_clock) ) {
ball_pixel_counter = ball_size;
}
}
}
uint8_t get_paddle_state(int index)
{
if (vblank_dump) return 0x00;
return (current_frame_cycles >= paddle_charge_cycles[index]) ? 0x80 : 0x00;
}
uint8_t read(uint16_t address)
{
switch (address & 0x0f) {
case 0x00: return collisions[0];
case 0x01: return collisions[1];
case 0x02: return collisions[2];
case 0x03: return collisions[3];
case 0x04: return collisions[4];
case 0x05: return collisions[5];
case 0x06: return collisions[6];
case 0x07: return collisions[7];
case 0x08: return get_paddle_state(0);
case 0x09: return get_paddle_state(1);
case 0x0A: return get_paddle_state(2);
case 0x0B: return get_paddle_state(3);
case 0x0C: return joy0_button_pressed ? 0x00 : 0x80;
case 0x0D: return joy1_button_pressed ? 0x00 : 0x80;
}
}
void write(uint16_t address, uint8_t value)
{
address &= 0x3f;
if (address >= 0x2d) address -= 0x20;
// [TODO] Procesar segons l'adreça resultant
switch (address) {
case 0x00: // VSYNC
const bool old_vsync = vsync_set;
vsync_set = value & 0x20;
if (old_vsync && !vsync_set) { // Resetejem el frame
current_scanline = 0;
}
break;
case 0x01: // VBLANK
bool prev_dump = vblank_dump;
vblank_set = value & 0x20;
vblank_latch = value & 0x40;
vblank_dump = value & 0x80;
if (prev_dump && !vblank_dump) {
current_frame_cycles = 0;
for (int i = 0; i < 4; ++i) paddle_charge_cycles[i] = static_cast<uint32_t>(paddle_pos[i] * 2400.0f);
}
break;
case 0x02: // WSYNC
rdy_low = true;
break;
case 0x03: // RSYNC
color_clock = 0;
rdy_low = false;
break;
case 0x004: // NUSIZ0
missile0_size = 1 << ((value >> 4) & 0x03);
switch (value & 0x07) {
case 0: player0_num_copies = 1; player0_copies_distance = 0; player0_size = 1; break;
case 1: player0_num_copies = 2; player0_copies_distance = 2; player0_size = 1; break;
case 2: player0_num_copies = 2; player0_copies_distance = 4; player0_size = 1; break;
case 3: player0_num_copies = 3; player0_copies_distance = 2; player0_size = 1; break;
case 4: player0_num_copies = 2; player0_copies_distance = 8; player0_size = 1; break;
case 5: player0_num_copies = 1; player0_copies_distance = 0; player0_size = 2; break;
case 6: player0_num_copies = 3; player0_copies_distance = 4; player0_size = 1; break;
case 7: player0_num_copies = 1; player0_copies_distance = 0; player0_size = 4; break;
}
break;
case 0x005: // NUSIZ1
missile1_size = 1 << ((value >> 4) & 0x03);
switch (value & 0x07) {
case 0: player1_num_copies = 1; player1_copies_distance = 0; player1_size = 1; break;
case 1: player1_num_copies = 2; player1_copies_distance = 2; player1_size = 1; break;
case 2: player1_num_copies = 2; player1_copies_distance = 4; player1_size = 1; break;
case 3: player1_num_copies = 3; player1_copies_distance = 2; player1_size = 1; break;
case 4: player1_num_copies = 2; player1_copies_distance = 8; player1_size = 1; break;
case 5: player1_num_copies = 1; player1_copies_distance = 0; player1_size = 2; break;
case 6: player1_num_copies = 3; player1_copies_distance = 4; player1_size = 1; break;
case 7: player1_num_copies = 1; player1_copies_distance = 0; player1_size = 4; break;
}
break;
case 0x06: // COLUP0
player0_color = NTSC_PALETTE[(value >> 1) & 0x7F]; break;
case 0x07: // COLUP1
player1_color = NTSC_PALETTE[(value >> 1) & 0x7F]; break;
case 0x08: // COLUPF
playfield_color = NTSC_PALETTE[(value >> 1) & 0x7F]; break;
case 0x09: // COLUBK
background_color = NTSC_PALETTE[(value >> 1) & 0x7F]; break;
case 0x0A: // CTRLPF
playfield_reflected = value & 0x01;
playfield_score = value & 0x02;
playfield_priority = value & 0x04;
ball_size = 1 << ((value >> 4) & 0x03);
break;
case 0x0B: // REFP0
player0_reflect = value & 0x08; break;
case 0x0C: // REFP1
player1_reflect = value & 0x08; break;
case 0x0D: // PF0
playfield_bits = (playfield_bits & 0x0000ffff) | (uint32_t(value>>4)<<16); break;
case 0x0E: // PF1
playfield_bits = (playfield_bits & 0x000f00ff) | (uint32_t(value)<<8); break;
case 0x0F: // PF2
playfield_bits = (playfield_bits & 0x000fff00) | (uint32_t(value)); break;
case 0x10: // RESP0
player0_pos = color_clock < 68 ? 0 : color_clock-68; break;
case 0x11: // RESP1
player1_pos = color_clock < 68 ? 0 : color_clock-68; break;
case 0x12: // RESM0
missile0_pos = color_clock < 68 ? 0 : color_clock-68; break;
case 0x13: // RESM1
missile1_pos = color_clock < 68 ? 0 : color_clock-68; break;
case 0x14: // RESBL
ball_pos = color_clock < 68 ? 0 : color_clock-68; break;
case 0x15: // AUDC0
case 0x16: // AUDC1
case 0x17: // AUDF0
case 0x18: // AUDF1
case 0x19: // AUDV0
case 0x1A: // AUDV1
audio::write(address, value);
break;
case 0x1B: //GRP0
player0_bits = value; break;
case 0x1C: //GRP1
player1_bits = value; break;
case 0x1D: //ENAM0
missile0_enabled = value & 0x02; break;
case 0x1E: //ENAM1
missile0_enabled = value & 0x02; break;
case 0x1F: //ENABL
missile0_enabled = value & 0x02; break;
case 0x20: //HMP0
player0_hmotion = int8_t(value) >> 4; break;
case 0x21: //HMP1
player1_hmotion = int8_t(value) >> 4; break;
case 0x22: //HMM0
missile0_hmotion = int8_t(value) >> 4; break;
case 0x23: //HMM1
missile1_hmotion = int8_t(value) >> 4; break;
case 0x24: //HMBL
ball_hmotion = int8_t(value) >> 4; break;
case 0x25: //VDELP0
player0_vert_delay = value & 0x01; break;
case 0x26: //VDELP1
player1_vert_delay = value & 0x01; break;
case 0x27: //VDELBL
ball_vert_delay = value & 0x01; break;
case 0x28: // RESMP0
missile0_reset = value & 0x02; break;
case 0x29: // RESMP1
missile1_reset = value & 0x02; break;
case 0x2A: // HMOVE
// [TODO] Implementació bàsica, reemplazar per una que use els 15 color_clocks en el futur
player0_pos += player0_hmotion;
player1_pos += player1_hmotion;
missile0_pos += missile0_hmotion;
missile1_pos += missile1_hmotion;
ball_pos += ball_hmotion;
hmove = true;
break;
case 0x2B: // HMCLR
player0_hmotion = player1_hmotion = missile0_hmotion = missile1_hmotion = ball_hmotion = 0;
break;
case 0x2C: // CXCLR
for (int i=0;i<8;++i) collisions[i] = 0;
break;
}
}
bool is_rdy_low()
{
return rdy_low;
}
}