From 9da265f7e5ea1d80feb5e18f20e5b1d64c9fa0b8 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Wed, 12 Apr 2017 12:23:24 -0400 Subject: [PATCH] spinner while compiling; updated presets --- index.html | 8 +- presets/williams-z80/game1.c | 379 +++++++++++++++++++++------------ spinner.gif | Bin 0 -> 24927 bytes src/emu.js | 10 + src/platform/sound_williams.js | 2 +- src/platform/williams.js | 19 +- src/ui.js | 7 +- tools/sintbl.py | 13 ++ 8 files changed, 288 insertions(+), 150 deletions(-) create mode 100644 spinner.gif create mode 100644 tools/sintbl.py diff --git a/index.html b/index.html index d69da0ba..8327e725 100644 --- a/index.html +++ b/index.html @@ -93,6 +93,9 @@ span.hilite { div.has-errors { background-color: #ff6666 !important; } +div.is-busy-unused { + background-color: #8888bb !important; +} div.menu_div { position: absolute; width: 200px; @@ -223,7 +226,8 @@ div.bitmap_editor { - + + @@ -232,8 +236,8 @@ div.bitmap_editor { - +
diff --git a/presets/williams-z80/game1.c b/presets/williams-z80/game1.c index 6e722255..9f90f9de 100644 --- a/presets/williams-z80/game1.c +++ b/presets/williams-z80/game1.c @@ -4,6 +4,7 @@ typedef unsigned char byte; typedef signed char sbyte; typedef unsigned short word; +typedef enum { false, true } bool; byte __at (0x0) vidmem[152][256]; // 304x256x4bpp video memory byte __at (0xc000) palette[16]; @@ -15,6 +16,8 @@ volatile byte __at (0xcb00) video_counter; byte __at (0xcbff) watchdog0x39; byte __at (0xcc00) nvram[0x400]; +__sfr __at (0) debug; + // blitter flags #define SRCSCREEN 0x1 #define DSTSCREEN 0x2 @@ -108,6 +111,94 @@ void fill_char_table() { } } +inline word swapw(word j) { + return ((j << 8) | (j >> 8)); +} + +// x1: 0-151 +// y1: 0-255 +inline void blit_solid(byte x1, byte y1, byte w, byte h, byte color) { + blitter.width = w^4; + blitter.height = h^4; + blitter.dstart = x1+y1*256; // swapped + blitter.solid = color; + blitter.flags = DSTSCREEN|SOLID; +} + +inline void draw_solid(word x1, byte y1, byte w, byte h, byte color) { + blitter.width = w^4; + blitter.height = h^4; + blitter.dstart = (x1>>1)+y1*256; // swapped + blitter.solid = color; + blitter.flags = (x1&1) ? DSTSCREEN|SOLID|RSHIFT : DSTSCREEN|SOLID; +} + +inline void draw_vline(word x1, byte y1, byte h, byte color) { + blitter.width = 1^4; + blitter.height = h^4; + blitter.dstart = (x1>>1)+y1*256; // swapped + blitter.solid = color; + blitter.flags = (x1&1) ? DSTSCREEN|SOLID|ODDONLY : DSTSCREEN|SOLID|EVENONLY; +} + +inline void blit_copy(byte x1, byte y1, byte w, byte h, const byte* data) { + blitter.width = w^4; + blitter.height = h^4; + blitter.sstart = swapw((word)data); + blitter.dstart = x1+y1*256; // swapped + blitter.flags = DSTSCREEN|FGONLY; +} + +inline void blit_copy_solid(byte x1, byte y1, byte w, byte h, const byte* data, byte solid) { + blitter.width = w^4; + blitter.height = h^4; + blitter.sstart = swapw((word)data); + blitter.dstart = x1+y1*256; // swapped + blitter.solid = solid; + blitter.flags = DSTSCREEN|FGONLY|SOLID; +} + +// bias sprites by +12 pixels +#define XBIAS 6 + +inline void draw_sprite(const byte* data, byte x, byte y) { + blitter.width = data[0]^4; + blitter.height = data[1]^4; + blitter.sstart = swapw((word)(data+2)); + blitter.dstart = (x>>1)+y*256+XBIAS; // swapped + blitter.flags = (x&1) ? DSTSCREEN|FGONLY|RSHIFT : DSTSCREEN|FGONLY; +} + +inline void draw_sprite_solid(const byte* data, byte x, byte y, byte color) { + blitter.width = data[0]^4; + blitter.height = data[1]^4; + blitter.sstart = swapw((word)(data+2)); + blitter.dstart = (x>>1)+y*256+XBIAS; // swapped + blitter.solid = color; + blitter.flags = (x&1) ? DSTSCREEN|FGONLY|RSHIFT|SOLID : DSTSCREEN|FGONLY|SOLID; +} + +inline void draw_char(char ch, byte x, byte y, byte color) { + if (ch < LOCHAR || ch > HICHAR) return; + blit_copy_solid(x, y, 4, 8, font_table[ch - LOCHAR], color); +} + +void draw_string(const char* str, byte x, byte y, byte color) { + while (*str) { + draw_char(*str++, x, y, color); + x += 4; + } +} + +void draw_box(word x1, byte y1, word x2, byte y2, byte color) { + draw_solid(x1, y1, (x2-x1)>>1, 1, color); + draw_solid(x1, y2, (x2-x1)>>1, 1, color); + draw_vline(x1, y1, y2-y1, color); + draw_vline(x2, y1, y2-y1, color); +} + +// GRAPHIC DATA + const byte palette_data[16] = { 0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1f, 0x37, 0xe0, 0xa4, 0xfd, 0xff, 0x38, 0x70, 0x7f, 0xf8, }; @@ -335,112 +426,60 @@ const byte* const all_sprites[9] = { sprite9, }; -inline word swapw(word j) { - return ((j << 8) | (j >> 8)); -} +// GAME CODE -// x1: 0-151 -// y1: 0-255 -inline void blit_solid(byte x1, byte y1, byte w, byte h, byte color) { - blitter.width = w^4; - blitter.height = h^4; - blitter.dstart = x1+y1*256; // swapped - blitter.solid = color; - blitter.flags = DSTSCREEN|SOLID; -} +typedef struct Actor; +typedef struct Task; -inline void blit_copy(byte x1, byte y1, byte w, byte h, const byte* data) { - blitter.width = w^4; - blitter.height = h^4; - blitter.sstart = swapw((word)data); - blitter.dstart = x1+y1*256; // swapped - blitter.flags = DSTSCREEN|FGONLY; -} - -inline void blit_copy_solid(byte x1, byte y1, byte w, byte h, const byte* data, byte solid) { - blitter.width = w^4; - blitter.height = h^4; - blitter.sstart = swapw((word)data); - blitter.dstart = x1+y1*256; // swapped - blitter.solid = solid; - blitter.flags = DSTSCREEN|FGONLY|SOLID; -} - -inline void draw_sprite(const byte* data, byte x, byte y) { - blitter.width = data[0]^4; - blitter.height = data[1]^4; - blitter.sstart = swapw((word)(data+2)); - blitter.dstart = x+y*256; // swapped - blitter.flags = DSTSCREEN|FGONLY; -} - -inline void draw_sprite_solid(const byte* data, byte x, byte y, byte color) { - blitter.width = data[0]^4; - blitter.height = data[1]^4; - blitter.sstart = swapw((word)(data+2)); - blitter.dstart = x+y*256; // swapped - blitter.solid = color; - blitter.flags = DSTSCREEN|FGONLY|SOLID; -} - -inline void erase_sprite_rect(const byte* data, byte x, byte y) { - blitter.width = data[0]^4; - blitter.height = data[1]^4; - blitter.dstart = x+y*256; // swapped - blitter.solid = 0; - blitter.flags = DSTSCREEN|SOLID; -} - -inline void draw_sprite_strided(const byte* data, byte x, byte y, byte stride) { - const byte* src = data+2; - byte height = data[1]^4; - byte width = data[0]^4; - while (height--) { - blit_copy(x, y, width, 1, src); - y += stride; - src += width; - } -} - -inline void draw_char(char ch, byte x, byte y, byte color) { - if (ch < LOCHAR || ch > HICHAR) return; - blit_copy_solid(x, y, 4, 8, font_table[ch - LOCHAR], color); -} - -void draw_string(const char* str, byte x, byte y, byte color) { - while (*str) { - draw_char(*str++, x, y, color); - x += 4; - } -} - -typedef struct Actor* a; - -typedef void ActorUpdateFn(struct Actor* a); -typedef void ActorDrawFn(struct Actor* a); -typedef void ActorEnumerateFn(struct Actor* a); +typedef void (*ActorUpdateFn)(struct Actor* a); +typedef void (*ActorDrawFn)(const struct Actor* a); +typedef void (*ActorEnumerateFn)(const struct Actor* a); +typedef bool (*TaskFn)(struct Task* task); typedef struct Actor { byte grid_index; byte next_actor; + byte last_update_frame; byte x,y; byte* shape; - ActorUpdateFn* update; - ActorDrawFn* draw; + byte flags; + byte updatefreq; + byte updatetimer; + ActorUpdateFn update; + ActorDrawFn draw; union { struct { sbyte dx,dy; } laser; + struct { byte exploding; } enemy; } u; } Actor; +typedef struct Task { + Actor* actor; + struct Task* next; + TaskFn func; +} Task; + #define GBITS 3 #define GDIM (1<> (8-GBITS)) | (y & ((GDIM-1) << GBITS)); +#define PLAYER 1 +#define LASER 2 +#define F_PLAYER 0x1 +#define F_KILLABLE 0x2 + +byte xy2grid(byte x, byte y) { + return (x >> (8-GBITS)) | ((y >> (8-GBITS)) << GBITS); } void insert_into_grid(byte gi, byte actor_index) { @@ -476,16 +515,22 @@ void add_actor(byte actor_index) { insert_into_grid(xy2grid(a->x, a->y), actor_index); } -void enumerate_actors_at_grid(ActorEnumerateFn* enumfn, byte gi) { - byte ai = grid[gi]; +char in_rect(const Actor* e, byte x, byte y, byte w, byte h) { + byte eh = e->shape[0]; + byte ew = e->shape[1]; + return (x >= e->x-w && x <= e->x+ew && y >= e->y-h && y <= e->y+eh); +} + +void enumerate_actors_at_grid(ActorEnumerateFn enumfn, byte gi) { + byte ai = grid[gi & GMASK]; while (ai) { - Actor* a = &actors[ai]; - enumfn(a); + const Actor* a = &actors[ai]; ai = a->next_actor; + enumfn(a); } } -void enumerate_actors_in_rect(ActorEnumerateFn* enumfn, +void enumerate_actors_in_rect(ActorEnumerateFn enumfn, byte x1, byte y1, byte x2, byte y2) { byte gi = xy2grid(x1, y1); byte gi1 = xy2grid(x2, y2) + 1; @@ -506,12 +551,17 @@ void enumerate_actors_in_rect(ActorEnumerateFn* enumfn, } while (1); } -void draw_actor_normal(struct Actor* a) { +void draw_actor_normal(Actor* a) { draw_sprite(a->shape, a->x, a->y); } -void draw_actor_debug(struct Actor* a) { - draw_sprite_solid(a->shape, a->x, a->y, a->next_actor?0xff:0x33); +void draw_actor_debug(Actor* a) { + draw_sprite_solid(a->shape, a->x, a->y, a->grid_index); +} + +byte time_to_update(Actor* a) { + byte t0 = a->updatetimer; + return (a->updatetimer += a->updatefreq) < t0; } byte update_actor(byte actor_index) { @@ -519,20 +569,26 @@ byte update_actor(byte actor_index) { byte next_actor = a->next_actor; byte gi0,gi1; // if NULL shape, we don't have anything - if (a->shape) { + if (a->shape && time_to_update(a)) { gi0 = a->grid_index; + // erase the sprite draw_sprite_solid(a->shape, a->x, a->y, 0); + // call update callback if (a->update) a->update(a); + // set last_update_frame + a->last_update_frame = frame; // did we delete it? if (a->shape) { + // draw the sprite if (a->draw) a->draw(a); - gi1 = xy2grid(a->x, a->y); // grid bucket changed? + gi1 = xy2grid(a->x, a->y); if (gi0 != gi1) { delete_from_grid(gi0, actor_index); insert_into_grid(gi1, actor_index); } } else { + // shape NULL, delete from grid delete_from_grid(gi0, actor_index); } } @@ -560,6 +616,9 @@ signed char random_dir() { void random_walk(Actor* a) { a->x += random_dir(); a->y += random_dir(); + if (a->u.enemy.exploding) { + a->shape = NULL; + } } void update_grid_cell(byte grid_index) { @@ -569,12 +628,16 @@ void update_grid_cell(byte grid_index) { } } -void update_grid_rows(byte row_start, byte row_end) { - byte i0 = row_start * GDIM; - byte i1 = row_end * GDIM; - byte i; - for (i=i0; i!=i1; i++) { - update_grid_cell(i); +void update_screen_section(byte gi0, byte gi1, byte vc0, byte vc1) { + while (!(video_counter >= vc0 && video_counter <= vc1)) ; + while (gi0 < gi1) { + update_grid_cell(gi0++); + } +} + +inline void ensure_update(byte actor_index) { + if (actors[actor_index].last_update_frame != frame) { + update_actor(actor_index); } } @@ -586,72 +649,110 @@ byte did_overflow() { __endasm; } +static byte test_flags; +static byte test_x, test_y; +static Actor* test_collided; + +void enumerate_check_point(Actor* a) { + // check flags to see if we should test this + if (!(a->flags & test_flags)) return; + draw_actor_debug(a); + if (in_rect(a, test_x, test_y, 1, 1)) test_collided = a; +} + void laser_move(Actor* a) { + // did we hit something? + test_x = a->x; + test_y = a->y; + test_flags = F_KILLABLE; + test_collided = NULL; + enumerate_actors_at_grid(enumerate_check_point, a->grid_index); + enumerate_actors_at_grid(enumerate_check_point, a->grid_index-1); + enumerate_actors_at_grid(enumerate_check_point, a->grid_index-GDIM); + enumerate_actors_at_grid(enumerate_check_point, a->grid_index-GDIM-1); + if (test_collided) { + // get rid of laser (we can do this in our 'update' fn) + a->shape = NULL; + // set exploding flag for enemy (we're not in its update) + test_collided->u.enemy.exploding = 1; + return; + } + // move laser + // check for wall collisions a->x += a->u.laser.dx; - if (a->x > 152) a->shape = NULL; + if (a->x > 255-8) a->shape = NULL; a->y += a->u.laser.dy; if (a->y > 255-8) a->shape = NULL; } void shoot_laser(sbyte dx, sbyte dy, const byte* shape) { - actors[2].shape = (void*) shape; - actors[2].x = actors[1].x; - actors[2].y = actors[1].y; - actors[2].u.laser.dx = dx; - actors[2].u.laser.dy = dy; - add_actor(2); + actors[LASER].shape = (void*) shape; + actors[LASER].x = actors[1].x; + actors[LASER].y = actors[1].y; + actors[LASER].u.laser.dx = dx; + actors[LASER].u.laser.dy = dy; + add_actor(LASER); } -void player_move(Actor* a) { - byte x = a->x; - byte y = a->y; - if (UP1) y-=2; - if (DOWN1) y+=2; - if (LEFT1) x-=1; - if (RIGHT1) x+=1; - a->x = x; - a->y = y; +void player_laser() { // shoot laser - if (actors[2].shape == NULL) { - if (UP2) shoot_laser(0,-8,laser_vert); - else if (DOWN2) shoot_laser(0,8,laser_vert); + if (actors[LASER].shape == NULL) { + if (UP2) shoot_laser(0,-4,laser_vert); + else if (DOWN2) shoot_laser(0,4,laser_vert); else if (LEFT2) shoot_laser(-4,0,laser_horiz); else if (RIGHT2) shoot_laser(4,0,laser_horiz); } } +void player_move(Actor* a) { + byte x = a->x; + byte y = a->y; + if (UP1) y-=1; + if (DOWN1) y+=1; + if (LEFT1) x-=1; + if (RIGHT1) x+=1; + a->x = x; + a->y = y; + player_laser(); +} + void main() { byte i; - byte num_actors = 32; + byte num_actors = 16; blit_solid(0, 0, 255, 255, 0); memset(grid, 0, sizeof(grid)); memset(actors, 0, sizeof(actors)); memcpy(palette, palette_data, 16); + draw_box(0,0,303,255,0x11); fill_char_table(); WATCHDOG; for (i=1; ix = (i & 7) * 16 + 16; - a->y = (i / 4) * 16 + 32; + a->x = (i & 7) * 24 + 16; + a->y = (i / 4) * 24 + 16; a->shape = (void*) all_sprites[i%9]; - //a->update = random_walk; + a->update = random_walk; a->draw = draw_actor_normal; - add_actor(i); + a->updatefreq = i*8; + a->updatetimer = i*8; + if (i > LASER) a->flags = F_KILLABLE; + if (i != LASER) add_actor(i); } - actors[1].shape = (void*) playersprite1; - actors[1].update = player_move; - actors[2].update = laser_move; - actors[2].shape = NULL; + actors[PLAYER].shape = (void*) playersprite1; + actors[PLAYER].update = player_move; + actors[PLAYER].updatefreq = 0xff; + actors[LASER].update = laser_move; + actors[LASER].shape = NULL; + actors[LASER].updatefreq = 0xff; WATCHDOG; while (1) { - while (video_counter == 0) ; - update_grid_rows(GDIM*3/4, GDIM*4/4); - while (video_counter == 0x3c) ; - update_grid_rows(GDIM*0/4, GDIM*1/4); - while (video_counter == 0xbc) ; - update_grid_rows(GDIM*1/4, GDIM*2/4); - while (video_counter == 0xfc) ; - update_grid_rows(GDIM*2/4, GDIM*3/4); + update_screen_section(GDIM2*3/4, GDIM2*4/4, 0x00, 0x2f); + update_screen_section(GDIM2*0/4, GDIM2*1/4, 0x30, 0x7f); + update_screen_section(GDIM2*1/4, GDIM2*2/4, 0x80, 0xbf); + update_screen_section(GDIM2*2/4, GDIM2*3/4, 0xc0, 0xff); + ensure_update(PLAYER); + ensure_update(LASER); + frame++; WATCHDOG; } } diff --git a/spinner.gif b/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..a3ade8a362dc25152928d25a81b9bb8e78c6ba9a GIT binary patch literal 24927 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%0AasD-FBS7n`xufh1P?gf?g8g27*kL@_ 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 literal 0 HcmV?d00001 diff --git a/src/emu.js b/src/emu.js index 909828d0..9d95f134 100644 --- a/src/emu.js +++ b/src/emu.js @@ -929,3 +929,13 @@ function AddressDecoder(table, options) { } return makeFunction(0x0, 0xffff).bind(self); } + +var BusProbe = function(bus) { + this.read = function(a) { + var val = bus.read(a); + return val; + } + this.write = function(a,v) { + return bus.write(a,v); + } +} diff --git a/src/platform/sound_williams.js b/src/platform/sound_williams.js index 98e13a2b..e8fe2f70 100644 --- a/src/platform/sound_williams.js +++ b/src/platform/sound_williams.js @@ -1,7 +1,7 @@ "use strict"; var WILLIAMS_SOUND_PRESETS = [ - {id:'minimal.c', name:'Minimal Test'}, + {id:'wavetable.c', name:'Wavetable Synth'}, ]; /**************************************************************************** diff --git a/src/platform/williams.js b/src/platform/williams.js index 0a17117e..9a65c9e9 100644 --- a/src/platform/williams.js +++ b/src/platform/williams.js @@ -23,7 +23,7 @@ var WilliamsPlatform = function(mainElement, proto) { var video, timer, pixels, displayPCs; var screenNeedsRefresh = false; - var membus, iobus; + var membus; var video_counter; var audio, worker, workerchannel; @@ -169,7 +169,7 @@ var WilliamsPlatform = function(mainElement, proto) { function write_display_byte(a,v) { ram.mem[a] = v; drawDisplayByte(a, v); - displayPCs[a] = cpu.getPC(); // save program counter + if (displayPCs) displayPCs[a] = cpu.getPC(); // save program counter } function drawDisplayByte(a,v) { @@ -272,15 +272,18 @@ var WilliamsPlatform = function(mainElement, proto) { ram = new RAM(0xc000); nvram = new RAM(0x400); // TODO: save in browser storage? - // defender nvram.mem.set([240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,242,241,242,247,240,244,244,245,242,244,250,240,241,248,243,241,245,245,243,244,241,244,253,240,241,245,249,242,240,244,252,244,245,244,244,240,241,244,242,248,245,245,240,244,247,244,244,240,241,242,245,242,240,244,243,245,242,244,242,240,241,241,240,243,245,244,253,245,242,245,243,240,240,248,242,246,245,245,243,245,243,245,242,240,240,246,240,241,240,245,244,244,253,244,248,240,240,245,250,240,241,240,240,240,243,240,243,240,241,240,244,240,241,240,241,240,240,240,240,240,240,240,245,241,245,240,241,240,245,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240]); - - displayPCs = new Uint16Array(new ArrayBuffer(0x9800*2)); + //displayPCs = new Uint16Array(new ArrayBuffer(0x9800*2)); rom = padBytes(new lzgmini().decode(ROBOTRON_ROM).slice(0), 0xc001); membus = { read: memread_williams, write: memwrite_williams, }; - cpu = self.newCPU(membus); + //var probebus = new BusProbe(membus); + var iobus = { + read: function(a) {return 0;}, + write: function(a,v) {console.log(hex(a),hex(v));} + } + cpu = self.newCPU(membus, iobus); audio = new MasterAudio(); worker = new Worker("./src/audio/z80worker.js"); @@ -293,7 +296,7 @@ var WilliamsPlatform = function(mainElement, proto) { var x = Math.floor(e.offsetX * video.canvas.width / $(video.canvas).width()); var y = Math.floor(e.offsetY * video.canvas.height / $(video.canvas).height()); var addr = (x>>3) + (y*32) + 0x400; - console.log(x, y, hex(addr,4), "PC", hex(displayPCs[addr],4)); + if (displayPCs) console.log(x, y, hex(addr,4), "PC", hex(displayPCs[addr],4)); }); var idata = video.getFrameData(); setKeyboardFromMap(video, pia6821, ROBOTRON_KEYCODE_MAP); @@ -419,3 +422,5 @@ var WilliamsZ80Platform = function(mainElement) { PLATFORMS['williams'] = WilliamsPlatform; PLATFORMS['williams-z80'] = WilliamsZ80Platform; + +// http://seanriddle.com/willhard.html diff --git a/src/ui.js b/src/ui.js index c66c38c3..aa5e5621 100644 --- a/src/ui.js +++ b/src/ui.js @@ -36,6 +36,8 @@ var PRESETS; // presets array var platform_id; var platform; // platform object +var toolbar = $("#controls_top"); + var FileStore = function(storage, prefix) { var self = this; this.saveFile = function(name, text) { @@ -344,6 +346,8 @@ function setCode(text) { return; worker.postMessage({code:text, platform:platform_id, tool:platform.getToolForFilename(current_preset_id)}); + toolbar.addClass("is-busy"); + $('#compile_spinner').css('visibility', 'visible'); } function arrayCompare(a,b) { @@ -363,7 +367,6 @@ function setCompileOutput(data) { assemblyfile = new SourceFile(data.asmlines, data.intermediate.listing); } // errors? - var toolbar = $("#controls_top"); function addErrorMarker(line, msg) { var div = document.createElement("div"); div.setAttribute("class", "tooltipbox tooltiperror"); @@ -442,6 +445,8 @@ function setCompileOutput(data) { } worker.onmessage = function(e) { + toolbar.removeClass("is-busy"); + $('#compile_spinner').css('visibility', 'hidden'); // TODO: this doesn't completely work yet if (pendingWorkerMessages > 1) { pendingWorkerMessages = 0; diff --git a/tools/sintbl.py b/tools/sintbl.py new file mode 100644 index 00000000..d21e75b4 --- /dev/null +++ b/tools/sintbl.py @@ -0,0 +1,13 @@ + +import math + +#n = 8 +#m = 127 + +n = 64 +m = 127 + +for i in range(0,n*4): + print '%d,' % int(round(math.sin(i*math.pi/2/n)*m)), + if i % 16 == 15: + print