1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-14 00:29:35 +00:00

NES: fixed clipping, update presets, apu.c/h, vrambuf.c

This commit is contained in:
Steven Hugg 2019-02-24 10:36:38 -05:00
parent 8daa260527
commit db60c8e380
17 changed files with 470 additions and 253 deletions

View File

@ -95,6 +95,7 @@ TODO:
- show .map file in listings? memory map view? - show .map file in listings? memory map view?
- open ROM from URL? - open ROM from URL?
- NES: disassembly not aligned on PC - NES: disassembly not aligned on PC
- NES: vrambuf.c for Solarian
WEB WORKER FORMAT WEB WORKER FORMAT

20
presets/nes/apu.c Normal file
View File

@ -0,0 +1,20 @@
#include "apu.h"
#include <string.h>
const unsigned char APUINIT[0x13] = {
0x30,0x08,0x00,0x00,
0x30,0x08,0x00,0x00,
0x80,0x00,0x00,0x00,
0x30,0x00,0x00,0x00,
0x00,0x00,0x00
};
void apu_init() {
// from https://wiki.nesdev.com/w/index.php/APU_basics
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
APU.fcontrol = 0x40; // frame counter 5-step
APU.status = 0x0f; // turn on all channels except DMC
}

77
presets/nes/apu.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef _APU_H
#define _APU_H
#include <nes.h>
// Functions/macros for direct control
// of APU sound generation
// enable
#define ENABLE_PULSE0 0x1
#define ENABLE_PULSE1 0x2
#define ENABLE_TRIANGLE 0x4
#define ENABLE_NOISE 0x8
#define ENABLE_DMC 0x10
#define APU_ENABLE(enable)\
APU.status = (enable);
// pulse channels
#define DUTY_75 0xc0
#define DUTY_50 0x80
#define DUTY_25 0x40
#define DUTY_12 0x00
#define PULSE_ENVLOOP 0x20
#define PULSE_CONSTVOL 0x10
#define PULSE_VOLENVMASK 0xf
#define PULSE_CH0 0
#define PULSE_CH1 1
#define APU_PULSE_DECAY(channel,period,duty,decay,len)\
APU.pulse[channel].period_low = (period)&0xff;\
APU.pulse[channel].len_period_high = (((period)>>8)&7) | ((len)<<3);\
APU.pulse[channel].control = (duty) | (decay);
#define APU_PULSE_SUSTAIN(channel,period,duty,vol)\
APU.pulse[channel].period_low = (period)&0xff;\
APU.pulse[channel].len_period_high = (((period)>>8)&7);\
APU.pulse[channel].control = (duty) | (vol) | (PULSE_CONSTVOL|PULSE_ENVLOOP);
#define APU_PULSE_CONTROL(channel,duty,decay)\
APU.pulse[channel].control = (duty) | (decay);
// triangle channel
#define TRIANGLE_LC_HALT 0x80
#define TRIANGLE_LC_MASK 0x7f
#define APU_TRIANGLE_SUSTAIN(period)\
APU.triangle.counter = 0xc0;\
APU.triangle.period_low = (period) & 0xff;\
APU.triangle.len_period_high = (period) >> 8;
// noise channel
#define NOISE_ENVLOOP 0x20
#define NOISE_CONSTVOL 0x10
#define NOISE_VOLENVMASK 0xf
#define NOISE_PERIOD_BUZZ 0x80
#define APU_NOISE_SUSTAIN(_period,vol)\
APU.noise.control = (vol) | (NOISE_ENVLOOP|NOISE_CONSTVOL);\
APU.noise.period = (_period);
#define APU_NOISE_DECAY(_period,_decay,_len)\
APU.noise.control = (_decay);\
APU.noise.period = (_period);\
APU.noise.len = (_len);
// initialize APU with default state
void apu_init(void);
#endif

22
presets/nes/bcd.c Normal file
View File

@ -0,0 +1,22 @@
#include "neslib.h"
word bcd_add(word a, word b) {
word result = 0;
byte c = 0;
byte shift = 0;
while (shift < 16) {
byte d = (a & 0xf) + (b & 0xf) + c;
c = 0;
while (d >= 10) {
c++;
d -= 10;
}
result |= d << shift;
shift += 4;
a >>= 4;
b >>= 4;
}
return result;
}

2
presets/nes/bcd.h Normal file
View File

@ -0,0 +1,2 @@
unsigned int bcd_add(unsigned int a, unsigned int b);

View File

@ -8,6 +8,14 @@
// include CC65 NES Header (PPU) // include CC65 NES Header (PPU)
#include <nes.h> #include <nes.h>
// BCD arithmetic support
#include "bcd.h"
//#link "bcd.c"
// VRAM update buffer
#include "vrambuf.h"
//#link "vrambuf.c"
// link the pattern table into CHR ROM // link the pattern table into CHR ROM
//#link "chr_generic.s" //#link "chr_generic.s"
@ -22,12 +30,6 @@ extern char demo_sounds[];
typedef enum { SND_START, SND_HIT, SND_COIN, SND_JUMP } SFXIndex; typedef enum { SND_START, SND_HIT, SND_COIN, SND_JUMP } SFXIndex;
// define basic types
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned short word;
typedef enum { false, true } bool;
///// DEFINES ///// DEFINES
#define COLS 30 // floor width in tiles #define COLS 30 // floor width in tiles
@ -66,7 +68,7 @@ static byte player_screen_y = 0;
// score (BCD) // score (BCD)
static byte score = 0; static byte score = 0;
// flash animation (virtual bright) // screen flash animation (virtual bright)
static byte vbright = 4; static byte vbright = 4;
// random byte between (a ... b-1) // random byte between (a ... b-1)
@ -75,15 +77,6 @@ byte rndint(byte a, byte b) {
return (rand() % (b-a)) + a; return (rand() % (b-a)) + a;
} }
byte bcdadd(byte a, byte b) {
byte c = (a & 0xf) + (b & 0xf);
if (c < 10) {
return a + b;
} else {
return (c-10) + 0x10 + (a & 0xf0) + (b & 0xf0);
}
}
///// OAM buffer (for sprites) ///// OAM buffer (for sprites)
#define OAMBUF ((unsigned char*) 0x200) #define OAMBUF ((unsigned char*) 0x200)
@ -102,46 +95,10 @@ word getntaddr(byte x, byte y) {
// convert nametable address to attribute address // convert nametable address to attribute address
word nt2attraddr(word a) { word nt2attraddr(word a) {
return (a & 0x2c00) | 0x3c0 | (a & 0x0C00) | return (a & 0x2c00) | 0x3c0 |
((a >> 4) & 0x38) | ((a >> 2) & 0x07); ((a >> 4) & 0x38) | ((a >> 2) & 0x07);
} }
///// VRAM UPDATE BUFFER
#define VBUFSIZE 64
byte updbuf[VBUFSIZE]; // update buffer
byte updptr = 0; // index to end of buffer
// add EOF marker to buffer
void cendbuf() {
updbuf[updptr] = NT_UPD_EOF;
}
// flush buffer now, waiting for next frame
void cflushnow() {
// make sure buffer has EOF marker
cendbuf();
// wait for next frame to flush update buffer
// this will also set the scroll registers properly
ppu_wait_frame();
// clear the buffer
updptr = 0;
cendbuf();
}
// add multiple characters to update buffer
// using horizontal increment
void putbytes(word addr, char* str, byte len) {
if (updptr >= VBUFSIZE-4-len) cflushnow();
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = len;
while (len--) {
updbuf[updptr++] = *str++;
}
cendbuf();
}
/// METASPRITES /// METASPRITES
// define a 2x2 metasprite // define a 2x2 metasprite
@ -178,7 +135,13 @@ DEF_METASPRITE_2x2_FLIP(playerLJump, 0xe8, 0);
DEF_METASPRITE_2x2_FLIP(playerLClimb, 0xec, 0); DEF_METASPRITE_2x2_FLIP(playerLClimb, 0xec, 0);
DEF_METASPRITE_2x2_FLIP(playerLSad, 0xf0, 0); DEF_METASPRITE_2x2_FLIP(playerLSad, 0xf0, 0);
DEF_METASPRITE_2x2(personToSave, 0xba, 1); //DEF_METASPRITE_2x2(personToSave, 0xba, 1);
const unsigned char personToSave[]={
0, 0, (0xba)+0, 3,
0, 8, (0xba)+2, 0,
8, 0, (0xba)+1, 3,
8, 8, (0xba)+3, 0,
128};
const unsigned char* const playerRunSeq[16] = { const unsigned char* const playerRunSeq[16] = {
playerLRun1, playerLRun2, playerLRun3, playerLRun1, playerLRun2, playerLRun3,
@ -353,6 +316,8 @@ void draw_entire_stage() {
byte y; byte y;
for (y=0; y<ROWS; y++) { for (y=0; y<ROWS; y++) {
draw_floor_line(y); draw_floor_line(y);
// allow buffer to flush, delaying a frame
cflushnow();
} }
} }
@ -649,7 +614,7 @@ void pickup_object(Actor* actor) {
sfx_play(SND_HIT,0); sfx_play(SND_HIT,0);
vbright = 8; // flash vbright = 8; // flash
} else { } else {
score = bcdadd(score, 1); score = bcd_add(score, 1);
sfx_play(SND_COIN,0); sfx_play(SND_COIN,0);
} }
} }

View File

@ -6,8 +6,11 @@
seg.u RAM seg.u RAM
org $0 org $0
ScrollX byte ; used during NMI ScrollPos byte ; used during NMI
ScrollY byte ; used during NMI Rand byte
Temp1 byte
SpriteBuf equ $200
;;;;; NES CARTRIDGE HEADER ;;;;; NES CARTRIDGE HEADER
@ -24,6 +27,7 @@ Start:
jsr SetPalette ;set colors jsr SetPalette ;set colors
jsr FillVRAM ;set PPU RAM jsr FillVRAM ;set PPU RAM
jsr WaitSync ;wait for VSYNC (and PPU warmup) jsr WaitSync ;wait for VSYNC (and PPU warmup)
jsr InitSprites
lda #0 lda #0
sta PPU_ADDR sta PPU_ADDR
sta PPU_ADDR ;PPU addr = 0 sta PPU_ADDR ;PPU addr = 0
@ -51,6 +55,33 @@ FillVRAM: subroutine
bne .loop bne .loop
rts rts
;
InitSprites: subroutine
lda #1
ldx #0
.loop
sta SpriteBuf,x
jsr NextRandom
inx
bne .loop
rts
;
MoveSprites: subroutine
lda #1
ldx #0
.loop
sta Temp1
and #3
clc
adc SpriteBuf,x
sta SpriteBuf,x
lda Temp1
jsr NextRandom
inx
bne .loop
rts
; set palette colors ; set palette colors
SetPalette: subroutine SetPalette: subroutine
@ -76,34 +107,21 @@ SetPalette: subroutine
NMIHandler: NMIHandler:
SAVE_REGS SAVE_REGS
; load sprites
lda #$02
sta PPU_OAM_DMA
; update scroll position (must be done after VRAM updates) ; update scroll position (must be done after VRAM updates)
jsr ReadJoypad inc ScrollPos
pha lda ScrollPos
and #$03
tay
lda ScrollDirTab,y
clc
adc ScrollX
sta ScrollX
sta PPU_SCROLL sta PPU_SCROLL
pla lda #0
lsr
lsr
and #$03
tay
lda ScrollDirTab,y
clc
adc ScrollY
sta ScrollY
sta PPU_SCROLL sta PPU_SCROLL
; move sprites
jsr MoveSprites
; reload registers ; reload registers
RESTORE_REGS RESTORE_REGS
rti rti
; Scroll direction lookup table
ScrollDirTab:
hex 00 01 ff 00
;;;;; CONSTANT DATA ;;;;; CONSTANT DATA
align $100 align $100

147
presets/nes/horizmask.c Normal file
View File

@ -0,0 +1,147 @@
#include "neslib.h"
#include <string.h>
// 0 = horizontal mirroring
// 1 = vertical mirroring
#define NES_MIRRORING 1
// VRAM update buffer
#include "vrambuf.h"
//#link "vrambuf.c"
// link the pattern table into CHR ROM
//#link "chr_generic.s"
// function to write a string into the name table
// adr = start address in name table
// str = pointer to string
void put_str(unsigned int adr, const char *str) {
vram_adr(adr); // set PPU read/write address
vram_write(str, strlen(str)); // write bytes to PPU
}
static word x_scroll = 0;
static byte bldg_height = 8;
static byte bldg_width = 8;
static byte bldg_char = 1;
static byte bldg_attr = 0x55;
#define PLAYROWS 24
word nt2attraddr(word a) {
return (a & 0x2c00) | 0x3c0 |
((a >> 4) & 0x38) | ((a >> 2) & 0x07);
}
void update_nametable() {
word addr;
char buf[PLAYROWS];
// divide x_scroll by 8
// to get nametable X position
byte x = ((x_scroll >> 3)+32) & 63;
if (x < 32)
addr = NTADR_A(x, 4);
else
addr = NTADR_B(x&31, 4);
// create vertical slice
// clear empty space
memset(buf, 0, PLAYROWS-bldg_height);
// draw roof
buf[PLAYROWS-bldg_height-1] = bldg_char & 3;
// draw rest of building
memset(buf+PLAYROWS-bldg_height, bldg_char, bldg_height);
// draw vertical slice in name table
putbytes(addr ^ 0xc000, buf, sizeof(buf));
// every 4 columns, update attribute table
if ((x & 3) == 1) {
// compute attribute table address
// of upper attribute block
addr = nt2attraddr(addr) + 8*4;
VRAMBUF_PUT(addr, bldg_attr, 0);
// put lower attribute block
addr += 8;
VRAMBUF_PUT(addr, bldg_attr, 0);
cendbuf();
}
// generate new building?
if (--bldg_width == 0) {
bldg_height = (rand8() & 7) + 2;
bldg_width = (rand8() & 3) * 4 + 4;
bldg_char = (rand8() & 15);
bldg_attr = rand8();
}
}
// function to scroll window up and down until end
void scroll_demo() {
// infinite loop
while (1) {
// update nametable every 8 pixels
if ((x_scroll & 7) == 0) {
update_nametable();
}
// manually force vram update
ppu_wait_nmi();
flush_vram_update(updbuf);
cclearbuf();
// reset ppu address
vram_adr(0x0);
// set scroll register
// and increment x_scroll
split(x_scroll++, 0);
}
}
const char PALETTE[32] = {
0x03, // background color
0x11,0x30,0x27, 0, // background 0
0x1c,0x20,0x2c, 0, // background 1
0x00,0x10,0x20, 0, // background 2
0x06,0x16,0x26, 0, // background 3
0x16,0x35,0x24, 0, // sprite 0
0x00,0x37,0x25, 0, // sprite 1
0x0d,0x2d,0x3a, 0, // sprite 2
0x0d,0x27,0x2a // sprite 3
};
// main function, run after console reset
void main(void) {
// set palette colors
pal_all(PALETTE);
// write text to name table
put_str(NTADR_A(7,0), "Nametable A, Line 0");
put_str(NTADR_A(7,1), "Nametable A, Line 1");
put_str(NTADR_A(7,2), "Nametable A, Line 2");
vram_adr(NTADR_A(0,3));
vram_fill(5, 32);
put_str(NTADR_A(2,4), "Nametable A, Line 4");
put_str(NTADR_A(2,15),"Nametable A, Line 15");
put_str(NTADR_A(2,27),"Nametable A, Line 27");
put_str(NTADR_B(2,4), "Nametable B, Line 4");
put_str(NTADR_B(2,15),"Nametable B, Line 15");
put_str(NTADR_B(2,27),"Nametable B, Line 27");
// set attributes
vram_adr(0x23c0);
vram_fill(0x55, 8);
// set sprite 0
oam_clear();
oam_spr(0, 30, 1, 1, 0);
// clip left 8 pixels of screen
ppu_mask(MASK_SPR|MASK_BG);
// clear vram buffer
cclearbuf();
// enable PPU rendering (turn on screen)
ppu_on_all();
// scroll window back and forth
scroll_demo();
}

View File

@ -4,7 +4,8 @@
#include "neslib.h" #include "neslib.h"
typedef unsigned char byte; #include "apu.h"
//#link "apu.c"
// //
// MUSIC ROUTINES // MUSIC ROUTINES
@ -41,13 +42,6 @@ byte next_music_byte() {
return *music_ptr++; return *music_ptr++;
} }
#define DUTY_75 0xc0
#define DUTY_50 0x80
#define DUTY_25 0x40
#define DUTY_12 0x00
#define DUTY (DUTY_25 | 0x1) // decay rate == 1
void play_music() { void play_music() {
static byte ch = 0; static byte ch = 0;
if (music_ptr) { if (music_ptr) {
@ -56,25 +50,9 @@ void play_music() {
if ((note & 0x80) == 0) { if ((note & 0x80) == 0) {
int period = note_table[note & 63]; int period = note_table[note & 63];
if (ch == 0) { if (ch == 0) {
APU.pulse[0].period_low = period & 0xff; APU_PULSE_DECAY(0, period, DUTY_25, 2, 10);
APU.pulse[0].len_period_high = period >> 8;
APU.pulse[0].control = DUTY;
} else { } else {
APU.pulse[1].period_low = period & 0xff; APU_PULSE_DECAY(1, period, DUTY_25, 2, 10);
APU.pulse[1].len_period_high = period >> 8;
APU.pulse[1].control = DUTY;
/*
//period = note_table_tri[note & 63];
period >>= 1;
APU.triangle.counter = 0xc0;
APU.triangle.period_low = period & 0xff;
APU.triangle.len_period_high = period >> 8;
*/
/*
APU.noise.control = 0x4;
APU.noise.period = 0xe;
APU.noise.len = 0xf;
*/
} }
ch = ch^1; ch = ch^1;
} else { } else {
@ -92,24 +70,9 @@ void start_music(const byte* music) {
cur_duration = 0; cur_duration = 0;
} }
const byte APUINIT[0x13] = {
0x30,0x08,0x00,0x00,
0x30,0x08,0x00,0x00,
0x80,0x00,0x00,0x00,
0x30,0x00,0x00,0x00,
0x00,0x00,0x00
};
void init_apu() {
// from https://wiki.nesdev.com/w/index.php/APU_basics
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
APU.fcontrol = 0x40; // frame counter 5-step
APU.status = 0x0f; // turn on all channels except DMC
}
void main(void) void main(void)
{ {
init_apu(); apu_init();
music_ptr = 0; music_ptr = 0;
while (1) { while (1) {
if (!music_ptr) start_music(music1); if (!music_ptr) start_music(music1);

View File

@ -1,3 +1,5 @@
#ifndef _NESLIB_H
#define _NESLIB_H
/* /*
(C) 2015 Alex Semenov (Shiru) (C) 2015 Alex Semenov (Shiru)
(C) 2016 Lauri Kasanen (C) 2016 Lauri Kasanen
@ -30,8 +32,14 @@
// unrle_vram renamed to vram_unrle, with adr argument removed // unrle_vram renamed to vram_unrle, with adr argument removed
// 060414 - many fixes and improvements, including sequental VRAM updates // 060414 - many fixes and improvements, including sequental VRAM updates
// previous versions were created since mid-2011, there were many updates // previous versions were created since mid-2011, there were many updates
// xxxx19 - updated by sehugg@8bitworkshop
// define basic types for convenience
typedef unsigned char byte; // 8-bit unsigned
typedef signed char sbyte; // 8-bit signed
typedef unsigned short word; // 16-bit signed
typedef enum { false, true } bool; // boolean
@ -300,3 +308,6 @@ void __fastcall__ nmi_set_callback(void (*callback)(void));
#define MSB(x) (((x)>>8)) #define MSB(x) (((x)>>8))
#define LSB(x) (((x)&0xff)) #define LSB(x) (((x)&0xff))
#endif /* neslib.h */

View File

@ -5,17 +5,23 @@
#include "neslib.h" #include "neslib.h"
// APU (sound) support
#include "apu.h"
//#link "apu.c"
// BCD arithmetic support
#include "bcd.h"
//#link "bcd.c"
// VRAM update buffer
#include "vrambuf.h"
//#link "vrambuf.c"
#define COLS 32 #define COLS 32
#define ROWS 28 #define ROWS 28
//#define DEBUG_FRAMERATE //#define DEBUG_FRAMERATE
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned short word;
///
const char PALETTE[32] = { const char PALETTE[32] = {
0x0f, 0x0f,
@ -131,59 +137,11 @@ const char TILESET[128*8*2] = {/*{w:8,h:8,bpp:1,count:128,brev:1,np:2,pofs:8,rem
#define CHAR(x) ((x)-' ') #define CHAR(x) ((x)-' ')
#define BLANK 0 #define BLANK 0
// VRAM UPDATE BUFFER
#define VBUFSIZE 96
byte updbuf[VBUFSIZE];
byte updptr = 0;
void cendbuf() {
updbuf[updptr] = NT_UPD_EOF;
}
void cflushnow() {
cendbuf();
ppu_wait_nmi();
flush_vram_update(updbuf);
updptr = 0;
cendbuf();
vram_adr(0x0);
}
void vdelay(byte count) {
while (count--) cflushnow();
}
void putchar(byte x, byte y, char ch) {
word addr = NTADR_A(x,y);
if (updptr >= VBUFSIZE-4) cflushnow();
updbuf[updptr++] = addr >> 8;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = ch;
cendbuf();
}
void putbytes(byte x, byte y, char* str, byte len) {
word addr = NTADR_A(x,y);
if (updptr >= VBUFSIZE-4-len) cflushnow();
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = len;
while (len--) {
updbuf[updptr++] = *str++;
}
cendbuf();
}
void putstring(byte x, byte y, char* str) {
putbytes(x, y, str, strlen(str));
}
void clrscr() { void clrscr() {
updptr = 0; updptr = 0;
cendbuf(); cendbuf();
ppu_off(); ppu_off();
vram_adr(0x2000); vram_adr(NAMETABLE_A);
vram_fill(BLANK, 32*28); vram_fill(BLANK, 32*28);
vram_adr(0x0); vram_adr(0x0);
ppu_on_all(); ppu_on_all();
@ -203,26 +161,7 @@ void draw_bcd_word(byte col, byte row, word bcd) {
buf[j] = CHAR('0'+(bcd&0xf)); buf[j] = CHAR('0'+(bcd&0xf));
bcd >>= 4; bcd >>= 4;
} }
putbytes(col, row, buf, 5); putbytes(NTADR_A(col, row), buf, 5);
}
word bcd_add(word a, word b) {
word result = 0;
byte c = 0;
byte shift = 0;
while (shift < 16) {
byte d = (a & 0xf) + (b & 0xf) + c;
c = 0;
while (d >= 10) {
c++;
d -= 10;
}
result |= d << shift;
shift += 4;
a >>= 4;
b >>= 4;
}
return result;
} }
// GAME CODE // GAME CODE
@ -261,17 +200,17 @@ typedef struct {
} AttackingEnemy; } AttackingEnemy;
typedef struct { typedef struct {
signed char dx;
byte xpos; byte xpos;
signed char dy;
byte ypos; byte ypos;
signed char dx;
signed char dy;
} Missile; } Missile;
typedef struct { typedef struct {
byte name;
byte tag;
byte x; byte x;
byte y; byte y;
byte name;
byte tag;
} Sprite; } Sprite;
#define ENEMIES_PER_ROW 8 #define ENEMIES_PER_ROW 8
@ -366,7 +305,7 @@ void draw_row(byte row) {
} }
x += 3; x += 3;
} }
putbytes(0, y, buf, sizeof(buf)); putbytes(NTADR_A(0, y), buf, sizeof(buf));
} }
void draw_next_row() { void draw_next_row() {
@ -724,37 +663,30 @@ void new_player_ship() {
void set_sounds() { void set_sounds() {
byte i; byte i;
byte enable = 0x1 | 0x2 | 0x8; // these channels decay, so ok to always enable
byte enable = ENABLE_PULSE0|ENABLE_PULSE1|ENABLE_NOISE;
// missile fire sound // missile fire sound
if (missiles[PLYRMISSILE].ypos != YOFFSCREEN) { if (missiles[PLYRMISSILE].ypos != YOFFSCREEN) {
APU.pulse[0].period_low = missiles[PLYRMISSILE].ypos ^ 0xff; APU_PULSE_SUSTAIN(0, missiles[PLYRMISSILE].ypos ^ 0xff, DUTY_50, 6);
APU.pulse[0].len_period_high = 0;
APU.pulse[0].control = 0x80 | 0x30 | 6;
} else { } else {
APU.pulse[0].control = 0x30; APU_PULSE_CONTROL(0, DUTY_50, 1);
} }
// enemy explosion sound // enemy explosion sound
if (player_exploding && player_exploding < 8) { if (player_exploding && player_exploding < 8) {
APU.noise.control = 4; APU_NOISE_DECAY(8 + player_exploding, 5, 15);
APU.noise.period = 8 + player_exploding;
APU.noise.len = 15;
} else if (enemy_exploding) { } else if (enemy_exploding) {
APU.noise.control = 2; APU_NOISE_DECAY(8 + enemy_exploding, 2, 8);
APU.noise.period = 8 + enemy_exploding;
APU.noise.len = 8;
} }
// set diving sounds for spaceships // set diving sounds for spaceships
for (i=0; i<2; i++) { for (i=0; i<2; i++) {
register AttackingEnemy* a = i ? &attackers[4] : &attackers[0]; register AttackingEnemy* a = i ? &attackers[4] : &attackers[0];
if (a->findex && !a->returning) { if (a->findex && !a->returning) {
byte y = a->y >> 8; byte y = a->y >> 8;
APU.triangle.counter = 0xc0; APU_TRIANGLE_SUSTAIN(0x100 | y);
APU.triangle.period_low = y; enable |= ENABLE_TRIANGLE;
APU.triangle.len_period_high = 1;
enable |= 0x4;
} }
} }
APU.status = enable; APU_ENABLE(enable);
} }
static char starx[32]; static char starx[32];
@ -783,7 +715,7 @@ void play_round() {
register byte t0; register byte t0;
byte end_timer = 255; byte end_timer = 255;
add_score(0); add_score(0);
putstring(0, 0, "PLAYER 1"); //putbytes(NTADR_A(0, 1), "PLAYER 1", 8);
setup_formation(); setup_formation();
clrobjs(); clrobjs();
formation_direction = 1; formation_direction = 1;
@ -851,7 +783,7 @@ void set_shifted_pattern(const byte* src, word dest, byte shift) {
vram_write(buf, sizeof(buf)); vram_write(buf, sizeof(buf));
} }
void setup_tileset() { void setup_graphics() {
byte i; byte i;
word src; word src;
word dest; word dest;
@ -869,26 +801,14 @@ void setup_tileset() {
set_shifted_pattern(&TILESET[src], dest, i); set_shifted_pattern(&TILESET[src], dest, i);
dest += 3*16; dest += 3*16;
} }
} // activate vram buffer
cendbuf();
const byte APUINIT[0x13] = { set_vram_update(updbuf);
0x30,0x08,0x00,0x00,
0x30,0x08,0x00,0x00,
0x80,0x00,0x00,0x00,
0x30,0x00,0x00,0x00,
0x00,0x00,0x00
};
void init_apu() {
// from https://wiki.nesdev.com/w/index.php/APU_basics
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
APU.fcontrol = 0x40; // frame counter 5-step
APU.status = 0x0f; // turn on all channels except DMC
} }
void main() { void main() {
setup_tileset(); setup_graphics();
init_apu(); apu_init();
init_stars(); init_stars();
player_score = 0; player_score = 0;
while (1) { while (1) {

View File

@ -12,13 +12,7 @@ extern unsigned char palSprites[16];
extern unsigned char TILESET[8*256]; extern unsigned char TILESET[8*256];
#define COLS 32 #define COLS 32
#define ROWS 28 #define ROWS 27
#define NTADR(x,y) ((0x2000|((y)<<5)|(x)))
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned short word;
// read a character from VRAM. // read a character from VRAM.
// this is tricky because we have to wait // this is tricky because we have to wait
@ -27,10 +21,10 @@ typedef unsigned short word;
// back to the start of the frame. // back to the start of the frame.
byte getchar(byte x, byte y) { byte getchar(byte x, byte y) {
// compute VRAM read address // compute VRAM read address
word addr = NTADR(x,y); word addr = NTADR_A(x,y);
byte rd; byte rd;
// wait for VBLANK to start // wait for VBLANK to start
waitvsync(); ppu_wait_nmi();
vram_adr(addr); vram_adr(addr);
vram_read(&rd, 1); vram_read(&rd, 1);
vram_adr(0x0); vram_adr(0x0);
@ -60,7 +54,7 @@ void vdelay(byte count) {
} }
void cputcxy(byte x, byte y, char ch) { void cputcxy(byte x, byte y, char ch) {
word addr = NTADR(x,y); word addr = NTADR_A(x,y);
if (updptr >= 60) cflushnow(); if (updptr >= 60) cflushnow();
updbuf[updptr++] = addr >> 8; updbuf[updptr++] = addr >> 8;
updbuf[updptr++] = addr & 0xff; updbuf[updptr++] = addr & 0xff;
@ -69,7 +63,7 @@ void cputcxy(byte x, byte y, char ch) {
} }
void cputsxy(byte x, byte y, char* str) { void cputsxy(byte x, byte y, char* str) {
word addr = NTADR(x,y); word addr = NTADR_A(x,y);
byte len = strlen(str); byte len = strlen(str);
if (updptr >= 60 - len) cflushnow(); if (updptr >= 60 - len) cflushnow();
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ; updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;

40
presets/nes/vrambuf.c Normal file
View File

@ -0,0 +1,40 @@
#include "neslib.h"
#include "vrambuf.h"
// index to end of buffer
byte updptr = 0;
// add EOF marker to buffer
void cendbuf(void) {
updbuf[updptr] = NT_UPD_EOF;
}
void cclearbuf(void) {
updptr = 0;
cendbuf();
}
// flush buffer now, waiting for next frame
void cflushnow(void) {
// make sure buffer has EOF marker
cendbuf();
// wait for next frame to flush update buffer
// this will also set the scroll registers properly
ppu_wait_frame();
// clear the buffer
cclearbuf();
}
// add multiple characters to update buffer
// using horizontal increment
void putbytes(word addr, char* str, byte len) {
if (updptr >= VBUFSIZE-4-len) cflushnow();
updbuf[updptr++] = (addr >> 8) ^ NT_UPD_HORZ;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = len;
while (len--) {
updbuf[updptr++] = *str++;
}
cendbuf();
}

38
presets/nes/vrambuf.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef _VRAMBUF_H
#define _VRAMBUF_H
#include "neslib.h"
// VBUFSIZE = maximum update buffer bytes
#define VBUFSIZE 128
// update buffer starts at $100 (stack page)
#define updbuf ((byte*)0x100)
// index to end of buffer
extern byte updptr;
// macros
#define VRAMBUF_PUT(addr,len,flags)\
updbuf[updptr++] = ((addr) >> 8) | (flags);\
updbuf[updptr++] = (addr) & 0xff;\
updbuf[updptr++] = (len);
#define VRAMBUF_ADD(b)\
updbuf[updptr++] = (b);
// add EOF marker to buffer
void cendbuf(void);
// clear update buffer
void cclearbuf(void);
// flush buffer now, waiting for next frame
void cflushnow(void);
// add multiple characters to update buffer
// using horizontal increment
void putbytes(word addr, char* str, byte len);
#endif // vrambuf.h

View File

@ -17,6 +17,7 @@ const JSNES_PRESETS = [
{id:'neslib1.c', name:'Text'}, {id:'neslib1.c', name:'Text'},
{id:'scroll.c', name:'Scrolling'}, {id:'scroll.c', name:'Scrolling'},
{id:'statusbar.c', name:'Status Bar'}, {id:'statusbar.c', name:'Status Bar'},
{id:'horizmask.c', name:'Horizontal Scrolling'},
{id:'sprites.c', name:'Sprites'}, {id:'sprites.c', name:'Sprites'},
{id:'metasprites.c', name:'Metasprites'}, {id:'metasprites.c', name:'Metasprites'},
{id:'flicker.c', name:'Flickering Sprites'}, {id:'flicker.c', name:'Flickering Sprites'},
@ -118,6 +119,7 @@ const _JSNESPlatform = function(mainElement) {
}, },
//TODO: onBatteryRamWrite //TODO: onBatteryRamWrite
}); });
//nes.ppu.clipToTvSize = false;
nes.stop = function() { nes.stop = function() {
// TODO: trigger breakpoint // TODO: trigger breakpoint
self.pause(); self.pause();
@ -300,10 +302,10 @@ const _JSNESPlatform = function(mainElement) {
var PPUFLAGS = [ var PPUFLAGS = [
["f_nmiOnVblank","NMI_ON_VBLANK"], ["f_nmiOnVblank","NMI_ON_VBLANK"],
["f_spVisibility","SPRITES"], ["f_spVisibility","SPRITES"],
["f_spClipping","CLIP_SPRITES"], ["f_spClipping","NO_CLIP_SPRITES"],
["f_dispType","MONOCHROME"], ["f_dispType","MONOCHROME"],
["f_bgVisibility","BACKGROUND"], ["f_bgVisibility","BACKGROUND"],
["f_bgClipping","CLIP_BACKGROUND"], ["f_bgClipping","NO_CLIP_BACKGROUND"],
]; ];
for (var i=0; i<PPUFLAGS.length; i++) { for (var i=0; i<PPUFLAGS.length; i++) {
var flag = PPUFLAGS[i]; var flag = PPUFLAGS[i];

View File

@ -892,7 +892,7 @@ function _recordVideo() {
}; };
f(); f();
}); });
return true; //TODO? return true;
} }
function setFrameRateUI(fps:number) { function setFrameRateUI(fps:number) {

View File

@ -881,13 +881,12 @@ function linkLD65(step:BuildStep) {
var binpath = "main"; var binpath = "main";
if (staleFiles(step, [binpath])) { if (staleFiles(step, [binpath])) {
var errors = []; var errors = [];
var errmsg = '';
var LD65 = emglobal.ld65({ var LD65 = emglobal.ld65({
instantiateWasm: moduleInstFn('ld65'), instantiateWasm: moduleInstFn('ld65'),
noInitialRun:true, noInitialRun:true,
//logReadFiles:true, //logReadFiles:true,
print:print_fn, print:print_fn,
printErr:function(s) { errmsg += s + '\n'; } printErr:function(s) { errors.push({msg:s,line:0}); }
}); });
var FS = LD65['FS']; var FS = LD65['FS'];
var cfgfile = '/' + platform + '.cfg'; var cfgfile = '/' + platform + '.cfg';
@ -905,8 +904,6 @@ function linkLD65(step:BuildStep) {
'-o', 'main', '-m', 'main.map'].concat(step.args, libargs); '-o', 'main', '-m', 'main.map'].concat(step.args, libargs);
//console.log(args); //console.log(args);
execMain(step, LD65, args); execMain(step, LD65, args);
if (errmsg.length)
errors.push({line:0, msg:errmsg});
if (errors.length) if (errors.length)
return {errors:errors}; return {errors:errors};
var aout = FS.readFile("main", {encoding:'binary'}); var aout = FS.readFile("main", {encoding:'binary'});