diff --git a/.gitignore b/.gitignore index 6157417..56f681c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,6 @@ Player/player Player/player.wasm Player/Cosmo Master/master +Tools/assets Player/Out/Cosmo/player* diff --git a/Common/Snd/bitcrsh.h b/Common/Snd/bitcrsh.h new file mode 100644 index 0000000..32f1b8f --- /dev/null +++ b/Common/Snd/bitcrsh.h @@ -0,0 +1,29 @@ +/* + Simple fixed point bitcrash. + https://github.com/schollz/fpfx +*/ + +#ifndef COM_SND_BITCRSH_H +#define COM_SND_BITCRSH_H + +#include "../fix.h" +#include + +struct com_snd_bitcrsh { + uint8_t bits; /* How much to trim when triggered. */ + uint8_t reduce; /* Every Nth sample frame. */ +}; + +static void com_snd_bitcrsh_process(Bitcrush *bitcrush, int32_t *buf, + unsigned int nr_samples) { + for (int i = 0; i < nr_samples; i++) { + if (i % bitcrush->reduce == 0) { + // bitcrush fixedpoint + buf[i] = buf[i] >> (16 - bitcrush->bits) << (16 - bitcrush->bits); + } else { + buf[i] = buf[i - 1]; + } + } +} + +#endif diff --git a/Common/Snd/oscs.h b/Common/Snd/oscs.h new file mode 100644 index 0000000..f8578b4 --- /dev/null +++ b/Common/Snd/oscs.h @@ -0,0 +1,91 @@ +/* + Dinky fixed point oscillators. + https://git.poto.cafe/veclavtalica/stfu/src/branch/master/Source/oscillators.c +*/ + +#ifndef COM_SND_OSCS_H +#define COM_SND_OSCS_H + +#include "../cfg.h" +#include "../fix.h" +#include "../std.h" +#include + +/* Intended to be executed offline with values then embedded in the binary. */ +struct com_snd_swave { + uint16_t f, s, c; +}; + +static struct com_snd_swave +com_snd_swave_init(com_fix_t frequency, uint16_t phase, uint16_t amplitude) { + com_std_assert(frequency > 0); + struct com_snd_swave r; + r.f = 2.f * sinf((float)M_PI * frequency / STFU_AUDIO_FRAME_RATE); + r.s = amplitude * sinf(phase); + r.c = amplitude * cosf(phase); + return r; +} + +static inline int16_t com_snd_swave_pump(struct com_snd_swave *wave) { + int16_t const f = com_fix_sin(com_fix_mul( + wave->phase, com_fix_div(com_fix_mul(wave->frequency, COM_FIX_PI2), + COM_CFG_SNDFRMRATE))); + wave->phase += 1; + return f; +} + +/* Implemented over sinewave */ +// struct com_snd_oscs_sqrtwave { +// struct com_snd_swave w; +// union { +// float f; +// uint32_t u; +// } v; +// } com_snd_oscs_init_sqrtwave(float frequency, float phase, float amplitude) { +// struct com_snd_oscs_sqrtwave r; +// union { +// float f; +// uint32_t u; +// } v, a; +// r.w = com_snd_oscs_init_sinewave(frequency, phase, 1.f); +// v.f = r.w.s; +// a.f = amplitude; +// r.v.u = (a.u & 0x7fffffff) | (v.u & 0x80000000); +// return r; +// } + +// /* Use floating point bit representation to infer sign, all other bits are +// set +// * to amplitude */ +// struct com_snd_oscs_sqrtwave +// com_snd_oscs_pump_sqrtwave(struct com_snd_oscs_sqrtwave wave) { +// union { +// float f; +// uint32_t u; +// } v; +// com_snd_oscs_pump_sinewave(wave.w); +// v.f = wave.w.s; +// wave.v.u = (wave.v.u & 0x7fffffff) | (v.u & 0x80000000); +// return wave; +// } + +// struct com_snd_oscs_sawtooth { +// float v, a, i; +// } com_snd_oscs_init_sawtooth(float frequency, float phase, float amplitude) { +// struct com_snd_oscs_sawtooth r; +// r.v = sinf(phase) * amplitude; +// r.a = amplitude; +// r.i = 2.f * frequency / STFU_AUDIO_FRAME_RATE * amplitude; +// return r; +// } + +// /* Simple amplitude overflow check with truncation */ +// struct com_snd_oscs_sawtooth +// com_snd_oscs_pump_sawtooth(struct com_snd_oscs_sawtooth wave) { +// wave.v += wave.i; +// if (wave.v > wave.a) +// wave.v -= wave.a * 2.f; +// return wave; +// } + +#endif diff --git a/Common/Std/libc.c b/Common/Std/libc.c index abfc6b0..565ebcc 100644 --- a/Common/Std/libc.c +++ b/Common/Std/libc.c @@ -1,5 +1,4 @@ #include "../std.h" -#include "any.c" #include #include #include @@ -10,6 +9,8 @@ #include #include +#include "any.c" + void *com_std_alloc(void *ptr, size_t size) { return size != 0 ? realloc(ptr, size) : (free(ptr), NULL); } diff --git a/Common/Std/nolibc.c b/Common/Std/nolibc.c index 0c87382..d1b93a1 100644 --- a/Common/Std/nolibc.c +++ b/Common/Std/nolibc.c @@ -24,6 +24,8 @@ size_t strlen(char const *s) { return p - s; } +#if COM_DEF_CPU == COM_DEF_CPU_AMD64 || COM_DEF_CPU == COM_DEF_CPU_X86 + double sin(double v) { double r; __asm__ __volatile__("fsin" : "=t"(r) : "0"(v)); @@ -49,3 +51,17 @@ double sqrt(double v) { __asm__ volatile("sqrtss %1, %0" : "=x"(r) : "x"(v)); return r; } + +#else + +double sin(double v) { return __builtin_sin(v); } + +double cos(double v) { return __builtin_cos(v); } + +void sincos(double v, double *s, double *c) { + return __builtin_sincos(v, s, c); +} + +double sqrt(double v) { return __builtin_sqrt(v); } + +#endif diff --git a/Common/Std/stub.c b/Common/Std/stub.c index 6f5443f..d138370 100644 --- a/Common/Std/stub.c +++ b/Common/Std/stub.c @@ -1,23 +1,13 @@ #include "../std.h" #include "any.c" +#include "nolibc.c" #include #include void *com_std_alloc(void *ptr, size_t size) { return NULL; } -double com_std_sqrt(double v) { return __builtin_sqrt(v); } -double com_std_fabs(double v) { return __builtin_fabs(v); } -#if COM_DEF_COMPILER == COM_DEF_COMPILER_CLANG -double com_std_sin(double v) { return __builtin_sin(v); } -double com_std_cos(double v) { return __builtin_cos(v); } -#else -/* TODO: Use fsin/fcos, no matter how slow. */ -double com_std_sin(double v) { return 0; } -double com_std_cos(double v) { return 0; } -#endif - com_std_fd com_std_open(char const *path, uint32_t flags) { return NULL; } bool com_std_write(com_std_fd fd, void const *data, uint32_t bytes) { return false; @@ -28,7 +18,7 @@ com_std_fd com_std_socket(bool isnetwork) { return NULL; } bool com_std_connect(com_std_fd fd, bool isnetwork, char const *address) { return false; } -void com_std_exit(uint32_t status) { +void com_std_exit(int32_t status) { __builtin_trap(); while (1) { } diff --git a/Common/bnch.h b/Common/bnch.h new file mode 100644 index 0000000..dfcfa22 --- /dev/null +++ b/Common/bnch.h @@ -0,0 +1,54 @@ +/* + Centralized benchmark suite. +*/ + +#include "fix.h" +#include "std.h" +#include "time.h" +#include + +#define TEST(p_e) \ + do { \ + if (!(p_e)) { \ + com_std_print('c', COM_DEF_STRLIT(p_e) " failed!"); \ + anyfail = true; \ + } \ + } while (0) + +static void com_bnch_fix(void) { + uint64_t start = com_timer_count_ns(); + for (int i = 5000000; i--;) { + com_fix_t sqrt = com_fix_sqrt(i); + com_def_profile_sink(sqrt); + } + com_timer_profile(start, "fixed: sqrt"); + + start = com_timer_count_ns(); + for (int i = 50000000; i--;) { + com_fix_t sin = com_fix_sin(i); + com_def_profile_sink(sin); + } + com_timer_profile(start, "fixed: sin"); + + start = com_timer_count_ns(); + for (int i = 50000000; i--;) { + com_fix_t cos = com_fix_cos(i); + com_def_profile_sink(cos); + } + com_timer_profile(start, "fixed: cos"); + + start = com_timer_count_ns(); + for (int i = 50000000; i--;) { + com_fix_t sin, cos; + com_fix_sincos(i, &sin, &cos); + com_fix_t sinv = sin; + com_fix_t cosv = cos; + com_def_profile_sink(sinv); + com_def_profile_sink(cosv); + } + com_timer_profile(start, "fixed: sincos"); +} + +static void com_bnch_run(void) { com_bnch_fix(); } + +#undef TEST diff --git a/Common/cfg.h b/Common/cfg.h index 6262a24..9284c6b 100644 --- a/Common/cfg.h +++ b/Common/cfg.h @@ -19,4 +19,9 @@ #define COM_CFG_FBSIZE (COM_CFG_WIDTH * COM_CFG_HEIGHT * COM_CFG_COLORS) +#define COM_CFG_SNDFRMRATE 44100 + +/* TODO: Generate file with precompiled note frequencies. */ +#define COM_CFG_A4FIX (440 << COM_FIX_FRACBITS) + #endif diff --git a/Common/fix.c b/Common/fix.c index 200d706..7df26ce 100644 --- a/Common/fix.c +++ b/Common/fix.c @@ -70,37 +70,3 @@ com_fix_t com_fix_sqrt(com_fix_t a) { return final_shift > 0 ? (int32_t)(x << final_shift) : (int32_t)(x >> (-final_shift)); } - -void com_fix_run_bench(void) { - uint64_t start = com_timer_count_ns(); - for (int i = 5000000; i--;) { - com_fix_t sqrt = com_fix_sqrt(i); - com_def_profile_sink(sqrt); - } - com_timer_profile(start, "fixed: sqrt"); - - start = com_timer_count_ns(); - for (int i = 50000000; i--;) { - com_fix_t sin = com_fix_sin(i); - com_def_profile_sink(sin); - } - com_timer_profile(start, "fixed: sin"); - - start = com_timer_count_ns(); - for (int i = 50000000; i--;) { - com_fix_t cos = com_fix_cos(i); - com_def_profile_sink(cos); - } - com_timer_profile(start, "fixed: cos"); - - start = com_timer_count_ns(); - for (int i = 50000000; i--;) { - com_fix_t sin, cos; - com_fix_sincos(i, &sin, &cos); - com_fix_t sinv = sin; - com_fix_t cosv = cos; - com_def_profile_sink(sinv); - com_def_profile_sink(cosv); - } - com_timer_profile(start, "fixed: sincos"); -} diff --git a/Common/fix.h b/Common/fix.h index f380fe2..af7bc82 100644 --- a/Common/fix.h +++ b/Common/fix.h @@ -23,9 +23,6 @@ typedef int32_t com_fix_t; extern const uint16_t com_fix_sin_lut[128]; -void com_fix_run_tests(void); -void com_fix_run_bench(void); - static inline com_fix_t com_fix_add(com_fix_t a, com_fix_t b) { return a + b; } static inline com_fix_t com_fix_sub(com_fix_t a, com_fix_t b) { return a - b; } @@ -64,6 +61,7 @@ static inline com_fix_t com_fix_floor(com_fix_t const a) { com_fix_t com_fix_sqrt(com_fix_t a); /* Approximated using LUT. */ +/* Integer part only houses sign. */ /* TODO: test whether linearly interpolated version could be default. */ /* things like camera movement can be jarring if done without.*/ /* https://namoseley.wordpress.com/2015/07/26/sincos-generation-using-table-lookup-and-iterpolation/ @@ -90,9 +88,11 @@ static inline com_fix_t com_fix_sin(com_fix_t q) { } } +static inline com_fix_t com_fix_sin_from_lut(uint8_t l) { + return com_fix_sin_lut[l]; +} + /* Implemented over sin, as to share one single LUT. */ -/* Additionally, optimizer probably can collapse some math for combined sincos - * case. */ static inline com_fix_t com_fix_cos(com_fix_t a) { return com_fix_sin(a + COM_FIX_PIHALF); } diff --git a/Common/std.h b/Common/std.h index 2141af0..d4c79c3 100644 --- a/Common/std.h +++ b/Common/std.h @@ -6,6 +6,8 @@ #ifndef COM_STD_H #define COM_STD_H +#include "def.h" + #include #include #include @@ -40,20 +42,6 @@ void com_std_close(com_std_fd fd); /* Note: Freeing is done by passing 0 size to pointer. */ void *com_std_alloc(void *ptr, size_t size); -/* Note: Functions listed below are only available for dev builds. */ -/* Trigonometry functions are for reference testing of our own fixed point - * implementations. Do not use otherwise. */ -#define com_std_assert(p_h) \ - do { \ - if (!(p_h)) { \ - const char msg[] = \ - "Assertion failed at "__FILE__ \ - ":" COM_DEF_STRLIT(__LINE__) ": " COM_DEF_STRLIT(p_h); \ - com_std_print_str(msg, sizeof msg, true); \ - com_std_exit(-1); \ - } \ - } while (0) - /* Basic printing utility that doesn't utilize format string. Instead a vector of arguments is passed and concatenated, based on type. @@ -81,9 +69,25 @@ void com_std_print_(int32_t start, ...); #define com_std_print(...) com_std_print_(__VA_ARGS__, '.') void com_std_print_str(char const *str, uint32_t len, bool newline); +#define COM_STD_PIF ((float)3.14159265358979323846) + double com_std_sqrt(double v); double com_std_fabs(double v); double com_std_sin(double v); double com_std_cos(double v); +/* Note: Functions listed below are only available for dev builds. */ +/* Trigonometry functions are for reference testing of our own fixed point + * implementations. Do not use otherwise. */ +#define com_std_assert(p_h) \ + do { \ + if (!(p_h)) { \ + const char msg[] = \ + "Assertion failed at "__FILE__ \ + ":" COM_DEF_STRLIT(__LINE__) ": " COM_DEF_STRLIT(p_h); \ + com_std_print_str(msg, sizeof msg, true); \ + com_std_exit(-1); \ + } \ + } while (0) + #endif diff --git a/Common/str.h b/Common/str.h index aa51dd0..fbb2adc 100644 --- a/Common/str.h +++ b/Common/str.h @@ -49,7 +49,7 @@ static inline int32_t com_str_ext_find_char(char const *s, char ch) { static inline bool com_str_ext_prefixed(char const *__restrict a, struct com_str const *prefix) { uint32_t n = prefix->len; - char *b = prefix->data; + char const *b = prefix->data; while (n && *a && (*a == *b)) { ++a; ++b; diff --git a/Player/Audio/alsa.c b/Player/Audio/alsa.c new file mode 100644 index 0000000..0a24cda --- /dev/null +++ b/Player/Audio/alsa.c @@ -0,0 +1,3 @@ +/* + https://gist.github.com/takaswie/d8a9553c230971d974a3dfc759b2ff67 +*/ diff --git a/Player/Display/dos.c b/Player/Display/dos.c index ca1a112..846084a 100644 --- a/Player/Display/dos.c +++ b/Player/Display/dos.c @@ -1,3 +1,4 @@ +#include "../../Common/def.h" #include "display.h" #include #include @@ -259,6 +260,17 @@ static inline bool set_segment_rights(uint16_t selector, bool read, return !carry_clear; } +static inline intptr_t get_segment_base_address(uint16_t selector) { + bool carry_clear; + uint16_t high, low; + __asm__ volatile("int $0x31" + : "=@ccnc"(carry_clear), "=c"(high), "=d"(low) + : "a"(0x0006), "b"(selector) + : "memory", "cc"); + // return carry_clear ? 0 : (((intptr_t)high << 16) | (intptr_t)low); + return ((intptr_t)high << 16) | (intptr_t)low; +} + static inline void use_segment(uint16_t selector) { __asm__ volatile("" : "=S"(selector)::); } @@ -271,40 +283,19 @@ static inline void print_to_console(char *const str) { static uint32_t test = 0x1420124; -// extern int plr_display_dos_main(int argc, char *argv[]) { extern int plr_display_dos_main(void) { - print_to_console("Fuck you stupid shit"); - return -1; - - uint16_t base = alloc_ldt_descriptor(); - if (!base) { - print_to_console("Cannot allocate LDT descriptor"); + /* Fat DS trick, see __djgpp_nearptr_enable() */ + unsigned short ds_selector; + asm("movw %%ds, %0" : "=r"(ds_selector)); + set_segment_limit(ds_selector, 0xFFFFFFFF); + intptr_t baseaddr = get_segment_base_address(ds_selector); + if (baseaddr == 0) return -1; - } - if (!set_segment_base_address(base, 0xA0000)) { - print_to_console("Cannot set LDT base address"); - return -1; - } - - if (!set_segment_limit(base, 0x10000)) { - print_to_console("Cannot set LDT limit"); - return -1; - } - - use_segment(base); - - // char *volatile buf = (void *)(intptr_t)0xA0000; // VGA memory address - // char *buf = map_physical_address(0xA0000, 0x10000); // VGA memory - char *buf = map_physical_address(0xA0000, 0x10000); // VGA memory - // address - if (!buf) - return -1; - int x, y; - - // if (!buf) - // return -1; + unsigned char *volatile buf = + (unsigned char *)((intptr_t)0xA0000 - baseaddr); // VGA memory address + /* TODO: Make VESA work for sweet sweet 640x480. */ // unsigned char saved_mode = get_video_mode(); // Save original mode // set_vesa_video_mode(MODE_VESA_640_480_8); // Set our VGA mode set_video_mode(MODE_VGA_320_200_8); // Set our VGA mode @@ -327,6 +318,7 @@ extern int plr_display_dos_main(void) { // wait_for_key(); // set_video_mode(saved_mode); // Restore original mode + int x, y; while (1) { for (y = 0; y < HEIGHT; y++) { for (x = 0; x < WIDTH; x++) { @@ -339,7 +331,6 @@ extern int plr_display_dos_main(void) { } } // writetest(); - __asm__ volatile("int $0x16" : : "a"(0x00) : "memory", "cc"); } // dpmi_regs = (struct dpmi_regs){.eax = 0x00004C00}; diff --git a/Player/Display/x11.c b/Player/Display/x11.c index 044490e..8484ff0 100644 --- a/Player/Display/x11.c +++ b/Player/Display/x11.c @@ -126,9 +126,11 @@ static bool plr_x11_handshake(com_std_fd xfd, if (!com_std_write(xfd, AUTH_ENTRY_MAGIC_COOKIE, sizeof AUTH_ENTRY_MAGIC_COOKIE - 1)) return false; - /* Use 2 bytes back for padding. */ - if (!com_std_write(xfd, &auth_entry.auth_data->data[-2], - auth_entry.auth_data->len + 2)) + /* 2 bytes padding after magic cookie. */ + if (!com_std_write(xfd, &request, 2)) + return false; + if (!com_std_write(xfd, auth_entry.auth_data->data, + auth_entry.auth_data->len)) return false; struct { @@ -293,8 +295,8 @@ static uint32_t plr_x11_create_graphical_context(com_std_fd xfd) { static uint32_t plr_x11_create_window(com_std_fd xfd) { /* TODO: Use the ones provided in headers ? */ -#define FLAG_WIN_BG_PIXEL 2 -#define FLAG_WIN_EVENT 0x800 +#define FLAG_WIN_BG_PIXEL 0x0002 +#define FLAG_WIN_EVENT 0x0800 #define EVENT_FLAG_EXPOSURE 0x8000 #define EVENT_FLAG_KEY_PRESS 0x1 #define EVENT_FLAG_KEY_RELEASE 0x2 @@ -331,7 +333,7 @@ static uint32_t plr_x11_create_window(com_std_fd xfd) { .class = 1, .root_visual_id = plr_x11_root_screen.root_visual_id, .bitmask = FLAG_WIN_BG_PIXEL | FLAG_WIN_EVENT, - .value1 = 0x00000000, + .value1 = 0xff000000, .value2 = EVENT_FLAG_EXPOSURE | EVENT_FLAG_BUTTON_RELEASE | EVENT_FLAG_BUTTON_PRESS | EVENT_FLAG_KEY_PRESS | EVENT_FLAG_KEY_RELEASE, @@ -419,6 +421,10 @@ static uint8_t plr_x11_query_extension(com_std_fd xfd, com_str const *ext) { static bool plr_x11_enable_big_requests(com_std_fd xfd) { static com_str bigreqext = com_str_lit("BIG-REQUESTS"); uint8_t const opcode = plr_x11_query_extension(xfd, &bigreqext); + if (!opcode) { + com_std_print('c', "Big request can't accommodate for framebuffer."); + return false; + } /* Request the thing. */ struct { @@ -439,8 +445,8 @@ static bool plr_x11_enable_big_requests(com_std_fd xfd) { plr_x11_poll_reply(xfd, seq, &response); uint32_t const limit = *(uint32_t *)response.reply.pad; - com_std_print('c', "Request limit: ", 'u', limit); - if (limit < COM_CFG_FBSIZE) { + com_std_print('c', "X11 request limit: ", 'u', limit); + if (limit < COM_CFG_FBSIZE / 4) { com_std_print('c', "Big request can't accommodate for framebuffer."); return false; } @@ -449,7 +455,7 @@ static bool plr_x11_enable_big_requests(com_std_fd xfd) { } static bool plr_x11_put_framebuffer(com_std_fd xfd, uint32_t gc_id, - uint32_t pixmap_id, + uint32_t drawable_id, uint8_t pixels[COM_CFG_FBSIZE]) { uint32_t const padded = com_bits_nalign(COM_CFG_FBSIZE, 4); uint32_t const seq = plr_x11_next_seqid(); @@ -472,7 +478,7 @@ static bool plr_x11_put_framebuffer(com_std_fd xfd, uint32_t gc_id, .opcode = 72, .format = 2, // ZPixmap .request_length = sizeof request / 4 + padded / 4, - .drawable_id = pixmap_id, + .drawable_id = drawable_id, .gc_id = gc_id, .width = COM_CFG_WIDTH, .height = COM_CFG_HEIGHT, @@ -600,11 +606,11 @@ extern int plr_display_x11_main(int argc, char const *const *argv) { return -1; } - uint32_t const fb = plr_x11_create_pixmap(xfd, win); - if (!fb) { - com_std_print('c', "Failure to initialize X11"); - return -1; - } + // uint32_t const fb = plr_x11_create_pixmap(xfd, win); + // if (!fb) { + // com_std_print('c', "Failure to initialize X11"); + // return -1; + // } // Display *display = XOpenDisplay(NULL); // if (NULL == display) { @@ -704,7 +710,7 @@ extern int plr_display_x11_main(int argc, char const *const *argv) { uint8_t *fbm = plr_draw_get_pixel_buffer(); plr_draw_screen(fbm); - if (!plr_x11_put_framebuffer(xfd, gc, fb, fbm)) { + if (!plr_x11_put_framebuffer(xfd, gc, win, fbm)) { com_std_print('c', "Failure to drive X11"); return -1; } diff --git a/Player/Main/dos.s b/Player/Main/dos.s index d6c7ed3..34d837a 100644 --- a/Player/Main/dos.s +++ b/Player/Main/dos.s @@ -1,8 +1,6 @@ .section .data .align 4 # TODO: Make sure it's placed under 1 MiB. -# The DPMI Register Structure (50 bytes total) -# This holds the register states injected into Real Mode. .section .text .global _start diff --git a/Player/Socket/socket.h b/Player/Socket/socket.h deleted file mode 100644 index e69de29..0000000 diff --git a/Player/Socket/unix.c b/Player/Socket/unix.c deleted file mode 100644 index 6019422..0000000 --- a/Player/Socket/unix.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - Inter-process communicating socket, useful for local testing and admin - playing. -*/ - -#include -#include -#include - -#define MAXBUFFSIZE 4194304 /* 4.0MiB */ - -int plr_socket_unix_try_connect(const char *filepath) { - int sock = 0; - - if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { - perror("Error creating client UNIX socket: "); - return -1; - } - - struct sockaddr_un remote = {0}; - remote.sun_family = AF_UNIX; - strcpy(remote.sun_path, filepath); - int const remote_len = SUN_LEN(&remote); - - return 0; -} diff --git a/Player/docs.txt b/Player/docs.txt index d11f84d..0cb8b99 100644 --- a/Player/docs.txt +++ b/Player/docs.txt @@ -6,29 +6,26 @@ We do not use abstractions, as the simplicity of the required set of features sh ---- Restrictions ---- * Fixed sized paletted frambuffer, defaulted to 640x480. * Deterministic logic via Q15.16 fixed point numbers. -* .gam image format, derived from .gif. RLE and Delta Frame Optimiztion is added, palette is predefined. +* .gam image format, derived from .gif. Palette is globally predefined. * Textures are 32x32 pixels. * 30 FPS display rate. * Fonts are in ASCII bitmaps. Tile slicing commands are issued to render them in. -* Audio samples are in our own .sam format (s8 frame delta + lzw). +* Audio samples are in our own .sam format (s8 + ulaw + lzw). * Music pieces are written in our own simple .tam tracker format, using the same .sam sample db. * Connections are over TCP and their supersets (like WebSocket). -* Textbox provides another client-controlled view, with history. * Content usage has to be predefined, to allow preloading and compilation. ---- Building ---- ==== DOS ==== -Open Watcom 2.0 32bit toolchain is used under Linux host. -Place a copy of DOS4GW.EXE alongside the executable. -Expect to need to configure $INCLUDE and $LIB variables. +Nothing is needed. Just compile. ---- .sam format ---- Thin sample compressing scheme tailored for use with .tam tracker. Leading and tailing silence is stored as number of frames. Sample data is 1 channel only, but panning can be expressed via graph. -Sample bit format is signed 8 bit delta, first frame is delta against 0. Clamping is allowed. +Sample bit format is signed 8 bit delta. Clamping is allowed. Generic LZW compression is applied as the last step. diff --git a/Tools/Tracker/main.c b/Tools/Tracker/main.c new file mode 100644 index 0000000..c1b2f09 --- /dev/null +++ b/Tools/Tracker/main.c @@ -0,0 +1,31 @@ +/* + Tracker program to create awesome music! + Is supposed to only work on Linux, as it's where the development happens. +*/ + +#include "../../Common/Snd/oscs.h" +#include "../../Common/Std/libc.c" +#include "../../Common/Timer/posix.c" +#include "../../Common/fix.c" + +#include +#include + +int main(int argc, char **argv) { + struct com_snd_swave swave = com_snd_swave_init(COM_CFG_A4FIX, 0, UINT16_MAX); + + FILE *f = fopen("test.raw", "w"); + if (!f) { + printf("Can't open file %s\n", "test.raw"); + return -1; + } + + /* Generate 5 seconds of sinwave. */ + int16_t frames[COM_CFG_SNDFRMRATE * 5]; + for (int i = 0; i < COM_CFG_SNDFRMRATE * 5; ++i) + frames[i] = com_snd_swave_pump(&swave); + + fwrite(frames, 2, COM_CFG_SNDFRMRATE * 5, f); + + return 0; +} diff --git a/Tools/Tracker/test.raw b/Tools/Tracker/test.raw new file mode 100644 index 0000000..87a26f2 Binary files /dev/null and b/Tools/Tracker/test.raw differ diff --git a/Tools/compress_img_test.py b/Tools/compress_img_test.py new file mode 100755 index 0000000..82fa01e --- /dev/null +++ b/Tools/compress_img_test.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# Compression testing for image data for expected cases of ours. + +# TODO: Implement our own CLI tool that uses our LZW implementation, as it differs. + +import imageio.v3 as iio +import ncompress +import numpy as np +import sys + +uncompressed = iio.imread(sys.argv[1]) +compressed = ncompress.compress(uncompressed.tobytes()) +print("Uncompressed:", uncompressed.nbytes) +print("Compressed:", len(compressed), len(compressed) / uncompressed.nbytes) + +with open(sys.argv[1] + "-indexed.raw", "rb") as file: + raw_bytes = file.read() +indexmapping = {} +indexset = set(raw_bytes) +for idx, value in enumerate(indexset): + indexmapping[value] = idx +print(indexmapping) +indexmap = np.zeros((uncompressed.shape[0] * uncompressed.shape[1], 1), dtype=np.uint8) +for y in range(uncompressed.shape[0]): + for x in range(uncompressed.shape[1]): + indexmap[y * uncompressed.shape[0] + x] = indexmapping[raw_bytes[y * uncompressed.shape[0] + x]] +indexedcompressed = ncompress.compress(indexmap.tobytes()) +print("Indexed:", len(indexedcompressed), len(indexedcompressed) / len(raw_bytes)) + +# https://arxiv.org/html/2006.02639v2 +def pixel_art_predictive_filter(image_channel): + """ + Applies a spatial prediction filter to a 2D image matrix + to prepare it for lossless compression. + """ + rows, cols = image_channel.shape + residual_map = np.zeros((rows, cols), dtype=np.int16) + + for r in range(rows): + for c in range(cols): + # Handle edge cases (boundaries where neighbors don't exist) + if r == 0 and c == 0: + pred = 0 + elif r == 0: + pred = image_channel[r, c-1] # Only Left neighbor exists + elif c == 0: + pred = image_channel[r-1, c] # Only Top neighbor exists + else: + # Core DSP Neighborhood definition + A = image_channel[r, c-1] # Left + B = image_channel[r-1, c] # Top + C = image_channel[r-1, c-1] # Top-Left + + # Median Edge Detector logic + if C >= max(A, B): + pred = min(A, B) + elif C <= min(A, B): + pred = max(A, B) + else: + pred = int(A) + int(B) - int(C) + + # Map the actual value to the residual difference + residual_map[r, c] = int(image_channel[r, c]) - int(pred) + + return residual_map + +predictivefilter = pixel_art_predictive_filter(indexmap) +predictivefiltercompressed = ncompress.compress(predictivefilter.tobytes()) +print("Predictive filter:", len(predictivefiltercompressed), len(predictivefiltercompressed) / len(raw_bytes)) + +def encode_zorder_2d(x: int, y: int) -> int: + z = 0 + for i in range(16): + z |= ((x & (1 << i)) << i) | ((y & (1 << i)) << (i + 1)) + return z + +zordered = np.zeros((uncompressed.shape[0] * uncompressed.shape[1], 1), dtype=np.uint8) +for y in range(uncompressed.shape[0]): + for x in range(uncompressed.shape[1]): + idx = encode_zorder_2d(x, y) + zordered[idx] = uncompressed[y, x] + +zcompressed = ncompress.compress(zordered.tobytes()) +print("Zordered:", len(zcompressed), len(zcompressed) / uncompressed.nbytes) + +delta = np.zeros((uncompressed.shape[0] * uncompressed.shape[1], 1), dtype=np.int8) +for y in range(uncompressed.shape[0]): + for x in range(uncompressed.shape[1]): + delta[y * uncompressed.shape[0] + x] = int(uncompressed[y, x]) - int(uncompressed[y, (x + 1) % uncompressed.shape[0]]) +deltacompressed = ncompress.compress(delta.tobytes()) +print("Delta:", len(deltacompressed), len(deltacompressed) / uncompressed.nbytes) + +deltaz = np.zeros((uncompressed.shape[0] * uncompressed.shape[1], 1), dtype=np.int8) +for y in range(uncompressed.shape[0]): + for x in range(uncompressed.shape[1]): + idx = encode_zorder_2d(x, y) + deltaz[idx] = int(uncompressed[y, x]) - int(uncompressed[y, (x + 1) % uncompressed.shape[0]]) +deltazcompressed = ncompress.compress(deltaz.tobytes()) +print("Deltaz:", len(deltazcompressed), len(deltazcompressed) / uncompressed.nbytes) + diff --git a/Tools/compress_snd_test.py b/Tools/compress_snd_test.py new file mode 100755 index 0000000..f3a1e2f --- /dev/null +++ b/Tools/compress_snd_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Compression testing for sound sample data for expected cases of ours. + +# TODO: Implement our own CLI tool that uses our LZW implementation, as it differs. + +import ncompress +import sys +import wave +import audioop +import numpy as np +import array + +with wave.open(sys.argv[1], 'rb') as wf: + channels = wf.getnchannels() + samplerate = wf.getframerate() + frames = wf.getnframes() + uncompressed = wf.readframes(frames) + depth = wf.getsampwidth() + +compressed = ncompress.compress(uncompressed) +print("Uncompressed:", len(uncompressed)) +print("Compressed:", len(compressed), len(compressed) / len(uncompressed)) + +delta = bytearray(uncompressed[0].to_bytes(2, signed=False)) +for i in range(len(uncompressed)-1): + delta += (uncompressed[i+1] - uncompressed[i]).to_bytes(2, signed=True) + +dtcompressed = ncompress.compress(bytes(delta)) +print("Delta:", len(dtcompressed), len(dtcompressed) / len(uncompressed)) +assert(len(delta) == len(uncompressed)*2) + +ulaw = audioop.lin2ulaw(uncompressed, depth) +ulawcompressed = ncompress.compress(ulaw) +print("Ulaw:", len(ulawcompressed), len(ulawcompressed) / len(uncompressed)) + +absfold = bytearray() +for i in range(len(uncompressed)): + absfold.append(uncompressed[i] - 128 if uncompressed[i] >= 128 else uncompressed[i]) +absfoldulaw = audioop.lin2ulaw(absfold, depth) +absfoldulawcompressed = ncompress.compress(absfoldulaw) +print("Ulawabs:", len(absfoldulawcompressed), len(absfoldulawcompressed) / len(absfold)) + +with open(sys.argv[1] + ".ulaw", "wb") as file: + file.write(audioop.ulaw2lin(ulaw, depth)) diff --git a/Helpers/fixedgen.py b/Tools/fixedgen.py similarity index 100% rename from Helpers/fixedgen.py rename to Tools/fixedgen.py diff --git a/Tools/img-index.sh b/Tools/img-index.sh new file mode 100755 index 0000000..bb25f65 --- /dev/null +++ b/Tools/img-index.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 + +magick $1 -resize 32x32 +dither -type Palette -depth 8 -colors 16 PNG8:$1-indexed +magick stream -map r -storage-type char $1-indexed $1-indexed.raw