Compare commits
20
Commits
ae22c95dbe
...
06849b54ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06849b54ba | ||
|
|
eef255b507 | ||
|
|
db966c8cb8 | ||
|
|
b4feff41f0 | ||
|
|
4449acada0 | ||
|
|
799ac9f51a | ||
|
|
b83b49221d | ||
|
|
27e926bd58 | ||
|
|
d802d39d8d | ||
|
|
18b2c589e6 | ||
|
|
f45636a491 | ||
|
|
9ce8c9c778 | ||
|
|
c90b8137de | ||
|
|
bc864644f7 | ||
|
|
e97b52e944 | ||
|
|
7e31fbd582 | ||
|
|
b4b9131f39 | ||
|
|
8b9152dde9 | ||
|
|
26a0357afa | ||
|
|
53826584b8 |
+1
-5
@@ -91,11 +91,6 @@
|
||||
#define CPU_XORBSU 82
|
||||
#define CPU_XORNBSU 83
|
||||
|
||||
/* Functional operand types */
|
||||
#define CPU_LITERAL 0
|
||||
#define CPU_MEMORY 1
|
||||
#define CPU_REGISTER 2
|
||||
|
||||
/* Bit string operations */
|
||||
#define CPU_AND_BS 0
|
||||
#define CPU_ANDN_BS 1
|
||||
@@ -1642,6 +1637,7 @@ static void cpuSUBF_S(VB *sim) {
|
||||
/* TRAP */
|
||||
static void cpuTRAP(VB *sim) {
|
||||
sim->cpu.clocks += cpuClocks(15);
|
||||
sim->cpu.pc += 2;
|
||||
cpuThrow(sim, 0xFFA0 + cpuGetImm5U(sim));
|
||||
}
|
||||
|
||||
|
||||
+41
-14
@@ -5,6 +5,15 @@
|
||||
|
||||
/***************************** Module Functions ******************************/
|
||||
|
||||
/* Compute clocks until the next decrement to zero */
|
||||
static uint32_t tmrGetUntil(VB *sim) {
|
||||
uint32_t fullTick = sim->tmr.t_clk_sel ? 400 : 2000;
|
||||
uint32_t thisTick = sim->tmr.clocks +
|
||||
(sim->tmr.t_clk_sel ? 0 : 400 * (4 - sim->tmr.tick20));
|
||||
return thisTick + fullTick *
|
||||
(sim->tmr.counter == 0 ? sim->tmr.reload : sim->tmr.counter - 1);
|
||||
}
|
||||
|
||||
/* Update the counter to a new value */
|
||||
static void tmrUpdate(VB *sim, uint16_t value) {
|
||||
if (value == 0 && sim->tmr.counter != 0) {
|
||||
@@ -23,10 +32,6 @@ static void tmrUpdate(VB *sim, uint16_t value) {
|
||||
/* Process component */
|
||||
static void tmrEmulate(VB *sim, uint32_t clocks) {
|
||||
|
||||
/* Timer is disabled */
|
||||
if (!sim->tmr.t_enb)
|
||||
return;
|
||||
|
||||
/* Process all clocks */
|
||||
for (;;) {
|
||||
|
||||
@@ -38,15 +43,22 @@ static void tmrEmulate(VB *sim, uint32_t clocks) {
|
||||
}
|
||||
|
||||
/* Advance forward the component's number of clocks */
|
||||
clocks -= sim->tmr.clocks;
|
||||
sim->tmr.until -= sim->tmr.clocks;
|
||||
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
|
||||
clocks -= sim->tmr.clocks;
|
||||
sim->tmr.until -= sim->tmr.clocks;
|
||||
sim->tmr.clocks = 400;
|
||||
sim->tmr.tick20 += sim->tmr.tick20 == 4 ? -4 : 1;
|
||||
|
||||
/* Do not decrement counter */
|
||||
if (
|
||||
!sim->tmr.t_enb ||
|
||||
(!sim->tmr.t_clk_sel && sim->tmr.tick20 != 0)
|
||||
) continue;
|
||||
|
||||
/* Advance to the next counter value */
|
||||
tmrUpdate(sim, sim->tmr.counter == 0 ?
|
||||
sim->tmr.reload : sim->tmr.counter - 1);
|
||||
if (sim->tmr.counter == 0)
|
||||
sim->tmr.until = sim->tmr.clocks * ((uint32_t)sim->tmr.reload + 1);
|
||||
sim->tmr.until = tmrGetUntil(sim);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,8 +83,10 @@ static void tmrReset(VB *sim) {
|
||||
sim->tmr.z_stat = 0;
|
||||
|
||||
/* Other */
|
||||
sim->tmr.clocks = 400;
|
||||
sim->tmr.counter = 0xFFFF;
|
||||
sim->tmr.reload = 0x0000;
|
||||
sim->tmr.tick20 = 0;
|
||||
}
|
||||
|
||||
/* Determine how many clocks are guaranteed to process */
|
||||
@@ -88,14 +102,27 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
|
||||
if (
|
||||
(value & 0x04) && /* Z-Stat-Clr */
|
||||
(
|
||||
sim->tmr.counter != 0 ||
|
||||
!sim->tmr.t_enb
|
||||
!sim->tmr.t_enb ||
|
||||
(
|
||||
sim->tmr.counter != 0 &&
|
||||
!(value & 0x01) /* T-Enb */
|
||||
)
|
||||
)
|
||||
) {
|
||||
sim->tmr.z_stat = sim->tmr.counter != 0;
|
||||
sim->cpu.irq &= ~0x0002;
|
||||
}
|
||||
|
||||
/* Hardware bug: decrement on switch to 20us mode */
|
||||
if (
|
||||
!sim->tmr.t_clk_sel &&
|
||||
(value & 0x10) && /* T-Clk-Sel */
|
||||
sim->tmr.tick20 != 0
|
||||
) {
|
||||
tmrUpdate(sim, sim->tmr.counter == 0 ?
|
||||
sim->tmr.reload : sim->tmr.counter - 1);
|
||||
}
|
||||
|
||||
/* Parse fields */
|
||||
sim->tmr.t_clk_sel = value >> 4 & 1;
|
||||
sim->tmr.tim_z_int = value >> 3 & 1;
|
||||
@@ -105,10 +132,8 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
|
||||
if (!sim->tmr.tim_z_int)
|
||||
sim->cpu.irq &= ~0x0002;
|
||||
|
||||
/* Configure countdowns */
|
||||
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
|
||||
sim->tmr.until = sim->tmr.clocks * (sim->tmr.counter == 0 ?
|
||||
(uint32_t) sim->tmr.reload + 1 : sim->tmr.counter);
|
||||
/* Configure state */
|
||||
sim->tmr.until = tmrGetUntil(sim);
|
||||
|
||||
/* TODO: Will Z-Stat raise an interrupt when Tim-Z-Int is set? */
|
||||
}
|
||||
@@ -117,12 +142,14 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
|
||||
static void tmrWriteHigh(VB *sim, uint8_t value) {
|
||||
sim->tmr.reload = (uint16_t) value << 8 | (sim->tmr.reload & 0x00FF);
|
||||
tmrUpdate(sim, sim->tmr.reload);
|
||||
sim->tmr.until = tmrGetUntil(sim);
|
||||
}
|
||||
|
||||
/* Write to the low data register */
|
||||
static void tmrWriteLow(VB *sim, uint8_t value) {
|
||||
sim->tmr.reload = (sim->tmr.reload & 0xFF00) | value;
|
||||
tmrUpdate(sim, sim->tmr.reload);
|
||||
sim->tmr.until = tmrGetUntil(sim);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
|
||||
/*********************************** Types ***********************************/
|
||||
|
||||
/* Output image */
|
||||
typedef uint8_t Pixels[2][384*224];
|
||||
/* VIP BG map cell */
|
||||
typedef struct {
|
||||
uint8_t *palette; /* Points to a VB.vip.gplt */
|
||||
uint8_t *pixels; /* Points to a VB.vip.characters */
|
||||
} Cell;
|
||||
|
||||
/* VSU channel */
|
||||
typedef struct {
|
||||
@@ -64,9 +67,58 @@ typedef struct {
|
||||
} wave;
|
||||
|
||||
/* Other state */
|
||||
uint32_t clocks; /* Clocks until next sample */
|
||||
uint32_t clocks; /* Clocks until next wave or noise sample */
|
||||
uint8_t freqmod; /* Frequency modifications are active */
|
||||
uint32_t until; /* Clocks until channel component update */
|
||||
} Channel;
|
||||
|
||||
/* VIP character (hflp << 1 | vflp) */
|
||||
typedef uint8_t Character[4][64];
|
||||
|
||||
/* VIP object */
|
||||
typedef struct {
|
||||
Cell cell; /* BG map-like attributes */
|
||||
int16_t jp; /* Parallax */
|
||||
int16_t jx; /* Screen base X */
|
||||
int16_t jy; /* Screen Y */
|
||||
uint8_t lron; /* Visible in left and right images */
|
||||
} Object;
|
||||
|
||||
/* Output image */
|
||||
typedef uint8_t Pixels[2][384*224];
|
||||
|
||||
/* VIP world */
|
||||
typedef struct {
|
||||
|
||||
/* Attributes */
|
||||
uint8_t bgm; /* Mode */
|
||||
uint8_t end; /* Control world */
|
||||
int16_t gp; /* World parallax */
|
||||
int16_t gx; /* World base X */
|
||||
int16_t gy; /* World Y */
|
||||
int16_t mx; /* Scroll base X */
|
||||
int16_t mp; /* Scroll parallax */
|
||||
int16_t my; /* Scroll Y */
|
||||
uint8_t over; /* Use overplane */
|
||||
Cell *overplane; /* Overplane cell */
|
||||
|
||||
/* Non-attributes */
|
||||
Cell *bg[64]; /* Background arrangement */
|
||||
int32_t bgHeight; /* Height of background in pixels */
|
||||
uint32_t bgMaskX; /* Horizontal background mask */
|
||||
uint32_t bgMaskY; /* Vertical background mask */
|
||||
uint32_t bgShift; /* Bits to shift when selecting a BG map */
|
||||
int32_t bgWidth; /* Width of background in pixels */
|
||||
uint32_t hAffine; /* H for affine mode */
|
||||
uint32_t hNaffine; /* H for not affine mode */
|
||||
uint32_t height; /* Height of world in pixels */
|
||||
uint8_t lron; /* Visible in left and right images */
|
||||
uint32_t paramBase; /* H-bias and affine parameter address */
|
||||
uint32_t wAffine; /* W for affine mode */
|
||||
uint32_t wNaffine; /* W for not affine mode */
|
||||
uint32_t width; /* Width of world in pixels */
|
||||
} World;
|
||||
|
||||
/* Simulation state */
|
||||
struct VB {
|
||||
|
||||
@@ -199,6 +251,7 @@ struct VB {
|
||||
uint32_t clocks; /* Master clocks to wait */
|
||||
uint16_t counter; /* Current counter value */
|
||||
uint16_t reload; /* Reload counter value */
|
||||
uint8_t tick20; /* Current 20-microsecond tick */
|
||||
uint32_t until; /* Clocks until interrupt condition */
|
||||
} tmr;
|
||||
|
||||
@@ -278,7 +331,11 @@ struct VB {
|
||||
Pixels shadow; /* Drawing shadow image, column-major */
|
||||
|
||||
/* Other state */
|
||||
uint8_t ram[0x40000]; /* Video memory */
|
||||
Cell cells[0x10000]; /* Pre-computed BG map attributes */
|
||||
Character characters[2048]; /* Pre-computed character pixels */
|
||||
Object objects[1024]; /* Pre-computed object attributes */
|
||||
uint8_t ram[0x40000]; /* Video memory */
|
||||
World worlds[32]; /* Pre-computed world attributes */
|
||||
} vip;
|
||||
|
||||
/* VSU */
|
||||
@@ -643,6 +700,7 @@ VBAPI vbOnWrite vbGetWriteCallback(VB *sim) {
|
||||
VBAPI VB* vbInit(VB *sim) {
|
||||
sim->cart.ram = NULL;
|
||||
sim->cart.rom = NULL;
|
||||
sim->ph.enabled = 0;
|
||||
sim->vsu.out.samples = NULL;
|
||||
sim->onExecute = NULL;
|
||||
sim->onFetch = NULL;
|
||||
@@ -652,7 +710,7 @@ VBAPI VB* vbInit(VB *sim) {
|
||||
sim->onSamples = NULL;
|
||||
sim->onWrite = NULL;
|
||||
sim->peer = NULL;
|
||||
sim->ph.enabled = 0;
|
||||
sim->tag = NULL;
|
||||
vbReset(sim);
|
||||
return sim;
|
||||
}
|
||||
@@ -716,28 +774,28 @@ VBAPI int vbSetCartROM(VB *sim, void *rom, uint32_t size) {
|
||||
/* Specify a new exception callback handler */
|
||||
VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback) {
|
||||
vbOnException prev = sim->onException;
|
||||
sim->onException = callback;
|
||||
sim->onException = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
/* Specify a new execute callback handler */
|
||||
VBAPI vbOnExecute vbSetExecuteCallback(VB *sim, vbOnExecute callback) {
|
||||
vbOnExecute prev = sim->onExecute;
|
||||
sim->onExecute = callback;
|
||||
sim->onExecute = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
/* Specify a new fetch callback handler */
|
||||
VBAPI vbOnFetch vbSetFetchCallback(VB *sim, vbOnFetch callback) {
|
||||
vbOnFetch prev = sim->onFetch;
|
||||
sim->onFetch = callback;
|
||||
sim->onFetch = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
/* Specify a new frame callback handler */
|
||||
VBAPI vbOnFrame vbSetFrameCallback(VB *sim, vbOnFrame callback) {
|
||||
vbOnFrame prev = sim->onFrame;
|
||||
sim->onFrame = callback;
|
||||
sim->onFrame = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
@@ -749,7 +807,7 @@ VBAPI uint16_t vbSetKeys(VB *sim, uint16_t keys) {
|
||||
/* Specify a new link callback handler */
|
||||
VBAPI vbOnLink vbSetLinkCallback(VB *sim, vbOnLink callback) {
|
||||
vbOnLink prev = sim->onLink;
|
||||
sim->onLink = callback;
|
||||
sim->onLink = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
@@ -774,6 +832,8 @@ VBAPI int vbSetOption(VB *sim, int key, int value) {
|
||||
VBAPI void vbSetPeer(VB *sim, VB *peer) {
|
||||
if (sim->peer == peer)
|
||||
return;
|
||||
if (sim->peer != NULL)
|
||||
sim->peer->peer = NULL;
|
||||
sim->peer = peer;
|
||||
if (peer == NULL)
|
||||
return;
|
||||
@@ -784,6 +844,7 @@ VBAPI void vbSetPeer(VB *sim, VB *peer) {
|
||||
|
||||
/* Specify a new value for the program counter */
|
||||
VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
|
||||
sim->cpu.clocks = 0;
|
||||
sim->cpu.operation = CPU_FETCH;
|
||||
sim->cpu.pc = sim->cpu.nextPC = value & 0xFFFFFFFE;
|
||||
sim->cpu.step = 0;
|
||||
@@ -798,7 +859,7 @@ VBAPI int32_t vbSetProgramRegister(VB *sim, unsigned index, int32_t value) {
|
||||
/* Specify a new read callback handler */
|
||||
VBAPI vbOnRead vbSetReadCallback(VB *sim, vbOnRead callback) {
|
||||
vbOnRead prev = sim->onRead;
|
||||
sim->onRead = callback;
|
||||
sim->onRead = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
@@ -829,7 +890,7 @@ VBAPI uint32_t vbSetSystemRegister(VB *sim, unsigned index, uint32_t value) {
|
||||
/* Specify a new write callback handler */
|
||||
VBAPI vbOnWrite vbSetWriteCallback(VB *sim, vbOnWrite callback) {
|
||||
vbOnWrite prev = sim->onWrite;
|
||||
sim->onWrite = callback;
|
||||
sim->onWrite = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
@@ -841,7 +902,7 @@ VBAPI size_t vbSizeOf() {
|
||||
/* Specify a simulation's userdata pointer */
|
||||
VBAPI void* vbSetUserData(VB *sim, void *tag) {
|
||||
void *prev = sim->tag;
|
||||
sim->tag = tag;
|
||||
sim->tag = tag;
|
||||
return prev;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,24 @@ extern "C" {
|
||||
/* Option keys */
|
||||
#define VB_PSEUDO_HALT 0
|
||||
|
||||
/* Controller buttons */
|
||||
#define VB_PWR 0x0001
|
||||
#define VB_SGN 0x0002
|
||||
#define VB_A 0x0004
|
||||
#define VB_B 0x0008
|
||||
#define VB_RT 0x0010
|
||||
#define VB_LT 0x0020
|
||||
#define VB_RU 0x0040
|
||||
#define VB_RR 0x0080
|
||||
#define VB_LR 0x0100
|
||||
#define VB_LL 0x0200
|
||||
#define VB_LD 0x0400
|
||||
#define VB_LU 0x0800
|
||||
#define VB_STA 0x1000
|
||||
#define VB_SEL 0x2000
|
||||
#define VB_RL 0x4000
|
||||
#define VB_RD 0x8000
|
||||
|
||||
|
||||
|
||||
/*********************************** Types ***********************************/
|
||||
|
||||
+581
-314
@@ -56,37 +56,18 @@ static const uint8_t BRIGHT8[] = {
|
||||
|
||||
/*********************************** Types ***********************************/
|
||||
|
||||
/* World attribtues per eye */
|
||||
/* Intersection of the screen and a world rectangle */
|
||||
typedef struct {
|
||||
int32_t bp; /* BG source parallax */
|
||||
int32_t left, right; /* Window bounds */
|
||||
int32_t wx; /* World horizontal position */
|
||||
} EyeAttribs;
|
||||
|
||||
/* Parsed world attributes */
|
||||
typedef struct {
|
||||
int32_t base; /* Base BG map index */
|
||||
uint8_t *bg; /* Background template */
|
||||
int32_t bgm; /* World type */
|
||||
int32_t bgw, bgh; /* Background dimension masks */
|
||||
int32_t bottom, top; /* Window bounds */
|
||||
int32_t gx, gp, gy; /* World screen position */
|
||||
int32_t mx, mp, my; /* Background scroll */
|
||||
uint8_t *over; /* Overplane character */
|
||||
uint32_t params; /* Line parameters address */
|
||||
int32_t scx, scy; /* Background dimensions */
|
||||
int32_t w, h; /* Window size */
|
||||
} WorldAttribs;
|
||||
int32_t x1; /* Left edge inclusive */
|
||||
int32_t x2; /* Right edge exclusive */
|
||||
int32_t y1; /* Top edge inclusive */
|
||||
int32_t y2; /* Bottom edge exclusive */
|
||||
} Window;
|
||||
|
||||
|
||||
|
||||
/***************************** Module Functions ******************************/
|
||||
|
||||
/* Retrieve a pointer to character data in host memory */
|
||||
static uint8_t* vipCharacter(VB *sim, uint32_t c) {
|
||||
return &sim->vip.ram[0x06000 | (c << 6 & 0x18000) | (c << 4 & 0x01FF0)];
|
||||
}
|
||||
|
||||
/* Read a palette */
|
||||
static int32_t vipReadPalette(uint8_t *entries) {
|
||||
return entries[3] << 6 | entries[2] << 4 | entries[1] << 2;
|
||||
@@ -244,6 +225,178 @@ static int32_t vipReadIO(VB *sim, uint32_t address, int type) {
|
||||
value;
|
||||
}
|
||||
|
||||
/* Write into BG map cell memory */
|
||||
static void vipWriteCell(VB *sim, uint32_t offset, int type, int32_t value) {
|
||||
Cell *cell;
|
||||
|
||||
/* Adjustments for byte and word writes */
|
||||
switch (type) {
|
||||
case VB_S8:
|
||||
case VB_U8:
|
||||
value = offset & 1 ?
|
||||
value << 8 | sim->vip.ram[0x20001 ^ offset] :
|
||||
value | (int32_t) sim->vip.ram[0x20001 | offset] << 8
|
||||
;
|
||||
break;
|
||||
case VB_S32:
|
||||
vipWriteCell(sim, offset + 2, VB_U16, value >> 16);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Update pixel pointer */
|
||||
cell = &sim->vip.cells[offset >> 1];
|
||||
cell->palette = sim->vip.gplt[value >> 14 & 3];
|
||||
cell->pixels = sim->vip.characters[value & 0x07FF][value >> 12 & 3];
|
||||
}
|
||||
|
||||
/* Write into character memory */
|
||||
static void vipWriteChr(VB *sim, uint32_t offset, int type, int32_t value) {
|
||||
Character *chr; /* Pixel state */
|
||||
int b, l, r, t; /* Pixel bounds */
|
||||
int x, y, X, Y; /* Iterators */
|
||||
|
||||
/* Byte advance by data type */
|
||||
static const uint8_t HORZ[] = { 4, 4, 8, 8, 8 };
|
||||
static const uint8_t VERT[] = { 8, 8, 8, 8, 16 };
|
||||
|
||||
/* Working variables */
|
||||
chr = &sim->vip.characters[offset >> 4];
|
||||
l = (offset & 0x1) << 2;
|
||||
t = (offset & 0xE) << 2;
|
||||
r = l + HORZ[type];
|
||||
b = t + VERT[type];
|
||||
|
||||
/* Process all pixels */
|
||||
for (y = t, Y = 56 - y; y < b; y += 8, Y -= 8)
|
||||
for (x = l, X = 7 - x; x < r; x += 1, X -= 1, value >>= 2) {
|
||||
(*chr)[0][y | x] = (*chr)[1][Y | x] =
|
||||
(*chr)[2][y | X] = (*chr)[3][Y | X] = value & 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Write into object attribute memory */
|
||||
static void vipWriteObject(VB *sim, uint32_t offset, int type, int32_t value) {
|
||||
Object *obj; /* Object state */
|
||||
|
||||
/* Adjustments for byte and word writes */
|
||||
switch (type) {
|
||||
case VB_S8:
|
||||
case VB_U8:
|
||||
value = offset & 1 ?
|
||||
value << 8 | sim->vip.ram[0x3E001 ^ offset] :
|
||||
value | (int32_t) sim->vip.ram[0x3E001 | offset] << 8
|
||||
;
|
||||
break;
|
||||
case VB_S32:
|
||||
vipWriteObject(sim, offset + 2, VB_U16, value >> 16);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Processing by offset */
|
||||
obj = &sim->vip.objects[offset >> 3];
|
||||
switch (offset >> 1 & 3) {
|
||||
case 0: obj->jx = SignExtend(value, 10); break;
|
||||
case 1:
|
||||
obj->jp = SignExtend(value, 10);
|
||||
obj->lron = value >> 14 & 3;
|
||||
break;
|
||||
case 2:
|
||||
value = (int8_t) value;
|
||||
obj->jy = value > -8 ? value : value & 0xFF;
|
||||
break;
|
||||
case 3:
|
||||
obj->cell.palette = sim->vip.jplt[value >> 14 & 3];
|
||||
obj->cell.pixels =
|
||||
sim->vip.characters[value & 0x07FF][value >> 12 & 3];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Write into world attribute memory */
|
||||
static void vipWriteWorld(VB *sim, uint32_t offset, int type, int32_t value) {
|
||||
uint32_t base; /* Base BG map index */
|
||||
uint8_t *bg; /* Background template */
|
||||
uint32_t count; /* Number of BG maps in background */
|
||||
World *world; /* World state */
|
||||
uint32_t z; /* Iterator */
|
||||
|
||||
/* Adjustments for byte and word writes */
|
||||
switch (type) {
|
||||
case VB_S8:
|
||||
case VB_U8:
|
||||
value = offset & 1 ?
|
||||
value << 8 | sim->vip.ram[0x3D801 ^ offset] :
|
||||
value | (int32_t) sim->vip.ram[0x3D801 | offset] << 8
|
||||
;
|
||||
break;
|
||||
case VB_S32:
|
||||
vipWriteWorld(sim, offset + 2, VB_U16, value >> 16);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Processing by offset */
|
||||
world = &sim->vip.worlds[offset >> 5];
|
||||
switch (offset >> 1 & 15) {
|
||||
case 0:
|
||||
|
||||
/* Parse attributes */
|
||||
world->lron = value >> 14 & 3;
|
||||
world->bgm = value >> 12 & 3;
|
||||
world->over = value >> 7 & 1;
|
||||
world->end = value >> 6 & 1;
|
||||
|
||||
/* Update background fields */
|
||||
z = value >> 10 & 3;
|
||||
world->bgMaskX = (1 << z) - 1;
|
||||
world->bgWidth = 512 << z;
|
||||
world->bgShift = z;
|
||||
z = value >> 8 & 3;
|
||||
world->bgMaskY = (1 << z) - 1;
|
||||
world->bgHeight = 512 << z;
|
||||
|
||||
/* Update world dimensions */
|
||||
if (world->bgm != 2) {
|
||||
world->height = world->hNaffine;
|
||||
world->width = world->wNaffine;
|
||||
} else {
|
||||
world->height = world->hAffine;
|
||||
world->width = world->wAffine;
|
||||
}
|
||||
|
||||
/* Update background arrangement */
|
||||
count = (world->bgMaskX + 1) * (world->bgMaskY + 1);
|
||||
base = value & 15 & ~(count > 8 ? 7 : count - 1);
|
||||
bg = (uint8_t *) BG_TEMPLATES[value >> 8 & 15];
|
||||
for (z = 0; z < count; z++)
|
||||
world->bg[z] = &sim->vip.cells[(base + bg[z]) << 12];
|
||||
break;
|
||||
|
||||
case 1: world->gx = SignExtend(value, 10); break;
|
||||
case 2: world->gp = SignExtend(value, 10); break;
|
||||
case 3: world->gy = (int16_t) value; break;
|
||||
case 4: world->mx = SignExtend(value, 13); break;
|
||||
case 5: world->mp = SignExtend(value, 15); break;
|
||||
case 6: world->my = SignExtend(value, 13); break;
|
||||
case 7: /* W */
|
||||
value = SignExtend(value, 13) + 1;
|
||||
world->wNaffine = value < 0 ? 0 : value;
|
||||
value = SignExtend(value, 10) + 1;
|
||||
world->wAffine = value < 0 ? 0 : value;
|
||||
world->width = world->bgm != 2 ? world->wNaffine : world->wAffine;
|
||||
break;
|
||||
case 8: /* H */
|
||||
value = (int16_t) value + 1;
|
||||
world->hNaffine = value < 8 ? 8 : value;
|
||||
world->hAffine = value < 0 ? 0 : value;
|
||||
world->height = world->bgm != 2 ? world->hNaffine : world->hAffine;
|
||||
break;
|
||||
case 9: world->paramBase = 0x20000|(uint32_t)(uint16_t)value<<1;break;
|
||||
case 10: world->overplane = &sim->vip.cells[(uint16_t) value]; break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Write a typed value to an I/O register */
|
||||
static void vipWriteIO(
|
||||
VB *sim, uint32_t address, int type, int32_t value, int debug) {
|
||||
@@ -328,10 +481,12 @@ static void vipWriteIO(
|
||||
break;
|
||||
|
||||
case 0x5F830>>1: /* CTA */
|
||||
if ((mask & 0xFF00) == 0)
|
||||
sim->vip.cta.cta_r = value >> 8;
|
||||
if ((mask & 0x00FF) == 0)
|
||||
sim->vip.cta.cta_l = value;
|
||||
if (debug) {
|
||||
if ((mask & 0xFF00) == 0)
|
||||
sim->vip.cta.cta_r = value >> 8;
|
||||
if ((mask & 0x00FF) == 0)
|
||||
sim->vip.cta.cta_l = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x5F842>>1: /* XPCTRL */
|
||||
@@ -627,298 +782,311 @@ static int vipEmulateDisplay(VB *sim, uint32_t clocks) {
|
||||
|
||||
/****************************** Pixel Processor ******************************/
|
||||
|
||||
/* Parse eye attributes and test window bounds */
|
||||
static int vipParseEye(WorldAttribs *wttr, EyeAttribs *ettr, int eye) {
|
||||
/* Draw a cell into shadow memory */
|
||||
static void vipDrawCell(uint8_t *shadow, Window *wnd,
|
||||
Cell *cell, int32_t x, int32_t y) {
|
||||
uint8_t *col, *row; /* Output pixel */
|
||||
uint8_t pixel; /* Character pixel value */
|
||||
int32_t px1, px2, py1, py2; /* Pixel bounds in cell */
|
||||
int32_t px, py; /* Pixel origin in cell */
|
||||
|
||||
/* Validate the world isn't right of frame */
|
||||
ettr->wx = wttr->gx - (eye == 0 ? wttr->gp : -wttr->gp);
|
||||
if (ettr->wx > 383)
|
||||
return 1;
|
||||
ettr->left = ettr->wx < 0 ? 0 : ettr->wx;
|
||||
/* Identify the visible pixels */
|
||||
py1 = y > wnd->y1 ? y : wnd->y1;
|
||||
py2 = y + 8 < wnd->y2 ? y + 8 : wnd->y2;
|
||||
px1 = x > wnd->x1 ? x : wnd->x1;
|
||||
px2 = x + 8 < wnd->x2 ? x + 8 : wnd->x2;
|
||||
|
||||
/* Validate the world isn't left of frame */
|
||||
ettr->right = ettr->wx + wttr->w - 1;
|
||||
if (ettr->right < 0)
|
||||
return 1;
|
||||
if (ettr->right > 383)
|
||||
ettr->right = 383;
|
||||
|
||||
/* BG source parallax */
|
||||
if (wttr->bgm != 2)
|
||||
ettr->bp = eye == 0 ? -wttr->mp : wttr->mp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse world attributes and test window bounds */
|
||||
static int vipParseWorld(VB*sim,uint8_t*world,uint16_t bits,WorldAttribs*attr){
|
||||
int32_t z; /* Scratch */
|
||||
|
||||
/* Validate the world isn't below the frame */
|
||||
attr->gy = busReadBuffer(world + 6, VB_S16);
|
||||
if (attr->gy > 223)
|
||||
return 1;
|
||||
|
||||
/* Validate the world isn't above the frame, and height is positive */
|
||||
attr->bgm = bits >> 12 & 3;
|
||||
attr->h = busReadBuffer(world + 16, VB_S16) + 1;
|
||||
z = attr->bgm == 2 ? 0 : 8;
|
||||
if (attr->h < z)
|
||||
attr->h = z;
|
||||
if (attr->h == 0)
|
||||
return 1;
|
||||
attr->bottom = attr->gy + attr->h - 1;
|
||||
attr->top = (int32_t) sim->vip.xp.sbcount << 3;
|
||||
if (attr->bottom < attr->top)
|
||||
return 1;
|
||||
if (attr->bottom > 223)
|
||||
attr->bottom = 223;
|
||||
if (attr->top < attr->gy)
|
||||
attr->top = attr->gy;
|
||||
|
||||
/* Validate width is positive */
|
||||
attr->w = SignExtend(busReadBuffer(world + 14, VB_U16),
|
||||
attr->bgm == 2 ? 10 : 13) + 1;
|
||||
if (attr->w < 1)
|
||||
return 1;
|
||||
|
||||
/* Parse attributes */
|
||||
attr->bg = (uint8_t *) BG_TEMPLATES[bits >> 8 & 15];
|
||||
attr->gx = SignExtend(busReadBuffer(world + 2, VB_U16), 10);
|
||||
attr->gp = SignExtend(busReadBuffer(world + 4, VB_U16), 10);
|
||||
attr->over = (bits & 0x0080) == 0 ? NULL :
|
||||
&sim->vip.ram[0x00020000 | busReadBuffer(world + 10, VB_U16) << 1];
|
||||
attr->scx = bits >> 10 & 3;
|
||||
attr->scy = bits >> 8 & 3;
|
||||
attr->bgw = (1 << attr->scx) - 1;
|
||||
attr->bgh = (1 << attr->scy) - 1;
|
||||
z = attr->scx + attr->scy;
|
||||
attr->base = bits & 15 & ~((1 << (z < 3 ? z : 3)) - 1);
|
||||
if (attr->bgm != 2) {
|
||||
attr->mx = SignExtend(busReadBuffer(world + 8, VB_U16), 13);
|
||||
attr->mp = SignExtend(busReadBuffer(world + 10, VB_U16), 15);
|
||||
attr->my = SignExtend(busReadBuffer(world + 12, VB_U16), 13);
|
||||
}
|
||||
if (attr->bgm != 0) {
|
||||
attr->params =
|
||||
0x20000 +
|
||||
(attr->top - attr->gy) * (attr->bgm == 1 ? 4 : 16) +
|
||||
((uint32_t) busReadBuffer(world + 18, VB_U16) << 1)
|
||||
;
|
||||
/* Process all columns of pixels */
|
||||
col = &shadow[px1 * 224 + py1];
|
||||
for (px = px1; px < px2; px++, col += 224)
|
||||
for (py = py1, row = col; py < py2; py++, row++) {
|
||||
pixel = cell->pixels[(py - y) << 3 | (px - x)];
|
||||
if (pixel != 0)
|
||||
*row = cell->palette[pixel];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Draw an object group into shadow memory */
|
||||
static void vipDrawObjects(VB *sim, int group) {
|
||||
uint8_t *dest; /* Pointer to object position in shadow memory */
|
||||
int fx; /* Output horizontal position */
|
||||
int fy; /* Output vertical position */
|
||||
uint8_t *obj; /* Pointer to object attributes in host memory */
|
||||
int32_t ox; /* Eye horizontal position */
|
||||
int pixel; /* Character input pixel */
|
||||
int start; /* Index of first object in group */
|
||||
int stop; /* Index of last object in group */
|
||||
int sx; /* Input horizontal position */
|
||||
int sy; /* Input vertical position */
|
||||
int32_t top; /* Window boundary */
|
||||
int i, o, x, y; /* Iterators */
|
||||
/* Draw a BG map into shadow memory */
|
||||
static void vipDrawBGMap(uint8_t *shadow, Window *wnd,
|
||||
uint8_t over, Cell *map, int32_t x, int32_t y) {
|
||||
Cell *col, *row; /* Source cell */
|
||||
int32_t cx1, cx2, cy1, cy2; /* Cell bounds in BG map pixels */
|
||||
int32_t cx, cy; /* Cell origin in BG map pixels */
|
||||
|
||||
/* Object attributes */
|
||||
uint32_t attr1; /* Attribute bits */
|
||||
uint32_t attr2; /* Attribute bits */
|
||||
int jhflp; /* Is flipped horizontally */
|
||||
int32_t jp; /* Stereo parallax */
|
||||
int jvflp; /* Is flipped vetically */
|
||||
int32_t jx; /* Base horizontal position */
|
||||
int32_t jy; /* Vertical position */
|
||||
uint8_t *plt; /* Palette */
|
||||
uint8_t *src; /* Pointer to character source data in host memory */
|
||||
/* Identify the visible cells */
|
||||
cy1 = (wnd->y1-y ) & ~7; if (cy1 < 0) cy1 = 0; if (cy1 > 512) cy1 = 512;
|
||||
cy2 = (wnd->y2-y+7) & ~7; if (cy2 < 0) cy2 = 0; if (cy2 > 512) cy2 = 512;
|
||||
cx1 = (wnd->x1-x ) & ~7; if (cx1 < 0) cx1 = 0; if (cx1 > 512) cx1 = 512;
|
||||
cx2 = (wnd->x2-x+7) & ~7; if (cx2 < 0) cx2 = 0; if (cx2 > 512) cx2 = 512;
|
||||
|
||||
/* Process all objects in the group */
|
||||
start = group == 0 ? 0 : (sim->vip.spt[group - 1] + 1) & 1023;
|
||||
stop = sim->vip.spt[group];
|
||||
top = (int32_t) sim->vip.xp.sbcount << 3;
|
||||
for (o = stop; o != -1; o = o == start ? -1 : (o - 1) & 1023) {
|
||||
obj = &sim->vip.ram[0x3E000 | o << 3];
|
||||
/* Process all cells as overplane */
|
||||
if (over) {
|
||||
for (cy = cy1; cy < cy2; cy += 8)
|
||||
for (cx = cx1; cx < cx2; cx += 8)
|
||||
vipDrawCell(shadow, wnd, map, x + cx, y + cy);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Validate object is enabled */
|
||||
attr1 = busReadBuffer(obj + 2, VB_U16);
|
||||
if ((attr1 & 0xC000) == 0) /* JLON, JRON */
|
||||
/* Process all cells normally */
|
||||
row = &map[cy1 << 3 | cx1 >> 3];
|
||||
for (cy = cy1 ; cy < cy2; cy += 8, row += 64)
|
||||
for (cx = cx1, col = row; cx < cx2; cx += 8, col++)
|
||||
vipDrawCell(shadow, wnd, col, x + cx, y + cy);
|
||||
}
|
||||
|
||||
/* Draw a background into shadow memory */
|
||||
static void vipDrawBackground(uint8_t *shadow, Window *wnd,
|
||||
World *world, int32_t x, int32_t y) {
|
||||
int32_t bx1, bx2, by1, by2; /* BG map bounds in background pixels */
|
||||
int32_t bx, by; /* BG map origin in background pixels */
|
||||
|
||||
/* Identify the visible BG maps */
|
||||
by1 = (wnd->y1 - y ) & ~511;
|
||||
by2 = (wnd->y2 - y - 1) & ~511;
|
||||
bx1 = (wnd->x1 - x ) & ~511;
|
||||
bx2 = (wnd->x2 - x - 1) & ~511;
|
||||
|
||||
/* Process all rows of BG maps */
|
||||
for (by = by1; by <= by2; by += 512) {
|
||||
|
||||
/* Row is out of bounds */
|
||||
if (world->over && (by < 0 || by >= world->bgHeight)) {
|
||||
for (bx = bx1; bx <= bx2; bx++)
|
||||
vipDrawBGMap(shadow, wnd, 1, world->overplane, x + bx, y + by);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Validate Y position */
|
||||
jy = (int8_t) obj[4];
|
||||
if (jy < 7)
|
||||
jy &= 0xFF;
|
||||
if (jy < top - 7 || jy > 223)
|
||||
continue;
|
||||
/* Process all columns of BG maps */
|
||||
for (bx = bx1; bx <= bx2; bx += 512) {
|
||||
|
||||
/* Parse attributes */
|
||||
jx = SignExtend(busReadBuffer(obj, VB_U16), 10);
|
||||
jp = SignExtend(attr1, 10);
|
||||
attr2 = busReadBuffer(obj + 6, VB_U16);
|
||||
jhflp = attr2 >> 13 & 1;
|
||||
jvflp = attr2 >> 12 & 1;
|
||||
|
||||
/* Locate palette and character state in host memory */
|
||||
plt = sim->vip.jplt[attr2 >> 14];
|
||||
src = vipCharacter(sim, attr2);
|
||||
|
||||
/* Draw the character */
|
||||
for (i = 0; i < 2; i++) {
|
||||
if ((attr1 >> (15 - i) & 1) == 0) /* J*ON */
|
||||
/* Column is out of bounds */
|
||||
if (world->over && (bx < 0 || bx >= world->bgWidth)) {
|
||||
vipDrawBGMap(shadow, wnd, 1, world->overplane, x + bx, y + by);
|
||||
continue;
|
||||
ox = jx - (i == 0 ? jp : -jp);
|
||||
dest = &sim->vip.shadow[i][ox * 224 + jy];
|
||||
|
||||
/* Draw all columns */
|
||||
for (x = 0; x < 8; x++, dest += 224) {
|
||||
fx = ox + x;
|
||||
if (fx < 0 || fx > 383)
|
||||
continue;
|
||||
sx = jhflp ? 7 - x : x;
|
||||
|
||||
/* Draw all rows */
|
||||
for (y = 0; y < 8; y++) {
|
||||
fy = jy + y;
|
||||
if (fy < top || fy > 223)
|
||||
continue;
|
||||
sy = jvflp ? 7 - y : y;
|
||||
pixel = src[sy << 1 | sx >> 2] >> ((sx & 3) << 1) & 3;
|
||||
if (pixel != 0)
|
||||
dest[y] = plt[pixel]; /* TODO: Research clocks */
|
||||
} /* y */
|
||||
|
||||
} /* x */
|
||||
|
||||
} /* i */
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Draw a background world into shadow memory */
|
||||
static void vipDrawWorld(VB *sim, uint8_t *world, uint16_t bits) {
|
||||
uint16_t bgAttr; /* BG map cell attributes */
|
||||
int32_t bx; /* BG source X */
|
||||
int32_t by; /* BG source Y */
|
||||
uint8_t *cell; /* Pointer to BG map cell in host memory */
|
||||
uint8_t *chr; /* Pointer to character data in host memory */
|
||||
int32_t cx; /* BG cell/character source X */
|
||||
int32_t cy; /* BG cell/character source Y */
|
||||
uint8_t *dest; /* Pointer to output in shadow memory */
|
||||
EyeAttribs ettr; /* Eye attributes */
|
||||
uint32_t param; /* Current line parameter address */
|
||||
int8_t pixel; /* Character pixel value */
|
||||
int32_t i, x, y; /* Iterators */
|
||||
|
||||
/* World attributes */
|
||||
WorldAttribs wttr; /* Common attributes */
|
||||
int32_t dx; /* Affine per-pixel X delta */
|
||||
int32_t dy; /* Affine per-pixel Y delta */
|
||||
int32_t mp; /* BG source parallax */
|
||||
int32_t mx; /* BG source X */
|
||||
int32_t my; /* BG source Y */
|
||||
int16_t hofst; /* H-bias shift */
|
||||
|
||||
/* Working variables */
|
||||
ettr.bp = wttr.mp = wttr.mx = wttr.my = wttr.params = 0;
|
||||
|
||||
/* Parse attributes */
|
||||
if (vipParseWorld(sim, world, bits, &wttr))
|
||||
return; /* Window not in frame */
|
||||
|
||||
/* Draw the world */
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
/* World is not visible */
|
||||
if ((bits & (int32_t) 0x8000 >> i) == 0) /* LON, RON */
|
||||
continue;
|
||||
|
||||
/* Process attributes */
|
||||
if (vipParseEye(&wttr, &ettr, i))
|
||||
continue;
|
||||
|
||||
/* Draw all rows */
|
||||
for (y = wttr.top, param = wttr.params; y <= wttr.bottom; y++) {
|
||||
|
||||
/* Parse line parameters */
|
||||
hofst = 0;
|
||||
mp = wttr.mp;
|
||||
mx = wttr.mx;
|
||||
my = wttr.my;
|
||||
switch (wttr.bgm) {
|
||||
case 1: /* H-bias */
|
||||
hofst = SignExtend(vipParam(param | i << 1), 13);
|
||||
param += 4;
|
||||
break;
|
||||
case 2: /* Affine */
|
||||
dx = (int32_t) (int16_t) vipParam(param|6) << 7;
|
||||
dy = (int32_t) (int16_t) vipParam(param|8) << 7;
|
||||
mp = (int32_t) (int16_t) vipParam(param|2);
|
||||
mp = ettr.left - ettr.wx - ((mp < 0) ^ i ? mp : 0);
|
||||
mx = ((int32_t) (int16_t) vipParam(param ) << 13) + dx*mp;
|
||||
my = ((int32_t) (int16_t) vipParam(param|4) << 13) + dy*mp;
|
||||
param += 16;
|
||||
}
|
||||
|
||||
/* Select output in shadow memory */
|
||||
dest = &sim->vip.shadow[i][ettr.left * 224 + y];
|
||||
/* Column is in bounds */
|
||||
vipDrawBGMap(shadow, wnd, 0, world->bg[
|
||||
(by >> 9 & world->bgMaskY) << world->bgShift |
|
||||
(bx >> 9 & world->bgMaskX)
|
||||
], x + bx, y + by);
|
||||
}
|
||||
|
||||
/* Draw all columns */
|
||||
for (x = ettr.left; x <= ettr.right; x++, dest += 224) {
|
||||
}
|
||||
|
||||
/* Locate the pixel in the background */
|
||||
if (wttr.bgm != 2) { /* Normal, H-bias */
|
||||
cx = x - ettr.wx + mx + ettr.bp + hofst;
|
||||
cy = y - wttr.gy + my;
|
||||
} else { /* Affine */
|
||||
cx = (int16_t) (mx >> 16);
|
||||
cy = (int16_t) (my >> 16);
|
||||
mx += dx;
|
||||
my += dy;
|
||||
}
|
||||
}
|
||||
|
||||
/* Locate the BG map in the background */
|
||||
bx = SignExtend(cx >> 9, 23);
|
||||
by = SignExtend(cy >> 9, 23);
|
||||
cell = NULL;
|
||||
/* Draw a Normal world into shadow memory */
|
||||
static void vipDrawNormal(VB *sim, World *world) {
|
||||
int32_t mx, my; /* Background origin */
|
||||
Window wnd; /* Window bounds in pixels */
|
||||
int i; /* Iterator */
|
||||
|
||||
/* BG map is out of bounds */
|
||||
if (
|
||||
bx < 0 || bx > wttr.bgw ||
|
||||
by < 0 || by > wttr.bgh
|
||||
) {
|
||||
if (wttr.over == NULL) {
|
||||
bx &= wttr.bgw;
|
||||
by &= wttr.bgh;
|
||||
} else cell = wttr.over;
|
||||
}
|
||||
/* No visible content */
|
||||
if (world->width == 0)
|
||||
return;
|
||||
|
||||
/* Locate the cell in the BG map */
|
||||
if (cell == NULL) {
|
||||
cell = &sim->vip.ram[
|
||||
0x20000 | ((
|
||||
wttr.base +
|
||||
wttr.bg[by << wttr.scx | bx]
|
||||
) << 13) |
|
||||
(cy << 4 & 0x1F80) |
|
||||
(cx >> 2 & 0x007E)
|
||||
];
|
||||
}
|
||||
/* Vertical window bounds */
|
||||
wnd.y1 = (int32_t) sim->vip.xp.sbcount << 3;
|
||||
if (world->gy > wnd.y1)
|
||||
wnd.y1 = world->gy;
|
||||
if (wnd.y1 >= 224)
|
||||
return;
|
||||
wnd.y2 = world->gy + world->height;
|
||||
if (wnd.y2 <= 0)
|
||||
return;
|
||||
my = world->gy - world->my;
|
||||
if (wnd.y1 < 0)
|
||||
wnd.y1 = 0;
|
||||
if (wnd.y2 > 224)
|
||||
wnd.y2 = 224;
|
||||
|
||||
/* Extract the pixel from the character */
|
||||
bgAttr = busReadBuffer(cell, VB_U16);
|
||||
chr = vipCharacter(sim, bgAttr);
|
||||
cx = (bgAttr & 0x2000 ? 7 - cx : cx) & 7;
|
||||
cy = (bgAttr & 0x1000 ? 7 - cy : cy) & 7;
|
||||
pixel = chr[cy << 1 | cx >> 2] >> ((cx & 3) << 1) & 3;
|
||||
/* Process both eyes */
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
/* Write the pixel into shadow memory */
|
||||
/* Check visibility */
|
||||
if (!(world->lron >> (i ^ 1) & 1))
|
||||
continue;
|
||||
|
||||
/* Horizontal window bounds */
|
||||
wnd.x1 = world->gx + (i == 0 ? -world->gp : world->gp);
|
||||
if (wnd.x1 >= 384)
|
||||
continue;
|
||||
wnd.x2 = wnd.x1 + world->width;
|
||||
if (wnd.x2 <= 0)
|
||||
continue;
|
||||
mx = wnd.x1 - world->mx - (i == 0 ? -world->mp : world->mp);
|
||||
if (wnd.x1 < 0)
|
||||
wnd.x1 = 0;
|
||||
if (wnd.x2 > 384)
|
||||
wnd.x2 = 384;
|
||||
|
||||
/* Draw the background into the window */
|
||||
vipDrawBackground(sim->vip.shadow[i], &wnd, world, mx, my);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Draw an H-bias world into shadow memory */
|
||||
static void vipDrawHBias(VB *sim, World *world) {
|
||||
int32_t mx, my; /* Background origin */
|
||||
uint8_t *param; /* World parameter memory */
|
||||
Window wnd; /* Window bounds in pixels */
|
||||
int32_t y1, y2; /* Vertical window bounds */
|
||||
int i, y; /* Iterators */
|
||||
|
||||
/* No visible content */
|
||||
if (world->width == 0)
|
||||
return;
|
||||
|
||||
/* Vertical window bounds */
|
||||
y1 = (int32_t) sim->vip.xp.sbcount << 3;
|
||||
if (world->gy > y1)
|
||||
y1 = world->gy;
|
||||
if (y1 >= 224)
|
||||
return;
|
||||
y2 = world->gy + world->height;
|
||||
if (y2 <= 0)
|
||||
return;
|
||||
my = world->gy - world->my;
|
||||
if (y1 < 0)
|
||||
y1 = 0;
|
||||
if (y2 > 224)
|
||||
y2 = 224;
|
||||
|
||||
/* Process both eyes */
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
/* Check visibility */
|
||||
if (!(world->lron >> (i ^ 1) & 1))
|
||||
continue;
|
||||
|
||||
/* Horizontal window bounds */
|
||||
wnd.x1 = world->gx + (i == 0 ? -world->gp : world->gp);
|
||||
if (wnd.x1 >= 384)
|
||||
continue;
|
||||
wnd.x2 = wnd.x1 + world->width;
|
||||
if (wnd.x2 <= 0)
|
||||
continue;
|
||||
mx = wnd.x1 - world->mx - (i == 0 ? -world->mp : world->mp);
|
||||
if (wnd.x1 < 0)
|
||||
wnd.x1 = 0;
|
||||
if (wnd.x2 > 384)
|
||||
wnd.x2 = 384;
|
||||
|
||||
/* Process all visible lines */
|
||||
param = &sim->vip.ram[world->paramBase +
|
||||
((y1 - world->gy) << 2) + (i << 1)];
|
||||
for (y = y1; y < y2; y++, param += 2) {
|
||||
|
||||
/* Configure window bounds */
|
||||
wnd.y1 = y;
|
||||
wnd.y2 = y + 1;
|
||||
|
||||
/* Draw the background into the window */
|
||||
vipDrawBackground(sim->vip.shadow[i], &wnd, world,
|
||||
mx + busReadBuffer(param, VB_S16), my);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Draw an Affine world into shadow memory */
|
||||
static void vipDrawAffine(VB *sim, World *world) {
|
||||
Cell *cell; /* Source cell */
|
||||
uint8_t *col, *row; /* Output pixel */
|
||||
uint8_t pixel; /* Character pixel value */
|
||||
uint8_t *param; /* World parameter memory */
|
||||
int32_t px, py; /* Pixel source coordinates */
|
||||
int32_t dx, dy, mp, mx, my; /* Affine parameters */
|
||||
int32_t wx; /* Base world X coordinate */
|
||||
int32_t x1, x2, y1, y2; /* Window bounds */
|
||||
int i, x, y; /* Iterators */
|
||||
|
||||
/* No visible content */
|
||||
if (world->width == 0 || world->height == 0)
|
||||
return;
|
||||
|
||||
/* Vertical window bounds */
|
||||
y1 = (int32_t) sim->vip.xp.sbcount << 3;
|
||||
if (world->gy > y1)
|
||||
y1 = world->gy;
|
||||
if (y1 >= 224)
|
||||
return;
|
||||
y2 = world->gy + world->height;
|
||||
if (y2 <= 0)
|
||||
return;
|
||||
if (y1 < 0)
|
||||
y1 = 0;
|
||||
if (y2 > 224)
|
||||
y2 = 224;
|
||||
|
||||
/* Process both eyes */
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
/* Check visibility */
|
||||
if (!(world->lron >> (i ^ 1) & 1))
|
||||
continue;
|
||||
|
||||
/* Horizontal window bounds */
|
||||
x1 = world->gx + (i == 0 ? -world->gp : world->gp);
|
||||
if (x1 >= 384)
|
||||
continue;
|
||||
x2 = x1 + world->width;
|
||||
if (x2 <= 0)
|
||||
continue;
|
||||
wx = x1;
|
||||
if (x1 < 0)
|
||||
x1 = 0;
|
||||
if (x2 > 384)
|
||||
x2 = 384;
|
||||
wx = x1 - wx;
|
||||
|
||||
/* Process all visible rows */
|
||||
param = &sim->vip.ram[world->paramBase | (y1 - world->gy) << 4];
|
||||
row = &sim->vip.shadow[i][x1 * 224 + y1];
|
||||
for (y = y1; y < y2; y++, param += 16, row++) {
|
||||
|
||||
/* Parse line parameters */
|
||||
mx = (int32_t) busReadBuffer(param + 0, VB_S16) << 6;
|
||||
mp = (int32_t) busReadBuffer(param + 2, VB_S16);
|
||||
my = (int32_t) busReadBuffer(param + 4, VB_S16) << 6;
|
||||
dx = (int32_t) busReadBuffer(param + 6, VB_S16);
|
||||
dy = (int32_t) busReadBuffer(param + 8, VB_S16);
|
||||
|
||||
/* Adjust left-edge parameters */
|
||||
if ((mp < 0) ^ i) {
|
||||
mx += dx * (wx - mp);
|
||||
my += dy * (wx - mp);
|
||||
} else {
|
||||
mx += dx * wx;
|
||||
my += dy * wx;
|
||||
}
|
||||
|
||||
/* Process all visible columns */
|
||||
col = row;
|
||||
for (x = x1; x < x2; x++, mx += dx, my += dy, col += 224) {
|
||||
px = (int16_t) (mx >> 9);
|
||||
py = (int16_t) (my >> 9);
|
||||
|
||||
/* Overplane */
|
||||
if (world->over && (
|
||||
px < 0 || px >= world->bgWidth ||
|
||||
py < 0 || py >= world->bgHeight
|
||||
)) cell = world->overplane;
|
||||
|
||||
/* Regular */
|
||||
else cell = &world->bg[
|
||||
(py >> 9 & world->bgMaskY) << world->bgShift |
|
||||
(px >> 9 & world->bgMaskX)
|
||||
][(py << 3 & 0xFC0) | (px >> 3 & 63)];
|
||||
|
||||
/* Sample the pixel */
|
||||
pixel = cell->pixels[(py & 7) << 3 | (px & 7)];
|
||||
if (pixel != 0)
|
||||
*dest = sim->vip.gplt[bgAttr >> 14 & 3][pixel];
|
||||
*col = cell->palette[pixel];
|
||||
|
||||
} /* x */
|
||||
|
||||
@@ -928,12 +1096,40 @@ static void vipDrawWorld(VB *sim, uint8_t *world, uint16_t bits) {
|
||||
|
||||
}
|
||||
|
||||
/* Draw an object group into shadow memory */
|
||||
static void vipDrawObjects(VB *sim, int group) {
|
||||
Object *obj; /* Object state */
|
||||
int start; /* First object in the group */
|
||||
int stop; /* Last object in the group */
|
||||
Window wnd; /* Window bounds in pixels */
|
||||
int i, o; /* Iterators */
|
||||
|
||||
/* Establish the viewing window */
|
||||
wnd.x1 = 0;
|
||||
wnd.y1 = (int32_t) sim->vip.xp.sbcount << 3;
|
||||
wnd.x2 = 384;
|
||||
wnd.y2 = 224;
|
||||
|
||||
/* Process all objects in the group */
|
||||
start = group == 0 ? 0 : (sim->vip.spt[group - 1] + 1) & 1023;
|
||||
stop = sim->vip.spt[group];
|
||||
for (o = stop; o != -1; o = o == start ? -1 : (o - 1) & 1023) {
|
||||
obj = &sim->vip.objects[o];
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (!(obj->lron >> (i ^ 1) & 1))
|
||||
continue;
|
||||
vipDrawCell(sim->vip.shadow[i], &wnd, &obj->cell,
|
||||
obj->jx + (i == 0 ? -obj->jp : obj->jp), obj->jy);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Draw the current graphics configuration into shadow memory */
|
||||
static void vipRender(VB *sim) {
|
||||
uint16_t attr; /* World attribute bits */
|
||||
int group; /* Next object group index */
|
||||
uint16_t *timing; /* Column timing in host memory */
|
||||
uint8_t *world; /* World attribute source in host memory */
|
||||
World *world; /* World attribute source in host memory */
|
||||
int32_t x, y; /* Iterators */
|
||||
|
||||
/* Erase all pixels */
|
||||
@@ -943,25 +1139,25 @@ static void vipRender(VB *sim) {
|
||||
|
||||
/* Process all worlds */
|
||||
group = 3;
|
||||
world = &sim->vip.ram[0x3DBE0]; /* World 31 */
|
||||
for (x = 31; x >= 0; x--, world -= 32) {
|
||||
attr = busReadBuffer(world, VB_U16);
|
||||
for (x = 31; x >= 0; x--) {
|
||||
world = &sim->vip.worlds[x];
|
||||
|
||||
/* Non-graphical world */
|
||||
if (attr & 0x0040) /* END */
|
||||
if (world->end)
|
||||
break; /* Control world */
|
||||
if ((attr & 0xC000) == 0) /* LON, RON */
|
||||
if (world->lron == 0)
|
||||
continue; /* Dummy world */
|
||||
|
||||
/* Object world */
|
||||
if ((attr >> 12 & 3) == 3) {
|
||||
vipDrawObjects(sim, group);
|
||||
group = (group - 1) & 3;
|
||||
/* Processing by world type */
|
||||
switch (world->bgm) {
|
||||
case 0: vipDrawNormal(sim, world); break;
|
||||
case 1: vipDrawHBias (sim, world); break;
|
||||
case 2: vipDrawAffine(sim, world); break;
|
||||
case 3:
|
||||
vipDrawObjects(sim, group);
|
||||
group = (group - 1) & 3;
|
||||
}
|
||||
|
||||
/* Background world */
|
||||
else if (attr & 0xC000) /* LON, RON */
|
||||
vipDrawWorld(sim, world, attr);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1134,7 +1330,11 @@ static void vipRead(VB *sim, uint32_t address, int type, int32_t *value) {
|
||||
|
||||
/* Simulate a hardware reset */
|
||||
static void vipReset(VB *sim) {
|
||||
int x, y; /* Iterators */
|
||||
Cell *cell; /* BG map cell state */
|
||||
Character *chr; /* Character state */
|
||||
Object *obj; /* Object state */
|
||||
World *world; /* World state */
|
||||
int x, y; /* Iterators */
|
||||
|
||||
/* Normal */
|
||||
sim->vip.intenb = 0x0000;
|
||||
@@ -1159,6 +1359,53 @@ static void vipReset(VB *sim) {
|
||||
sim->vip.jplt[x][y] = 0;
|
||||
}
|
||||
}
|
||||
for (x = 0; x < 0x10000; x++) {
|
||||
cell = &sim->vip.cells[x];
|
||||
cell->palette = sim->vip.gplt[0];
|
||||
cell->pixels = sim->vip.characters[0][0];
|
||||
}
|
||||
for (x = 0; x < 2048; x++) {
|
||||
chr = &sim->vip.characters[x];
|
||||
for (y = 0; y < 64; y++)
|
||||
(*chr)[0][y] = (*chr)[1][y] = (*chr)[2][y] = (*chr)[3][y] = 0;
|
||||
}
|
||||
for (x = 0; x < 32; x++) {
|
||||
world = &sim->vip.worlds[x];
|
||||
world->bgm = 0;
|
||||
world->end = 0;
|
||||
world->gp = 0;
|
||||
world->gx = 0;
|
||||
world->gy = 0;
|
||||
world->mx = 0;
|
||||
world->mp = 0;
|
||||
world->my = 0;
|
||||
world->over = 0;
|
||||
world->overplane = &sim->vip.cells[0];
|
||||
for (y = 0; y < 64; y++)
|
||||
world->bg[0] = world->overplane;
|
||||
world->bgHeight = 512;
|
||||
world->bgMaskX = 0;
|
||||
world->bgMaskY = 0;
|
||||
world->bgShift = 0;
|
||||
world->bgWidth = 512;
|
||||
world->hAffine = 0;
|
||||
world->hNaffine = 0;
|
||||
world->height = 8;
|
||||
world->lron = 0;
|
||||
world->paramBase = 0;
|
||||
world->wAffine = 0;
|
||||
world->wNaffine = 0;
|
||||
world->width = 0;
|
||||
}
|
||||
for (x = 0; x < 1024; x++) {
|
||||
obj = &sim->vip.objects[x];
|
||||
obj->cell.palette = sim->vip.jplt[0];
|
||||
obj->cell.pixels = sim->vip.characters[0][0];
|
||||
obj->jp = 0;
|
||||
obj->jx = 0;
|
||||
obj->jy = 0;
|
||||
obj->lron = 0;
|
||||
}
|
||||
|
||||
/* Display processor extra (the hardware does not do this) */
|
||||
sim->vip.dp.fclk = 0;
|
||||
@@ -1206,9 +1453,28 @@ static void vipWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
|
||||
address &= 0x0007FFFF;
|
||||
|
||||
/* RAM */
|
||||
if (address < 0x40000)
|
||||
if (address < 0x40000) {
|
||||
busWriteBuffer(&sim->vip.ram[address], type, value);
|
||||
|
||||
/* Character memory */
|
||||
if ((address & 0x26000) == 0x06000) {
|
||||
address = (address & 0x18000) >> 2 | (address & 0x01FFF);
|
||||
vipWriteChr(sim, address, type, value);
|
||||
}
|
||||
|
||||
/* BG map memory */
|
||||
if (address >= 0x20000)
|
||||
vipWriteCell(sim, address & 0x1FFFF, type, value);
|
||||
|
||||
/* World memory */
|
||||
if ((address & 0x3FC00) == 0x3D800)
|
||||
vipWriteWorld(sim, address &0x003FF, type, value);
|
||||
|
||||
/* Object memory */
|
||||
if (address >= 0x3E000)
|
||||
vipWriteObject(sim, address & 0x01FFF, type, value);
|
||||
}
|
||||
|
||||
/* Unmapped */
|
||||
else if (address < 0x5E000)
|
||||
;
|
||||
@@ -1223,6 +1489,7 @@ static void vipWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
|
||||
|
||||
/* Mirrors of character memory */
|
||||
else {
|
||||
vipWriteChr(sim, address & 0x07FFF, type, value);
|
||||
address = 0x06000 | (address << 2 & 0x18000) | (address & 0x01FFF);
|
||||
busWriteBuffer(&sim->vip.ram[address], type, value);
|
||||
}
|
||||
|
||||
+97
-90
@@ -74,100 +74,57 @@ static void vsuNextFreqMod(VB *sim, Channel *chan) {
|
||||
}
|
||||
|
||||
/* Process one channel */
|
||||
static void vsuEmulateChannel(VB *sim, int index, uint32_t clocks) {
|
||||
uint32_t bit; /* Pseudorandom bit */
|
||||
Channel *chan; /* Channel handle */
|
||||
int freqmod; /* Frequency modifications enabled */
|
||||
uint32_t until; /* Clocks to process sub-channel components */
|
||||
static void vsuEmulateChannel(VB *sim, Channel *chan) {
|
||||
uint32_t bit; /* Pseudorandom bit */
|
||||
|
||||
/* Select channel */
|
||||
chan = &sim->vsu.channels[index];
|
||||
|
||||
/* Channel is disabled */
|
||||
if (!chan->int_.enb)
|
||||
/* Automatic shutoff */
|
||||
if (chan->int_.auto_ && chan->int_.clocks == 0) {
|
||||
chan->int_.enb = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Process all clocks */
|
||||
do {
|
||||
/* Next input sample */
|
||||
if (chan->clocks == 0) {
|
||||
|
||||
/* Frequency modifications are active */
|
||||
freqmod =
|
||||
index == 4 && /* Channel 5 */
|
||||
sim->vsu.freqmod.enb && /* Modifications enabled */
|
||||
sim->vsu.freqmod.interval != 0 /* Modifications valid */
|
||||
;
|
||||
/* Wave */
|
||||
if (chan != &sim->vsu.channels[5]) {
|
||||
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
|
||||
chan->wave.sample = (chan->wave.sample + 1) & 31;
|
||||
}
|
||||
|
||||
/* Clocks until next state change */
|
||||
until = clocks;
|
||||
if (chan->clocks < until)
|
||||
until = chan->clocks;
|
||||
if (chan->env.enb && chan->env.clocks < until)
|
||||
until = chan->env.clocks;
|
||||
if (chan->int_.auto_ && chan->int_.clocks < until)
|
||||
until = chan->int_.clocks;
|
||||
if (freqmod && sim->vsu.freqmod.clocks < until)
|
||||
until = sim->vsu.freqmod.clocks;
|
||||
/* Noise */
|
||||
else {
|
||||
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
|
||||
bit = ((
|
||||
sim->vsu.noise.register_ >> NOISE_TAPS[sim->vsu.noise.tap]^
|
||||
sim->vsu.noise.register_ >> 7
|
||||
) & 1) ^ 1;
|
||||
sim->vsu.noise.register_ = bit |
|
||||
(sim->vsu.noise.register_ << 1 & 0x7FFE);
|
||||
}
|
||||
|
||||
/* Manage clocks */
|
||||
clocks -= until;
|
||||
chan->clocks -= until;
|
||||
if (chan->env.enb)
|
||||
chan->env.clocks -= until;
|
||||
if (chan->int_.auto_)
|
||||
chan->int_.clocks -= until;
|
||||
if (freqmod)
|
||||
sim->vsu.freqmod.clocks -= until;
|
||||
}
|
||||
|
||||
/* Automatic shutoff */
|
||||
if (chan->int_.auto_ && chan->int_.clocks == 0) {
|
||||
chan->int_.enb = 0;
|
||||
/* Envelope modification */
|
||||
if (chan->env.enb && chan->env.clocks == 0) {
|
||||
if (chan->env.dir == 0 && chan->env.value != 0)
|
||||
chan->env.value--;
|
||||
else if (chan->env.dir == 1 && chan->env.value != 15)
|
||||
chan->env.value++;
|
||||
else if (chan->env.rep)
|
||||
chan->env.value = chan->env.reload;
|
||||
chan->env.clocks = ((uint32_t) chan->env.interval + 1) * 307220;
|
||||
}
|
||||
|
||||
/* Frequency modification */
|
||||
if (chan->freqmod && sim->vsu.freqmod.clocks == 0) {
|
||||
chan->freq.current = sim->vsu.freqmod.next;
|
||||
vsuNextFreqMod(sim, chan);
|
||||
if (!chan->int_.enb)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Next sample */
|
||||
if (chan->clocks == 0) {
|
||||
|
||||
/* Wave */
|
||||
if (index != 5) {
|
||||
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
|
||||
chan->wave.sample = (chan->wave.sample + 1) & 31;
|
||||
}
|
||||
|
||||
/* Noise */
|
||||
else {
|
||||
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
|
||||
bit = ((
|
||||
sim->vsu.noise.register_ >> NOISE_TAPS[sim->vsu.noise.tap]^
|
||||
sim->vsu.noise.register_ >> 7
|
||||
) & 1) ^ 1;
|
||||
sim->vsu.noise.register_ = bit |
|
||||
(sim->vsu.noise.register_ << 1 & 0x7FFE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Envelope modification */
|
||||
if (chan->env.enb && chan->env.clocks == 0) {
|
||||
if (chan->env.dir == 0 && chan->env.value != 0)
|
||||
chan->env.value--;
|
||||
else if (chan->env.dir == 1 && chan->env.value != 15)
|
||||
chan->env.value++;
|
||||
else if (chan->env.rep)
|
||||
chan->env.value = chan->env.reload;
|
||||
chan->env.clocks = ((uint32_t) chan->env.interval + 1) * 307220;
|
||||
}
|
||||
|
||||
/* Frequency modification */
|
||||
if (freqmod && sim->vsu.freqmod.clocks == 0) {
|
||||
chan->freq.current = sim->vsu.freqmod.next;
|
||||
vsuNextFreqMod(sim, chan);
|
||||
if (!chan->int_.enb)
|
||||
return;
|
||||
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
|
||||
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
||||
}
|
||||
|
||||
} while (clocks != 0);
|
||||
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
|
||||
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -230,6 +187,8 @@ static void vsuWriteEV1(VB *sim, int index, uint8_t value) {
|
||||
sim->vsu.freqmod.func = value >> 4 & 1;
|
||||
sim->vsu.freqmod.rep = value >> 5 & 1;
|
||||
vsuNextFreqMod(sim, chan);
|
||||
chan->freqmod =
|
||||
sim->vsu.freqmod.enb && sim->vsu.freqmod.interval != 0;
|
||||
break;
|
||||
|
||||
case 5: /* Channel 6 */
|
||||
@@ -313,6 +272,8 @@ static void vsuWriteSWP(VB *sim, uint8_t value) {
|
||||
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
||||
if (clocks < sim->vsu.freqmod.clocks)
|
||||
sim->vsu.freqmod.clocks = clocks;
|
||||
sim->vsu.channels[4].freqmod =
|
||||
sim->vsu.freqmod.enb && sim->vsu.freqmod.interval != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -321,6 +282,8 @@ static void vsuWriteSWP(VB *sim, uint8_t value) {
|
||||
|
||||
/* Process component */
|
||||
static void vsuEmulate(VB *sim, uint32_t clocks) {
|
||||
Channel *chan; /* Input channel */
|
||||
uint32_t chantil; /* Clocks until next channel state update */
|
||||
float i0; /* Current analog input sample */
|
||||
float o0; /* Current analog output sample */
|
||||
uint16_t output[2]; /* Digital output samples */
|
||||
@@ -340,12 +303,54 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
|
||||
sim->vsu.clocks -= until;
|
||||
|
||||
/* Process all channels */
|
||||
for (x = 0; x < 6; x++)
|
||||
vsuEmulateChannel(sim, x, until);
|
||||
for (x = 0; x < 6; x++) {
|
||||
chan = &sim->vsu.channels[x];
|
||||
chantil = until;
|
||||
|
||||
/* Process all clocks */
|
||||
while (chan->int_.enb && chantil != 0) {
|
||||
|
||||
/* Determine when the next state change will occur */
|
||||
if (chan->until == 0) {
|
||||
|
||||
/* Clocks until next state change */
|
||||
chan->until = chan->clocks;
|
||||
if (chan->env.enb && chan->env.clocks < chan->until)
|
||||
chan->until = chan->env.clocks;
|
||||
if (chan->int_.auto_ && chan->int_.clocks < chan->until)
|
||||
chan->until = chan->int_.clocks;
|
||||
if (chan->freqmod && sim->vsu.freqmod.clocks < chan->until)
|
||||
chan->until = sim->vsu.freqmod.clocks;
|
||||
|
||||
/* Manage clocks */
|
||||
chan->clocks -= chan->until;
|
||||
if (chan->env.enb)
|
||||
chan->env.clocks -= chan->until;
|
||||
if (chan->int_.auto_)
|
||||
chan->int_.clocks -= chan->until;
|
||||
if (chan->freqmod)
|
||||
sim->vsu.freqmod.clocks -= chan->until;
|
||||
}
|
||||
|
||||
/* Manage clocks */
|
||||
if (chan->until > chantil) {
|
||||
chan->until -= chantil;
|
||||
chantil = 0;
|
||||
} else {
|
||||
chantil -= chan->until;
|
||||
chan->until = 0;
|
||||
}
|
||||
|
||||
/* Update channel state */
|
||||
if (chan->until == 0)
|
||||
vsuEmulateChannel(sim, chan);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Wait for the current sample to finish */
|
||||
if (sim->vsu.clocks != 0)
|
||||
continue;
|
||||
return;
|
||||
|
||||
/* Compute the output sample */
|
||||
output[0] = output[1] = 0;
|
||||
@@ -369,7 +374,7 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
|
||||
sim->vsu.out.offset >> 1 < sim->vsu.out.capacity
|
||||
) {
|
||||
|
||||
/* Processing by data type*/
|
||||
/* Processing by data type */
|
||||
switch (sim->vsu.out.type) {
|
||||
case VB_S16:
|
||||
((int16_t *) sim->vsu.out.samples)
|
||||
@@ -416,6 +421,8 @@ static void vsuReset(VB *sim) {
|
||||
for (x = 0; x < 6; x++) {
|
||||
chan = &sim->vsu.channels[x];
|
||||
chan->clocks = 0;
|
||||
chan->freqmod = 0;
|
||||
chan->until = 0;
|
||||
chan->env.clocks = 0;
|
||||
chan->env.enb = 0;
|
||||
chan->env.dir = 0;
|
||||
|
||||
+24
-18
@@ -1,4 +1,4 @@
|
||||
/* This file is included into vb.c and cannot be compiled on its own. */
|
||||
/* This file is included into vbu.c and cannot be compiled on its own. */
|
||||
#ifdef VBUAPI
|
||||
|
||||
|
||||
@@ -253,18 +253,24 @@ static void dasmOpDisp26(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||
}
|
||||
|
||||
/* Format a 5-bit sign-extended immediate operand */
|
||||
static void dasmOpImm5S(char *dest, VBU_DasmLine *line) {
|
||||
static void dasmOpImm5S(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){
|
||||
if (config->immediateNotation == VBU_NUMBER)
|
||||
*dest++ = '#';
|
||||
sprintf(dest, "%d", SignExtend(line->code[0], 5));
|
||||
}
|
||||
|
||||
/* Format a 5-bit zero-filled immediate operand */
|
||||
static void dasmOpImm5U(char *dest, VBU_DasmLine *line) {
|
||||
static void dasmOpImm5U(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){
|
||||
if (config->immediateNotation == VBU_NUMBER)
|
||||
*dest++ = '#';
|
||||
sprintf(dest, "%d", line->code[0] & 31);
|
||||
}
|
||||
|
||||
/* Format a 16-bit sign-extended immediate operand */
|
||||
static void dasmOpImm16S(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||
int32_t imm = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
|
||||
int32_t imm = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
|
||||
if (config->immediateNotation == VBU_NUMBER)
|
||||
*dest++ = '#';
|
||||
if (imm >= -256 && imm <= 256) {
|
||||
sprintf(dest, "%d", imm);
|
||||
return;
|
||||
@@ -279,6 +285,8 @@ static void dasmOpImm16S(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||
/* Format a 16-bit zero-filled immediate operand */
|
||||
static void dasmOpImm16U(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||
uint16_t imm = (uint16_t) line->code[3] << 8 | line->code[2];
|
||||
if (config->immediateNotation == VBU_NUMBER)
|
||||
*dest++ = '#';
|
||||
dasmToHex(dest, config, 4, imm);
|
||||
}
|
||||
|
||||
@@ -441,12 +449,6 @@ static VBU_DasmLine* dasmGrow(
|
||||
return *lines == NULL ? NULL : &(*lines)[index];
|
||||
}
|
||||
|
||||
/* Determine the size of an instruction */
|
||||
static uint32_t dasmInstSize(VB *sim, uint32_t address) {
|
||||
unsigned opcode = vbRead(sim, address, VB_U16) >> 10 & 63;
|
||||
return opcode < 0x20 || opcode == 0x32 || opcode == 0x36 ? 2 : 4;
|
||||
}
|
||||
|
||||
/* Format an operand */
|
||||
static void dasmOperand(char *dest, VBU_DasmConfig *config,
|
||||
VBU_DasmLine *line, uint8_t type) {
|
||||
@@ -454,8 +456,8 @@ static void dasmOperand(char *dest, VBU_DasmConfig *config,
|
||||
case DASM_BCOND : dasmOpBCOND (dest, config, line); break;
|
||||
case DASM_DISP9 : dasmOpDisp9 (dest, config, line); break;
|
||||
case DASM_DISP26: dasmOpDisp26(dest, config, line); break;
|
||||
case DASM_IMM5S : dasmOpImm5S (dest, line); break;
|
||||
case DASM_IMM5U : dasmOpImm5U (dest, line); break;
|
||||
case DASM_IMM5S : dasmOpImm5S (dest, config, line); break;
|
||||
case DASM_IMM5U : dasmOpImm5U (dest, config, line); break;
|
||||
case DASM_IMM16S: dasmOpImm16S(dest, config, line); break;
|
||||
case DASM_IMM16U: dasmOpImm16U(dest, config, line); break;
|
||||
case DASM_JMP : dasmOpJMP (dest, config, line); break;
|
||||
@@ -507,11 +509,13 @@ static int dasmLine(VB *sim, uint32_t *address, uint32_t pc,
|
||||
/* Process non-text members */
|
||||
line = &(*lines)[index];
|
||||
line->address = *address;
|
||||
line->codeLength = dasmInstSize(sim, *address);
|
||||
line->codeLength = vbuCodeSize(sim, *address);
|
||||
line->isPC = *address == pc;
|
||||
for (x = 0; x < line->codeLength; x++)
|
||||
line->code[x] = vbRead(sim, *address + x, VB_U8);
|
||||
*address += pc - *address < line->codeLength ?
|
||||
|
||||
/* Advance to the next instruction or PC, whichever is sooner */
|
||||
*address += pc != *address && pc - *address < line->codeLength ?
|
||||
pc - *address : line->codeLength;
|
||||
|
||||
/* Do not process text members */
|
||||
@@ -652,10 +656,12 @@ static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
|
||||
}
|
||||
|
||||
/* Check if the instruction contains the reference address */
|
||||
size = dasmInstSize(sim, addr);
|
||||
size = vbuCodeSize(sim, addr);
|
||||
if (address - addr < size)
|
||||
break;
|
||||
addr += pc - addr < size ? pc - addr : size;
|
||||
|
||||
/* Advance to the next instruction or PC, whichever is sooner */
|
||||
addr += addr != pc && pc - addr < size ? pc - addr : size;
|
||||
}
|
||||
|
||||
/* Address of first line is in the circular buffer */
|
||||
@@ -666,7 +672,7 @@ static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
|
||||
|
||||
/* Keep decoding until the first line of output */
|
||||
else for (; line < 0; line++)
|
||||
addr += dasmInstSize(sim, addr);
|
||||
addr += vbuCodeSize(sim, addr);
|
||||
|
||||
/* Working variables */
|
||||
size = length * sizeof (VBU_DasmLine);
|
||||
@@ -680,7 +686,7 @@ static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
|
||||
if (dasmLine(sim, &addr, pc, config, &lines, &size, &offset, x))
|
||||
goto catch;
|
||||
}
|
||||
return lines;
|
||||
return VBU_REALLOC(lines, offset);
|
||||
|
||||
/* Exception handler */
|
||||
catch:
|
||||
|
||||
@@ -38,6 +38,12 @@ static int32_t SignExtend(int32_t value, int32_t bits) {
|
||||
|
||||
/******************************* API Commands ********************************/
|
||||
|
||||
/* Determine the size in bytes of an instruction */
|
||||
VBUAPI int vbuCodeSize(VB *sim, uint32_t address) {
|
||||
int opcode = vbRead(sim, address, VB_U16) >> 10 & 63;
|
||||
return opcode < 0x20 || opcode == 0x32 || opcode == 0x36 ? 2 : 4;
|
||||
}
|
||||
|
||||
/* Initialize disassembler options with default settings */
|
||||
VBUAPI VBU_DasmConfig* vbuDasmInit(VBU_DasmConfig *config) {
|
||||
config->bcondNotation = VBU_JOINED;
|
||||
@@ -47,6 +53,7 @@ VBUAPI VBU_DasmConfig* vbuDasmInit(VBU_DasmConfig *config) {
|
||||
config->conditionNotation = VBU_NAMES;
|
||||
config->hexCase = VBU_UPPER;
|
||||
config->hexNotation = VBU_0X;
|
||||
config->immediateNotation = VBU_NONE;
|
||||
config->memoryNotation = VBU_OUTSIDE;
|
||||
config->mnemonicCase = VBU_UPPER;
|
||||
config->operandOrder = VBU_DEST_LAST;
|
||||
|
||||
@@ -29,6 +29,8 @@ extern "C" {
|
||||
#define VBU_L 0
|
||||
#define VBU_LOWER 1
|
||||
#define VBU_NAMES 1
|
||||
#define VBU_NONE 0
|
||||
#define VBU_NUMBER 1
|
||||
#define VBU_NUMBERS 0
|
||||
#define VBU_OUTSIDE 0
|
||||
#define VBU_SPLIT 1
|
||||
@@ -48,6 +50,7 @@ typedef struct { /* Defaults listed first */
|
||||
uint8_t conditionNotation; /* NAMES, NUMBERS */
|
||||
uint8_t hexCase; /* UPPER, LOWER */
|
||||
uint8_t hexNotation; /* 0X, H, DOLLAR */
|
||||
uint8_t immediateNotation; /* NONE, NUMBER */
|
||||
uint8_t memoryNotation; /* OUTSIDE, INSIDE */
|
||||
uint8_t mnemonicCase; /* UPPER, LOWER */
|
||||
uint8_t operandOrder; /* DEST_LAST, DEST_FIRST */
|
||||
@@ -82,6 +85,7 @@ typedef struct {
|
||||
|
||||
/******************************* API Commands ********************************/
|
||||
|
||||
VBUAPI int vbuCodeSize (VB *sim, uint32_t address);
|
||||
VBUAPI VBU_DasmConfig* vbuDasmInit (VBU_DasmConfig *config);
|
||||
VBUAPI VBU_DasmLine* vbuDisassemble(VB *sim, uint32_t address, VBU_DasmConfig *config, unsigned length, int line);
|
||||
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
"use strict";
|
||||
|
||||
//////////////////////////////////// Audio ////////////////////////////////////
|
||||
|
||||
// Dedicated audio output processor
|
||||
class Audio extends AudioWorkletProcessor {
|
||||
|
||||
// Instance fields
|
||||
buffers; // Input sample buffer queue
|
||||
core; // Communications with core thread
|
||||
dom; // Communications with DOM thread
|
||||
offset; // Offset into oldest buffer
|
||||
|
||||
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.port.onmessage = async e=>{
|
||||
await this.#construct(e.data.core);
|
||||
this.port.postMessage(0);
|
||||
};
|
||||
}
|
||||
|
||||
// Asynchronous constructor
|
||||
async #construct(core) {
|
||||
|
||||
// Configure instance fields
|
||||
this.buffers = [];
|
||||
this.core = core;
|
||||
this.dom = this.port;
|
||||
this.offset = 0;
|
||||
|
||||
// Configure communications
|
||||
this.core.onmessage = e=>this.#onCore(e.data);
|
||||
this.dom .onmessage = e=>this.#onDOM (e.data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Produce output samples (called by the user agent)
|
||||
process(inputs, outputs, parameters) {
|
||||
let output = outputs[0];
|
||||
let length = output [0].length;
|
||||
let empty = null;
|
||||
|
||||
// Process all samples
|
||||
for (let x = 0; x < length;) {
|
||||
|
||||
// No bufferfed samples are available
|
||||
if (this.buffers.length == 0) {
|
||||
for (; x < length; x++)
|
||||
output[0][x] = output[1][x] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Transfer samples from the oldest buffer
|
||||
let buffer = this.buffers[0];
|
||||
let y = this.offset;
|
||||
for (; x < length && y < buffer.length; x++, y+=2) {
|
||||
output[0][x] = buffer[y ];
|
||||
output[1][x] = buffer[y + 1];
|
||||
}
|
||||
|
||||
// Advance to the next buffer
|
||||
if (y == buffer.length) {
|
||||
if (empty == null)
|
||||
empty = [];
|
||||
empty.push(this.buffers.shift().buffer);
|
||||
this.offset = 0;
|
||||
}
|
||||
|
||||
// Buffer is not empty
|
||||
else this.offset = y;
|
||||
}
|
||||
|
||||
// Return emptied sample buffers to the core thread
|
||||
if (empty != null)
|
||||
this.core.postMessage(empty, empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Event Handlers //////////////////////////////
|
||||
|
||||
// Message received from core thread
|
||||
#onCore(e) {
|
||||
this.buffers.push(new Float32Array(e));
|
||||
}
|
||||
|
||||
// Message received from DOM thread
|
||||
#onDOM(e) {
|
||||
}
|
||||
|
||||
}
|
||||
registerProcessor("shrooms-vb", Audio);
|
||||
@@ -0,0 +1,89 @@
|
||||
let Constants = {
|
||||
|
||||
// Core
|
||||
VB: {
|
||||
|
||||
// System registers
|
||||
ADTRE: 25,
|
||||
CHCW : 24,
|
||||
ECR : 4,
|
||||
EIPC : 0,
|
||||
EIPSW: 1,
|
||||
FEPC : 2,
|
||||
FEPSW: 3,
|
||||
PIR : 6,
|
||||
PSW : 5,
|
||||
TKCW : 7,
|
||||
|
||||
// Memory access data types
|
||||
S8 : 0,
|
||||
U8 : 1,
|
||||
S16: 2,
|
||||
U16: 3,
|
||||
S32: 4,
|
||||
F32: 5,
|
||||
|
||||
// Option keys
|
||||
PSEUDO_HALT: 0,
|
||||
|
||||
// Controller buttons
|
||||
PWR: 0x0001,
|
||||
SGN: 0x0002,
|
||||
A : 0x0004,
|
||||
B : 0x0008,
|
||||
RT : 0x0010,
|
||||
LT : 0x0020,
|
||||
RU : 0x0040,
|
||||
RR : 0x0080,
|
||||
LR : 0x0100,
|
||||
LL : 0x0200,
|
||||
LD : 0x0400,
|
||||
LU : 0x0800,
|
||||
STA: 0x1000,
|
||||
SEL: 0x2000,
|
||||
RL : 0x4000,
|
||||
RD : 0x8000
|
||||
},
|
||||
|
||||
// Utility
|
||||
VBU: {
|
||||
|
||||
// Disassembler options
|
||||
"0X" : 0,
|
||||
C : 1,
|
||||
DEST_FIRST: 1,
|
||||
DEST_LAST : 0,
|
||||
DOLLAR : 1,
|
||||
E : 0,
|
||||
H : 2,
|
||||
INSIDE : 1,
|
||||
JOINED : 0,
|
||||
L : 0,
|
||||
LOWER : 1,
|
||||
NAMES : 1,
|
||||
NONE : 0,
|
||||
NUMBER : 1,
|
||||
NUMBERS : 0,
|
||||
OUTSIDE : 0,
|
||||
SPLIT : 1,
|
||||
UPPER : 0,
|
||||
Z : 1
|
||||
},
|
||||
|
||||
// Web interface
|
||||
web: {
|
||||
|
||||
// Break types
|
||||
BREAK_FRAME: 1,
|
||||
BREAK_POINT: 2,
|
||||
|
||||
// Anaglyph colors
|
||||
STEREO_CYAN : 0x00C6F0,
|
||||
STEREO_GREEN : 0x00B400,
|
||||
STEREO_MAGENTA: 0xC800FF,
|
||||
STEREO_RED : 0xFF0000
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export { Constants };
|
||||
+570
@@ -0,0 +1,570 @@
|
||||
"use strict";
|
||||
import { Constants } from "./Constants.js";
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////// Core /////////////////////////////////////
|
||||
|
||||
// Emulation processor
|
||||
new class Core {
|
||||
|
||||
// Instance fields
|
||||
audio; // Audio communication
|
||||
automatic; // Automatic emulation state
|
||||
clocked; // Clocked emulation state
|
||||
dom; // DOM communication
|
||||
mallocs; // Memory allocations by pointer
|
||||
pointerType; // TypedArray for WebAssembly pointers
|
||||
sims; // Simulations by pointer
|
||||
|
||||
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
onmessage = async e=>{
|
||||
await this.#construct(e.data.audio, e.data.wasmUrl);
|
||||
this.dom.postMessage(0);
|
||||
};
|
||||
}
|
||||
|
||||
// Asynchronous constructor
|
||||
async #construct(audio, wasmUrl) {
|
||||
|
||||
// Configure instance fields
|
||||
this.mallocs = new Map();
|
||||
this.sims = new Map();
|
||||
|
||||
// DOM thread communication
|
||||
this.dom = globalThis;
|
||||
this.dom.onmessage = e=>this[e.data.command](e.data);
|
||||
|
||||
// Instantiate the WebAssembly module
|
||||
this.wasm = (await WebAssembly.instantiateStreaming(
|
||||
fetch(wasmUrl), {
|
||||
env: {
|
||||
emscripten_notify_memory_growth: ()=>this.#onGrowth()
|
||||
}
|
||||
}));
|
||||
Object.assign(this, this.wasm.instance.exports);
|
||||
this.pointerType = this.PointerSize() == 8 ?
|
||||
BigUint64Array : Uint32Array;
|
||||
|
||||
// Configure audio state
|
||||
this.audio = audio;
|
||||
audio.buffers = [0,0,0].map(v=>new Float32Array(41700 / 50 * 2));
|
||||
audio.samples =
|
||||
this.#malloc(41700 / 50 * 2, audio, "samples", Float32Array);
|
||||
audio.onmessage = e=>this.#onAudio(e.data);
|
||||
|
||||
// Configure emulation states
|
||||
this.automatic = { emulating: false };
|
||||
this.clocked = {};
|
||||
for (let s of [ this.automatic, this.clocked ]) {
|
||||
s.clocks = this.#malloc(1, s, "clocks" , Uint32Array);
|
||||
s.pointers = this.#malloc(1, s, "pointers", this.pointerType);
|
||||
s.sims = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////// Core Commands //////////////////////////////
|
||||
|
||||
// Instantiate sims
|
||||
createSims(message) {
|
||||
let sims = new Array(message.count);
|
||||
let size = this.vbSizeOf();
|
||||
|
||||
// Process all sims
|
||||
for (let x = 0; x < message.count; x++) {
|
||||
let sim = {
|
||||
canvas : null,
|
||||
keys : Constants.VB.SGN,
|
||||
pointer : sims[x] = this.CreateSim()
|
||||
};
|
||||
this.sims.set(sim.pointer, sim);
|
||||
|
||||
// Video
|
||||
sim.pixels = this.#getPixels(sim);
|
||||
sim.image = new ImageData(sim.pixels, 384, 224);
|
||||
|
||||
// Audio
|
||||
sim.samples = this.#noalloc(
|
||||
this.GetExtSamples(sim.pointer),
|
||||
41700 / 50 * 2, sim, "samples", Float32Array
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
this.dom.postMessage({
|
||||
sims : sims,
|
||||
promised: true
|
||||
});
|
||||
}
|
||||
|
||||
// Produce disassembly from a sim
|
||||
disassemble(message) {
|
||||
|
||||
// Disassemble from the simulation
|
||||
let dasm = message.config == null ?
|
||||
this.vbuDisassemble(
|
||||
message.sim,
|
||||
message.address,
|
||||
0,
|
||||
message.length,
|
||||
message.line
|
||||
)
|
||||
:
|
||||
this.Disassemble(
|
||||
message.config.bcondNotation,
|
||||
message.config.conditionCase,
|
||||
message.config.conditionCL,
|
||||
message.config.conditionEZ,
|
||||
message.config.conditionNotation,
|
||||
message.config.hexCase,
|
||||
message.config.hexNotation,
|
||||
message.config.immediateNotation,
|
||||
message.config.memoryNotation,
|
||||
message.config.mnemonicCase,
|
||||
message.config.operandOrder,
|
||||
message.config.programCase,
|
||||
message.config.programNotation,
|
||||
message.config.setfNotation,
|
||||
message.config.systemCase,
|
||||
message.config.systemNotation,
|
||||
message.sim,
|
||||
message.address,
|
||||
message.length,
|
||||
message.line
|
||||
)
|
||||
;
|
||||
|
||||
// A memory error occurred
|
||||
if (dasm == 0) {
|
||||
this.dom.postMessage({
|
||||
promised: message.promised,
|
||||
success : false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve all disassembly data into a working buffer
|
||||
let pointer = this.Realloc(0, message.length * 17 * 4);
|
||||
let buffer = new Uint32Array(
|
||||
this.memory.buffer, pointer, message.length * 17);
|
||||
this.GetDasm(pointer, dasm, message.length);
|
||||
|
||||
// Consume output lines
|
||||
let lines = new Array(message.length);
|
||||
for (let x = 0, z = 0; x < lines.length; x++) {
|
||||
let line = lines[x] = { text: {} };
|
||||
line.address = buffer[z++];
|
||||
line.code = new Array(buffer[z++]);
|
||||
for (let y = 0; y < line.code.length; y++)
|
||||
line.code[y] = buffer[z++];
|
||||
z += 4 - line.code.length;
|
||||
line.isPC = buffer[z++] != 0;
|
||||
line.text.address = this.#string(dasm + buffer[z++], true);
|
||||
line.text.code = new Array(line.code.length);
|
||||
for (let y = 0; y < line.code.length; y++)
|
||||
line.text.code[y] = this.#string(dasm + buffer[z++], true);
|
||||
z += 4 - line.code.length;
|
||||
line.text.mnemonic = this.#string(dasm + buffer[z++], true);
|
||||
line.text.operands = new Array(buffer[z++]);
|
||||
for (let y = 0; y < line.text.operands.length; y++)
|
||||
line.text.operands[y] = this.#string(dasm + buffer[z++], true);
|
||||
z += 3 - line.text.operands.length;
|
||||
}
|
||||
|
||||
// Memory cleanup
|
||||
this.Realloc(pointer, 0);
|
||||
this.Realloc(dasm , 0);
|
||||
|
||||
// Send response
|
||||
this.dom.postMessage({
|
||||
success : true,
|
||||
lines : lines,
|
||||
promised: message.promised
|
||||
});
|
||||
}
|
||||
|
||||
// Emulate automatically
|
||||
emulateAutomatic(message) {
|
||||
|
||||
// Configure sims
|
||||
this.automatic.pointers = this.#realloc(
|
||||
this.automatic.pointers, message.sims.length);
|
||||
for (let x = 0; x < message.sims.length; x++) {
|
||||
this.automatic.pointers[x] = message.sims[x];
|
||||
this.automatic.sims [x] = this.sims.get(message.sims[x]);
|
||||
}
|
||||
|
||||
// Notify the DOM thread
|
||||
this.dom.postMessage({ promised: true });
|
||||
|
||||
// Begin automatic emulation
|
||||
this.automatic.emulating = true;
|
||||
this.#autoEmulate();
|
||||
}
|
||||
|
||||
// Emulate for a given number of clocks
|
||||
emulateClocked(message) {
|
||||
|
||||
// Configure sims
|
||||
this.clocked.pointers = this.#realloc(
|
||||
this.clocked.pointers, message.sims.length);
|
||||
for (let x = 0; x < message.sims.length; x++) {
|
||||
this.clocked.pointers[x] = message.sims[x];
|
||||
this.clocked.sims [x] = this.sims.get(message.sims[x]);
|
||||
}
|
||||
|
||||
// Process simulations
|
||||
let broke = false;
|
||||
this.clocked.clocks[0] = message.clocks;
|
||||
while (!broke && this.clocked.clocks[0] != 0) {
|
||||
|
||||
// Process simulations until a suspension
|
||||
this.Emulate(
|
||||
this.clocked.pointers.pointer,
|
||||
message.sims.length,
|
||||
this.clocked.clocks.pointer
|
||||
);
|
||||
|
||||
// Monitor break conditions
|
||||
for (let x = 0; x < message.sims.length; x++) {
|
||||
let sim = this.clocked.sims[x];
|
||||
sim.breaks = this.GetBreaks(sim.pointer);
|
||||
if (breaks & Constants.web.BREAK_POINT)
|
||||
broke = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update images
|
||||
for (let sim of this.clocked.sims) {
|
||||
if (!(sim.breaks & Constants.web.BREAK_FRAME))
|
||||
continue;
|
||||
this.GetPixels(sim.pointer);
|
||||
sim.context.putImageData(sim.image, 0, 0);
|
||||
}
|
||||
|
||||
// Notify DOM thread
|
||||
this.dom.postMessage({
|
||||
promised: true,
|
||||
broke : broke,
|
||||
clocks : this.clocked.clocks[0]
|
||||
});
|
||||
}
|
||||
|
||||
// Specify anaglyph colors
|
||||
setAnaglyph(message) {
|
||||
this.SetAnaglyph(message.sim, message.left, message.right);
|
||||
this.dom.postMessage({ promised: true });
|
||||
}
|
||||
|
||||
// Specify the OffscreenCanvas that goes with a sim
|
||||
setCanvas(message) {
|
||||
let sim = this.sims.get(message.sim);
|
||||
sim.canvas = message.canvas;
|
||||
sim.context = sim.canvas.getContext("2d");
|
||||
sim.context.putImageData(sim.image, 0, 0);
|
||||
this.dom.postMessage({ promised: true });
|
||||
}
|
||||
|
||||
// Specify a game pak RAM buffer
|
||||
setCartRAM(message) {
|
||||
this.#setCartMemory(message.sim, message.data,
|
||||
this.vbGetCartRAM, this.vbSetCartRAM);
|
||||
}
|
||||
|
||||
// Specify a game pak ROM buffer
|
||||
setCartROM(message) {
|
||||
this.#setCartMemory(message.sim, message.data,
|
||||
this.vbGetCartROM, this.vbSetCartROM);
|
||||
}
|
||||
|
||||
// Specify new game pad keys
|
||||
setKeys(message) {
|
||||
this.vbSetKeys(message.sim, message.keys);
|
||||
this.dom.postMessage({ promised: true });
|
||||
}
|
||||
|
||||
// Specify audio panning
|
||||
setPanning(message) {
|
||||
this.SetPanning(message.sim, message.panning);
|
||||
this.dom.postMessage({ promised: true });
|
||||
}
|
||||
|
||||
// Specify a new communication peer
|
||||
setPeer(message) {
|
||||
let orphaned = [];
|
||||
let prev = this.vbGetPeer(message.sim);
|
||||
if (prev != message.peer) {
|
||||
if (prev != 0) // Sim's previous peer has been orphaned
|
||||
orphaned.push(prev);
|
||||
if (message.peer != 0) {
|
||||
prev = this.vbGetPeer(message.peer);
|
||||
if (prev != null) // Peer's previous peer has been orphaned
|
||||
orphaned.push(prev);
|
||||
}
|
||||
this.vbSetPeer(message.sim, message.peer);
|
||||
}
|
||||
this.dom.postMessage({
|
||||
orphaned: orphaned,
|
||||
promised: true
|
||||
});
|
||||
}
|
||||
|
||||
// Specify audio volume
|
||||
setVolume(message) {
|
||||
this.SetVolume(message.sim, message.volume);
|
||||
this.dom.postMessage({ promised: true });
|
||||
}
|
||||
|
||||
// Suspend automatic emulation
|
||||
suspend(message) {
|
||||
this.automatic.emulating = false;
|
||||
this.dom.postMessage({ promised: true });
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Event Handlers //////////////////////////////
|
||||
|
||||
// Message from audio thread
|
||||
#onAudio(e) {
|
||||
|
||||
// Output staged images
|
||||
if (this.automatic.emulating && this.audio.buffers.length == 0) {
|
||||
for (let sim of this.automatic.sims)
|
||||
sim.context.putImageData(sim.image, 0, 0);
|
||||
}
|
||||
|
||||
// Acquire the emptied buffers and resume emulation
|
||||
this.audio.buffers.push(... e.map(b=>new Float32Array(b)));
|
||||
this.#autoEmulate();
|
||||
}
|
||||
|
||||
// WebAssembly memory has grown
|
||||
#onGrowth() {
|
||||
for (let prev of this.mallocs.values()) {
|
||||
let buffer = new prev.constructor(
|
||||
this.memory.buffer, prev.pointer, prev.size);
|
||||
Object.assign(buffer, {
|
||||
assign : prev.assign,
|
||||
pointer: prev.pointer,
|
||||
size : prev.size,
|
||||
target : prev.target
|
||||
});
|
||||
this.mallocs.set(buffer.pointer, buffer);
|
||||
this.#updateTarget(buffer);
|
||||
}
|
||||
for (let sim of this.sims.values()) {
|
||||
sim.pixels = this.#getPixels(sim);
|
||||
sim.image = new ImageData(sim.pixels, 384, 224);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Automatic emulation processing
|
||||
#autoEmulate() {
|
||||
|
||||
// Error checking
|
||||
if (!this.automatic.emulating)
|
||||
return;
|
||||
|
||||
// Process all remaining audio buffers
|
||||
while (this.audio.buffers.length != 0) {
|
||||
|
||||
// Reset sample output
|
||||
for (let sim of this.automatic.sims) {
|
||||
this.vbSetSamples(sim.pointer, sim.samples.pointer,
|
||||
Constants.VB.F32, 41700 / 50);
|
||||
}
|
||||
|
||||
// Process all clocks
|
||||
this.automatic.clocks[0] = 400000; // 0.02s
|
||||
while (this.automatic.clocks[0] != 0) {
|
||||
this.Emulate(
|
||||
this.automatic.pointers.pointer,
|
||||
this.automatic.sims.length,
|
||||
this.automatic.clocks.pointer
|
||||
);
|
||||
|
||||
// Too many buffers left to output video
|
||||
if (this.audio.buffers.length > 2)
|
||||
continue;
|
||||
|
||||
// Stage the next video image
|
||||
for (let sim of this.automatic.sims) {
|
||||
let breaks = this.GetBreaks(sim.pointer);
|
||||
if (breaks & Constants.web.BREAK_FRAME)
|
||||
this.GetPixels(sim.pointer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Mix and output audio samples
|
||||
let buffer = this.audio.buffers.shift();
|
||||
this.Mix(
|
||||
this.audio.samples.pointer,
|
||||
this.automatic.pointers.pointer,
|
||||
this.automatic.sims.length
|
||||
);
|
||||
for (let x = 0; x < buffer.length; x++)
|
||||
buffer[x] = this.audio.samples[x];
|
||||
this.audio.postMessage(buffer.buffer, [ buffer.buffer ]);
|
||||
|
||||
// Output staged images if there's one audio buffer to go
|
||||
if (this.audio.buffers.length != 1)
|
||||
continue;
|
||||
for (let sim of this.automatic.sims)
|
||||
sim.context.putImageData(sim.image, 0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Delete an allocated buffer in WebAssembly memory
|
||||
#free(buffer) {
|
||||
this.mallocs.delete(buffer.pointer);
|
||||
this.Realloc(buffer.pointer, 0);
|
||||
}
|
||||
|
||||
// Register a sim's pixel buffer
|
||||
#getPixels(sim) {
|
||||
return this.#noalloc(
|
||||
this.GetExtPixels(sim.pointer),
|
||||
384*224*4, sim, "pixels", Uint8ClampedArray
|
||||
);
|
||||
}
|
||||
|
||||
// Allocate memory in WebAssembly and register the buffer
|
||||
#malloc(count, target = null, assign = null, type = Uint8ClampedArray) {
|
||||
return this.#noalloc(
|
||||
this.Realloc(0, count * type.BYTES_PER_ELEMENT),
|
||||
count, target, assign, type
|
||||
);
|
||||
}
|
||||
|
||||
// Register a buffer in WebAssembly memory without allocating it
|
||||
#noalloc(pointer, count, target=null, assign=null, type=Uint8ClampedArray){
|
||||
let buffer = new type(this.memory.buffer, pointer, count);
|
||||
Object.assign(buffer, {
|
||||
assign : assign?.split("."),
|
||||
count : count,
|
||||
pointer: pointer,
|
||||
target : target
|
||||
});
|
||||
this.mallocs.set(pointer, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Resize a previously allocated buffer in WebAssembly memory
|
||||
#realloc(prev, count) {
|
||||
this.mallocs.delete(prev.pointer);
|
||||
let pointer = this.Realloc(prev.pointer,
|
||||
count * prev.constructor.prototype.BYTES_PER_ELEMENT);
|
||||
let buffer = new prev.constructor(this.memory.buffer, pointer, count);
|
||||
Object.assign(buffer, {
|
||||
assign : prev.assign,
|
||||
count : count,
|
||||
pointer: pointer,
|
||||
target : prev.target
|
||||
});
|
||||
this.mallocs.set(pointer, buffer);
|
||||
this.#updateTarget(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Compute anaglyph color values
|
||||
#setAnaglyph(sim, left, right) {
|
||||
|
||||
// Split out the RGB channels
|
||||
let color = left | right;
|
||||
let stereo = [
|
||||
color >> 16 & 0xFF,
|
||||
color >> 8 & 0xFF,
|
||||
color & 0xFF
|
||||
];
|
||||
|
||||
// Compute scaled RGB values by output level
|
||||
sim.anaglyph = new Array(256);
|
||||
for (let x = 0; x < 256; x++) {
|
||||
let level = sim.anaglyph[x] = new Array(3);
|
||||
for (let y = 0; y < 3; y++)
|
||||
level[y] = Math.round(x * stereo[y] / 255.0);
|
||||
}
|
||||
|
||||
// Determine which channels are in each eye
|
||||
sim.anaglyph.left = [];
|
||||
sim.anaglyph.right = [];
|
||||
for (let x = 0, y = 16; x < 3; x++, y -= 8) {
|
||||
if (left >> y & 0xFF)
|
||||
sim.anaglyph.left .push(x);
|
||||
if (right >> y & 0xFF)
|
||||
sim.anaglyph.right.push(x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Specify a game pak memory buffer
|
||||
#setCartMemory(sim, mem, getter, setter) {
|
||||
|
||||
// Working variables
|
||||
let cart = new Uint8Array(mem);
|
||||
let prev = getter(sim);
|
||||
let cur = this.Realloc(0, cart.length);
|
||||
mem = new Uint8Array(this.memory.buffer, cur, cart.length);
|
||||
|
||||
// Transfer the data into core memory
|
||||
for (let x = 0; x < mem.length; x++)
|
||||
mem[x] = cart[x];
|
||||
|
||||
// Assign the ROM to the simulation
|
||||
let success = setter(sim, cur, mem.length) == 0;
|
||||
if (success) {
|
||||
if (prev != 0)
|
||||
this.Realloc(prev, 0);
|
||||
} else this.Realloc(cur, 0);
|
||||
|
||||
// Reply to the DOM thread
|
||||
this.dom.postMessage({
|
||||
success : success,
|
||||
promised: true
|
||||
});
|
||||
}
|
||||
|
||||
// Read a C string from WebAssembly memory
|
||||
#string(address, indirect = false) {
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
if (indirect) {
|
||||
let next = new this.pointerType(this.memory.buffer, address, 1)[0];
|
||||
address = next;
|
||||
}
|
||||
|
||||
let length = 0;
|
||||
let memory = new Uint8Array(this.memory.buffer);
|
||||
for (let addr = address; memory[addr++] != 0; length++);
|
||||
return (Array.from(memory.slice(address, address + length))
|
||||
.map(b=>String.fromCodePoint(b)).join(""));
|
||||
}
|
||||
|
||||
// Update an allocated buffer's assignment in its monitor object
|
||||
#updateTarget(buffer) {
|
||||
if (buffer.target == null)
|
||||
return;
|
||||
let obj = buffer.target;
|
||||
let assign = buffer.assign.slice();
|
||||
while (assign.length > 1)
|
||||
obj = obj[assign.shift()];
|
||||
obj[assign[0]] = buffer;
|
||||
}
|
||||
|
||||
}();
|
||||
@@ -0,0 +1,935 @@
|
||||
"use strict";
|
||||
import { Constants } from "./Constants.js";
|
||||
|
||||
// Instantiation guard
|
||||
const GUARD = Symbol();
|
||||
|
||||
|
||||
|
||||
///////////////////////////////// DasmConfig //////////////////////////////////
|
||||
|
||||
// Disassembler option settings
|
||||
class DasmConfig {
|
||||
|
||||
// Instance fields
|
||||
#bcondNotation;
|
||||
#conditionCase;
|
||||
#conditionCL;
|
||||
#conditionEZ;
|
||||
#conditionNotation;
|
||||
#hexCase;
|
||||
#hexNotation;
|
||||
#immediateNotation;
|
||||
#memoryNotation;
|
||||
#mnemonicCase;
|
||||
#operandOrder;
|
||||
#programCase;
|
||||
#programNotation;
|
||||
#setfNotation;
|
||||
#systemCase;
|
||||
#systemNotation;
|
||||
|
||||
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
this.#bcondNotation = Constants.VBU.JOINED;
|
||||
this.#conditionCase = Constants.VBU.LOWER;
|
||||
this.#conditionCL = Constants.VBU.L;
|
||||
this.#conditionEZ = Constants.VBU.Z;
|
||||
this.#conditionNotation = Constants.VBU.NAMES;
|
||||
this.#hexCase = Constants.VBU.UPPER;
|
||||
this.#hexNotation = Constants.VBU["0X"];
|
||||
this.#immediateNotation = Constants.VBU.NONE;
|
||||
this.#memoryNotation = Constants.VBU.OUTSIDE;
|
||||
this.#mnemonicCase = Constants.VBU.UPPER;
|
||||
this.#operandOrder = Constants.VBU.DEST_LAST;
|
||||
this.#programCase = Constants.VBU.LOWER;
|
||||
this.#programNotation = Constants.VBU.NAMES;
|
||||
this.#setfNotation = Constants.VBU.SPLIT;
|
||||
this.#systemCase = Constants.VBU.LOWER;
|
||||
this.#systemNotation = Constants.VBU.NAMES;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////// Property Accessors ////////////////////////////
|
||||
|
||||
get bcondNotation() { return this.#bcondNotation; }
|
||||
set bcondNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.JOINED:
|
||||
case Constants.VBU.SPLIT : break;
|
||||
default: return;
|
||||
}
|
||||
this.#bcondNotation = value;
|
||||
}
|
||||
|
||||
get conditionCase() { return this.#conditionCase; }
|
||||
set conditionCase(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.LOWER:
|
||||
case Constants.VBU.UPPER: break;
|
||||
default: return;
|
||||
}
|
||||
this.#conditionCase = value;
|
||||
}
|
||||
|
||||
get conditionCL() { return this.#conditionCL; }
|
||||
set conditionCL(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.C:
|
||||
case Constants.VBU.L: break;
|
||||
default: return;
|
||||
}
|
||||
this.#conditionCL = value;
|
||||
}
|
||||
|
||||
get conditionEZ() { return this.#conditionEZ; }
|
||||
set conditionEZ(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.E:
|
||||
case Constants.VBU.Z: break;
|
||||
default: return;
|
||||
}
|
||||
this.#conditionEZ = value;
|
||||
}
|
||||
|
||||
get conditionNotation() { return this.#conditionNotation; }
|
||||
set conditionNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.NAMES :
|
||||
case Constants.VBU.NUMBERS: break;
|
||||
default: return;
|
||||
}
|
||||
this.#conditionNotation = value;
|
||||
}
|
||||
|
||||
get hexCase() { return this.#hexCase; }
|
||||
set hexCase(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.LOWER:
|
||||
case Constants.VBU.UPPER: break;
|
||||
default: return;
|
||||
}
|
||||
this.#hexCase = value;
|
||||
}
|
||||
|
||||
get hexNotation() { return this.#hexNotation; }
|
||||
set hexNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU["0X"] :
|
||||
case Constants.VBU.DOLLAR:
|
||||
case Constants.VBU.H : break;
|
||||
default: return;
|
||||
}
|
||||
this.#hexNotation = value;
|
||||
}
|
||||
|
||||
get immediateNotation() { return this.#immediateNotation; }
|
||||
set immediateNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.NONE :
|
||||
case Constants.VBU.NUMBER: break;
|
||||
default: return;
|
||||
}
|
||||
this.#immediateNotation = value;
|
||||
}
|
||||
|
||||
get memoryNotation() { return this.#memoryNotation; }
|
||||
set memoryNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.INSIDE :
|
||||
case Constants.VBU.OUTSIDE: break;
|
||||
default: return;
|
||||
}
|
||||
this.#memoryNotation = value;
|
||||
}
|
||||
|
||||
get mnemonicCase() { return this.#mnemonicCase; }
|
||||
set mnemonicCase(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.LOWER:
|
||||
case Constants.VBU.UPPER: break;
|
||||
default: return;
|
||||
}
|
||||
this.#mnemonicCase = value;
|
||||
}
|
||||
|
||||
get operandOrder() { return this.#operandOrder; }
|
||||
set operandOrder(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.DEST_FIRST:
|
||||
case Constants.VBU.DEST_LAST : break;
|
||||
default: return;
|
||||
}
|
||||
this.#operandOrder = value;
|
||||
}
|
||||
|
||||
get programCase() { return this.#programCase; }
|
||||
set programCase(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.LOWER:
|
||||
case Constants.VBU.UPPER: break;
|
||||
default: return;
|
||||
}
|
||||
this.#programCase = value;
|
||||
}
|
||||
|
||||
get programNotation() { return this.#programNotation; }
|
||||
set programNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.NAMES :
|
||||
case Constants.VBU.NUMBERS: break;
|
||||
default: return;
|
||||
}
|
||||
this.#programNotation = value;
|
||||
}
|
||||
|
||||
get setfNotation() { return this.#setfNotation; }
|
||||
set setfNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.JOINED:
|
||||
case Constants.VBU.SPLIT : break;
|
||||
default: return;
|
||||
}
|
||||
this.#setfNotation = value;
|
||||
}
|
||||
|
||||
get systemCase() { return this.#systemCase; }
|
||||
set systemCase(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.LOWER:
|
||||
case Constants.VBU.UPPER: break;
|
||||
default: return;
|
||||
}
|
||||
this.#systemCase = value;
|
||||
}
|
||||
|
||||
get systemNotation() { return this.#systemNotation; }
|
||||
set systemNotation(value) {
|
||||
switch (value) {
|
||||
case Constants.VBU.NAMES :
|
||||
case Constants.VBU.NUMBERS: break;
|
||||
default: return;
|
||||
}
|
||||
this.#systemNotation = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////// DasmLine ///////////////////////////////////
|
||||
|
||||
// One line of disassembler output
|
||||
class DasmLine {
|
||||
|
||||
// Instance fields
|
||||
#address;
|
||||
#addressText;
|
||||
#code;
|
||||
#codeText;
|
||||
#isPC;
|
||||
#mnemonicText;
|
||||
#operandText;
|
||||
|
||||
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
if (arguments[0] != GUARD)
|
||||
throw new Error("Cannot be instantiated.");
|
||||
let line = arguments[1];
|
||||
this.#address = line.address;
|
||||
this.#addressText = line.text.address;
|
||||
this.#code = line.code;
|
||||
this.#codeText = line.text.code;
|
||||
this.#isPC = line.isPC;
|
||||
this.#mnemonicText = line.text.mnemonic;
|
||||
this.#operandText = line.text.operands;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////// Property Accessors ////////////////////////////
|
||||
|
||||
get address() { return this.#address; }
|
||||
get code () { return this.#code.slice(); }
|
||||
get isPC () { return this.#isPC; }
|
||||
get text () {
|
||||
return {
|
||||
address : this.#addressText,
|
||||
code : this.#codeText.slice(),
|
||||
mnemonic: this.#mnemonicText,
|
||||
operands: this.#operandText.slice()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Express self as a plain object
|
||||
toObject() {
|
||||
return {
|
||||
address: this.address,
|
||||
code : Array.from(this.#code),
|
||||
isPC : this.isPC,
|
||||
text : this.text
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////// Sim /////////////////////////////////////
|
||||
|
||||
// Simulation instance
|
||||
class Sim extends HTMLElement {
|
||||
|
||||
// Instance fields
|
||||
#anaglyph; // Anaglyph color values
|
||||
#canvas; // Canvas element
|
||||
#core; // Core proxy
|
||||
#emulating; // Current emulation status
|
||||
#keys; // Controller state
|
||||
#panning; // Audio stereo balance
|
||||
#peer; // Communication peer
|
||||
#pointer; // Pointer in core memory
|
||||
#volume; // Audio output volume
|
||||
|
||||
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
if (arguments[0] != GUARD)
|
||||
throw new Error("Must be created via VB.create()");
|
||||
super();
|
||||
this.proxy = {
|
||||
construct : (core, pointer)=>this.#construct(core, pointer),
|
||||
isEmulating : ()=>this.#emulating,
|
||||
setEmulating: e =>this.#emulating = e,
|
||||
setPeer : p =>this.#peer = p
|
||||
};
|
||||
}
|
||||
|
||||
// Asynchronous constructor
|
||||
async #construct(core, pointer) {
|
||||
|
||||
// Configure instance fields
|
||||
this.#anaglyph = [ VB.STEREO_RED, VB.STEREO_CYAN ];
|
||||
this.#core = core;
|
||||
this.#emulating = false;
|
||||
this.#keys = Constants.VB.SGN;
|
||||
this.#panning = 0.0;
|
||||
this.#peer = null;
|
||||
this.#pointer = pointer;
|
||||
this.#volume = 1.0;
|
||||
delete this.proxy;
|
||||
|
||||
// Create a <canvas> for the video image
|
||||
let canvas = this.#canvas = document.createElement("canvas");
|
||||
Object.assign(canvas, { width: 384, height: 224 });
|
||||
canvas.style.imageRendering = "pixelated";
|
||||
|
||||
// Configure elements
|
||||
Object.assign(this.style, {
|
||||
display : "inline-block",
|
||||
height : "224px",
|
||||
position: "relative",
|
||||
width : "384px"
|
||||
});
|
||||
Object.assign(canvas.style, {
|
||||
height : "100%",
|
||||
imageRendering: "pixelated",
|
||||
left : "0",
|
||||
position : "absolute",
|
||||
top : "0",
|
||||
width : "100%"
|
||||
});
|
||||
this.append(canvas);
|
||||
|
||||
// Send control of the canvas to the core worker
|
||||
let offscreen = canvas.transferControlToOffscreen();
|
||||
await core.toCore({
|
||||
command : "setCanvas",
|
||||
promised : true,
|
||||
sim : pointer,
|
||||
canvas : offscreen,
|
||||
transfers: [ offscreen ]
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////// Property Accessors ////////////////////////////
|
||||
|
||||
get anaglyph() { return this.#anaglyph.slice(); }
|
||||
get core () { return this.#core.core ; }
|
||||
get keys () { return this.#keys ; }
|
||||
get panning () { return this.#panning ; }
|
||||
get peer () { return this.#peer ; }
|
||||
get volume () { return this.#volume ; }
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Delete the sim
|
||||
async delete() {
|
||||
// Unlink peer
|
||||
// Deallocate memory
|
||||
// Unlink core
|
||||
}
|
||||
|
||||
// Disassemble from a simulation
|
||||
async disassemble(address, config, length, line) {
|
||||
|
||||
// Error checking
|
||||
if (!Number.isSafeInteger(address) ||
|
||||
address < 0 || address > 0xFFFFFFFF)
|
||||
throw new RangeError("Address must conform to Uint32.");
|
||||
if (config != null && !(config instanceof DasmConfig))
|
||||
throw new TypeError("Config must be an instance of DasmConfig.");
|
||||
if (!Number.isSafeInteger(address) || length < 0)
|
||||
throw new RangeError("Length must be nonnegative.");
|
||||
if (!Number.isSafeInteger(line))
|
||||
throw new TypeError("Line must be a safe integer.");
|
||||
|
||||
// Request disassembly from the core
|
||||
let response = await this.#core.toCore({
|
||||
command : "disassemble",
|
||||
promised: true,
|
||||
sim : this.#pointer,
|
||||
address : address,
|
||||
length : length,
|
||||
line : line,
|
||||
config : config == null ? null : {
|
||||
bcondNotation : config.bcondNotation,
|
||||
conditionCase : config.conditionCase,
|
||||
conditionCL : config.conditionCL,
|
||||
conditionEZ : config.conditionEZ,
|
||||
conditionNotation: config.conditionNotation,
|
||||
hexCase : config.hexCase,
|
||||
hexNotation : config.hexNotation,
|
||||
immediateNotation: config.immediateNotation,
|
||||
memoryNotation : config.memoryNotation,
|
||||
mnemonicCase : config.mnemonicCase,
|
||||
operandOrder : config.operandOrder,
|
||||
programCase : config.programCase,
|
||||
programNotation : config.programNotation,
|
||||
setfNotation : config.setfNotation,
|
||||
systemCase : config.systemCase,
|
||||
systemNotation : config.systemNotation
|
||||
}
|
||||
});
|
||||
|
||||
// Process the response
|
||||
return !response.success ? null :
|
||||
response.lines.map(l=>new DasmLine(GUARD, l));
|
||||
}
|
||||
|
||||
// Specify anaglyph colors
|
||||
async setAnaglyph(left, right) {
|
||||
|
||||
// Error checking
|
||||
if (!Number.isSafeInteger(left ) || left < 0 || left > 0xFFFFFF)
|
||||
throw new RangeError("Left must conform to Uint24.");
|
||||
if (!Number.isSafeInteger(right) || right < 0 || right > 0xFFFFFF)
|
||||
throw new RangeError("Right must conform to Uint24.");
|
||||
if (
|
||||
left & 0xFF0000 && right & 0xFF0000 ||
|
||||
left & 0x00FF00 && right & 0x00FF00 ||
|
||||
left & 0x0000FF && right & 0x0000FF
|
||||
) throw new RangeError("Left and right overlap RGB channels.");
|
||||
|
||||
// Configure instance fields
|
||||
this.#anaglyph[0] = left;
|
||||
this.#anaglyph[1] = right;
|
||||
|
||||
// Send the colors to the core
|
||||
await this.#core.toCore({
|
||||
command : "setAnaglyph",
|
||||
promised: true,
|
||||
sim : this.#pointer,
|
||||
left : left,
|
||||
right : right
|
||||
});
|
||||
}
|
||||
|
||||
// Specify a game pak RAM buffer
|
||||
setCartRAM(wram) {
|
||||
return this.#setCartMemory("setCartRAM", wram);
|
||||
}
|
||||
|
||||
// Specify a game pak ROM buffer
|
||||
setCartROM(rom) {
|
||||
return this.#setCartMemory("setCartROM", rom);
|
||||
}
|
||||
|
||||
// Specify new game pad keys
|
||||
async setKeys(keys) {
|
||||
|
||||
// Error checking
|
||||
if (!Number.isSafeInteger(keys) || keys < 0 || keys > 0xFFFF)
|
||||
throw new RangeError("Keys must conform to Uint16.");
|
||||
if (keys == this.#keys)
|
||||
return;
|
||||
|
||||
// Configure instance fields
|
||||
this.#keys = keys;
|
||||
|
||||
// Send the keys to the core
|
||||
await this.#core.toCore({
|
||||
command : "setKeys",
|
||||
promised: true,
|
||||
sim : this.#pointer,
|
||||
keys : keys
|
||||
});
|
||||
}
|
||||
|
||||
// Specify audio panning
|
||||
async setPanning(panning) {
|
||||
|
||||
// Error checking
|
||||
if (!Number.isFinite(panning) ||panning < -1 || panning > +1) {
|
||||
throw new RangeError(
|
||||
"Panning must be a number from -1 to +1.");
|
||||
}
|
||||
|
||||
// Configure instance fields
|
||||
this.#panning = panning;
|
||||
|
||||
// Send the panning to the core
|
||||
await this.#core.toCore({
|
||||
command : "setPanning",
|
||||
promised: true,
|
||||
sim : this.#pointer,
|
||||
panning : panning
|
||||
});
|
||||
}
|
||||
|
||||
// Specify a new communication peer
|
||||
async setPeer(peer = null) {
|
||||
|
||||
// Error checking
|
||||
if (peer !== null && peer.#core != this.#core)
|
||||
throw new RangeError("Peer sim must belong to the same core.");
|
||||
|
||||
// Configure peers on the core
|
||||
if (peer != this.#peer)
|
||||
await this.#core.setPeer(this, peer);
|
||||
}
|
||||
|
||||
// Specify audio volume
|
||||
async setVolume(volume) {
|
||||
|
||||
// Error checking
|
||||
if (!Number.isFinite(volume) ||volume < 0 || volume > 10) {
|
||||
throw new RangeError(
|
||||
"Volume must be a number from 0\u00d7 to 10\u00d7.");
|
||||
}
|
||||
|
||||
// Configure instance fields
|
||||
this.#volume = volume;
|
||||
|
||||
// Send the volume to the core
|
||||
await this.#core.toCore({
|
||||
command : "setVolume",
|
||||
promised: true,
|
||||
sim : this.#pointer,
|
||||
volume : volume
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Specify a game pak memory buffer
|
||||
async #setCartMemory(command, mem) {
|
||||
|
||||
// Validation
|
||||
if (mem instanceof ArrayBuffer)
|
||||
mem = new Uint8Array(mem);
|
||||
if (
|
||||
!(mem instanceof Uint8Array) &&
|
||||
!(mem instanceof Uint8ClampedArray)
|
||||
) mem = Uint8Array.from(mem);
|
||||
|
||||
// Send the memory to the core
|
||||
let response = await this.#core.toCore({
|
||||
command : command,
|
||||
promised : true,
|
||||
sim : this.#pointer,
|
||||
data : mem.buffer,
|
||||
transfers: [ mem.buffer ]
|
||||
});
|
||||
return response.success;
|
||||
}
|
||||
|
||||
}
|
||||
customElements.define("shrooms-vb", Sim);
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////// VB //////////////////////////////////////
|
||||
|
||||
// Emulation core interface
|
||||
class VB {
|
||||
|
||||
// Static fields
|
||||
static get DasmConfig() { return DasmConfig; }
|
||||
static get DasmLine () { return DasmLine; }
|
||||
static get Sim () { return Sim; }
|
||||
|
||||
// Instance fields
|
||||
#audio; // Audio worklet
|
||||
#automatic; // Current automatic emulation group
|
||||
#commands; // Computed method table
|
||||
#core; // Core worker
|
||||
#proxy; // Self proxy for sim access
|
||||
#sims; // All sims
|
||||
#state; // Operations state
|
||||
|
||||
|
||||
|
||||
//////////////////////////////// Constants ////////////////////////////////
|
||||
|
||||
// Operations states
|
||||
static #SUSPENDED = Symbol();
|
||||
static #RESUMING = Symbol();
|
||||
static #EMULATING = Symbol();
|
||||
static #SUSPENDING = Symbol();
|
||||
|
||||
// System registers
|
||||
static get ADTRE() { return Constants.VB.ADTRE; }
|
||||
static get CHCW () { return Constants.VB.CHCW ; }
|
||||
static get ECR () { return Constants.VB.ECR ; }
|
||||
static get EIPC () { return Constants.VB.EIPC ; }
|
||||
static get EIPSW() { return Constants.VB.EIPSW; }
|
||||
static get FEPC () { return Constants.VB.FEPC ; }
|
||||
static get FEPSW() { return Constants.VB.FEPSW; }
|
||||
static get PIR () { return Constants.VB.PIR ; }
|
||||
static get PSW () { return Constants.VB.PSW ; }
|
||||
static get TKCW () { return Constants.VB.TKCW ; }
|
||||
|
||||
// Memory access data types
|
||||
static get S8 () { return Constants.VB.S8 ; }
|
||||
static get U8 () { return Constants.VB.U8 ; }
|
||||
static get S16() { return Constants.VB.S16; }
|
||||
static get U16() { return Constants.VB.U16; }
|
||||
static get S32() { return Constants.VB.S32; }
|
||||
|
||||
// Option keys
|
||||
static get PSEUDO_HALT() { return Constants.VB.PSEUDO_HALT; }
|
||||
|
||||
// Controller buttons
|
||||
static get PWR() { return Constants.VB.PWR; }
|
||||
static get SGN() { return Constants.VB.SGN; }
|
||||
static get A () { return Constants.VB.A ; }
|
||||
static get B () { return Constants.VB.B ; }
|
||||
static get RT () { return Constants.VB.RT ; }
|
||||
static get LT () { return Constants.VB.LT ; }
|
||||
static get RU () { return Constants.VB.RU ; }
|
||||
static get RR () { return Constants.VB.RR ; }
|
||||
static get LR () { return Constants.VB.LR ; }
|
||||
static get LL () { return Constants.VB.LL ; }
|
||||
static get LD () { return Constants.VB.LD ; }
|
||||
static get LU () { return Constants.VB.LU ; }
|
||||
static get STA() { return Constants.VB.STA; }
|
||||
static get SEL() { return Constants.VB.SEL; }
|
||||
static get RL () { return Constants.VB.RL ; }
|
||||
static get RD () { return Constants.VB.RD ; }
|
||||
|
||||
// Disassembler options
|
||||
static get ["0X"] () { return Constants.VBU["0X"] ; }
|
||||
static get ABSOLUTE () { return Constants.VBU.ABSOLUTE ; }
|
||||
static get C () { return Constants.VBU.C ; }
|
||||
static get DEST_FIRST() { return Constants.VBU.DEST_FIRST; }
|
||||
static get DEST_LAST () { return Constants.VBU.DEST_LAST ; }
|
||||
static get DOLLAR () { return Constants.VBU.DOLLAR ; }
|
||||
static get E () { return Constants.VBU.E ; }
|
||||
static get H () { return Constants.VBU.H ; }
|
||||
static get INSIDE () { return Constants.VBU.INSIDE ; }
|
||||
static get JOINED () { return Constants.VBU.JOINED ; }
|
||||
static get L () { return Constants.VBU.L ; }
|
||||
static get LOWER () { return Constants.VBU.LOWER ; }
|
||||
static get NAMES () { return Constants.VBU.NAMES ; }
|
||||
static get NONE () { return Constants.VBU.NONE ; }
|
||||
static get NUMBER () { return Constants.VBU.NUMBER ; }
|
||||
static get NUMBERS () { return Constants.VBU.NUMBERS ; }
|
||||
static get OUTSIDE () { return Constants.VBU.OUTSIDE ; }
|
||||
static get RELATIVE () { return Constants.VBU.RELATIVE ; }
|
||||
static get SPLIT () { return Constants.VBU.SPLIT ; }
|
||||
static get UPPER () { return Constants.VBU.UPPER ; }
|
||||
static get Z () { return Constants.VBU.Z ; }
|
||||
|
||||
// Anaglyph colors
|
||||
static get STEREO_CYAN () { return Constants.web.STEREO_CYAN ; }
|
||||
static get STEREO_GREEN () { return Constants.web.STEREO_GREEN ; }
|
||||
static get STEREO_MAGENTA() { return Constants.web.STEREO_MAGENTA; }
|
||||
static get STEREO_RED () { return Constants.web.STEREO_RED ; }
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Static Methods //////////////////////////////
|
||||
|
||||
// Create a core instance
|
||||
static async create(options) {
|
||||
return await new VB(GUARD).#construct(options);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
if (arguments[0] != GUARD)
|
||||
throw new Error("Must be created via VB.create()");
|
||||
}
|
||||
|
||||
// Asynchronous constructor
|
||||
async #construct(options) {
|
||||
|
||||
// Configure instance fields
|
||||
this.#automatic = null;
|
||||
this.#sims = new Map();
|
||||
this.#state = VB.#SUSPENDED;
|
||||
|
||||
// Ensure default options
|
||||
options ??= {};
|
||||
options.audioUrl ??= import.meta.resolve("./Audio.js");
|
||||
options.coreUrl ??= import.meta.resolve("./Core.js");
|
||||
options.wasmUrl ??= import.meta.resolve("./core.wasm");
|
||||
|
||||
// Core<->audio communications
|
||||
let channel = new MessageChannel();
|
||||
|
||||
// Audio output context
|
||||
let audio = new AudioContext({
|
||||
latencyHint: "interactive",
|
||||
sampleRate : 41700
|
||||
});
|
||||
await audio.suspend();
|
||||
|
||||
// Audio node
|
||||
await audio.audioWorklet.addModule(options.audioUrl);
|
||||
audio = this.#audio = new AudioWorkletNode(audio, "shrooms-vb", {
|
||||
numberOfInputs : 0,
|
||||
numberOfOutputs : 1,
|
||||
outputChannelCount: [2]
|
||||
});
|
||||
audio.connect(audio.context.destination);
|
||||
|
||||
// Send one message channel port to the audio worklet
|
||||
await new Promise(resolve=>{
|
||||
audio.port.onmessage = resolve;
|
||||
audio.port.postMessage({
|
||||
core: channel.port1
|
||||
}, [channel.port1]);
|
||||
});
|
||||
audio.port.onmessage = null;//e=>this.#onAudio(e.data);
|
||||
|
||||
// Core worker
|
||||
let core = this.#core = new Worker(options.coreUrl, {type: "module"});
|
||||
core.promises = [];
|
||||
|
||||
// Send the other message channel port to the core worker
|
||||
await new Promise(resolve=>{
|
||||
core.onmessage = resolve;
|
||||
core.postMessage({
|
||||
audio : channel.port2,
|
||||
wasmUrl: options.wasmUrl
|
||||
}, [ channel.port2 ]);
|
||||
});
|
||||
core.onmessage = e=>this.#onCore(e.data);
|
||||
|
||||
// Establish a concealed proxy for sim objects
|
||||
this.#proxy = {
|
||||
core : this,
|
||||
setPeer: (a,b)=>this.#setPeer(a,b),
|
||||
toCore : m=>this.#toCore(m)
|
||||
};
|
||||
|
||||
// Configure command table
|
||||
this.#commands = {
|
||||
// Will be used with subscriptions
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Create one or more sims
|
||||
async create(count = null) {
|
||||
|
||||
// Error checking
|
||||
if (count !== null && (!Number.isSafeInteger(count) || count < 1)) {
|
||||
throw new RangeError(
|
||||
"Count must be a safe integer and at least 1.");
|
||||
}
|
||||
|
||||
// Allocate memory in the core
|
||||
let response = await this.#toCore({
|
||||
command : "createSims",
|
||||
promised: true,
|
||||
count : count ?? 1
|
||||
});
|
||||
|
||||
// Produce Sim elements for each instance
|
||||
let sims = response.sims;
|
||||
for (let x = 0; x < (count ?? 1); x++) {
|
||||
let proxy = new Sim(GUARD).proxy;
|
||||
proxy.pointer = sims[x];
|
||||
proxy.sim = sims[x] =
|
||||
await proxy.construct(this.#proxy, sims[x]);
|
||||
this.#sims.set(sims[x], proxy);
|
||||
this.#sims.set(proxy.pointer, proxy);
|
||||
}
|
||||
return count === null ? sims[0] : sims;
|
||||
}
|
||||
|
||||
// Begin emulation
|
||||
async emulate(sims, clocks) {
|
||||
|
||||
// Error checking
|
||||
if (sims instanceof Sim)
|
||||
sims = [sims];
|
||||
if (
|
||||
!Array.isArray(sims) ||
|
||||
sims.length == 0 ||
|
||||
sims.find(s=>!this.#sims.has(s))
|
||||
) {
|
||||
throw new TypeError("Must specify a Sim or array of Sims " +
|
||||
"that belong to this core.");
|
||||
}
|
||||
if (sims.find(s=>this.#sims.get(s).isEmulating()))
|
||||
throw new Error("Sims cannot already be part of emulation.");
|
||||
if (
|
||||
clocks !== true &&
|
||||
!(Number.isSafeInteger(clocks) && clocks >= 0)
|
||||
) {
|
||||
throw new RangeError(
|
||||
"Clocks must be true or a nonnegative safe integer.");
|
||||
}
|
||||
|
||||
// Cannot resume automatic emulation
|
||||
if (clocks === true && this.#state != VB.#SUSPENDED)
|
||||
return false;
|
||||
|
||||
// Manage sims
|
||||
let proxies = sims .map(s=>this.#sims.get(s));
|
||||
let pointers = proxies.map(p=>p.pointer);
|
||||
for (let sim of proxies)
|
||||
sim.setEmulating(true);
|
||||
|
||||
// Clocked emulation
|
||||
if (clocks !== true) {
|
||||
let response = await this.#toCore({
|
||||
command : "emulateClocked",
|
||||
promised: true,
|
||||
sims : pointers,
|
||||
clocks : clocks
|
||||
});
|
||||
for (let sim of proxies)
|
||||
sim.setEmulating(false);
|
||||
return {
|
||||
broke : response.broke,
|
||||
clocks: response.clocks
|
||||
};
|
||||
}
|
||||
|
||||
// Resume automatic emulation
|
||||
this.#automatic = proxies;
|
||||
this.#state = VB.#RESUMING;
|
||||
if (this.#audio.context.state == "suspended")
|
||||
await this.#audio.context.resume();
|
||||
await this.#toCore({
|
||||
command : "emulateAutomatic",
|
||||
promised: true,
|
||||
sims : pointers
|
||||
});
|
||||
this.#state = VB.#EMULATING;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Suspend automatic emulation
|
||||
async suspend() {
|
||||
|
||||
// Error checking
|
||||
if (this.#state != VB.#EMULATING)
|
||||
return false;
|
||||
|
||||
// Tell the core to stop emulating
|
||||
this.#state = VB.#SUSPENDING;
|
||||
await this.#toCore({
|
||||
command : "suspend",
|
||||
promised: true
|
||||
});
|
||||
|
||||
// Configure state
|
||||
this.#state = VB.#SUSPENDED;
|
||||
for (let sim of this.#automatic)
|
||||
sim.setEmulating(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Message received from core worker
|
||||
#onCore(message) {
|
||||
if (message.promised)
|
||||
this.#core.promises.shift()(message);
|
||||
if ("command" in message)
|
||||
this.#commands[message.command](message);
|
||||
}
|
||||
|
||||
// Specify a new communication peer
|
||||
async #setPeer(sim, peer) {
|
||||
|
||||
// Associate the peers on the core
|
||||
let response = await this.#toCore({
|
||||
command : "setPeer",
|
||||
promised: true,
|
||||
sim : this.#sims.get(sim).pointer,
|
||||
peer : peer == null ? 0 : this.#sims.get(peer).pointer
|
||||
});
|
||||
|
||||
// Link sims
|
||||
this.#sims.get(sim).setPeer(peer);
|
||||
if (peer != null)
|
||||
this.#sims.get(peer).setPeer(sim);
|
||||
|
||||
// Unlink orphaned sims
|
||||
for (let pointer of response.orphaned)
|
||||
this.#sims.get(pointer).setPeer(null);
|
||||
}
|
||||
|
||||
// Send a message to the core worker
|
||||
async #toCore(message) {
|
||||
let transfers = message.transfers;
|
||||
if (transfers != null)
|
||||
delete message.transfers;
|
||||
return await new Promise(resolve=>{
|
||||
if (message.promised)
|
||||
this.#core.promises.push(resolve);
|
||||
this.#core.postMessage(message, transfers ?? []);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { VB };
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <vb.h>
|
||||
#include <vbu.h>
|
||||
|
||||
|
||||
|
||||
////////////////////////////////// Constants //////////////////////////////////
|
||||
|
||||
// Break conditions
|
||||
#define BREAK_FRAME 1
|
||||
#define BREAK_POINT 2
|
||||
|
||||
// Anaglyph colors
|
||||
#define STEREO_CYAN 0x00C6F0
|
||||
#define STEREO_GREEN 0x00B400
|
||||
#define STEREO_MAGENTA 0xC800FF
|
||||
#define STEREO_RED 0xFF0000
|
||||
|
||||
// Element counts
|
||||
#define NUM_SAMPLES (41700 / 50 * 2)
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////// Types ////////////////////////////////////
|
||||
|
||||
// Additional monitor state for simulations
|
||||
typedef struct {
|
||||
int32_t breaks;
|
||||
uint32_t left[256];
|
||||
float panning;
|
||||
uint8_t pixels[384 * 224 * 4];
|
||||
uint32_t right[256];
|
||||
float samples[NUM_SAMPLES];
|
||||
float volume;
|
||||
} Ext;
|
||||
|
||||
|
||||
|
||||
////////////////////////////////// Callbacks //////////////////////////////////
|
||||
|
||||
// Frame callback
|
||||
int wasmOnFrame(VB *sim) {
|
||||
((Ext *) vbGetUserData(sim))->breaks |= BREAK_FRAME;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////// Module Exports ////////////////////////////////
|
||||
|
||||
// Specify anaglyph colors
|
||||
EMSCRIPTEN_KEEPALIVE void SetAnaglyph(VB *sim, uint32_t left, uint32_t right) {
|
||||
Ext *ext = (Ext *) vbGetUserData(sim);
|
||||
|
||||
// Erase all RGB values
|
||||
for (int x = 0; x < 256; x++)
|
||||
ext->left[x] = ext->right[x] = 0xFF000000;
|
||||
|
||||
// Process all RGB channels
|
||||
for (int c = 0, shift = 16; c < 3; c++, shift -= 8) {
|
||||
double max; // Magnitude of channel value
|
||||
uint32_t *dest; // Lookup data
|
||||
|
||||
// Select the magnitude and lookup channel
|
||||
dest = ext->left;
|
||||
max = (left >> shift & 0xFF) / 255.0;
|
||||
if (max == 0) {
|
||||
dest = ext->right;
|
||||
max = (right >> shift & 0xFF) / 255.0;
|
||||
if (max == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute the resulting RGB values
|
||||
for (int x = 0; x < 256; x++)
|
||||
*dest++ |= (uint32_t) (x * max + 0.5) << (16 - shift);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Instantiate a simulation
|
||||
EMSCRIPTEN_KEEPALIVE void* CreateSim() {
|
||||
size_t sizeOfSim = vbSizeOf();
|
||||
uint8_t *pointer = malloc(sizeOfSim + sizeof (Ext));
|
||||
|
||||
// Configure sim
|
||||
VB *sim = vbInit((VB *) pointer);
|
||||
vbSetFrameCallback(sim, &wasmOnFrame);
|
||||
vbSetOption(sim, VB_PSEUDO_HALT, 1);
|
||||
|
||||
// Configure extra
|
||||
Ext *ext = (Ext *) (pointer + sizeOfSim);
|
||||
ext->breaks = 0;
|
||||
ext->panning = 0.0f;
|
||||
ext->volume = 1.0f;
|
||||
vbSetUserData(sim, ext);
|
||||
SetAnaglyph(sim, STEREO_RED, STEREO_CYAN);
|
||||
|
||||
// Initialize pixels with opaque black
|
||||
for (unsigned x = 0; x < 384 * 224; x++)
|
||||
((uint32_t *) ext->pixels)[x] = 0xFF000000;
|
||||
|
||||
return sim;
|
||||
}
|
||||
|
||||
// Disassemble from a simulation
|
||||
EMSCRIPTEN_KEEPALIVE void* Disassemble(
|
||||
int bcondNotation, int conditionCase, int conditionCL, int conditionEZ,
|
||||
int conditionNotation, int hexCase, int hexNotation, int immediateNotation,
|
||||
int memoryNotation, int mnemonicCase, int operandOrder, int programCase,
|
||||
int programNotation, int setfNotation, int systemCase, int systemNotation,
|
||||
VB *sim, uint32_t address, unsigned length, int line
|
||||
) {
|
||||
VBU_DasmConfig config;
|
||||
config.bcondNotation = bcondNotation;
|
||||
config.conditionCase = conditionCase;
|
||||
config.conditionCL = conditionCL;
|
||||
config.conditionEZ = conditionEZ;
|
||||
config.conditionNotation = conditionNotation;
|
||||
config.hexCase = hexCase;
|
||||
config.hexNotation = hexNotation;
|
||||
config.immediateNotation = immediateNotation;
|
||||
config.memoryNotation = memoryNotation;
|
||||
config.mnemonicCase = mnemonicCase;
|
||||
config.operandOrder = operandOrder;
|
||||
config.programCase = programCase;
|
||||
config.programNotation = programNotation;
|
||||
config.setfNotation = setfNotation;
|
||||
config.systemCase = systemCase;
|
||||
config.systemNotation = systemNotation;
|
||||
return vbuDisassemble(sim, address, &config, length, line);
|
||||
}
|
||||
|
||||
// Process simulations
|
||||
EMSCRIPTEN_KEEPALIVE int Emulate(VB **sims, unsigned count, uint32_t *clocks) {
|
||||
for (unsigned x = 0; x < count; x++)
|
||||
((Ext *) vbGetUserData(sims[x]))->breaks = 0;
|
||||
return vbEmulateEx(sims, count, clocks);
|
||||
}
|
||||
|
||||
// Retrieve a sim's pixel pointer
|
||||
EMSCRIPTEN_KEEPALIVE void* GetExtPixels(VB *sim) {
|
||||
return ((Ext *) vbGetUserData(sim))->pixels;
|
||||
}
|
||||
|
||||
// Retrieve a sim's sample pointer
|
||||
EMSCRIPTEN_KEEPALIVE void* GetExtSamples(VB *sim) {
|
||||
return ((Ext *) vbGetUserData(sim))->samples;
|
||||
}
|
||||
|
||||
// Retrieve the break condition flags for a sim
|
||||
EMSCRIPTEN_KEEPALIVE int32_t GetBreaks(VB *sim) {
|
||||
return ((Ext *) vbGetUserData(sim))->breaks;
|
||||
}
|
||||
|
||||
// Serialize disassembled lines into a linear buffer
|
||||
EMSCRIPTEN_KEEPALIVE void GetDasm(uint32_t *buffer, void *dasm, int count) {
|
||||
for (int x = 0; x < count; x++) {
|
||||
VBU_DasmLine *line = &((VBU_DasmLine *) dasm)[x];
|
||||
|
||||
// Numeric data
|
||||
*buffer++ = line->address;
|
||||
*buffer++ = line->codeLength;
|
||||
for (int y = 0; y < 4; y++)
|
||||
*buffer++ = line->code[y];
|
||||
*buffer++ = line->isPC;
|
||||
|
||||
// Text data -- Store offset of string pointer from start of dasm
|
||||
*buffer++ = (uint32_t) ((void *) &line->text.address - dasm);
|
||||
for (int y = 0; y < 4; y++)
|
||||
*buffer++ = (uint32_t) ((void *) &line->text.code[y] - dasm);
|
||||
*buffer++ = (uint32_t) ((void *) &line->text.mnemonic - dasm);
|
||||
*buffer++ = line->text.operandsLength;
|
||||
for (int y = 0; y < 3; y++)
|
||||
*buffer++ = (uint32_t) ((void *) &line->text.operands[y] - dasm);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve anaglyph-tinted pixels from a sim
|
||||
EMSCRIPTEN_KEEPALIVE void GetPixels(VB *sim) {
|
||||
Ext *ext = (Ext *) vbGetUserData(sim);
|
||||
uint8_t *pixels = ext->pixels;
|
||||
vbGetPixels(sim, pixels, 4, 384 * 4, pixels + 1, 4, 384 * 4);
|
||||
for (unsigned x = 0; x < 384 * 224 * 4; x += 4, pixels += 4)
|
||||
*(uint32_t *) pixels = ext->left[pixels[0]] | ext->right[pixels[1]];
|
||||
}
|
||||
|
||||
// Mix audio samples for output
|
||||
EMSCRIPTEN_KEEPALIVE void Mix(float *buffer, VB **sims, int count) {
|
||||
|
||||
// Process all sims
|
||||
for (int x = 0; x < count; x++) {
|
||||
Ext *ext = (Ext *) vbGetUserData(sims[x]);
|
||||
float *samples = ext->samples;
|
||||
float v = ext->volume;
|
||||
|
||||
// First sim initializes buffer
|
||||
if (x == 0) {
|
||||
if (ext->panning < 0) {
|
||||
float l = -ext->panning * v;
|
||||
float r = (1.0f + ext->panning) * v;
|
||||
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||
buffer[y ] = samples[y] * v + samples[y + 1] * l;
|
||||
buffer[y + 1] = samples[y + 1] * r;
|
||||
}
|
||||
} else if (ext->panning > 0) {
|
||||
float r = ext->panning * v;
|
||||
float l = (1.0f - ext->panning) * v;
|
||||
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||
buffer[y ] = samples[y] * l;
|
||||
buffer[y + 1] = samples[y + 1] * v + samples[y] * r;
|
||||
}
|
||||
} else {
|
||||
for (unsigned y = 0; y < NUM_SAMPLES; y++)
|
||||
buffer[y] = samples[y] * v;
|
||||
}
|
||||
}
|
||||
|
||||
// Subsequent sims add to buffer
|
||||
else {
|
||||
if (ext->panning < 0) {
|
||||
float l = -ext->panning * v;
|
||||
float r = (1.0f + ext->panning) * v;
|
||||
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||
buffer[y ] += samples[y] * v + samples[y + 1] * l;
|
||||
buffer[y + 1] += samples[y + 1] * r;
|
||||
}
|
||||
} else if (ext->panning > 0) {
|
||||
float r = ext->panning * v;
|
||||
float l = (1.0f - ext->panning) * v;
|
||||
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||
buffer[y ] += samples[y] * l;
|
||||
buffer[y + 1] += samples[y + 1] * v + samples[y] * r;
|
||||
}
|
||||
} else {
|
||||
for (unsigned y = 0; y < NUM_SAMPLES; y++)
|
||||
buffer[y] += samples[y] * v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clipping
|
||||
for (unsigned y = 0; y < NUM_SAMPLES; y++) {
|
||||
if (buffer[y] < -1.0f)
|
||||
buffer[y] = -1.0f;
|
||||
else if (buffer[y] > +1.0f)
|
||||
buffer[y] = +1.0f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Determine the size in bytes of a pointer
|
||||
EMSCRIPTEN_KEEPALIVE int PointerSize() {
|
||||
return sizeof (void *);
|
||||
}
|
||||
|
||||
// Memory management
|
||||
EMSCRIPTEN_KEEPALIVE void* Realloc(void *data, size_t size) {
|
||||
return realloc(data, size);
|
||||
}
|
||||
|
||||
// Specify audio panning
|
||||
EMSCRIPTEN_KEEPALIVE void SetPanning(VB *sim, float panning) {
|
||||
((Ext *) vbGetUserData(sim))->panning = panning;
|
||||
}
|
||||
|
||||
// Specify audio volume
|
||||
EMSCRIPTEN_KEEPALIVE void SetVolume(VB *sim, float volume) {
|
||||
((Ext *) vbGetUserData(sim))->volume = volume;
|
||||
}
|
||||
Reference in New Issue
Block a user