Compare commits

...
5 Commits
10 changed files with 464 additions and 43 deletions
+7 -5
View File
@@ -28,8 +28,9 @@ typedef struct {
uint8_t value; /* Master output level */
/* Other state */
uint32_t clocks; /* Clocks until modification */
uint8_t reload; /* Automatic reload value */
uint32_t clocks; /* Clocks until modification */
uint8_t modmask; /* Modifications masked */
uint8_t reload; /* Automatic reload value */
} env;
/* Frequency */
@@ -359,9 +360,10 @@ struct VB {
uint8_t shift; /* Sweep shift amount */
/* Other state */
uint32_t clocks; /* Clocks until modification */
uint16_t next; /* Next frequency value */
int sample; /* Current sample index */
uint32_t clocks; /* Clocks until modification */
uint8_t modmask; /* Modifications masked */
uint16_t next; /* Next frequency value */
int sample; /* Current sample index */
} freqmod;
/* Channel 6 noise generator */
+1 -1
View File
@@ -546,7 +546,7 @@ static void vipWriteIO(
break;
case 0x5F870>>1: /* BKCOL */
sim->vip.bkcol = (sim->vip.bkcol & mask) | (value & 3 * ~mask);
sim->vip.bkcol = (sim->vip.bkcol & mask) | (value & 3 & ~mask);
break;
}
+90 -29
View File
@@ -45,9 +45,28 @@ static void vsuOnSamples(VB *sim) {
/***************************** Module Functions ******************************/
/* Determine while frequency modifications should be processed */
static uint8_t vsuCheckFreqMod(VB *sim) {
return
sim->vsu.freqmod.enb &&
sim->vsu.freqmod.interval != 0 && (
sim->vsu.freqmod.modmask != 2 || (
sim->vsu.freqmod.func == 1 &&
sim->vsu.freqmod.rep
)
)
;
}
/* Stage the next frequency modification value */
static void vsuNextFreqMod(VB *sim, Channel *chan) {
uint16_t next;
int32_t next;
/* Modulation mask control */
if (sim->vsu.freqmod.modmask == 1 && sim->vsu.freqmod.func == 1) {
sim->vsu.freqmod.modmask = 2;
chan->freqmod = vsuCheckFreqMod(sim);
}
/* Sweep */
if (sim->vsu.freqmod.func == 0) {
@@ -57,25 +76,37 @@ static void vsuNextFreqMod(VB *sim, Channel *chan) {
else next = chan->freq.current + next;
if (next > 2047)
chan->int_.enb = 0;
if (next < 0)
next = 0;
}
/* Modulation */
else {
next = (chan->freq.written +
sim->vsu.modulation[sim->vsu.freqmod.sample]) & 2047;
if (sim->vsu.freqmod.sample != 31)
sim->vsu.freqmod.sample++;
else if (sim->vsu.freqmod.rep)
sim->vsu.freqmod.sample = 0;
next = (int32_t) chan->freq.written +
sim->vsu.modulation[sim->vsu.freqmod.sample];
if (next < 0)
next = 0;
if (next > 2047)
next = 2047;
}
/* Configure state */
sim->vsu.freqmod.next = next;
if (chan->freqmod)
sim->vsu.freqmod.next = next;
}
/* Advance the noise channel's shift register */
static void vsuNextNoise(VB *sim) {
uint32_t 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);
}
/* Process one channel */
static void vsuEmulateChannel(VB *sim, Channel *chan) {
uint32_t bit; /* Pseudorandom bit */
/* Automatic shutoff */
if (chan->int_.auto_ && chan->int_.clocks == 0) {
@@ -95,35 +126,55 @@ static void vsuEmulateChannel(VB *sim, Channel *chan) {
/* 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);
vsuNextNoise(sim);
}
}
/* Envelope modification */
if (chan->env.enb && chan->env.clocks == 0) {
if (chan->env.enb && !chan->env.modmask && 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;
else chan->env.modmask = 1;
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 == &sim->vsu.channels[4] && sim->vsu.freqmod.clocks == 0) {
/* Apply previously computed frequency modification */
if (chan->freqmod)
chan->freq.current = sim->vsu.freqmod.next;
/* Channel itself is disabled */
if (!chan->int_.enb)
return;
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
/* Frequency modifications are enabled */
if (chan->freqmod) {
/* Not the final modulation sample */
if (sim->vsu.freqmod.sample != 31)
sim->vsu.freqmod.sample++;
/* Last modulation sample processed */
else {
sim->vsu.freqmod.modmask = 1;
if (sim->vsu.freqmod.rep)
sim->vsu.freqmod.sample = 0;
}
/* Schedule next modification */
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
}
/* Always process and validate next frequency value */
vsuNextFreqMod(sim, chan);
}
}
@@ -164,8 +215,10 @@ static void vsuWriteEV0(VB *sim, int index, uint8_t value) {
chan->env.interval = value & 7;
chan->env.value = value >> 4 & 15;
chan->env.reload = chan->env.value;
if (index == 4)
if (index == 4) {
chan->freqmod = vsuCheckFreqMod(sim);
vsuNextFreqMod(sim, chan);
}
/* Configure state */
chan->env.clocks = 307220 * ((uint32_t) chan->env.interval + 1);
@@ -186,14 +239,14 @@ static void vsuWriteEV1(VB *sim, int index, uint8_t value) {
sim->vsu.freqmod.enb = value >> 6 & 1;
sim->vsu.freqmod.func = value >> 4 & 1;
sim->vsu.freqmod.rep = value >> 5 & 1;
chan->freqmod = vsuCheckFreqMod(sim);
vsuNextFreqMod(sim, chan);
chan->freqmod =
sim->vsu.freqmod.enb && sim->vsu.freqmod.interval != 0;
break;
case 5: /* Channel 6 */
sim->vsu.noise.tap = value >> 4 & 7;
sim->vsu.noise.register_ = 0x0000;
vsuNextNoise(sim);
}
}
@@ -228,6 +281,7 @@ static void vsuWriteINT(VB *sim, int index, uint8_t value) {
/* Update state */
chan->int_.clocks = 76805 * ((uint32_t) chan->int_.interval + 1);
chan->env.modmask = 0;
if (chan->env.enb)
chan->env.clocks = 307220 * ((uint32_t) chan->env.interval + 1);
if (index != 5) {
@@ -235,12 +289,16 @@ static void vsuWriteINT(VB *sim, int index, uint8_t value) {
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
} else {
sim->vsu.noise.register_ = 0x0000;
vsuNextNoise(sim);
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
}
if (index == 4) {
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
sim->vsu.freqmod.sample = 0;
sim->vsu.freqmod.modmask = 0;
sim->vsu.freqmod.sample = 0;
chan->freqmod = vsuCheckFreqMod(sim);
vsuNextFreqMod(sim, chan);
}
}
@@ -272,8 +330,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;
sim->vsu.channels[4].freqmod = vsuCheckFreqMod(sim);
vsuNextFreqMod(sim, &sim->vsu.channels[4]);
}
@@ -315,7 +373,8 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
/* Clocks until next state change */
chan->until = chan->clocks;
if (chan->env.enb && chan->env.clocks < chan->until)
if (chan->env.enb && !chan->env.modmask &&
chan->env.clocks < chan->until)
chan->until = chan->env.clocks;
if (chan->int_.auto_ && chan->int_.clocks < chan->until)
chan->until = chan->int_.clocks;
@@ -324,7 +383,7 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
/* Manage clocks */
chan->clocks -= chan->until;
if (chan->env.enb)
if (chan->env.enb && !chan->env.modmask)
chan->env.clocks -= chan->until;
if (chan->int_.auto_)
chan->int_.clocks -= chan->until;
@@ -427,6 +486,7 @@ static void vsuReset(VB *sim) {
chan->env.enb = 0;
chan->env.dir = 0;
chan->env.interval = 0;
chan->env.modmask = 0;
chan->env.reload = 0;
chan->env.rep = 0;
chan->env.value = 0;
@@ -449,6 +509,7 @@ static void vsuReset(VB *sim) {
sim->vsu.freqmod.enb = 0;
sim->vsu.freqmod.func = 0;
sim->vsu.freqmod.interval = 0;
sim->vsu.freqmod.modmask = 0;
sim->vsu.freqmod.next = 0;
sim->vsu.freqmod.rep = 0;
sim->vsu.freqmod.sample = 0;
+297
View File
@@ -0,0 +1,297 @@
/* This file is included into vbu.c and cannot be compiled on its own. */
#ifdef VBUAPI
/********************************* Constants *********************************/
/* Range types */
/*
0x41 ('A') 8-bit integer (.db), string
0x42 ('B') 8-bit integer (.db), numeric
0x43 ('C') Compiled C code
0x44 ('D') 16-bit integer (.dd, high byte first)
0x45 ('E') 65C816 code (8-bit accumulator, 8-bit index)
0x46 ('F') 65C816 code (8-bit accumulator, 16-bit index)
0x47 ('G') 65C816 code (16-bit accumulator, 8-bit index)
0x48 ('H') 65C816 code (16-bit accumulator, 16-bit index)
0x4C ('L') 32-bit integer (.dl SNES, .dw Virtual Boy, low byte first)
0x53 ('S') Size reservation, as in .bss allocation (.ds)
0x57 ('W') 16-bit integer (.dw SNES, .dh Virtual Boy, low byte first)
*/
/***************************** Module Functions ******************************/
/* Skip ISX record type 0x01 */
static size_t isx01(uint8_t *bytes, size_t length, size_t src) {
/*
u8 Type =0x01
u8 Bank Cartridge bank number
u8 BankHigh Only if Bank >= 0x80
u16 Address CPU bus address
u16 DataLength Number of bytes in Data
u8[] Data Code bytes
*/
/* Bank field is not present */
if (src + 2 > length)
return (size_t) -1;
/* Advance to DataLength field */
src += bytes[src + 1] >= 0x80 ? 5 : 4;
/* DataLength field is not present */
if (src + 2 > length)
return (size_t) -1;
/* Advance past Data field */
return src + 2 + ((uint16_t) bytes[src + 1] << 8 | bytes[src]);
}
/* Decode ISX record type 0x11 */
static size_t isx11(uint8_t *bytes, size_t length, size_t src,
uint32_t *address, uint32_t *size) {
/*
u8 Type =0x11
u32 Address CPU bus address
u32 DataLength Number of bytes in Data
u8[] Data Code bytes
*/
/* DataLength field is not present */
if (src + 9 > length)
return (size_t) -1;
/* Parse Address and DataLength fields */
*address = (uint32_t) bytes[src+4] << 24 | (uint32_t) bytes[src+3] << 16 |
(uint32_t) bytes[src+2] << 8 | bytes[src+1];
*size = (uint32_t) bytes[src+8] << 24 | (uint32_t) bytes[src+7] << 16 |
(uint32_t) bytes[src+6] << 8 | bytes[src+5];
/* Advance past Data field */
return src + 9 + *size;
}
/* Skip ISX range records */
static size_t isxRange(uint8_t *bytes,size_t length,size_t src,size_t size) {
/*
u8 Type =0x03 (SNES) or 0x13 (Virtual Boy)
u16 Count Number of ranges
Range[] Ranges
=== SNES fields ===
u8 Bank Cartridge bank number
u16 StartAddress Lowest CPU bus address
u16 EndAddress Highest CPU bus address
u8 RangeType See range types
=== Virtual Boy fields ===
u32 StartAddress Lowest CPU bus address
u32 EndAddress Highest CPU bus address
u8 RangeType See range types
*/
/* Count field is not present */
if (src + 3 > length)
return (size_t) -1;
/* Advance past Ranges field */
return src + 3 + size * ((uint16_t) bytes[src + 2] << 8 | bytes[src + 1]);
}
/* Skip ISX symbol record */
static size_t isxSymbol(uint8_t *bytes,size_t length,size_t src,size_t size) {
size_t count; /* Number of symbols */
size_t x; /* Iterator */
/*
u8 Type =0x04 (SNES) or 0x14 (Virtual Boy)
u16 Count Number of symbols
Symbol[] Symbols
u8 NameLength Number of bytes in Name
u8[] Name Symbol name
=== SNES fields ===
u8 Flags Undocumented
u8 Bank Cartridge bank number
u16 Address CPU bus address
=== Virtual Boy fields ===
u16 Flags Undocumented
u32 Address CPU bus address
*/
/* Count field is not present */
if (src + 3 > length)
return (size_t) -1;
/* Working variables */
count = (uint16_t) bytes[src + 2] << 8 | bytes[src + 1];
src += 3;
/* Process all symbols */
for (x = 0; x < count; x++) {
/* NameLength field is not present */
if (src > length)
return (size_t) -1;
/* Advance past remainder of symbol */
src += size + bytes[src];
/* Remainder of symbol is not present */
if (src > length)
return (size_t) -1;
}
return src;
}
/* Skip ISX system record */
static size_t isxSystem(uint8_t *bytes, size_t length, size_t src) {
/*
u8 Type =0x20, 0x21 or 0x22
u32 DataLength Number of bytes in Data
u8[] Data Record data
*/
/* DataLength field is not present */
if (src + 5 > length)
return (size_t) -1;
/* Advance past Data field */
return src + 5 + (
(uint32_t) bytes[src + 4] << 24 | (uint32_t) bytes[src + 3] << 16 |
(uint32_t) bytes[src + 2] << 8 | bytes[src + 1]
);
}
/***************************** Library Functions *****************************/
/* Decode an ISX debugger file to a Virtual Boy ROM */
static void* isxFrom(uint8_t *bytes, size_t length, size_t *romLength) {
uint32_t addr; /* Code record base address */
uint32_t highMin; /* Lowest address in upper half of ROM */
uint32_t lowMax; /* Highest address in lower half of ROM */
uint8_t *rom; /* Output ROM */
uint32_t size; /* Code record size in bytes */
size_t src; /* Input position */
size_t start; /* Offset of first record */
/* Error checking */
if (length < 3)
return NULL;
/* Working variables */
highMin = 0xFFFFFF;
lowMax = 0x000000;
size = 0;
start = strncmp((char *) bytes, "ISX", 3) ? 0 : 32;
/* First pass: determine the size of the ROM */
for (src = start; src < length;) {
switch (bytes[src]) {
case 0x11: /* Code (Virtual Boy) */
/* Decode record fields */
src = isx11(bytes, length, src, &addr, &size);
if (src > length)
return NULL;
/* Validate address and data size */
addr &= 0x07FFFFFF;
if (
addr < 0x07000000 ||
0x08000000 - addr < size
) return NULL;
addr &= 0x00FFFFFF;
/* Requires 16 MiB */
if (addr < 0x800000 && addr + size > 0x800000) {
highMin = 0x800000;
lowMax = 0x7FFFFF;
}
/* Map from top of ROM space */
else if (addr & 0x800000) {
if (addr < highMin)
highMin = addr;
}
/* Map from bottom of ROM space */
else {
addr += size - 1;
if (addr > lowMax)
lowMax = addr;
}
break;
case 0x01: /* Code (SNES) */
src = isx01 (bytes, length, src ); break;
case 0x03: /* Range (SNES) */
src = isxRange (bytes, length, src, 6); break;
case 0x04: /* Symbol (SNES) */
src = isxSymbol(bytes, length, src, 5); break;
case 0x13: /* Range (Virtual Boy) */
src = isxRange (bytes, length, src, 9); break;
case 0x14: /* Symbol (Virtual Boy) */
src = isxSymbol(bytes, length, src, 7); break;
case 0x20: /* System (undocumented) */
case 0x21: /* System (undocumented) */
case 0x22: /* System (undocumented) */
src = isxSystem(bytes, length, src ); break;
default: /* Invalid record type */
return NULL;
}
/* Record contains invalid data */
if (src > length)
return NULL;
}
/* Produce a ROM buffer of minimal size to fit all the code records */
for (
src = 1;
src < lowMax + 0x1000001 - highMin;
src <<= 1
);
rom = VBU_REALLOC(NULL, src);
if (rom == NULL)
return NULL;
*romLength = src;
memset(rom, 0x00, src);
/* Second pass: fill the ROM buffer with the code records */
for (src = start; src < length;) {
switch (bytes[src]) {
case 0x11: /* Code (Virtual Boy) */
src = isx11(bytes, length, src, &addr, &size);
memcpy(&rom[addr & (*romLength-1)], &bytes[src - size], size);
break;
case 0x01: /* Code (SNES) */
src = isx01 (bytes, length, src ); break;
case 0x03: /* Range (SNES) */
src = isxRange (bytes, length, src, 9); break;
case 0x04: /* Symbol (SNES) */
src = isxSymbol(bytes, length, src, 5); break;
case 0x13: /* Range (Virtual Boy) */
src = isxRange (bytes, length, src, 12); break;
case 0x14: /* Symbol (Virtual Boy) */
src = isxSymbol(bytes, length, src, 7); break;
case 0x20: /* System (undocumented) */
case 0x21: /* System (undocumented) */
case 0x22: /* System (undocumented) */
src = isxSystem(bytes, length, src ); break;
}
}
return rom;
}
#endif /* VBUAPI */
+6
View File
@@ -33,6 +33,7 @@ static int32_t SignExtend(int32_t value, int32_t bits) {
/******************************** Sub-Modules ********************************/
#include "disassembler.c"
#include "isx.c"
@@ -70,3 +71,8 @@ VBUAPI VBU_DasmLine* vbuDisassemble(VB *sim, uint32_t address,
VBU_DasmConfig *config, unsigned length, int line) {
return dasmDisassemble(sim, address, config, length, line);
}
/* Decode an ISX debugger file to a Virtual Boy ROM */
VBUAPI void* vbuFromISX(void *bytes, size_t length, size_t *romLength) {
return isxFrom(bytes, length, romLength);
}
+1
View File
@@ -88,6 +88,7 @@ typedef struct {
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);
VBUAPI void* vbuFromISX (void *bytes, size_t length, size_t *romLength);
-2
View File
@@ -1,7 +1,5 @@
"use strict";
//////////////////////////////////// Audio ////////////////////////////////////
// Dedicated audio output processor
class Audio extends AudioWorkletProcessor {
+2 -3
View File
@@ -1,4 +1,5 @@
let Constants = {
"use strict";
export default {
// Core
VB: {
@@ -85,5 +86,3 @@ let Constants = {
}
};
export { Constants };
+36 -1
View File
@@ -1,5 +1,5 @@
"use strict";
import { Constants } from "./Constants.js";
import Constants from /***/"./Constants.js";
@@ -258,6 +258,41 @@ new class Core {
});
}
// Attempt to produce a ROM from an ISX debugger file
fromISX(message) {
// Transfer the input data into WebAssembly memory
let input = new Uint8Array(message.data);
let inPtr = this.Realloc(0, input.length);
let inMem = new Uint8Array(this.memory.buffer, inPtr, input.length);
for (let x = 0; x < input.length; x++)
inMem[x] = input[x];
// Attempt to decode the ISX file as a ROM
let outPtr = this.FromISX(inPtr, input.length);
this.Realloc(inPtr, 0);
// The data is not an ISX file
if (outPtr == 0) {
this.dom.postMessage({ promised: true, data: null });
return;
}
// Transfer the decoded ROM from WebAssembly memory
let output = new Uint8Array(this.GetISXLength(outPtr));
let outMem = new Uint8Array(this.memory.buffer,
this.GetISXROM(outPtr), output.length);
for (let x = 0; x < output.length; x++)
output[x] = outMem[x];
this.Realloc(outPtr, 0);
// Notify DOM thread
this.dom.postMessage({
promised: true,
data : output.buffer
}, [ output.buffer ]);
}
// Specify anaglyph colors
setAnaglyph(message) {
this.SetAnaglyph(message.sim, message.left, message.right);
+24 -2
View File
@@ -1,5 +1,5 @@
"use strict";
import { Constants } from "./Constants.js";
import Constants from /**/"./Constants.js";
// Instantiation guard
const GUARD = Symbol();
@@ -864,6 +864,28 @@ class VB {
return true;
}
// Decode an ISX debugger file to a Virtual Boy ROM
async fromISX(data) {
// Validation
if (data instanceof ArrayBuffer)
data = new Uint8Array(data);
if (
!(data instanceof Uint8Array) &&
!(data instanceof Uint8ClampedArray)
) data = Uint8Array.from(data);
// Send the memory to the core
data = data.slice();
let response = await this.#toCore({
command : "fromISX",
promised : true,
data : data.buffer,
transfers: [ data.buffer ]
});
return response.data == null ? null : new Uint8Array(response.data);
}
// Suspend automatic emulation
async suspend() {
@@ -932,4 +954,4 @@ class VB {
}
export { VB };
export default VB;