2017-01-02 19:24:04 +00:00
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cctype>
|
|
|
|
#include <deque>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
#include <chrono>
|
|
|
|
#include <iostream>
|
|
|
|
#include <map>
|
2017-01-02 20:35:27 +00:00
|
|
|
#include <emscripten/bind.h>
|
2017-01-02 19:24:04 +00:00
|
|
|
|
|
|
|
#include "interface.h"
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
namespace APPLE2Einterface
|
|
|
|
{
|
|
|
|
|
|
|
|
chrono::time_point<chrono::system_clock> start_time;
|
|
|
|
|
|
|
|
DisplayMode display_mode = TEXT;
|
|
|
|
int display_page = 0; // Apple //e page minus 1 (so 0,1 not 1,2)
|
|
|
|
bool mixed_mode = false;
|
|
|
|
bool vid80 = false;
|
|
|
|
bool altchar = false;
|
|
|
|
|
|
|
|
bool use_joystick = false;
|
|
|
|
|
|
|
|
deque<event> event_queue;
|
|
|
|
|
|
|
|
bool force_caps_on = true;
|
|
|
|
|
|
|
|
bool event_waiting()
|
|
|
|
{
|
|
|
|
return event_queue.size() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
event dequeue_event()
|
|
|
|
{
|
|
|
|
if(event_waiting()) {
|
|
|
|
event e = event_queue.front();
|
|
|
|
event_queue.pop_front();
|
|
|
|
return e;
|
|
|
|
} else
|
|
|
|
return {NONE, 0};
|
|
|
|
}
|
|
|
|
|
|
|
|
float paddle_values[4] = {0, 0, 0, 0};
|
|
|
|
bool paddle_buttons[4] = {false, false, false, false};
|
|
|
|
|
|
|
|
tuple<float,bool> get_paddle(int num)
|
|
|
|
{
|
|
|
|
if(num < 0 || num > 3)
|
|
|
|
make_tuple(-1, false);
|
|
|
|
return make_tuple(paddle_values[num], paddle_buttons[num]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void enqueue_audio_samples(char *buf, size_t sz)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void start(bool run_fast, bool add_floppies, bool floppy0_inserted, bool floppy1_inserted)
|
|
|
|
{
|
|
|
|
start_time = std::chrono::system_clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
void apply_writes(void);
|
|
|
|
|
|
|
|
bool textport_changed = false;
|
2017-01-02 23:18:14 +00:00
|
|
|
unsigned char textport[2][24][40];
|
2017-01-02 19:24:04 +00:00
|
|
|
|
|
|
|
void iterate()
|
|
|
|
{
|
|
|
|
apply_writes();
|
|
|
|
|
|
|
|
if(textport_changed) {
|
|
|
|
printf("------------------------------------------\n");
|
|
|
|
for(int row = 0; row < 24; row++) {
|
|
|
|
printf("|");
|
|
|
|
for(int col = 0; col < 40; col++) {
|
2017-01-02 23:18:14 +00:00
|
|
|
char ch = textport[display_page][row][col] & 0x7f;
|
2017-01-02 19:24:04 +00:00
|
|
|
putchar(isprint(ch) ? ch : '?');
|
|
|
|
}
|
|
|
|
printf("|\n");
|
|
|
|
}
|
|
|
|
printf("------------------------------------------\n");
|
|
|
|
textport_changed = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void shutdown()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_switches(DisplayMode mode_, bool mixed, int page, bool vid80_, bool altchar_)
|
|
|
|
{
|
|
|
|
display_mode = mode_;
|
|
|
|
mixed_mode = mixed;
|
|
|
|
display_page = page;
|
|
|
|
vid80 = vid80_;
|
|
|
|
altchar = altchar_;
|
|
|
|
|
|
|
|
// XXX
|
|
|
|
static bool altchar_warned = false;
|
|
|
|
if(altchar && !altchar_warned) {
|
|
|
|
fprintf(stderr, "Warning: ALTCHAR activated, is not implemented\n");
|
|
|
|
altchar_warned = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const int text_page1_base = 0x400;
|
|
|
|
static const int text_page2_base = 0x800;
|
|
|
|
static const int text_page_size = 0x400;
|
|
|
|
static const int hires_page1_base = 0x2000;
|
|
|
|
static const int hires_page2_base = 0x4000;
|
|
|
|
static const int hires_page_size = 8192;
|
|
|
|
|
|
|
|
extern int text_row_base_offsets[24];
|
|
|
|
extern int hires_memory_to_scanout_address[8192];
|
|
|
|
|
|
|
|
map< tuple<int, bool>, unsigned char> writes;
|
|
|
|
int collisions = 0;
|
|
|
|
|
|
|
|
void write2(int addr, bool aux, unsigned char data)
|
|
|
|
{
|
|
|
|
// We know text page 1 and 2 are contiguous
|
|
|
|
if((addr >= text_page1_base) && (addr < text_page2_base + text_page_size)) {
|
|
|
|
int page = (addr >= text_page2_base) ? 1 : 0;
|
|
|
|
int within_page = addr - text_page1_base - page * text_page_size;
|
|
|
|
for(int row = 0; row < 24; row++) {
|
|
|
|
int row_offset = text_row_base_offsets[row];
|
|
|
|
if((within_page >= row_offset) && (within_page < row_offset + 40)) {
|
|
|
|
int col = within_page - row_offset;
|
2017-01-02 23:18:14 +00:00
|
|
|
if(!aux) textport[page][row][col] = data;
|
2017-01-02 19:24:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(((addr >= hires_page1_base) && (addr < hires_page1_base + hires_page_size)) || ((addr >= hires_page2_base) && (addr < hires_page2_base + hires_page_size))) {
|
|
|
|
|
|
|
|
int page = (addr < hires_page2_base) ? 0 : 1;
|
|
|
|
int page_base = (page == 0) ? hires_page1_base : hires_page2_base;
|
|
|
|
int within_page = addr - page_base;
|
|
|
|
int scanout_address = hires_memory_to_scanout_address[within_page];
|
|
|
|
int row = scanout_address / 40;
|
|
|
|
int col = scanout_address % 40;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void apply_writes(void)
|
|
|
|
{
|
|
|
|
for(auto it : writes) {
|
|
|
|
int addr;
|
|
|
|
bool aux;
|
|
|
|
tie(addr, aux) = it.first;
|
|
|
|
write2(addr, aux, it.second);
|
|
|
|
textport_changed = true;
|
|
|
|
}
|
|
|
|
writes.clear();
|
|
|
|
collisions = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool write(int addr, bool aux, unsigned char data)
|
|
|
|
{
|
|
|
|
// We know text page 1 and 2 are contiguous
|
|
|
|
if((addr >= text_page1_base) && (addr < text_page2_base + text_page_size)) {
|
|
|
|
|
|
|
|
if(writes.find(make_tuple(addr, aux)) != writes.end())
|
|
|
|
collisions++;
|
|
|
|
writes[make_tuple(addr, aux)] = data;
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} else if(((addr >= hires_page1_base) && (addr < hires_page1_base + hires_page_size)) || ((addr >= hires_page2_base) && (addr < hires_page2_base + hires_page_size))) {
|
|
|
|
|
|
|
|
if(writes.find(make_tuple(addr, aux)) != writes.end())
|
|
|
|
collisions++;
|
|
|
|
writes[make_tuple(addr, aux)] = data;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int text_row_base_offsets[24] =
|
|
|
|
{
|
|
|
|
0x000,
|
|
|
|
0x080,
|
|
|
|
0x100,
|
|
|
|
0x180,
|
|
|
|
0x200,
|
|
|
|
0x280,
|
|
|
|
0x300,
|
|
|
|
0x380,
|
|
|
|
0x028,
|
|
|
|
0x0A8,
|
|
|
|
0x128,
|
|
|
|
0x1A8,
|
|
|
|
0x228,
|
|
|
|
0x2A8,
|
|
|
|
0x328,
|
|
|
|
0x3A8,
|
|
|
|
0x050,
|
|
|
|
0x0D0,
|
|
|
|
0x150,
|
|
|
|
0x1D0,
|
|
|
|
0x250,
|
|
|
|
0x2D0,
|
|
|
|
0x350,
|
|
|
|
0x3D0,
|
|
|
|
};
|
|
|
|
|
|
|
|
int hires_row_base_offsets[192] =
|
|
|
|
{
|
|
|
|
0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00,
|
|
|
|
0x0080, 0x0480, 0x0880, 0x0C80, 0x1080, 0x1480, 0x1880, 0x1C80,
|
|
|
|
0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00,
|
|
|
|
0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80,
|
|
|
|
0x0200, 0x0600, 0x0A00, 0x0E00, 0x1200, 0x1600, 0x1A00, 0x1E00,
|
|
|
|
0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80,
|
|
|
|
0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00,
|
|
|
|
0x0380, 0x0780, 0x0B80, 0x0F80, 0x1380, 0x1780, 0x1B80, 0x1F80,
|
|
|
|
0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28,
|
|
|
|
0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8,
|
|
|
|
0x0128, 0x0528, 0x0928, 0x0D28, 0x1128, 0x1528, 0x1928, 0x1D28,
|
|
|
|
0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8,
|
|
|
|
0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28,
|
|
|
|
0x02A8, 0x06A8, 0x0AA8, 0x0EA8, 0x12A8, 0x16A8, 0x1AA8, 0x1EA8,
|
|
|
|
0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28,
|
|
|
|
0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8,
|
|
|
|
0x0050, 0x0450, 0x0850, 0x0C50, 0x1050, 0x1450, 0x1850, 0x1C50,
|
|
|
|
0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0,
|
|
|
|
0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50,
|
|
|
|
0x01D0, 0x05D0, 0x09D0, 0x0DD0, 0x11D0, 0x15D0, 0x19D0, 0x1DD0,
|
|
|
|
0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50,
|
|
|
|
0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0,
|
|
|
|
0x0350, 0x0750, 0x0B50, 0x0F50, 0x1350, 0x1750, 0x1B50, 0x1F50,
|
|
|
|
0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0,
|
|
|
|
};
|
|
|
|
|
|
|
|
int hires_memory_to_scanout_address[8192];
|
|
|
|
|
|
|
|
static void initialize_memory_to_scanout() __attribute__((constructor));
|
|
|
|
void initialize_memory_to_scanout()
|
|
|
|
{
|
|
|
|
for(int row = 0; row < 192; row++) {
|
|
|
|
int row_address = hires_row_base_offsets[row];
|
|
|
|
for(int byte = 0; byte < 40; byte++) {
|
|
|
|
hires_memory_to_scanout_address[row_address + byte] = row * 40 + byte;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_floppy_activity(int number, bool activity)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-01-02 23:18:14 +00:00
|
|
|
void enqueue_console_keydown(int key)
|
2017-01-02 20:35:27 +00:00
|
|
|
{
|
|
|
|
static bool caps_lock_down = false;
|
|
|
|
|
|
|
|
// XXX not ideal, can be enqueued out of turn
|
|
|
|
if(caps_lock_down && !force_caps_on) {
|
|
|
|
caps_lock_down = false;
|
|
|
|
event_queue.push_back({KEYUP, CAPS_LOCK});
|
|
|
|
} else if(!caps_lock_down && force_caps_on) {
|
|
|
|
caps_lock_down = true;
|
|
|
|
event_queue.push_back({KEYDOWN, CAPS_LOCK});
|
|
|
|
}
|
|
|
|
|
2017-01-02 23:18:14 +00:00
|
|
|
switch(key) {
|
|
|
|
case 16: key = LEFT_SHIFT; break;
|
|
|
|
case 13: key = ENTER; break;
|
|
|
|
case 37: key = LEFT; break;
|
|
|
|
case 186: key = ';'; break;
|
|
|
|
case 187: key = '='; break;
|
|
|
|
case 188: key = ','; break;
|
|
|
|
case 189: key = '-'; break;
|
|
|
|
case 190: key = '.'; break;
|
|
|
|
case 191: key = '/'; break;
|
|
|
|
case 219: key = '['; break;
|
|
|
|
case 221: key = ']'; break;
|
|
|
|
case 222: key = '\''; break;
|
|
|
|
}
|
|
|
|
|
2017-01-02 20:35:27 +00:00
|
|
|
if(force_caps_on && (key >= 'a') && (key <= 'z'))
|
|
|
|
key = key - 'a' + 'A';
|
2017-01-02 23:18:14 +00:00
|
|
|
|
2017-01-02 20:35:27 +00:00
|
|
|
event_queue.push_back({KEYDOWN, key});
|
2017-01-02 23:18:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void enqueue_console_keyup(int key)
|
|
|
|
{
|
|
|
|
static bool caps_lock_down = false;
|
|
|
|
|
|
|
|
// XXX not ideal, can be enqueued out of turn
|
|
|
|
if(caps_lock_down && !force_caps_on) {
|
|
|
|
caps_lock_down = false;
|
|
|
|
event_queue.push_back({KEYUP, CAPS_LOCK});
|
|
|
|
} else if(!caps_lock_down && force_caps_on) {
|
|
|
|
caps_lock_down = true;
|
|
|
|
event_queue.push_back({KEYDOWN, CAPS_LOCK});
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(key) {
|
|
|
|
case 16: key = LEFT_SHIFT; break;
|
|
|
|
case 13: key = ENTER; break;
|
|
|
|
case 37: key = LEFT; break;
|
|
|
|
case 186: key = ';'; break;
|
|
|
|
case 187: key = '='; break;
|
|
|
|
case 188: key = ','; break;
|
|
|
|
case 189: key = '-'; break;
|
|
|
|
case 190: key = '.'; break;
|
|
|
|
case 191: key = '/'; break;
|
|
|
|
case 219: key = '['; break;
|
|
|
|
case 221: key = ']'; break;
|
|
|
|
case 222: key = '\''; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(force_caps_on && (key >= 'a') && (key <= 'z'))
|
|
|
|
key = key - 'a' + 'A';
|
|
|
|
|
2017-01-02 20:35:27 +00:00
|
|
|
event_queue.push_back({KEYUP, key});
|
|
|
|
}
|
|
|
|
|
|
|
|
EMSCRIPTEN_BINDINGS(my_module) {
|
2017-01-02 23:18:14 +00:00
|
|
|
emscripten::function("enqueue_console_keydown", &enqueue_console_keydown);
|
|
|
|
emscripten::function("enqueue_console_keyup", &enqueue_console_keyup);
|
2017-01-02 20:35:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-01-02 19:24:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|