diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..591916f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.24) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +if (NOT NEBULA_TARGET) + set(NEBULA_TARGET pc) +endif() + +# would be nice if this could be in rp2/CMakeLists.txt, but +# has to be before project() because it makes rpi asm +# compilation work, and project() has to be top level +if (NEBULA_TARGET STREQUAL "rpi") + include(src/rpi/pico_sdk_import.cmake) + include(src/rpi/pico_extras_import.cmake) +endif() + +project(nebula C CXX) +add_subdirectory("src/") \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..1312a13 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,39 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "pc", + "displayName": "PC Build", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/pc_build/", + "cacheVariables": { + "NEBULA_TARGET": "pc" + } + }, + { + "name": "rpi", + "displayName": "Pico Build", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/rpi_build/", + "cacheVariables": { + "NEBULA_TARGET": "rpi" + }, + "environment": { + "PICO_SDK_PATH": "~/raspberrypi/pico-sdk", + "PICO_EXTRAS_PATH": "~/raspberrypi/pico-extras" + } + } + ], + "buildPresets": [ + { + "name": "pc", + "displayName": "PC Build", + "configurePreset": "pc" + }, + { + "name": "rpi", + "displayName": "Pico Build", + "configurePreset": "rpi" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index a81f8b8..6dd9de1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ -# nebula +# Nebula +This is a C implementation of the nebula firmware you can find here: + +https://github.com/hackclub/sprig/pull/2065 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..966becc --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,21 @@ +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-void-pointer-to-int-cast -Wno-int-to-void-pointer-cast -Wno-pointer-sign -Werror=implicit-function-declaration") + +add_executable(nebula "${NEBULA_TARGET}/main.c") +include("${NEBULA_TARGET}/CMakeLists.txt") + +include_directories(./) + +pico_enable_stdio_usb(${PROJECT_NAME} 1) +pico_enable_stdio_uart(${PROJECT_NAME} 1) + +file(READ version.json VERSION_JSON) + +string(JSON VERSION GET ${VERSION_JSON} version) + +message("version: ${VERSION}") + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/shared/version.h.in" + "${CMAKE_CURRENT_SOURCE_DIR}/shared/version.h" +) \ No newline at end of file diff --git a/src/pc/CMakeLists.txt b/src/pc/CMakeLists.txt new file mode 100644 index 0000000..2b9fa2e --- /dev/null +++ b/src/pc/CMakeLists.txt @@ -0,0 +1,26 @@ +target_link_libraries(nebula PRIVATE -fno-omit-frame-pointer -fsanitize=undefined) + +if (APPLE) + add_definitions(-DNEBULA_AUDIO) + + find_library(COREAUDIO_LIBRARY NAMES CoreAudio) + find_library(AUDIOUNIT_LIBRARY NAMES AudioUnit) + target_link_libraries(nebula PRIVATE ${COREAUDIO_LIBRARY} ${AUDIOUNIT_LIBRARY}) +else() + message(WARNING "Nebula audio is only supported on macOS, audio will be muted") +endif() + +include(FetchContent) +FetchContent_Declare( + minifb + GIT_REPOSITORY https://github.com/emoon/minifb + GIT_TAG 19b1a867762f92ea9f28c0195ef51f60d329aaa7 +) +FetchContent_MakeAvailable(minifb) +target_link_libraries(nebula PRIVATE minifb) + +set(CMAKE_CXX_CLANG_TIDY + clang-tidy-11; + -format-style='file'; + -header-filter=${CMAKE_CURRENT_SOURCE_DIR}; +) \ No newline at end of file diff --git a/src/pc/audio.c b/src/pc/audio.c new file mode 100644 index 0000000..3e060cb --- /dev/null +++ b/src/pc/audio.c @@ -0,0 +1,210 @@ +/** + * Driver for the PC audio system. All actual sound generation code is in shared/audio/piano.c. + */ + +#include +#include +#include + +#ifdef SPADE_AUTOMATED + #define puts(...) ; + #define printf(...) ; +#endif + +#include "shared/audio/piano.c" +#include "shared/audio/audio.h" + +static int audio_hw_init (void); +static void audio_hw_cleanup(void); +static int audio_open (AURenderCallbackStruct *callback); +static void audio_close (void); + +static OSStatus audio_cb( + void *inRef, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + uint32_t inBusNumber, + uint32_t inNumberFrames, + AudioBufferList *ioData +); + +typedef enum { + SampleBufState_Empty, + SampleBufState_Full, +} SampleBufState; +typedef struct { + // stops try_push from infinilooping before audiocore launches + SampleBufState state; + + pthread_mutex_t mutex; + int16_t samples[SAMPLES_PER_BUFFER]; +} SampleBuf; + +static SampleBuf sample_bufs[3] = {0}; + +void audio_init(void) { + audio_hw_init(); + + // locked for audio_cb + pthread_mutex_lock(&sample_bufs[0].mutex); + + audio_open(&(AURenderCallbackStruct) { .inputProc = audio_cb }); +} + +void audio_try_push_samples(void) { + static int writer = 0; + while (1) { + SampleBuf *sb = sample_bufs + writer; + if (!pthread_mutex_trylock(&sb->mutex)) return; + + // stops us from infinilooping before audiocore launches + if (sb->state == SampleBufState_Full) { + pthread_mutex_unlock(&sb->mutex); + return; + } + + piano_fill_sample_buf(sb->samples, SAMPLES_PER_BUFFER); + sb->state = SampleBufState_Full; + + pthread_mutex_unlock(&sb->mutex); + writer = (writer + 1) % (sizeof(sample_bufs) / sizeof(sample_bufs[0])); + } +} + +static OSStatus audio_cb( + void *inRef, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + uint32_t inBusNumber, + uint32_t inNumberFrames, + AudioBufferList *ioData +) { + + // get a pointer to where our samples need to go + int channel = 0; + int16_t *buffer = (int16_t *)ioData->mBuffers[channel].mData; + + static int reader = 0; + static int reader_prog = 0; + + for (int i = 0; i < inNumberFrames; i++) { + SampleBuf *sb = sample_bufs + reader; + + buffer[i] = sb->samples[reader_prog++]; + + if (reader_prog == SAMPLES_PER_BUFFER) { + sb->state = SampleBufState_Empty; + pthread_mutex_unlock(&sb->mutex); + + reader_prog = 0; + reader = (reader + 1) % (sizeof(sample_bufs) / sizeof(sample_bufs[0])); + + sb = sample_bufs + reader; + pthread_mutex_lock(&sb->mutex); + } + } + + return noErr; +} + +#if 0 +int main(void) { + audio_hw_init(); + + audio_open(&(AURenderCallbackStruct) { .inputProc = audio_cb }); + + // sleep 1s, rendering via callback happens in another thread + usleep(1000000); + + audio_close(); + audio_hw_cleanup(); +} +#endif + +static AudioComponent output_comp; +static AudioComponentInstance output_instance; + +static int audio_hw_init(void) { + // open the default audio device + output_comp = AudioComponentFindNext(NULL, &(AudioComponentDescription) { + .componentType = kAudioUnitType_Output, + .componentSubType = kAudioUnitSubType_DefaultOutput, + .componentFlags = 0, + .componentFlagsMask = 0, + .componentManufacturer = kAudioUnitManufacturer_Apple, + }); + + if (!output_comp) { + fprintf(stderr, "Failed to open default audio device.\n"); + return 0; + } + + if (AudioComponentInstanceNew(output_comp, &output_instance)) { + fprintf(stderr, "Failed to open default audio device.\n"); + return 0; + } + + return 1; +} + +static void audio_hw_cleanup(void) { + AudioComponentInstanceDispose(output_instance); +} + +static int audio_open(AURenderCallbackStruct *callback) { + if (AudioUnitInitialize(output_instance)) { + fprintf (stderr, "Unable to initialize audio unit instance\n"); + return 0; + } + + const int CHAN = 1; + const int BYTES_PER_SAMPLE = 2; + AudioStreamBasicDescription streamFormat = { + .mSampleRate = SAMPLES_PER_SECOND, + .mFormatID = kAudioFormatLinearPCM, + .mFormatFlags = kAudioFormatFlagIsSignedInteger, // kAudioFormatFlagIsBigEndian ? + .mFramesPerPacket = 1, + .mChannelsPerFrame = CHAN, + .mBitsPerChannel = BYTES_PER_SAMPLE * 8, + .mBytesPerPacket = CHAN * BYTES_PER_SAMPLE, + .mBytesPerFrame = CHAN * BYTES_PER_SAMPLE, + }; + + // pass in input format + if (AudioUnitSetProperty( + output_instance, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(streamFormat) + )) { + fprintf(stderr, "Failed to set audio unit input property.\n"); + return 0; + } + + // pass in callback + if (AudioUnitSetProperty( + output_instance, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + callback, + sizeof(AURenderCallbackStruct) + )) { + fprintf(stderr, "Unable to attach an IOProc to the selected audio unit.\n"); + return 0; + } + + // start 'er up + if (AudioOutputUnitStart(output_instance)) { + printf("Unable to start audio unit.\n"); + return 0; + } + + return 1; +} + +static void audio_close(void) { + AudioOutputUnitStop(output_instance); +} diff --git a/src/pc/main.c b/src/pc/main.c new file mode 100644 index 0000000..7553b1a --- /dev/null +++ b/src/pc/main.c @@ -0,0 +1,365 @@ +#include +#include +#include +#include +#include +#include "shared/sprig_engine/script.h" + +#ifdef SPADE_AUDIO + #include "audio.c" +#endif + +/** + * SPADE_AUTOMATED isn't really used anymore, but we're keeping around the + * ifdefs just in case it comes in handy in the future. + * + * It disables keyboard input and adds some debugging prints for the sake + * of automated testing. + */ +// #define SPADE_AUTOMATED +#ifdef SPADE_AUTOMATED + #define puts(...) ; + #define printf(...) ; +#endif + +// Set to false to enable debug prints for development (this is janky) +#if true + #define dbg(...) ; + #define dbgf(...) ; +#else + #define dbgf printf + #define dbg puts +#endif + +// Debugging shortcut +#define yell puts + +// Other imports +#include "shared/sprig_engine/base_engine.c" +#include "shared/sprig_engine/module_native.c" + +// Externs for shared/ui/errorbuf.h +char errorbuf[512] = ""; +Color errorbuf_color; // Initialized in main() +static void fatal_error(void) { abort(); } +#include "shared/ui/errorbuf.h" + +#define SPADE_WIN_SIZE_X (SCREEN_SIZE_X) +#define SPADE_WIN_SIZE_Y (SCREEN_SIZE_Y + 3*8) +#define SPADE_WIN_SCALE (2) + +#ifdef SPADE_AUTOMATED +// Print the map as ascii for debugging +static void print_map(void) { + // find max on Z axis + int z_size = 0; + { + for (int x = 0; x < state->width; x++) + for (int y = 0; y < state->height; y++) { + Sprite *s = get_sprite(state->map[(y * state->width) + x]); + int sprite_stack_len = 0; + while (s) { + sprite_stack_len++; + s = get_sprite(s->next); + } + + if (sprite_stack_len > z_size) + z_size = sprite_stack_len; + } + } + + const int stride = z_size*(state->width+1); + + // +1 is for newlines + int mapstr_len = stride * state->height; + char *mapstr = malloc(1 + mapstr_len); + mapstr[mapstr_len] = 0; + memset(mapstr, '.', mapstr_len); + + // insert newlines + int w = state->width, h = state->height; + for (int y = 0; y < h; y++) { + mapstr[(y+1)*stride - 1] = '\n'; + for (int z = 1; z < z_size; z++) + mapstr[y*stride + z*(state->width+1) - 1] = '|'; + } + + for (int z = 0; z < z_size; z++) { + for (int x = 0; x < state->width; x++) + for (int y = 0; y < state->height; y++) { + int str_i = stride*y + (z*(state->width+1)) + x; + int map_i = (state->width+0)*y + x; + Sprite *s = get_sprite(state->map[map_i]); + + int sprite_z = 0; + while (s) sprite_z++, s = get_sprite(s->next); + + s = get_sprite(state->map[map_i]); + while(s) { + sprite_z--; + if (sprite_z == z) { + mapstr[str_i] = s->kind; + break; + } + s = get_sprite(s->next); + } + } + } + +#undef puts + puts(mapstr); +#define puts(...) ; + free(mapstr); + fflush(stdout); +} + +// Read keyboard inputs from stdin +static void simulated_keyboard(void) { + char key = getchar(); + if (key == 'w') spade_call_press( 5); // map_move(map_get_first('p'), 0, -1); + else if (key == 's') spade_call_press( 7); // map_move(map_get_first('p'), 0, 1); + else if (key == 'a') spade_call_press( 6); // map_move(map_get_first('p'), 1, 0); + else if (key == 'd') spade_call_press( 8); // map_move(map_get_first('p'), -1, 0); + else if (key == 'i') spade_call_press(12); // map_move(map_get_first('p'), 0, -1); + else if (key == 'k') spade_call_press(14); // map_move(map_get_first('p'), 0, 1); + else if (key == 'j') spade_call_press(13); // map_move(map_get_first('p'), 1, 0); + else if (key == 'l') spade_call_press(15); // map_move(map_get_first('p'), -1, 0); + else return; + + print_map(); +} +#else +// Window keyboard input handler +static void keyboard(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool isPressed) { + (void) window; + if (!isPressed) return; + + if (key == KB_KEY_ESCAPE +#ifdef __APPLE__ + || (key == KB_KEY_Q && (mod & KB_MOD_SUPER) != 0) +#endif + ) { + mfb_close(window); + } + + if (key == KB_KEY_W) spade_call_press( 5); // map_move(map_get_first('p'), 0, -1); + if (key == KB_KEY_S) spade_call_press( 7); // map_move(map_get_first('p'), 0, 1); + if (key == KB_KEY_A) spade_call_press( 6); // map_move(map_get_first('p'), 1, 0); + if (key == KB_KEY_D) spade_call_press( 8); // map_move(map_get_first('p'), -1, 0); + if (key == KB_KEY_I) spade_call_press(12); // map_move(map_get_first('p'), 0, -1); + if (key == KB_KEY_K) spade_call_press(14); // map_move(map_get_first('p'), 0, 1); + if (key == KB_KEY_J) spade_call_press(13); // map_move(map_get_first('p'), 1, 0); + if (key == KB_KEY_L) spade_call_press(15); // map_move(map_get_first('p'), -1, 0); +} +#endif + +// Render a character to screen (used only for stats display) +static void render_char(Color *screen, char c, Color color, int sx, int sy) { + for (int y = 0; y < 8; y++) { + uint8_t bits = font_pixels[c*8 + y]; + for (int x = 0; x < 8; x++) + if ((bits >> (7-x)) & 1) { + screen[(sy+y)*160 + sx+x] = color; + } + } +} + +// Render debug stats (memory usage, bitmap count, etc.) +void render_stats(Color *screen) { + static int peak_bitmap_count = 0; + static int peak_sprite_count = 0; + + /** + * 20 * 8 is the max number of characters we can fit on the screen + * + * +1 on mem size because sprintf might write an extra null term, doesn't matter for others + * bc their buffers should never be filled (given max lengths below) + * + * format with assumed max lengths: + * + * -------------------- + * mem: 1000kB (1000kB) + * bitmaps: 100 (100) + * sprites: 100 (100) + * maps: 100 (100) + */ + + char mem[20 * 8 + 1] = ""; + char bitmaps[20 * 8] = ""; + char sprites[20 * 8] = ""; + + Color mem_color = color16(255, 255, 255); + jerry_heap_stats_t stats = {0}; + if (jerry_get_memory_stats(&stats)) { + sprintf(mem, "mem: %lukB (%lukB)", stats.allocated_bytes / 1000, stats.peak_allocated_bytes / 1000); + if (stats.peak_allocated_bytes > 200000) mem_color = color16(255, 255, 0); // yellow + if (stats.allocated_bytes > 200000) mem_color = color16(255, 0, 0); // red + } + + int bitmap_count = state->render->doodle_index_count; + if (bitmap_count > peak_bitmap_count) peak_bitmap_count = bitmap_count; + sprintf(bitmaps, "bitmaps: %d (%d)", bitmap_count, peak_bitmap_count); + + int sprite_count = 0; + for (int i = 0; i < state->sprite_pool_size; i++) + sprite_count += state->sprite_slot_active[i]; + if (sprite_count > peak_sprite_count) peak_sprite_count = sprite_count; + sprintf(sprites, "sprites: %d (%d)", sprite_count, peak_sprite_count); + + // Draw! + for (int i = 0; i < 20 * 8; i++) { + if (mem[i] != '\0') render_char(screen, mem[i], mem_color, i*8, SCREEN_SIZE_Y); + if (bitmaps[i] != '\0') render_char(screen, bitmaps[i], color16(255, 255, 255), i*8, SCREEN_SIZE_Y + 8); + if (sprites[i] != '\0') render_char(screen, sprites[i], color16(255, 255, 255), i*8, SCREEN_SIZE_Y + 16); + } +} + +// Run the provided JS code. Copies from file, which can be safely freed after. +static void js_init(char *file, int file_size) { + // Concatenate the engine script and the user script + char *combined = calloc(sizeof(engine_script) - 1 + file_size, 1); + strcpy(combined, engine_script); + strcpy(combined + sizeof(engine_script) - 1, file); + + const jerry_length_t combined_size = sizeof (engine_script) - 1 + file_size; + js_run(combined, combined_size); +} + +/** + * Implementations for PianoOpts (see src/shared/audio/piano.h) + * + * p (the song object) is type erased because that's an implementation detail + * for us. It's actually a jerry_value_t, not a void pointer, so we gotta cast. + */ +#ifdef SPADE_AUDIO + void piano_jerry_song_free(void *p) { + jerry_value_t jvt = (jerry_value_t)p; + jerry_release_value(jvt); + } + + int piano_jerry_song_chars(void *p, char *buf, int buf_len) { + jerry_value_t jvt = (jerry_value_t)p; + int read = jerry_string_to_char_buffer(jvt, (jerry_char_t *)buf, (jerry_size_t)buf_len); + return read; + } +#endif + +// The screen! This will be non-null before we render. +Color *write_pixel_screen = NULL; + +// Screen offset. We're simulating the RPI screen, which is written +// in top to bottom, left to right order without coordinates. +int write_pixel_offset = 0; + +// Write a pixel! Must be called in top to bottom, left to right order. +static void write_pixel(Color c) { + // Transform write_pixel_offset into x and y coordinates. + int x = write_pixel_offset / SCREEN_SIZE_Y; + int y = write_pixel_offset % SCREEN_SIZE_Y; + + write_pixel_screen[y * SCREEN_SIZE_X + x] = c; + write_pixel_offset++; +} + +// Read a file to a buffer. Also populates size argument with the file size. +char *read_in_script(char *path, int *size) { + FILE *script = fopen(path, "r"); + if (script == NULL) perror("couldn't open file arg"), abort(); + + fseek(script, 0, SEEK_END); + int file_size = ftell(script); + rewind(script); + + char *chars = calloc(file_size, 1); + if (fread(chars, file_size, 1, script) != 1) + perror("couldn't read chars"), abort(); + if (size) *size = file_size; + return chars; +} + +int main(int argc, char *argv[]) { + // Make errors red + errorbuf_color = color16(255, 0, 0); + + // Make a window + struct mfb_window *window = mfb_open_ex("spade", SPADE_WIN_SIZE_X * SPADE_WIN_SCALE, SPADE_WIN_SIZE_Y * SPADE_WIN_SCALE, 0); + if (!window) { + yell("failed to open window"); + return 1; + } + + // Seed the C random number generator with the current time + union { double d; unsigned u; } now = { .d = jerry_port_get_current_time() }; + srand(now.u); + + // Initialize the JS engine + jerry_init(JERRY_INIT_MEM_STATS); + init(sprite_free_jerry_object); + + // First argument to this program is path to JS code to run + { + int script_len = 0; + char *script = read_in_script(argv[1], &script_len); + js_init(script, script_len); // <- run the code! + free(script); + } + + #ifdef SPADE_AUTOMATED + print_map(); + #else + // Not run if automated (we take input from stdin in the event loop instead of the window) + mfb_set_keyboard_callback(window, keyboard); + #endif + + Color screen[SPADE_WIN_SIZE_X * SPADE_WIN_SIZE_Y] = {0}; + write_pixel_screen = screen; + + #ifdef SPADE_AUDIO + // Initialize audio + piano_init((PianoOpts) { + .song_free = piano_jerry_song_free, + .song_chars = piano_jerry_song_chars, + }); + audio_init(); + #endif + + // Current time for timer handling (see frame_cb in shared/sprig_engine/engine.js) + struct mfb_timer *lastframe = mfb_timer_create(); + mfb_timer_now(lastframe); + + // Event loop! + do { + // Run async code + js_promises(); + + // setInterval/setTimeout impl + spade_call_frame(1000.0f * mfb_timer_delta(lastframe)); + mfb_timer_now(lastframe); + + // Audio + #ifdef SPADE_AUDIO + audio_try_push_samples(); + #endif + + // Render + memset(screen, 0, sizeof(screen)); // Clear screen + write_pixel_offset = 0; // Reset screen offset + render_errorbuf(); // Render errorbuf to game text + render(write_pixel); // Render game + render_stats(screen); // Render debug stats + + // If automated, read keypresses from stdin + #ifdef SPADE_AUTOMATED + simulated_keyboard(); + #endif + + // Update the window with new screen buffer + uint8_t ok = mfb_update_ex(window, screen, SPADE_WIN_SIZE_X, SPADE_WIN_SIZE_Y) == STATE_OK; + if (!ok) { + window = 0x0; + break; + } + } while(mfb_wait_sync(window)); + + return 0; +} \ No newline at end of file diff --git a/src/rpi/CMakeLists.txt b/src/rpi/CMakeLists.txt new file mode 100644 index 0000000..86d3160 --- /dev/null +++ b/src/rpi/CMakeLists.txt @@ -0,0 +1,29 @@ +pico_sdk_init() +add_definitions(-DNEBULA_EMBEDDED -DNEBULA_AUDIO -DPICO_NO_BI_PROGRAM_BUILD_DATE) +set(DCMAKE_BUILD_TYPE Release) +target_compile_definitions(nebula PRIVATE + # compile time configuration of I2S + PICO_AUDIO_I2S_MONO_INPUT=1 + USE_AUDIO_I2S=1 + PICO_AUDIO_I2S_DATA_PIN=9 + PICO_AUDIO_I2S_CLOCK_PIN_BASE=10 + # PICO_DEFAULT_UART=0 + # PICO_DEFAULT_UART_TX_PIN=28 + # PICO_DEFAULT_UART_RX_PIN=29 +) + +target_link_libraries(nebula PRIVATE + pico_stdlib + pico_audio_i2s + pico_multicore + hardware_spi + hardware_timer + hardware_pwm + hardware_adc +) + +pico_enable_stdio_usb(nebula 1) +pico_enable_stdio_uart(nebula 0) + +# create map/bin/hex file etc. +pico_add_extra_outputs(nebula) \ No newline at end of file diff --git a/src/rpi/ST7735_TFT.h b/src/rpi/ST7735_TFT.h new file mode 100644 index 0000000..3897270 --- /dev/null +++ b/src/rpi/ST7735_TFT.h @@ -0,0 +1,258 @@ +#ifndef __ST7735_TFT_H +#define __ST7735_TFT_H // -------------------------------------------------------------------------- +// ST7735 +// +// This code is based on work from Bernhard Bablok +// +// The code is also based on work from Gavin Lyons, see +// https://github.com/gavinlyonsrepo/pic_16F18346_projects +// +// https://github.com/bablokb/pic-st7735 +// -------------------------------------------------------------------------- + +#define SPI_TFT_PORT spi0 +#define SPI_TFT_CS 20 +#define SPI_TFT_DC 22 +#define SPI_TFT_RST 26 + +#define SPI_PORT spi0 +#define SPI_RX 16 +#define SPI_TX 19 +#define SPI_SCK 18 + +#define ST7735_NOP 0x00 +#define ST7735_SWRESET 0x01 +#define ST7735_RDDID 0x04 +#define ST7735_RDDST 0x09 +#define ST7735_SLPIN 0x10 +#define ST7735_SLPOUT 0x11 +#define ST7735_PTLON 0x12 +#define ST7735_NORON 0x13 +#define ST7735_INVOFF 0x20 +#define ST7735_INVON 0x21 +#define ST7735_DISPOFF 0x28 +#define ST7735_DISPON 0x29 +#define ST7735_CASET 0x2A +#define ST7735_RASET 0x2B +#define ST7735_RAMWR 0x2C +#define ST7735_RAMRD 0x2E +#define ST7735_PTLAR 0x30 +#define ST7735_VSCRDEF 0x33 +#define ST7735_COLMOD 0x3A +#define ST7735_MADCTL 0x36 +#define ST7735_MADCTL_MY 0x80 +#define ST7735_MADCTL_MX 0x40 +#define ST7735_MADCTL_MV 0x20 +#define ST7735_MADCTL_ML 0x10 +#define ST7735_MADCTL_RGB 0x00 +#define ST7735_VSCRSADD 0x37 +#define ST7735_FRMCTR1 0xB1 +#define ST7735_FRMCTR2 0xB2 +#define ST7735_FRMCTR3 0xB3 +#define ST7735_INVCTR 0xB4 +#define ST7735_DISSET5 0xB6 +#define ST7735_PWCTR1 0xC0 +#define ST7735_PWCTR2 0xC1 +#define ST7735_PWCTR3 0xC2 +#define ST7735_PWCTR4 0xC3 +#define ST7735_PWCTR5 0xC4 +#define ST7735_VMCTR1 0xC5 +#define ST7735_RDID1 0xDA +#define ST7735_RDID2 0xDB +#define ST7735_RDID3 0xDC +#define ST7735_RDID4 0xDD +#define ST7735_PWCTR6 0xFC +#define ST7735_GMCTRP1 0xE0 +#define ST7735_GMCTRN1 0xE1 + +// Color definitions +#define ST7735_BLACK 0x0000 +#define ST7735_BLUE 0x001F +#define ST7735_RED 0xF800 +#define ST7735_GREEN 0x07E0 +#define ST7735_CYAN 0x07FF +#define ST7735_MAGENTA 0xF81F +#define ST7735_YELLOW 0xFFE0 +#define ST7735_WHITE 0xFFFF + + +#define tft_cs_low() asm volatile("nop \n nop \n nop"); \ + gpio_put(SPI_TFT_CS,0); \ + asm volatile("nop \n nop \n nop") +#define tft_cs_high() asm volatile("nop \n nop \n nop"); \ + gpio_put(SPI_TFT_CS,1); \ + asm volatile("nop \n nop \n nop") + +#define tft_dc_low() asm volatile("nop \n nop \n nop"); \ + gpio_put(SPI_TFT_DC,0); \ + asm volatile("nop \n nop \n nop") +#define tft_dc_high() asm volatile("nop \n nop \n nop"); \ + gpio_put(SPI_TFT_DC,1); \ + asm volatile("nop \n nop \n nop") + +#define tft_rst_low() asm volatile("nop \n nop \n nop"); \ + gpio_put(SPI_TFT_RST,0); \ + asm volatile("nop \n nop \n nop") +#define tft_rst_high() asm volatile("nop \n nop \n nop"); \ + gpio_put(SPI_TFT_RST,1); \ + asm volatile("nop \n nop \n nop") + +static void spi_command(uint8_t x) { + tft_dc_low(); + spi_write_blocking(SPI_TFT_PORT, &x, sizeof(x)); +} +#define spi_data(...) { \ + tft_dc_high(); \ + uint8_t data[] = { __VA_ARGS__ }; \ + spi_write_blocking(SPI_TFT_PORT, data, sizeof(data)); } + +// todo: consolidate spi_command and spi_data, use everywhere +static void write_command(uint8_t cmd_){ + tft_dc_low(); + tft_cs_low(); + spi_write_blocking(SPI_TFT_PORT, &cmd_, 1); + tft_cs_high(); +} +static void write_data(uint8_t data_){ + tft_dc_high(); + tft_cs_low(); + spi_write_blocking(SPI_TFT_PORT, &data_, 1); + tft_cs_high(); +} + +static void st7735_fill_start() { + tft_cs_low(); + + spi_command(ST7735_CASET); + spi_data(0x00, 0x00, 0x00, 0x7F); + + spi_command(ST7735_RASET); + spi_data(0x00, 0x00, 0x00, 0x9F); + + spi_command(ST7735_RAMWR); + tft_dc_high(); // (no data) +} + +static void st7735_fill_send(uint16_t pixel) { + spi_write_blocking(SPI_TFT_PORT, (uint8_t *)&pixel, sizeof(uint16_t)); +} + +static void st7735_fill_finish(void) { + tft_cs_high(); +} + +static void st7735_reset() { + tft_rst_high() ; + sleep_ms(10); + tft_rst_low(); + sleep_ms(10); + tft_rst_high() ; + sleep_ms(10); +} + +static void st7735_init() { + // enable backlight + { + gpio_init(17); + gpio_set_dir(17, GPIO_OUT); + gpio_put(17, 1); + } + + // init SPI, gpio + { + // baud rate: + spi_init(SPI_PORT, 30000000); + + gpio_set_function(SPI_RX, GPIO_FUNC_SPI); + gpio_set_function(SPI_SCK,GPIO_FUNC_SPI); + gpio_set_function(SPI_TX, GPIO_FUNC_SPI); + + // enable SPI + gpio_init(SPI_TFT_CS); + gpio_set_dir(SPI_TFT_CS, GPIO_OUT); + gpio_put(SPI_TFT_CS, 1); // Chip select is active-low + + gpio_init(SPI_TFT_DC); + gpio_set_dir(SPI_TFT_DC, GPIO_OUT); + gpio_put(SPI_TFT_DC, 0); // Chip select is active-low + + gpio_init(SPI_TFT_RST); + gpio_set_dir(SPI_TFT_RST, GPIO_OUT); + gpio_put(SPI_TFT_RST, 0); + } + + st7735_reset(); + + tft_dc_low(); + + // read screen data sheet to understand + { + write_command(ST7735_SWRESET); + sleep_ms(150); + write_command(ST7735_SLPOUT); + sleep_ms(500); + write_command(ST7735_FRMCTR1); + write_data(0x01); + write_data(0x2C); + write_data(0x2D); + write_command(ST7735_FRMCTR2); + write_data(0x01); + write_data(0x2C); + write_data(0x2D); + write_command(ST7735_FRMCTR3); + write_data(0x01); write_data(0x2C); write_data(0x2D); + write_data(0x01); write_data(0x2C); write_data(0x2D); + write_command(ST7735_INVCTR); + write_data(0x07); + write_command(ST7735_PWCTR1); + write_data(0xA2); + write_data(0x02); + write_data(0x84); + write_command(ST7735_PWCTR2); + write_data(0xC5); + write_command(ST7735_PWCTR3); + write_data(0x0A); + write_data(0x00); + write_command(ST7735_PWCTR4); + write_data(0x8A); + write_data(0x2A); + write_command(ST7735_PWCTR5); + write_data(0x8A); + write_data(0xEE); + write_command(ST7735_VMCTR1); + write_data(0x0E); + write_command(ST7735_INVOFF); + write_command(ST7735_MADCTL); + write_data(0x40 | 0x10 | 0x08);// 0xC8); + write_command(ST7735_COLMOD); + write_data(0x05); + } + + { // initializes red version + write_command(ST7735_CASET); + write_data(0x00); write_data(0x00); + write_data(0x00); write_data(0x7F); + write_command(ST7735_RASET); + write_data(0x00); write_data(0x00); + write_data(0x00); write_data(0x9F); + } + + { + write_command(ST7735_GMCTRP1); + write_data(0x02); write_data(0x1C); write_data(0x07); write_data(0x12); + write_data(0x37); write_data(0x32); write_data(0x29); write_data(0x2D); + write_data(0x29); write_data(0x25); write_data(0x2B); write_data(0x39); + write_data(0x00); write_data(0x01); write_data(0x03); write_data(0x10); + write_command(ST7735_GMCTRN1); + write_data(0x03); write_data(0x1D); write_data(0x07); write_data(0x06); + write_data(0x2E); write_data(0x2C); write_data(0x29); write_data(0x2D); + write_data(0x2E); write_data(0x2E); write_data(0x37); write_data(0x3F); + write_data(0x00); write_data(0x00); write_data(0x02); write_data(0x10); + write_command(ST7735_NORON); + sleep_ms(10); + write_command(ST7735_DISPON); + sleep_ms(100); + } +} + +#endif \ No newline at end of file diff --git a/src/rpi/audio.c b/src/rpi/audio.c new file mode 100644 index 0000000..d57b8c3 --- /dev/null +++ b/src/rpi/audio.c @@ -0,0 +1,65 @@ +/** + * Driver for the Raspberry Pi audio system. All actual sound generation code is in shared/audio/piano.c. + */ + +#include "shared/audio/piano.c" + +#include "hardware/clocks.h" +#include "hardware/structs/clocks.h" +#include "pico/audio_i2s.h" +#include "pico/binary_info.h" +bi_decl(bi_3pins_with_names(PICO_AUDIO_I2S_DATA_PIN, "I2S DIN", PICO_AUDIO_I2S_CLOCK_PIN_BASE, "I2S BCK", PICO_AUDIO_I2S_CLOCK_PIN_BASE+1, "I2S LRCK")); + +static struct audio_buffer_pool *audio_buffer_pool_init() { + + static audio_format_t audio_format = { + .format = AUDIO_BUFFER_FORMAT_PCM_S16, + .sample_freq = SAMPLES_PER_SECOND, + .channel_count = 1, + }; + + static struct audio_buffer_format producer_format = { + .format = &audio_format, + .sample_stride = 2 + }; + + struct audio_buffer_pool *producer_pool = audio_new_producer_pool(&producer_format, 3, + SAMPLES_PER_BUFFER); // todo correct size + bool __unused ok; + const struct audio_format *output_format; + + struct audio_i2s_config config = { + .data_pin = PICO_AUDIO_I2S_DATA_PIN, + .clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE, + .dma_channel = 0, + .pio_sm = 0, + }; + + output_format = audio_i2s_setup(&audio_format, &config); + if (!output_format) { + panic("PicoAudio: Unable to open audio device.\n"); + } + + ok = audio_i2s_connect(producer_pool); + assert(ok); + audio_i2s_set_enabled(true); + + return producer_pool; +} + +struct audio_buffer_pool *audio_bufpool; + +void audio_init(void) { + audio_bufpool = audio_buffer_pool_init(); +} + +void audio_try_push_samples(void) { + struct audio_buffer *buffer = take_audio_buffer(audio_bufpool, false); + if (buffer == NULL) return; + + piano_fill_sample_buf((int16_t *)buffer->buffer->bytes, buffer->max_sample_count); + buffer->sample_count = buffer->max_sample_count; + + // send to PIO DMA + give_audio_buffer(audio_bufpool, buffer); +} diff --git a/src/rpi/display.c b/src/rpi/display.c new file mode 100644 index 0000000..7cff8ba --- /dev/null +++ b/src/rpi/display.c @@ -0,0 +1,713 @@ +#pragma once + +#include "ST7735_TFT.h" + +// display dimensions +#define HEIGHT 128 +#define WIDTH 160 + + +// text overlay buffer +#define TEXT_OVERLAY_BUF 416 +#define ST_TEXT_OVERLAY 0x20 + +/* + * Display + * Will be rendered using DMA eventually + */ +uint16_t display[HEIGHT * WIDTH]; + +// supports 10x20 text overlay +uint8_t text_overlay[TEXT_OVERLAY_BUF + 1] = { '0' }; + +/* + * bitmap array + * ASCII codes ST_TEXT_OVERLAY-0x7E can be displayed + * so subtract ST_TEXT_OVERLAY + * for the bitmap for the character + * these are 6x7 rotated -90 degrees and flipped + */ +char *bitmaps[] = { + "0000000" // (space) + "0000000" + "0000000" + "0000000" + "0000000" + "0000000", + + "0000000" // ! + "0000000" + "1111010" + "0000000" + "0000000" + "0000000", + + "0110000" // " + "0100000" + "0000000" + "0110000" + "0100000" + "0000000", + + "0100100" // # + "1111110" + "0100100" + "1111110" + "0100100" + "0000000", + + "0010000" // $ + "0101010" + "1111110" + "0101010" + "0000100" + "0000000", + + "0010010" // % + "0000100" + "0001000" + "0010000" + "0100100" + "0000000", + + "0101100" // & + "1010010" + "1010010" + "0101100" + "0010100" + "0100010", + + "0000000" // ' + "0000000" + "0110000" + "0000000" + "0000000" + "0000000", + + "0000000" // ( + "0011000" + "0100100" + "1000010" + "0000000" + "0000000", + + "0000000" // ) + "1000010" + "0100100" + "0011000" + "0000000" + "0000000", + + "0000000" // * + "0011100" + "0011100" + "0011100" + "0000000" + "0000000", + + "0000000" // + + "0001000" + "0011100" + "0001000" + "0000000" + "0000000", + + "0000000" // , + "0000001" + "0000010" + "0000000" + "0000000" + "0000000", + + "0000000" // - + "0001000" + "0001000" + "0001000" + "0000000" + "0000000", + + "0000000" // . + "0000110" + "0000110" + "0000000" + "0000000" + "0000000", + + "0000000" // / + "0000110" + "0011000" + "1100000" + "0000000" + "0000000", + + "0111100" // 0 + "1001110" + "1011010" + "1100010" + "0111100" + "0000000", + + "0000010" // 1 + "0100010" + "1111110" + "0000010" + "0000010" + "0000000", + + "0100010" // 2 + "1000110" + "1001010" + "1010010" + "0100010" + "0000000", + + "0100100" // 3 + "1000010" + "1010010" + "1010010" + "0101100" + "0000000", + + "1110000" // 4 + "0010000" + "0010000" + "0010000" + "1111110" + "0000000", + + "1110010" // 5 + "1010010" + "1010010" + "1010010" + "1001100" + "0000000", + + "0100100" // 6 + "1001010" + "1001010" + "1001010" + "0111100" + "0000000", + + "1000010" // 7 + "1000100" + "1001000" + "1010000" + "1100000" + "0000000", + + "0101100" // 8 + "1010010" + "1010010" + "1010010" + "0101100" + "0000000", + + "0100000" // 9 + "1010010" + "1010010" + "1010010" + "0111100" + "0000000", + + "0000000" // : + "0000000" + "0110110" + "0000000" + "0000000" + "0000000", + + "0000000" // ; + "0000010" + "0110100" + "0000000" + "0000000" + "0000000", + + "0000000" // < + "0001000" + "0010100" + "0100010" + "0000000" + "0000000", + + "0000000" // = + "0010100" + "0010100" + "0010100" + "0000000" + "0000000", + + "0000000" // > + "0100010" + "0010100" + "0001000" + "0000000" + "0000000", + + "0000000" // ? + "0100000" + "1001010" + "1010000" + "0100000" + "0000000", + + "0011100" // @ + "0110010" + "0101010" + "0100100" + "0010000" + "0000000", + + "1111110" // A + "1010000" + "1010000" + "1010000" + "1111110" + "0000000", + + "1111110" // B + "1010010" + "1010010" + "1010010" + "0101100" + "0000000", + + "0111100" // C + "1000010" + "1000010" + "1000010" + "0100100" + "0000000", + + "1111110" // D + "1000010" + "1000010" + "0100100" + "0011000" + "0000000", + + "1111110" // E + "1010010" + "1010010" + "1010010" + "1010010" + "0000000", + + "1111110" // F + "1010000" + "10100o0" + "1010000" + "1000000" + "0000000", + + "0111100" // G + "1000010" + "1001010" + "1001010" + "0101100" + "0000000", + + "1111110" // H + "0010000" + "0010000" + "0010000" + "1111110" + "0000000", + + "1000010" // I + "1000010" + "1111110" + "1000010" + "1000010" + "0000000", + + "1000010" // J + "1000010" + "1111100" + "1000000" + "1000000" + "0000000", + + "1111110" // K + "0010000" + "0010000" + "0101000" + "1000110" + "0000000", + + "1111110" // L + "0000010" + "0000010" + "0000010" + "0000010" + "0000000", + + "1111110" // M + "0100000" + "0010000" + "0100000" + "1111110" + "0000000", + + "1111110" // N + "0100000" + "0010000" + "0001000" + "1111110" + "0000000", + + "0111100" // O + "1000010" + "1000010" + "1000010" + "0111100" + "0000000", + + "1111110" // P + "1010000" + "1010000" + "1010000" + "0100000" + "0000000", + + "0111000" // Q + "1000100" + "1001100" + "1000100" + "0111010" + "0000000", + + "1111110" // R + "1010000" + "1011000" + "1010100" + "0100010" + "0000000", + + "0100000" // S + "1010010" + "1010010" + "1010010" + "0001100" + "0000000", + + "1000000" // T + "1000000" + "1111110" + "1000000" + "1000000" + "0000000", + + "1111100" // U + "0000010" + "0000010" + "0000010" + "1111100" + "0000000", + + "1100000" // V + "0011100" + "0000010" + "0011100" + "1100000" + "0000000", + + "1111110" // W + "0000100" + "0001000" + "0000100" + "1111110" + "0000000", + + "1000010" // X + "0100100" + "0011000" + "0100100" + "1000010" + "0000000", + + "1000000" // Y + "0100000" + "0011110" + "0100000" + "1000000" + "0000000", + + "1000010" // Z + "1100010" + "1010010" + "1001010" + "1000110" + "0000000", + + "0000000" // [ + "1111111" + "1000001" + "1000001" + "0000000" + "0000000", + + "0000000" /* \ */ + "1100000" + "0011000" + "0000110" + "0000000" + "0000000", + + "0000000" // ] + "1000001" + "1000001" + "1111111" + "0000000" + "0000000", + + "0000000" // ^ + "0100000" + "1000000" + "0100000" + "0000000" + "0000000", + + "0000010" // _ + "0000010" + "0000010" + "0000010" + "0000010" + "0000000", + + "0000000" // ` + "1000000" + "0100000" + "0010000" + "0000000" + "0000000", + + "0011100" // a + "0100010" + "0100010" + "0100010" + "0111110" + "0000000", + + "1111110" // b + "0100010" + "0100010" + "0100010" + "0011100" + "0000000", + + "0011100" // c + "0100010" + "0100010" + "0100010" + "0100010" + "0000000", + + "0011100" // d + "0100010" + "0100010" + "0100010" + "1111110" + "0000000", + + "0011100" // e + "0101010" + "0101010" + "0101010" + "0010010" + "0000000", + + "0011110" // f + "0110000" + "1010000" + "1010000" + "1000000" + "0000000", + + "0001000" // g + "0010101" + "0010101" + "0010101" + "0001110" + "0000000", + + "1111110" // h + "0010000" + "0010000" + "0010000" + "0001110" + "0000000", + + "0000000" // i + "0000000" + "1011110" + "0000000" + "0000000" + "0000000", + + "0000001" // j + "0000001" + "1011110" + "0000000" + "0000000" + "0000000", + + "1111110" // k + "0001000" + "0010100" + "0000010" + "0000000" + "0000000", + + "0000000" // l + "0000000" + "1111110" + "0000000" + "0000000" + "0000000", + + "0001110" // m + "0010000" + "0001110" + "0010000" + "0001110" + "0000000", + + "0001110" // n + "0010000" + "0010000" + "0010000" + "0001110" + "0000000", + + "0001100" // o + "0010010" + "0010010" + "0010010" + "0001100" + "0000000", + + "0011111" // p + "0010100" + "0010100" + "0010100" + "0001000" + "0000000", + + "0001000" // q + "0010100" + "0010100" + "0010110" + "0001001" + "0000000", + + "0000000" // r + "0011110" + "0001000" + "0010000" + "0000000" + "0000000", + + "0010000" // s + "0101010" + "0101010" + "0101010" + "0000100" + "0000000", + + "0010000" // t + "0010000" + "1111100" + "0010010" + "0010000" + "0000000", + + "0111100" // u + "0000010" + "0000010" + "0000010" + "0111100" + "0000000", + + "0011000" // v + "0000100" + "0000010" + "0000100" + "0011000" + "0000000", + + "0011100" // w + "0000010" + "0011100" + "0000010" + "0011100" + "0000000", + + "0100010" // x + "0010100" + "0001000" + "0010100" + "0100010" + "0000000", + + "0000010" // y + "0010001" + "0001001" + "0000110" + "0011000" + "0000000", + + "0010010" // z + "0010110" + "0011010" + "0010010" + "0000000" + "0000000" +}; + + +/* + * clear the display + * sets a bunch of white pixels + */ +void +display_clear(void) +{ + for (uint16_t i = 0; i < HEIGHT * WIDTH; i++) { + display[i] = ST7735_WHITE; + } +} + +/* + * manually render + * uses cpu time and is inefficient + */ +void +render(void) +{ + spi_write_blocking(SPI_TFT_PORT, (uint8_t *)display, HEIGHT * WIDTH * 2); +} + +/* + * puts the text overlay on top of the buffer + */ +void +render_text_overlay(void) +{ + // character in overlay + for (uint16_t i = 0; i < TEXT_OVERLAY_BUF; i++) { + // pixel in bitmap + for (uint8_t j = 0; j < 6; j++) { + for (uint8_t k = 0; k < 7; k++) { + // hopefully the compiler optimizes this + display[ + (k + HEIGHT * j) + // pixel in bitmap + ((i % 26) * HEIGHT * 6) + // char row in text overlay + ((i / 26) * 8) + // char col in text overlay + HEIGHT * 2 + 1 // start offset + ] = (bitmaps[text_overlay[i] - ST_TEXT_OVERLAY][k + j * 7] == '0') ? ST7735_WHITE : ST7735_BLACK; + } + } + } +} \ No newline at end of file diff --git a/src/rpi/main.c b/src/rpi/main.c new file mode 100644 index 0000000..a661f43 --- /dev/null +++ b/src/rpi/main.c @@ -0,0 +1,264 @@ +#include "pico/stdlib.h" +#include "hardware/pwm.h" +#include "hardware/spi.h" +#include "hardware/timer.h" +#include "hardware/watchdog.h" +#include "hardware/adc.h" +#include "pico/util/queue.h" +#include "pico/multicore.h" + +#include +#include +#include +#include +#include "shared/version.h" + +// Set to false to enable debug prints for development (this is janky) +#if true + #define dbg puts + #define dbgf printf +#else + #define dbg(...) ; + #define dbgf(...) ; +#endif + +// Debugging shortcut +#define yell puts + +#ifdef SPADE_AUDIO + #include "audio.c" +#endif + +// More firmware stuiff +#include "ST7735_TFT.h" + +#include "display.c" + + +#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0])) + +/** + * We store a 64-boolean ringbuffer of polled button states for a primitive + * sort of debouncing. The button counts as pressed if more than 5/6th of + * the ringbuffer is true. + * + * (gpio_set_input_hysteresis_enabled was too slow.) + */ +#define HISTORY_LEN (64) +typedef struct { + uint8_t history[HISTORY_LEN/8]; + uint8_t last_state; + uint8_t ring_i; +} ButtonState; +uint button_pins[] = { 5, 7, 6, 8, 12, 14, 13, 15 }; +static ButtonState button_states[ARR_LEN(button_pins)] = {0}; + +static bool button_history_read(ButtonState *bs, int i) { + // We want to store bools compactly so we have to do some bit twiddling. + int q = 1 << (i % 8); + return !!(bs->history[i/8] & q); +} +static void button_history_write(ButtonState *bs, int i, bool value) { + if (value) + bs->history[i/8] |= 1 << (i % 8) ; + else + bs->history[i/8] &= ~(1 << (i % 8)); +} + +static void button_init(void) { + for (int i = 0; i < ARR_LEN(button_pins); i++) { + ButtonState *bs = button_states + i; + gpio_set_dir(button_pins[i], GPIO_IN); + gpio_pull_up(button_pins[i]); + } +} + +/** + * Poll the buttons and push any keypresses to the main core. + * + * (Should be run in a loop on a non-primary core.) + */ +static void button_poll(void) { + for (int i = 0; i < ARR_LEN(button_pins); i++) { + ButtonState *bs = button_states + i; + + bs->ring_i = (bs->ring_i + 1) % HISTORY_LEN; // Incrememnt ringbuffer index + button_history_write(bs, bs->ring_i, gpio_get(button_pins[i])); + + // up is true if more than 5/6 are true + int up = 0; + for (int i = 0; i < HISTORY_LEN; i++) { + up += button_history_read(bs, i); + } + up = up > ((HISTORY_LEN*5)/6); // Here we convert to a bool + + if (up != bs->last_state) { + bs->last_state = up; + if (!up) { + // Send the keypress to the main core + multicore_fifo_push_blocking(button_pins[i]); + } + } + } +} + +// Turn on the power lights and dim them with PWM. +static void power_lights() { + // left white light + const int pin_num_0 = 28; + gpio_set_function(pin_num_0, GPIO_FUNC_PWM); + uint slice_num_0 = pwm_gpio_to_slice_num(pin_num_0); + pwm_set_enabled(slice_num_0, true); + pwm_set_gpio_level(pin_num_0, 65535/8); + + // right blue light + // const pin_num_1 = 4; + // gpio_set_function(pin_num_1, GPIO_FUNC_PWM); + // uint slice_num_1 = pwm_gpio_to_slice_num(pin_num_1); + // pwm_set_enabled(slice_num_1, true); + // pwm_set_gpio_level(pin_num_1, 65535/4); +} + +// Entry point for the second core that polls the buttons. +static void core1_entry(void) { + button_init(); + + while (1) { + button_poll(); + } +} + +/** + * Seed the random number generator with entropy from + * random electricity as well as temperature readings. + */ +static void rng_init(void) { + adc_init(); + uint32_t seed = 0; + + // Read some random electricity + for (int i = 0; i < 4; i++) { + adc_select_input(4); + sleep_ms(1); + seed ^= adc_read(); + } + + // Read some temperature data + adc_set_temp_sensor_enabled(true); + adc_select_input(4); + sleep_ms(1); + seed ^= adc_read(); + adc_set_temp_sensor_enabled(false); + + srand(seed); +} + +char serial_commands[][128] = { + "UPLOAD", + "VERSION", + {1, 2, 3, 4, '\0'} // null terminator so strlen() returns 4 +}; + +// returns which command is being sent from serial, or -1 for none +static int read_command() { + // each index keeps track of how many characters we've matched to each command + int serial_command_indexes[] = {0, 0, 0}; + + int timeout = 0; + + for (;;) { + int c = getchar_timeout_us(timeout); + if (c == PICO_ERROR_TIMEOUT) return -1; + + timeout = 100; + + int moved = 0; + + for (int i = 0; i < ARR_LEN(serial_commands); i++) { + if (strlen(serial_commands[i]) > serial_command_indexes[i] + && serial_commands[i][serial_command_indexes[i]] == c) { + serial_command_indexes[i]++; + moved = 1; + } + if (strlen(serial_commands[i]) == serial_command_indexes[i]) { + return i; + } + } + if (!moved) return -1; + } +} + + +/** + * Implementations for PianoOpts (see src/shared/audio/piano.h) + * + * p (the song object) is type erased because that's an implementation detail + * for us. It's actually a jerry_value_t, not a void pointer, so we gotta cast. + */ + +int main() { + timer_hw->dbgpause = 0; + + // Overclock the RP2040! + //set_sys_clock_khz(270000, true); + + power_lights(); // Turn on the power lights + stdio_init_all(); // Init serial port + rng_init(); // Init RNG + + // Initialize the display (call this once at the start) + st7735_init(); + + // Set up for drawing (set column and row addresses) + st7735_fill_start(); + + display_clear(); + + strncpy(text_overlay, + "Nebula is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.lif3" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.lif3" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.life" + "Nebula.is.the.best.at.lif3" + , 416); + + render_text_overlay(); + render(); + + // Finish drawing + st7735_fill_finish(); + + // Start a core to listen for keypresses. + multicore_launch_core1(core1_entry); + + /** + * We get a bunch of fake keypresses at startup, so we need to + * drain them from the FIFO queue. + * + * What really needs to be done here is to have button_init + * record when it starts so that we can ignore keypresses after + * that timestamp. + */ + sleep_ms(50); + while (multicore_fifo_rvalid()) multicore_fifo_pop_blocking(); + + // Event loop! + while (1) { + // Handle any new button presses + while (multicore_fifo_rvalid()) { + printf("%d\n", multicore_fifo_pop_blocking()); + } + + } +} \ No newline at end of file diff --git a/src/rpi/pico_extras_import.cmake b/src/rpi/pico_extras_import.cmake new file mode 100644 index 0000000..706add0 --- /dev/null +++ b/src/rpi/pico_extras_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_extras_import.cmake + +# This can be dropped into an external project to help locate pico-extras +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + PICO_EXTRAS + GIT_REPOSITORY https://github.com/raspberrypi/pico-extras + GIT_TAG master + ) + if (NOT PICO_EXTRAS) + message("Downloading PICO EXTRAS") + FetchContent_Populate(PICO_EXTRAS) + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + else() + message(FATAL_ERROR + "PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git." + ) + endif() + endif () +endif () + +set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") +set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") +set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + +get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") +endif () + +set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + +add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) \ No newline at end of file diff --git a/src/rpi/pico_sdk_import.cmake b/src/rpi/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/src/rpi/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/src/shared/audio/audio.h b/src/shared/audio/audio.h new file mode 100644 index 0000000..78899d7 --- /dev/null +++ b/src/shared/audio/audio.h @@ -0,0 +1,2 @@ +void audio_init(void); +void audio_try_push_samples(void); diff --git a/src/shared/audio/parse_tune/gen_hash.c b/src/shared/audio/parse_tune/gen_hash.c new file mode 100644 index 0000000..99229ef --- /dev/null +++ b/src/shared/audio/parse_tune/gen_hash.c @@ -0,0 +1,175 @@ +// prints out a C file containing a hashmap of fnv1_hash'd tone string -> frequency + +#include +#include +#include +#include + +#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0])) +#define HashIndex int16_t + +static uint64_t fnv1_hash(void *key, int n_bytes) { + unsigned char *p = (unsigned char *)key; + uint64_t h = 14695981039346656037ul; + for (int i = 0; i < n_bytes; i++) + h = (h * 1099511628211) ^ p[i]; + return h; +} + +char *fnv1_hash_src = + "static uint64_t fnv1_hash(void *key, int n_bytes) {\n" + " unsigned char *p = (unsigned char *)key;\n" + " uint64_t h = 14695981039346656037ul;\n" + " for (int i = 0; i < n_bytes; i++)\n" + " h = (h * 1099511628211) ^ p[i];\n" + " return h;\n" + "}\n"; + +typedef struct { + char *str; + uint16_t freq; +} Tone; + +Tone tones[] = { + { "b0", 31, }, + { "c1", 33, }, + { "c#1", 35, }, + { "d1", 37, }, + { "d#1", 39, }, + { "e1", 41, }, + { "f1", 44, }, + { "f#1", 46, }, + { "g1", 49, }, + { "g#1", 52, }, + { "a1", 55, }, + { "a#1", 58, }, + { "b1", 62, }, + { "c2", 65, }, + { "c#2", 69, }, + { "d2", 73, }, + { "d#2", 78, }, + { "e2", 82, }, + { "f2", 87, }, + { "f#2", 93, }, + { "g2", 98, }, + { "g#2", 104, }, + { "a2", 110, }, + { "a#2", 117, }, + { "b2", 123, }, + { "c3", 131, }, + { "c#3", 139, }, + { "d3", 147, }, + { "d#3", 156, }, + { "e3", 165, }, + { "f3", 175, }, + { "f#3", 185, }, + { "g3", 196, }, + { "g#3", 208, }, + { "a3", 220, }, + { "a#3", 233, }, + { "b3", 247, }, + { "c4", 262, }, + { "c#4", 277, }, + { "d4", 294, }, + { "d#4", 311, }, + { "e4", 330, }, + { "f4", 349, }, + { "f#4", 370, }, + { "g4", 392, }, + { "g#4", 415, }, + { "a4", 440, }, + { "a#4", 466, }, + { "b4", 494, }, + { "c5", 523, }, + { "c#5", 554, }, + { "d5", 587, }, + { "d#5", 622, }, + { "e5", 659, }, + { "f5", 698, }, + { "f#5", 740, }, + { "g5", 784, }, + { "g#5", 831, }, + { "a5", 880, }, + { "a#5", 932, }, + { "b5", 988, }, + { "c6", 1047, }, + { "c#6", 1109, }, + { "d6", 1175, }, + { "d#6", 1245, }, + { "e6", 1319, }, + { "f6", 1397, }, + { "f#6", 1480, }, + { "g6", 1568, }, + { "g#6", 1661, }, + { "a6", 1760, }, + { "a#6", 1865, }, + { "b6", 1976, }, + { "c7", 2093, }, + { "c#7", 2217, }, + { "d7", 2349, }, + { "d#7", 2489, }, + { "e7", 2637, }, + { "f7", 2794, }, + { "f#7", 2960, }, + { "g7", 3136, }, + { "g#7", 3322, }, + { "a7", 3520, }, + { "a#7", 3729, }, + { "b7", 3951, }, + { "c8", 4186, }, + { "c#8", 4435, }, + { "d8", 4699, }, + { "d#8", 4978, }, +}; + +int main(void) { + puts("// this file has been generated by gen_hash.c"); + puts(""); + + HashIndex *map = NULL; + + for (int size = 1; 1; size++) { + int map_size = ARR_LEN(tones) * size; + + if (map) free(map); + map = calloc(sizeof(HashIndex), map_size); + + for (int i = 0; i < map_size; i++) + map[i] = -1; + + for (int i = 0; i < ARR_LEN(tones); i++) { + Tone *t = tones + i; + uint64_t hash = fnv1_hash(t->str, strlen(t->str)) % map_size; + + if (map[hash] != -1) { + printf("// collision(%2d): %4s <-> %4s\n", size, t->str, tones[map[hash]].str); + goto RETRY; + } + map[hash] = i; + // printf("{ note: %s, hash: %llu }\n", t->str, hash % ARR_LEN(tones)); + } + + printf( + "\n// optimal size multiplier is %d! (%lu bytes)\n\n", + size, size*ARR_LEN(tones)*sizeof(HashIndex) + ); + + puts("#include \n\n"); + + puts(fnv1_hash_src); + printf("uint16_t tone_map[%d] = {\n", map_size); + + for (int i = 0; i < ARR_LEN(tones); i++) { + Tone *t = tones + i; + uint64_t hash = fnv1_hash(t->str, strlen(t->str)) % map_size; + printf(" [%4llu] = %4d,\n", hash, t->freq); + } + + puts("};"); + + break; + + RETRY: + ; + } +} diff --git a/src/shared/audio/parse_tune/parse_tune.h b/src/shared/audio/parse_tune/parse_tune.h new file mode 100644 index 0000000..f6865a1 --- /dev/null +++ b/src/shared/audio/parse_tune/parse_tune.h @@ -0,0 +1,151 @@ +#include "tone_map.h" + +typedef enum { + NrsRetKind_None, + NrsRetKind_Pause, + NrsRetKind_Sound, +} NrsRetKind; + +typedef struct { + NrsRetKind kind; + float duration; + + struct { // if NoteKind_Sound: + int freq; + Sound sound; + } sound; +} NrsRet; + +#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0])) +typedef struct { + uint8_t open; + + float to_wait; + + NrsRet ret; + + int i; + // char *str; // iterated through +} NoteReadState; + +static uint8_t note_read(NoteReadState *nrs, char *char_source) { + char *endptr = NULL; + char *str = char_source + nrs->i; + + // if (*str == 0) return 0; + + if (!nrs->open) { + nrs->to_wait = strtof(str, &endptr); + if (endptr) { + str = endptr; + + // if there's more than just a pause, we open for notes + if (*str == ':') { + str++; // consume the colon! + nrs->open = 1; + goto THERES_MORE; + } + + // if it's a comma and newline we eat that shit (and stay closed) + if (*str == ',') { + str += 2; + goto THERES_MORE; // where there's a comma in this spec, there's more + } + + #if 0 + this and a line up at the top of this function are commented out, + because i think ignoring the final duration is something we actually + want to do! + if (*str == '\0') { + // this is fucked up because 1 has meant "there's more" but + goto THERES_MORE; + } + #endif + + // just filter out empty lines between notes i guess + if (*str == '\n') str++; + } + } + else { + // go ahead and eat a comma + close, if we can + if (*str == ',') { + nrs->open = 0; + + // we eat commas and newlines + str += 2; + + goto THERES_MORE; + } + + while (1) { + if (*str == ' ') { str++; continue; } + if (*str == '+') { str++; continue; } + if (*str == 0) { nrs->open = 0; break; } + if (*str == '\n') { str++; continue; } // ignore newlines, as a newline is not a note (i think) + if (*str <= 'Z' && *str >= 'A') { *str ^= 32; } // convert uppercase notes from new sprig editor to lowercase + if (*str <= 'z' && *str >= 'a') { + + char note[4] = {0}; + int freq = 0; + { + int note_len = 0; + while (isalnum((int)*str) || *str == '#') + note[note_len++] = *str++; + freq = tone_map[fnv1_hash(note, note_len) % ARR_LEN(tone_map)]; + } + + char shape = *str++; + + endptr = NULL; + float duration = strtof(str, &endptr); + if (endptr) str = endptr; + + { + Sound sound = Sound_Sine; + { + if (shape == '~') sound = Sound_Sine; + else if (shape == '-') sound = Sound_Square; + else if (shape == '^') sound = Sound_Triangle; + else if (shape == '/') sound = Sound_Sawtooth; + // else dbg("unknown shape"); + } + nrs->ret = (NrsRet) { + .kind = NrsRetKind_Sound, + .duration = duration, + + .sound.freq = freq , + .sound.sound = sound, + }; + } + + goto THERES_MORE; + } + break; + } + } + + nrs->i = str - char_source; + return 0; + + THERES_MORE: + nrs->i = str - char_source; + return 1; +} + +static uint8_t tune_parse(NoteReadState *nrs, char *char_source) { + bzero(&nrs->ret, sizeof(NrsRet)); + + if (note_read(nrs, char_source)) { + if (!nrs->open) { + // okay, we've read a whole note, we can finish this note! ... + nrs->ret = (NrsRet) { + .kind = NrsRetKind_Pause, + .duration = nrs->to_wait, + }; + } + return 1; + } + if (char_source[nrs->i] == '\0') return 0; + + return 1; +} diff --git a/src/shared/audio/parse_tune/test.c b/src/shared/audio/parse_tune/test.c new file mode 100644 index 0000000..b2a0bc2 --- /dev/null +++ b/src/shared/audio/parse_tune/test.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +typedef enum { + Sound_Sine, + Sound_Triangle, + Sound_Sawtooth, + Sound_Square, + Sound_COUNT, + // todo align w js +} Sound; + +#include "parse_tune.h" + +char *examples[] = { + "166.66666666666666,\n" + "166.66666666666666: a4^166.66666666666666,\n" + "5000", + + "379.746835443038: d4^379.746835443038 + c5~379.746835443038,\n" + "379.746835443038: b4~379.746835443038 + e4^379.746835443038 + g5/379.746835443038,\n" + "379.746835443038: c5~379.746835443038,\n" + "379.746835443038: d5~379.746835443038 + f4^379.746835443038 + b5/379.746835443038,\n" + "379.746835443038: e5~379.746835443038 + g5/379.746835443038,\n" + "379.746835443038: e4^379.746835443038 + a5/379.746835443038,\n" + "379.746835443038: d5~379.746835443038,\n" + "379.746835443038: c5~379.746835443038 + d4^379.746835443038 + g5/379.746835443038,\n" + "379.746835443038: b4~379.746835443038,\n" + "379.746835443038: b5/379.746835443038,\n" + "379.746835443038: g5/379.746835443038,\n" + "379.746835443038: b4~379.746835443038 + f4^379.746835443038 + a5/379.746835443038,\n" + "379.746835443038: c5~379.746835443038,\n" + "379.746835443038: d5~379.746835443038 + e4^379.746835443038,\n" + "379.746835443038: g5/379.746835443038,\n" + "379.746835443038: b4~379.746835443038 + d4^379.746835443038 + b5/379.746835443038,\n" + "379.746835443038: a4~379.746835443038 + g5/379.746835443038,\n" + "379.746835443038: g4~379.746835443038 + c4^379.746835443038 + a5/379.746835443038,\n" + "379.746835443038: g5/379.746835443038 + a4~379.746835443038 + d4^379.746835443038,\n" + "379.746835443038: g4~379.746835443038,\n" + "379.746835443038: g5/379.746835443038 + e4^379.746835443038,\n" + "379.746835443038: a4~379.746835443038,\n" + "379.746835443038: b5/379.746835443038 + f4^379.746835443038 + b4~379.746835443038,\n" + "379.746835443038: g5/379.746835443038 + c5~379.746835443038,\n" + "379.746835443038: a5/379.746835443038 + e4^379.746835443038 + d5~379.746835443038,\n" + "379.746835443038,\n" + "379.746835443038: d4^379.746835443038 + c5~379.746835443038,\n" + "379.746835443038: g5/379.746835443038 + b4~379.746835443038,\n" + "379.746835443038: c4^379.746835443038 + a4~379.746835443038,\n" + "379.746835443038: b5/379.746835443038 + d4^379.746835443038,\n" + "379.746835443038: g5/379.746835443038 + e4^379.746835443038 + a4~379.746835443038,\n" + "379.746835443038: a5/379.746835443038 + f4^379.746835443038 + b4~379.746835443038", + + "166.66666666666666,\n" + "166.66666666666666: c5-166.66666666666666,\n" + "166.66666666666666: b4-166.66666666666666,\n" + "4833.333333333333", + + "500,\n" + "500: c5~500,\n" + "500: d5~500,\n" + "500: e5~500,\n" + "14000", + + "166.66666666666666,\n" + "166.66666666666666: c5~166.66666666666666,\n" + "166.66666666666666: d5~166.66666666666666,\n" + "166.66666666666666: e5~166.66666666666666,\n" + "166.66666666666666: b4~166.66666666666666,\n" + "166.66666666666666: c5~166.66666666666666,\n" + "166.66666666666666: d5~166.66666666666666,\n" + "166.66666666666666: e5~166.66666666666666,\n" + "166.66666666666666: d5~166.66666666666666,\n" + "166.66666666666666: c5~166.66666666666666,\n" + "166.66666666666666,\n" + "166.66666666666666: b4~166.66666666666666,\n" + "166.66666666666666: e5~166.66666666666666,\n" + "166.66666666666666: c5~166.66666666666666,\n" + "3000", + + "166.66666666666666\n" +}; + +int main(void) { + NoteReadState nrs = {0}; + + for (int i = 0; i < ARR_LEN(examples); i++) { + printf("--- EXAMPLE %d ---\n", i); + + memset(&nrs, 0, sizeof(nrs)); + char *song = examples[i]; + do { + if (nrs.ret.kind == NrsRetKind_Sound) + printf("sound { freq: %d, shape: %d, duration: %f }\n", + nrs.ret.sound.freq, nrs.ret.sound.sound, nrs.ret.duration); + if (nrs.ret.kind == NrsRetKind_Pause) + printf("pause { duration: %fms }\n", nrs.ret.duration); + + printf("%ld chars left\n", strlen(examples[i]) - nrs.i); + } while (tune_parse(&nrs, song)); + } + + return 0; +} diff --git a/src/shared/audio/parse_tune/tone_map.h b/src/shared/audio/parse_tune/tone_map.h new file mode 100644 index 0000000..4b58755 --- /dev/null +++ b/src/shared/audio/parse_tune/tone_map.h @@ -0,0 +1,117 @@ +// this file has been generated by gen_hash.c + +// collision( 1): d#2 <-> b1 +// collision( 2): d#2 <-> b1 +// collision( 3): f3 <-> d#1 +// collision( 4): c5 <-> f#1 +// collision( 5): d#2 <-> b1 +// collision( 6): d#8 <-> d5 +// collision( 7): g#3 <-> a2 +// collision( 8): c5 <-> f#1 +// collision( 9): f3 <-> d#1 +// collision(10): d#2 <-> b1 +// collision(11): d#2 <-> b1 + +// optimal size multiplier is 12! (2136 bytes) + +#include + +static uint64_t fnv1_hash(void *key, int n_bytes) { + unsigned char *p = (unsigned char *)key; + uint64_t h = 14695981039346656037ul; + for (int i = 0; i < n_bytes; i++) + h = (h * 1099511628211) ^ p[i]; + return h; +} + +uint16_t tone_map[1068] = { + [ 407] = 31, + [ 525] = 33, + [ 300] = 35, + [ 768] = 37, + [ 231] = 39, + [ 919] = 41, + [1034] = 44, + [ 877] = 46, + [ 85] = 49, + [ 464] = 52, + [ 291] = 55, + [ 858] = 58, + [ 406] = 62, + [ 526] = 65, + [ 303] = 69, + [ 771] = 73, + [ 228] = 78, + [ 916] = 82, + [1033] = 87, + [ 878] = 93, + [ 86] = 98, + [ 467] = 104, + [ 288] = 110, + [ 857] = 117, + [ 405] = 123, + [ 527] = 131, + [ 302] = 139, + [ 770] = 147, + [ 229] = 156, + [ 917] = 165, + [1032] = 175, + [ 879] = 185, + [ 87] = 196, + [ 466] = 208, + [ 289] = 220, + [ 856] = 233, + [ 404] = 247, + [ 520] = 262, + [ 297] = 277, + [ 773] = 294, + [ 226] = 311, + [ 914] = 330, + [1039] = 349, + [ 880] = 370, + [ 88] = 392, + [ 469] = 415, + [ 294] = 440, + [ 863] = 466, + [ 403] = 494, + [ 521] = 523, + [ 296] = 554, + [ 772] = 587, + [ 227] = 622, + [ 915] = 659, + [1038] = 698, + [ 881] = 740, + [ 89] = 784, + [ 468] = 831, + [ 295] = 880, + [ 862] = 932, + [ 402] = 988, + [ 522] = 1047, + [ 299] = 1109, + [ 775] = 1175, + [ 224] = 1245, + [ 912] = 1319, + [1037] = 1397, + [ 882] = 1480, + [ 90] = 1568, + [ 471] = 1661, + [ 292] = 1760, + [ 861] = 1865, + [ 401] = 1976, + [ 523] = 2093, + [ 298] = 2217, + [ 774] = 2349, + [ 225] = 2489, + [ 913] = 2637, + [1036] = 2794, + [ 883] = 2960, + [ 91] = 3136, + [ 470] = 3322, + [ 293] = 3520, + [ 860] = 3729, + [ 400] = 3951, + [ 532] = 4186, + [ 309] = 4435, + [ 777] = 4699, + [ 238] = 4978, +}; diff --git a/src/shared/audio/piano.c b/src/shared/audio/piano.c new file mode 100644 index 0000000..7979b5b --- /dev/null +++ b/src/shared/audio/piano.c @@ -0,0 +1,222 @@ +// piano ties together our sample table, our note reader, and pico's audio buffer pool + +#include +#include +#include +#include + +#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0])) + +typedef enum { + Sound_Sine, + Sound_Triangle, + Sound_Sawtooth, + Sound_Square, + Sound_COUNT, +} Sound; + +#include "piano.h" +#include "parse_tune/parse_tune.h" + +#define TABLE_LEN 2048 + +typedef struct { + uint32_t step; + Sound sound; + uint32_t pos; +} Note; +static Note i2snote_sound(int freq, Sound sound) { + return (Note) { + .sound = sound, + .step = (freq * TABLE_LEN) / SAMPLES_PER_SECOND * 0x10000, + }; +} + +typedef struct { + uint8_t active; + double times; + + NoteReadState nrs; + void *char_source; + + int samples_into_note; + Note notes[5]; + int notes_len; + int sample_duration; +} Song; + +#define SONG_COUNT 4 +const float sound_weights[Sound_COUNT] = { + [Sound_Sine] = 1.00f, + [Sound_Triangle] = 0.59f, + [Sound_Square] = 0.08f, + [Sound_Sawtooth] = 0.08f +}; +static struct { + int16_t sample_table[Sound_COUNT][TABLE_LEN]; + + Song song[SONG_COUNT]; + + PianoOpts opts; +} piano_state = {0}; + +/** + * char_source is a type erased jerry_value_t (well, doesn't matter what as long as + * opts.song_chars and opts.song_free work on it) + */ +int piano_queue_song(void *char_source, double times) { + for (int ci = 0; ci < SONG_COUNT; ci++) { + Song *song = piano_state.song + ci; + if (!song->active) { + song->active = 1; + song->times = times; + song->char_source = char_source; + return 1; + } + } + return 0; +} + +static void piano_chan_free(Song *song) { + if (song->char_source) piano_state.opts.song_free(song->char_source); + memset(song, 0, sizeof(Song)); + song->active = 0; // just to make sure >:) +} + +int piano_unqueue_song(void *p) { + for (int ci = 0; ci < SONG_COUNT; ci++) { + Song *song = piano_state.song + ci; + + if (song->char_source == p) { + piano_chan_free(song); + return 1; + } + } + + return 0; +} + +int piano_is_song_queued(void *p) { + for (int ci = 0; ci < SONG_COUNT; ci++) { + Song *song = piano_state.song + ci; + + if (song->char_source == p) return 1; + } + + return 0; +} + +void piano_init(PianoOpts opts) { + piano_state.opts = opts; + + // fill sample table + for (int i = 0; i < TABLE_LEN; i++) { + float t = (float)i / (float)TABLE_LEN; + float soundf[Sound_COUNT] = { + [Sound_Sine] = cosf(i * 2 * (float) (M_PI / TABLE_LEN)), + [Sound_Triangle] = (1.0f - 2.0f * 2.0f * fabsf(0.5f - t)), + [Sound_Sawtooth] = (1.0f - 2.0f * 2.0f * fmodf(t, 0.5f)), + [Sound_Square] = (fmodf(t, 0.5f) > 0.25f) ? 1.0f : -1.0f + }; + + for (int s = 0; s < Sound_COUNT; s++) + piano_state.sample_table[s][i] = (int)(soundf[s] * sound_weights[s] * 32767); + } +} + +static int32_t piano_compute_sample(Song *song) { + if (!song->active) return 0; + + song->samples_into_note++; + if (song->samples_into_note >= song->sample_duration) { + song->samples_into_note = 0; + + // clear out all old notes, we got new data + memset(&song->notes, 0, sizeof(song->notes)); + song->notes_len = 0; + + // pull the song into this buf so we can read next chord + char char_source[2048] = {0}; + if (!piano_state.opts.song_chars(song->char_source, char_source, 2048)) { + puts("song exceeds 2k chars, not playing"); + return 0; + } + + while (1) { + if (!tune_parse(&song->nrs, char_source)) { + // Song ended! + + song->nrs = (NoteReadState) {0}; + song->times -= 1.0; + if (song->times <= 0.0) + piano_chan_free(song); + + return 0; + } + + NrsRet *nrs_ret = &song->nrs.ret; + if (nrs_ret->kind != NrsRetKind_None) { + song->sample_duration = (SAMPLES_PER_SECOND / 1000) * nrs_ret->duration; + + // TODO: handle chords (multiple notes per pause) + if (nrs_ret->kind == NrsRetKind_Sound) { + if (song->notes_len < ARR_LEN(song->notes)) { + song->notes[song->notes_len++] = i2snote_sound( + nrs_ret->sound.freq, + nrs_ret->sound.sound + ); + } else { + puts("wow too many notes"); + } + } + if (nrs_ret->kind == NrsRetKind_Pause) { + // chords (and pauses) always end with pauses (unless something goes *horribly* wrong) + break; + } + } + } + } + + int32_t ret = 0; + for (int i = 0; i < song->notes_len; i++) { + Note *note = song->notes + i; + ret += piano_state.sample_table[note->sound][note->pos >> 16u]; + + note->pos += note->step; + + // wrap 'round + const int32_t pos_max = 0x10000 * TABLE_LEN; + if (note->pos >= pos_max) note->pos -= pos_max; + } + return ret; +} + +int32_t remap(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +int32_t clamp(int32_t x, int32_t min, int32_t max) { + if (x < min) return min; + if (x > max) return max; + return x; +} + +void piano_fill_sample_buf(int16_t *samples, int size) { + // fill buffer + for (int i = 0; i < size; i++) { + int32_t sum = 0; + int8_t sample_count = 0; + for (int ci = 0; ci < SONG_COUNT; ci++) { + Song *song = piano_state.song + ci; + if (!song->active) continue; + sum += piano_compute_sample(song); + sample_count += song->notes_len; + } + + if (sample_count != 0) { + samples[i] = sum/sample_count/2; + } else { + samples[i] = 0; + } + } +} diff --git a/src/shared/audio/piano.h b/src/shared/audio/piano.h new file mode 100644 index 0000000..2fc59c7 --- /dev/null +++ b/src/shared/audio/piano.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +float strtof(const char *restrict nptr, char **restrict endptr); + +// gaps in your audio? increase this +#define SAMPLES_PER_BUFFER (256*8) +#define SAMPLES_PER_SECOND 24000 + +typedef struct { + void (*song_free)(void *); + int (*song_chars)(void *p, char *buf, int buf_len); +} PianoOpts; + +void piano_init(PianoOpts); +void piano_fill_sample_buf(int16_t *samples, int size); + +int piano_queue_song(void *, double times); +int piano_unqueue_song(void *p); +int piano_is_song_queued(void *p); diff --git a/src/shared/hi.h b/src/shared/hi.h new file mode 100644 index 0000000..cadb2ee --- /dev/null +++ b/src/shared/hi.h @@ -0,0 +1,93 @@ +unsigned char hi_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x80, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x03, 0x01, 0xf5, 0x4c, 0x00, 0x00, 0x03, + 0xf8, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x98, 0x5b, 0x4f, 0x53, + 0x41, 0x14, 0x46, 0xf9, 0x65, 0xfe, 0x13, 0x7f, 0x81, 0x0f, 0xfa, 0x66, + 0x62, 0x4c, 0x0c, 0x2f, 0xc6, 0x44, 0xd4, 0xa0, 0x46, 0x41, 0x08, 0x42, + 0xc1, 0x42, 0xa1, 0x40, 0x91, 0x52, 0x28, 0x50, 0x14, 0x10, 0xb9, 0x23, + 0xf7, 0x5b, 0x21, 0xd4, 0x52, 0xca, 0x55, 0xa5, 0x48, 0x69, 0x69, 0x29, + 0xfa, 0xd9, 0x49, 0x8e, 0x84, 0x6a, 0xa1, 0xe1, 0x05, 0x74, 0xad, 0x34, + 0xcd, 0xcc, 0x9c, 0x99, 0x33, 0xbb, 0x5d, 0xfb, 0xec, 0x73, 0xda, 0xbc, + 0x1f, 0xf0, 0x4f, 0x93, 0xc7, 0x57, 0x80, 0x60, 0xf8, 0xbf, 0x05, 0x5f, + 0xbf, 0x76, 0xcb, 0xbc, 0x9b, 0x06, 0x5c, 0x5e, 0xc1, 0x46, 0x52, 0x76, + 0x4f, 0x99, 0x73, 0x10, 0x7c, 0x55, 0x05, 0x4f, 0x8d, 0xcf, 0x66, 0x51, + 0x6e, 0x0d, 0x5a, 0x73, 0xf4, 0xd2, 0x12, 0xbe, 0xd3, 0xcb, 0x2b, 0xd8, + 0x32, 0x9a, 0x69, 0x3d, 0xfb, 0x45, 0xac, 0x85, 0x66, 0x2d, 0x5f, 0xe8, + 0x65, 0xbf, 0x07, 0x67, 0x5e, 0x88, 0x99, 0x05, 0xf9, 0x6f, 0x97, 0x35, + 0x82, 0xaf, 0x4c, 0x89, 0xfe, 0x63, 0xf9, 0xcd, 0xac, 0xdb, 0xa7, 0x0a, + 0x32, 0x25, 0xfa, 0xaa, 0x0a, 0xe6, 0x4a, 0xe5, 0x77, 0x30, 0x20, 0x18, + 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x11, 0x0c, 0xff, 0xae, 0xe0, + 0x78, 0x3c, 0xde, 0x58, 0xed, 0x4e, 0x26, 0x93, 0x6a, 0x47, 0x76, 0xf7, + 0x2a, 0x5f, 0xd5, 0xd6, 0x94, 0x37, 0xba, 0x1c, 0x9e, 0xd4, 0x51, 0x6a, + 0x7f, 0x3f, 0x5a, 0x55, 0xea, 0x74, 0x94, 0x37, 0x6a, 0xe4, 0xf0, 0x30, + 0x61, 0xe6, 0x6f, 0x6f, 0xee, 0x68, 0x44, 0x8d, 0xe3, 0xe3, 0xe3, 0x17, + 0x0f, 0x5f, 0x3b, 0x2a, 0x5c, 0x7a, 0xf9, 0xe7, 0x97, 0x35, 0xb2, 0x11, + 0xde, 0x74, 0xbe, 0x69, 0xb6, 0x97, 0xd5, 0xc7, 0x63, 0xf1, 0xd5, 0x40, + 0xc8, 0x1c, 0x52, 0x77, 0xf0, 0xc3, 0x48, 0x32, 0x79, 0xd4, 0xec, 0x6c, + 0x6b, 0xae, 0x6b, 0xab, 0xab, 0x6c, 0x8a, 0x1d, 0xc4, 0xb4, 0x9d, 0xda, + 0xda, 0xd7, 0xe3, 0xea, 0xd4, 0x79, 0xb4, 0xb6, 0xd3, 0xd3, 0x5d, 0x6f, + 0x77, 0xb7, 0x34, 0xb4, 0x6b, 0x5f, 0xa2, 0xca, 0x29, 0xaa, 0x33, 0x04, + 0x0f, 0x7d, 0x1c, 0xbb, 0x7f, 0xbb, 0x30, 0x91, 0xf8, 0x15, 0x53, 0x32, + 0x91, 0x34, 0x6b, 0x7c, 0x6d, 0x3d, 0x9f, 0x57, 0x56, 0xb5, 0x93, 0x50, + 0xb7, 0xef, 0xfd, 0xe0, 0xb2, 0x3f, 0xa0, 0xc6, 0xfe, 0xf7, 0xfd, 0x26, + 0x47, 0x6b, 0xf1, 0x63, 0x9b, 0xda, 0xa9, 0xd4, 0x71, 0xd9, 0x73, 0xfb, + 0xc9, 0x44, 0xb1, 0x15, 0x39, 0xb4, 0xe4, 0xd4, 0xf9, 0xc7, 0x86, 0x26, + 0x16, 0xe7, 0x96, 0x56, 0x96, 0x02, 0x3a, 0x89, 0xba, 0xd3, 0x13, 0x73, + 0x7a, 0x8d, 0x0c, 0xe8, 0xef, 0xec, 0x05, 0x75, 0x07, 0xfb, 0x46, 0xe7, + 0xa6, 0x17, 0xd6, 0x56, 0xc3, 0xda, 0x51, 0xdd, 0xe1, 0xfe, 0x4f, 0x8b, + 0xe9, 0xcf, 0x4f, 0x54, 0xe7, 0x8f, 0xea, 0xec, 0x12, 0x5d, 0x55, 0x52, + 0x67, 0x82, 0xfe, 0x3d, 0x52, 0xea, 0xdc, 0x8b, 0xec, 0x99, 0xb6, 0xaf, + 0xb5, 0x5b, 0x49, 0xaa, 0x68, 0x12, 0x87, 0x89, 0x06, 0xbb, 0x5b, 0xe9, + 0x59, 0x52, 0x58, 0x69, 0x82, 0xbe, 0x7b, 0xe3, 0x81, 0xa7, 0xb1, 0xc3, + 0xfc, 0x61, 0x39, 0x3f, 0xe3, 0xd7, 0xaa, 0xa6, 0xda, 0xd6, 0x0e, 0xcf, + 0x7b, 0xeb, 0x6c, 0x9a, 0x5c, 0x51, 0xe4, 0x30, 0x1f, 0xe9, 0xd5, 0x13, + 0x9b, 0xd2, 0x53, 0x79, 0x17, 0x8d, 0x1e, 0xf4, 0xf7, 0x0e, 0x2b, 0x56, + 0x8d, 0x2b, 0x79, 0xbb, 0xbc, 0xbd, 0xe3, 0x23, 0x53, 0x73, 0xd3, 0x8b, + 0xa6, 0xdb, 0xdb, 0x35, 0x40, 0x54, 0xb9, 0x46, 0x95, 0x9b, 0xe0, 0x1e, + 0x5f, 0xbf, 0x52, 0xc9, 0xea, 0xaa, 0xce, 0x28, 0x13, 0xc3, 0xa1, 0xf5, + 0x56, 0x57, 0xa7, 0xe2, 0x0b, 0x06, 0x42, 0x8f, 0xf2, 0x8b, 0x76, 0xb6, + 0xbe, 0xe8, 0x90, 0xb6, 0x57, 0x34, 0xde, 0xe6, 0x2e, 0x45, 0x3c, 0x3a, + 0x38, 0x31, 0x31, 0x3a, 0xad, 0xc1, 0xd9, 0xa9, 0xf9, 0x1e, 0xdf, 0x47, + 0xb3, 0xb6, 0xbb, 0xa3, 0x4f, 0xd9, 0x6d, 0x4e, 0xd2, 0xee, 0x7e, 0xb7, + 0x16, 0x5a, 0x57, 0xe6, 0xea, 0x0c, 0xaa, 0x4b, 0x8e, 0x74, 0x71, 0x53, + 0x95, 0x53, 0x62, 0x6a, 0xbb, 0x85, 0x59, 0xbf, 0xa6, 0x85, 0x82, 0x6b, + 0xdd, 0x9d, 0x7d, 0x44, 0x95, 0x6b, 0x54, 0xe7, 0x15, 0xac, 0x12, 0xaf, + 0x1c, 0x34, 0x7b, 0x9b, 0xae, 0x69, 0x28, 0xd6, 0xb1, 0xa1, 0xc9, 0x50, + 0x30, 0xac, 0x54, 0xd2, 0xeb, 0xc1, 0x9d, 0x67, 0x6a, 0x5b, 0xcb, 0xd3, + 0x11, 0x8f, 0xab, 0xb0, 0x68, 0x7b, 0x75, 0x37, 0xd7, 0xb7, 0xbc, 0x6f, + 0x7d, 0xe9, 0x1a, 0x15, 0x55, 0x4c, 0x66, 0x8e, 0x0e, 0x99, 0x7b, 0xcf, + 0x5a, 0x30, 0xac, 0x5b, 0x88, 0xb5, 0x56, 0x1f, 0x78, 0x6b, 0x63, 0x5b, + 0x6b, 0x95, 0xa7, 0x56, 0x51, 0x22, 0xaa, 0x5c, 0xa3, 0xca, 0x26, 0xf8, + 0x9d, 0xb7, 0xf7, 0xde, 0xcd, 0x02, 0x77, 0xbd, 0x37, 0xb2, 0x1b, 0xd1, + 0xf6, 0xf9, 0xb7, 0x0a, 0xcc, 0x0d, 0x7f, 0x66, 0x72, 0x3e, 0x18, 0x58, + 0xb5, 0x15, 0xd7, 0xba, 0x1b, 0xda, 0x4b, 0x9f, 0x56, 0x1d, 0x44, 0x63, + 0xd6, 0x12, 0x53, 0x76, 0xf4, 0x98, 0x50, 0xfe, 0xb2, 0xa6, 0x25, 0x7d, + 0x34, 0x76, 0x10, 0xd7, 0x27, 0xd4, 0xf3, 0x85, 0x52, 0x4f, 0x81, 0xee, + 0x7e, 0x8b, 0x68, 0x82, 0x9e, 0x14, 0x34, 0xc7, 0x2c, 0xd1, 0x23, 0x89, + 0xa6, 0xe9, 0xd6, 0x52, 0xfd, 0xba, 0xe1, 0xeb, 0xce, 0x37, 0x8d, 0x68, + 0x61, 0xad, 0xcd, 0xa5, 0xbb, 0xda, 0xaf, 0x0a, 0x76, 0x94, 0xd2, 0x2a, + 0x65, 0xbd, 0x4e, 0x68, 0x9e, 0x50, 0x88, 0xea, 0xfc, 0x51, 0x5d, 0xe8, + 0x67, 0x92, 0x1e, 0x1c, 0x4e, 0x86, 0x7b, 0xe6, 0x51, 0x3d, 0xf5, 0x59, + 0xb9, 0x7c, 0x0a, 0x8d, 0xeb, 0x51, 0x53, 0x77, 0xa3, 0xbf, 0xcd, 0x8c, + 0xc5, 0xe2, 0x44, 0x75, 0xc1, 0xa8, 0xf8, 0x1d, 0xcc, 0x1f, 0x1d, 0x80, + 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, 0x23, 0x18, + 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x04, + 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, + 0x08, 0x46, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, + 0x01, 0xc1, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, + 0x30, 0x20, 0x18, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, + 0x08, 0x06, 0x04, 0x03, 0x82, 0x11, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, + 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, + 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x46, 0x30, 0x5f, 0x01, 0x82, 0x01, + 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x46, 0x30, + 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x08, + 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, + 0x10, 0x8c, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, + 0x03, 0x82, 0x11, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, + 0x60, 0x40, 0x30, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, + 0x10, 0x0c, 0x08, 0x06, 0x04, 0xff, 0x1f, 0xfc, 0x04, 0xd0, 0x2e, 0x1b, + 0x34, 0x16, 0xda, 0xd9, 0x88, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, + 0x44, 0xae, 0x42, 0x60, 0x82 +}; +unsigned int hi_png_len = 1073; diff --git a/src/shared/sprig_engine/base_engine.c b/src/shared/sprig_engine/base_engine.c new file mode 100644 index 0000000..60d0140 --- /dev/null +++ b/src/shared/sprig_engine/base_engine.c @@ -0,0 +1,789 @@ +#include + +#include "shared/ui/errorbuf.h" +#include "shared/ui/font.h" +#include "base_engine.h" + +static State *state = NULL; + +// Get the sign of an integer. Returns -1 if negative, 1 if positive, 0 if 0. +static int sign(int i) { + return (i > 0) - (i < 0); +} + +// Converte color id into palette index. +static uint8_t char_to_palette_index(char c) { + switch (c) { + case '0': return 0; + case 'L': return 1; + case '1': return 2; + case '2': return 3; + case '3': return 4; + case 'C': return 5; + case '7': return 6; + case '5': return 7; + case '6': return 8; + case 'F': return 9; + case '4': return 10; + case 'D': return 11; + case '8': return 12; + case 'H': return 13; + case '9': return 14; + case '.': return 15; + default: return 0; /* lmfao (anything to quiet the voices.) + (i meant clang warnings. same thing) */ + } +} + +/** + * Lots of getter/setters for bitpacked boolean arrays. See: doodles and push tables. + * + * Memory is precious! + */ + +static void doodle_pane_set_bit(uint8_t *pane, int x, int y) { + int i = y*SPRITE_SIZE + x; + pane[i/8] |= 1 << (i % 8); +} +static bool doodle_pane_read(uint8_t *pane, int x, int y) { + int i = y*SPRITE_SIZE + x; + int q = 1 << (i % 8); + return !!(pane[i/8] & q); +} + +static void push_table_set_bit(char x_char, char y_char) { + int x = state->char_to_index[(int) x_char]; + int y = state->char_to_index[(int) y_char]; + + int i = y*state->legend_size + x; + state->push_table[i/8] |= 1 << (i % 8); +} +static bool push_table_read(char x_char, char y_char) { + int x = state->char_to_index[(int) x_char]; + int y = state->char_to_index[(int) y_char]; + + int i = y*state->legend_size + x; + int q = 1 << (i % 8); + return !!(state->push_table[i/8] & q); +} + +// Sprite index to sprite pointer or NULL if index is 0. +static Sprite *get_sprite(uint16_t i) { + if (i == 0) return NULL; + return state->sprite_pool + i - 1; +} + +// Expand the sprite pool! Preserves current sprites. +static void sprite_pool_realloc(int size) { + dbg("reallocating the sprite pool!"); + size_t start_size = state->sprite_pool_size; + + #define realloc_n(arr, os, ns) jerry_realloc((arr), sizeof((arr)[0]) * os, sizeof((arr)[0]) * ns); + state->sprite_slot_active = realloc_n(state->sprite_slot_active , start_size, size); + state->sprite_slot_generation = realloc_n(state->sprite_slot_generation, start_size, size); + state->sprite_pool = realloc_n(state->sprite_pool , start_size, size); + #undef realloc_n + + int worked = state->sprite_slot_active && + state->sprite_slot_generation && + state->sprite_pool ; + // dbg("let's see if it worked ..."); + // dbgf("state->sprite_slot_active = %lu\n", state->sprite_slot_active ); + // dbgf("state->sprite_slot_generation = %lu\n", state->sprite_slot_generation); + // dbgf("state->sprite_pool = %lu\n", state->sprite_pool ); + + if (!worked) { + snprintf( + errorbuf, sizeof(errorbuf), + "%lu sprites (%lu bytes!) is too many to fit on the pico!", + state->sprite_pool_size, + state->sprite_pool_size * sizeof(Sprite) + ); + fatal_error(); + } + + // great, we were able to allocate enough memory + state->sprite_pool_size = size; +} + +// Expand the bitmap pool! Preserves current bitmaps. +static void legend_doodles_realloc(int size) { + size_t start_size = state->legend_size; + + size_t push_table_bytes_old = start_size * start_size / 8; + size_t push_table_bytes_new = size * size / 8; + state->push_table = jerry_realloc(state->push_table, + push_table_bytes_old, + push_table_bytes_new); + + State_Render *sr = state->render; + #define realloc_n(arr, os, ns) jerry_realloc((arr), \ + sizeof((arr)[0]) * os, \ + sizeof((arr)[0]) * ns); + sr->legend = realloc_n(sr->legend , start_size, size); + sr->legend_resized = realloc_n(sr->legend_resized, start_size, size); + #undef realloc_n + + int worked = state->push_table && + sr->legend && + sr->legend_resized ; + + dbgf("state->push_table = %lu\n", state->push_table ); + dbgf(" sr->legend = %lu\n", sr->legend ); + dbgf(" sr->legend_resized = %lu\n", sr->legend_resized ); + + if (!worked) { + snprintf( + errorbuf, sizeof(errorbuf), + "%d bitmaps (%lu bytes!) is too many to fit on the pico!", + state->legend_size, + state->legend_size * sizeof(Doodle) * 2 + push_table_bytes_new + ); + fatal_error(); + } + + // great, we were able to allocate enough memory + state->legend_size = size; +} + +// Add some text to the screen. +static void text_add(char *str, char palette_index, int x, int y) { + int x_initial = x; + for (; *str; str++) { + if (*str == '\n' || x >= (SCREEN_SIZE_X / 8)) { + y++; + x = x_initial; + if (*str == '\n') continue; + } + if (y >= (SCREEN_SIZE_Y / 8)) break; + state->text_char [y][x] = *str; + state->text_color[y][x] = state->render->palette[char_to_palette_index(palette_index)]; + x++; + } +} + +// Clear all text. +static void text_clear(void) { + memset(state->text_char , 0, sizeof(state->text_char )); + memset(state->text_color, 0, sizeof(state->text_color)); +} + +// Initialize the engine. +static void init(void (*sprite_free_cb)(Sprite *)) { + static State _state = {0}; + state = &_state; + + static State_Render _state_render = {0}; + state->render = &_state_render; + + state->sprite_free_cb = sprite_free_cb; + + // -- error handling for when state is dynamically allocated -- + // if (state->render == 0) { + // state->render = malloc(sizeof(State_Render)); + // printf("sizeof(State_Render) = %d, addr: %d\n", sizeof(State_Render), (unsigned int)state->render); + // } + // if (state->render == 0) yell("couldn't alloc state"); + + memset(state->render, 0, sizeof(State_Render)); + + sprite_pool_realloc(512); + legend_doodles_realloc(50); + + // Fill the palette + state->render->palette[char_to_palette_index('0')] = color16( 0, 0, 0); + state->render->palette[char_to_palette_index('L')] = color16( 73, 80, 87); + state->render->palette[char_to_palette_index('1')] = color16(145, 151, 156); + state->render->palette[char_to_palette_index('2')] = color16(248, 249, 250); + state->render->palette[char_to_palette_index('3')] = color16(235, 44, 71); + state->render->palette[char_to_palette_index('C')] = color16(139, 65, 46); + state->render->palette[char_to_palette_index('7')] = color16( 25, 177, 248); + state->render->palette[char_to_palette_index('5')] = color16( 19, 21, 224); + state->render->palette[char_to_palette_index('6')] = color16(254, 230, 16); + state->render->palette[char_to_palette_index('F')] = color16(149, 140, 50); + state->render->palette[char_to_palette_index('4')] = color16( 45, 225, 62); + state->render->palette[char_to_palette_index('D')] = color16( 29, 148, 16); + state->render->palette[char_to_palette_index('8')] = color16(245, 109, 187); + state->render->palette[char_to_palette_index('H')] = color16(170, 58, 197); + state->render->palette[char_to_palette_index('9')] = color16(245, 113, 23); + state->render->palette[char_to_palette_index('.')] = color16( 0, 0, 0); +} + +/* + * Zeroes and returns a reference to temp str memory. Used to pass + * strings between C and JS land. (JANK ALERT!) + */ +static char *temp_str_mem(void) { + memset(&state->temp_str_mem, 0, sizeof(state->temp_str_mem)); + return state->temp_str_mem; +} + +/** + * Resizes all the legend items to fit on screen. + * + * Call this when the map changes size, or when the legend changes. + */ +static void render_resize_legend(void) { + memset(state->render->legend_resized, 0, sizeof(Doodle) * state->legend_size); + + // how big do our tiles need to be to fit them all snugly on screen? + float min_tile_x = SCREEN_SIZE_X / state->width; + float min_tile_y = SCREEN_SIZE_Y / state->height; + state->tile_size = (min_tile_x < min_tile_y) ? min_tile_x : min_tile_y; + if (state->tile_size > 16) + state->tile_size = 16; + + for (int c = 0; c < PER_CHAR; c++) { + if (!state->render->legend_doodled[c]) continue; + int i = state->char_to_index[c]; + + Doodle *rd = state->render->legend_resized + i; + Doodle *od = state->render->legend + i; + + for (int y = 0; y < 16; y++) + for (int x = 0; x < 16; x++) { + int rx = (float) x / 16.0f * state->tile_size; + int ry = (float) y / 16.0f * state->tile_size; + + if (!doodle_pane_read(od->opacity, x, y)) continue; + doodle_pane_set_bit(rd->opacity , rx, ry); + if (doodle_pane_read(od->palette0, x, y)) doodle_pane_set_bit(rd->palette0, rx, ry); + if (doodle_pane_read(od->palette1, x, y)) doodle_pane_set_bit(rd->palette1, rx, ry); + if (doodle_pane_read(od->palette2, x, y)) doodle_pane_set_bit(rd->palette2, rx, ry); + if (doodle_pane_read(od->palette3, x, y)) doodle_pane_set_bit(rd->palette3, rx, ry); + } + } +} + +// Self-explanatory... sets the background sprite. +static void render_set_background(char kind) { + state->background_sprite = kind; +} + +// Bounds of the game area on the screen. +typedef struct { int x, y, width, height, scale; } BoundsRect; + +// Render a pixel! X and Y are screen-space, not game-space. +static Color render_pixel(BoundsRect *game, int x, int y) { + int cx = x / 8; + int cy = y / 8; + char c = state->text_char[cy][cx]; + if (c) { + int px = x % 8; + int py = y % 8; + uint8_t bits = font_pixels[c*8 + py]; + if ((bits >> (7-px)) & 1) + return state->text_color[cy][cx]; + } + + if (game->scale == 0) return color16(0, 0, 0); + + x = (x - game->x) / game->scale; + y = (y - game->y) / game->scale; + if (x < 0 ) return color16(0, 0, 0); + if (y < 0 ) return color16(0, 0, 0); + if (x >= game->width ) return color16(0, 0, 0); + if (y >= game->height) return color16(0, 0, 0); + + if (state->tile_size == 0) return color16(0, 0, 0); + int tx = x / state->tile_size; + int ty = y / state->tile_size; + if (tx >= state->width ) return color16(0, 0, 0); + if (ty >= state->height) return color16(0, 0, 0); + + Sprite *s = get_sprite(state->map[ty*state->width + tx]); + while (1) { + char sprite = (s == 0) ? state->background_sprite : s->kind; + if (sprite == 0) return color16(255, 255, 255); + Doodle *d = state->render->legend_resized + state->char_to_index[sprite]; + + int px = x % state->tile_size; + int py = y % state->tile_size; + if (!doodle_pane_read(d->opacity, px, py)) { + if (s) { + s = get_sprite(s->next); + continue; + } + return color16(255, 255, 255); + }; + return state->render->palette[ + (doodle_pane_read(d->palette0, px, py) << 0) | + (doodle_pane_read(d->palette1, px, py) << 1) | + (doodle_pane_read(d->palette2, px, py) << 2) | + (doodle_pane_read(d->palette3, px, py) << 3) + ]; + } +} + +// Calculate bounds of the game area on the screen. +static void render_calc_bounds(BoundsRect *rect) { + if (!(state->width && state->height)) { + *rect = (BoundsRect){0}; + return; + } + + int scale; + { + int scale_x = SCREEN_SIZE_X/(state->width*16); + int scale_y = SCREEN_SIZE_Y/(state->height*16); + + scale = (scale_x < scale_y) ? scale_x : scale_y; + if (scale < 1) scale = 1; + + state->render->scale = scale; + } + rect->scale = scale; + int size = state->tile_size*scale; + + rect->width = state->width*size; + rect->height = state->height*size; + + rect->x = (SCREEN_SIZE_X - rect->width)/2; + rect->y = (SCREEN_SIZE_Y - rect->height)/2; +} + +// write_pixel will be run in top to bottom, left to right order +static void render(void (*write_pixel)(Color c)) { + BoundsRect rect = {0}; + render_calc_bounds(&rect); + + for (int x = 0; x < 160; x++) + for (int y = 0; y < 128; y++) + write_pixel(render_pixel(&rect, x, y)); +} + +static Sprite *sprite_alloc(void) { + for (int i = 0; i < state->sprite_pool_size; i++) { + if (state->sprite_slot_active[i] == 0) { + state->sprite_slot_active[i] = 1; + return state->sprite_pool + i; + } + } + + sprite_pool_realloc(state->sprite_pool_size * 1.2f); + return sprite_alloc(); +} +static void sprite_free(Sprite *s) { + if (state->sprite_free_cb) state->sprite_free_cb(s); + + memset(s, 0, sizeof(Sprite)); + size_t i = s - state->sprite_pool; + state->sprite_slot_active [i] = 0; + state->sprite_slot_generation[i]++; +} +static bool sprite_is_active(Sprite *s, uint32_t generation) { + if (s == NULL) return 0; + size_t i = s - state->sprite_pool; + return state->sprite_slot_generation[i] == generation; +} +static uint32_t sprite_generation(Sprite *s) { + size_t i = s - state->sprite_pool; + return state->sprite_slot_generation[i]; +} + +/* removes the canonical reference to this sprite from the spatial grid. + * it is your responsibility to subsequently free the sprite. */ +static void sprite_pluck_from_map(Sprite *s) { + Sprite *top = get_sprite(state->map[s->x + s->y * state->width]); + // assert(top != 0); + + if (top == s) { + state->map[s->x + s->y * state->width] = s->next; + return; + } + + for (Sprite *t = top; t->next; t = get_sprite(t->next)) { + if (get_sprite(t->next) == s) { + t->next = s->next; + return; + } + } + + state->map[s->x + s->y * state->width] = 0; +} + +/** + * inserts pointer to sprite into the spritestack at this x and y, + * such that rendering z-order is preserved + * (as expressed in order of legend_doodle_set calls) + * + * see sprite_pluck_from_map about caller's responsibility + */ +static void sprite_plop_into_map(Sprite *sprite) { + Sprite *top = get_sprite(state->map[sprite->x + sprite->y * state->width]); + + // we want the sprite with the lowest z-order on the top. + + #define Z_ORDER(sprite) (state->char_to_index[(int)(sprite)->kind]) + if (top == 0 || Z_ORDER(top) >= Z_ORDER(sprite)) { + sprite->next = state->map[sprite->x + sprite->y * state->width]; + state->map[sprite->x + sprite->y * state->width] = sprite - state->sprite_pool + 1; + // dbg("top's me, early ret"); + return; + } + + Sprite *insert_after = top; + while (insert_after->next && Z_ORDER(get_sprite(insert_after->next)) < Z_ORDER(sprite)) + insert_after = get_sprite(insert_after->next); + #undef Z_ORDER + + sprite->next = insert_after->next; + insert_after->next = sprite - state->sprite_pool + 1; +} + +static Sprite *map_add(int x, int y, char kind) { + if (x < 0 || x >= state->width ) return 0; + if (y < 0 || y >= state->height) return 0; + + Sprite *s = sprite_alloc(); + if (s == 0) return 0; + + *s = (Sprite) { .x = x, .y = y, .kind = kind }; + // dbg("assigned to that mf"); + sprite_plop_into_map(s); + // dbg("stuck 'em on map, returning now"); + return s; +} + +static void map_set(char *str) { + dbg("wormed ya way down into base_engine.c"); + + // figure out how big of an allocation we need to make, if any + int tx = 0, ty = 0; + char *str_dup = str; + do { + switch (*str_dup) { + case ' ': continue; + case '\0': break; + case '\n': ty++, tx = 0; break; + default: tx++; break; + } + } while (*str_dup++); + int old_map_size = state->width * state->height * sizeof(Sprite *); + state->width = tx; + state->height = ty+1; + dbg("parsed, found dims"); + + if (!state->width || !state->height) return; + + // free stuff so we can create new ones + if (state->map != NULL) + jerry_heap_free(state->map, old_map_size); + for (int i = 0; i < state->sprite_pool_size; i++) + sprite_free(state->sprite_pool + i); + dbg("freed some sprites, maybe a map"); + + state->map = jerry_calloc(state->width * state->height, sizeof(Sprite*)); + if (state->map == NULL) { + yell("AAAAAAAAA (map too big)"); + snprintf(errorbuf, sizeof(errorbuf), "map too big to fit in memory (%dx%d)", state->width, state->height); + fatal_error(); + } + + dbg("so we got us a map, time to alloc sprites"); + + tx = 0, ty = 0; + do { + switch (*str) { + case ' ': continue; + case '\n': ty++, tx = 0; break; + case '.': tx++; break; + case '\0': break; + default: { + Sprite *s = sprite_alloc(); + dbg("alloced us a sprite"); + + *s = (Sprite) { .x = tx, .y = ty, .kind = *str }; + dbg("filled in some fields"); + + state->map[tx + ty * state->width] = s - state->sprite_pool + 1; + dbg("put 'em on the map"); + + tx++; + } break; + } + } while (*str++); + + dbg("alrighty, lemme resize a legend"); + + render_resize_legend(); +} + +static int map_width(void) { return state->width; } +static int map_height(void) { return state->height; } + +static Sprite *map_get_first(char kind) { + for (int y = 0; y < state->height; y++) + for (int x = 0; x < state->width; x++) { + Sprite *top = get_sprite(state->map[x + y * state->width]); + for (; top; top = get_sprite(top->next)) + if (top->kind == kind) + return top; + } + return 0; +} + +static uint8_t map_get_grid(MapIter *m) { + if (m->sprite && m->sprite->next) { + m->sprite = get_sprite(m->sprite->next); + return 1; + } + + while (1) { + if (!m->dirty) + m->dirty = 1; + else { + m->x++; + if (m->x >= state->width) { + m->x = 0; + m->y++; + if (m->y >= state->height) return 0; + } + } + + if (state->map[m->x + m->y * state->width]) { + m->sprite = get_sprite(state->map[m->x + m->y * state->width]); + return 1; + } + } +} + +/* you could easily do this in JS, but I suspect there is a + * great perf benefit to avoiding all of the calls back and forth + */ +static uint8_t map_get_all(MapIter *m, char kind) { + while (map_get_grid(m)) + if (m->sprite->kind == kind) + return 1; + return 0; +} + +static uint8_t map_tiles_with(MapIter *m, char *kinds) { + char kinds_needed[255] = {0}; + int kinds_len = 0; + for (; *kinds; kinds++) { + int c = (int)*kinds; + + // filters out duplicates! + if (kinds_needed[c] != 0) continue; + + kinds_len++; + kinds_needed[c] = 1; + } + + while (1) { + if (!m->dirty) + m->dirty = 1; + else { + m->x++; + if (m->x >= state->width) { + m->x = 0; + m->y++; + if (m->y >= state->height) return 0; + } + } + + if (state->map[m->x + m->y * state->width]) { + uint8_t kinds_seen[255] = {0}; + int kinds_found = 0; + + for (Sprite *s = get_sprite(state->map[m->x + m->y * state->width]); + s; + s = get_sprite(s->next) + ) { + kinds_found += kinds_needed[(int)s->kind] && !kinds_seen[(int)s->kind]; + kinds_seen[(int)s->kind] = 1; + } + + if (kinds_found == kinds_len) { + m->sprite = get_sprite(state->map[m->x + m->y * state->width]); + return 1; + } + } + } +} + +static void map_remove(Sprite *s) { + sprite_pluck_from_map(s); + sprite_free(s); +} + +// removes all of the sprites at a given location +static void map_drill(int x, int y) { + if (x < 0 || x >= state->width ) return; + if (y < 0 || y >= state->height) return; + + Sprite *top = get_sprite(state->map[x + y * state->width]); + for (; top; top = get_sprite(top->next)) { + sprite_free(top); + } + state->map[x + y * state->width] = 0; +} + + +/* move a sprite by one unit along the specified axis + * returns how much it was moved on that axis (may be 0 if path obstructed) */ +static int _map_move(Sprite *s, int big_dx, int big_dy) { + int dx = sign(big_dx); + int dy = sign(big_dy); + + // expected input: x and y aren't both 0, either x or y is non-zero (not both) + if (dx == 0 && dy == 0) return 0; + + int prog = 0; + int goal = (abs(big_dx) > abs(big_dy)) ? big_dx : big_dy; + + while (prog != goal) { + int x = s->x+dx; + int y = s->y+dy; + + // no moving off of the map! + if (x < 0) return prog; + if (y < 0) return prog; + if (x >= state->width) return prog; + if (y >= state->height) return prog; + + if (state->solid[(int)s->kind]) { + // no moving into a solid! + Sprite *n = get_sprite(state->map[x + y * state->width]); + + for (; n; n = get_sprite(n->next)) + if (state->solid[(int)n->kind]) { + // unless you can push them out of the way ig + if (push_table_read(s->kind, n->kind)) { + if (_map_move(n, dx, dy) == 0) + return prog; + } + else + return prog; + } + } + + sprite_pluck_from_map(s); + s->x += dx; + s->y += dy; + sprite_plop_into_map(s); + prog += (abs(dx) > abs(dy)) ? dx : dy; + } + + return prog; +} + +static void map_move(Sprite *s, int big_dx, int big_dy) { + int moved = _map_move(s, big_dx, big_dy); + if (big_dx != 0) s->dx = moved; + else s->dy = moved; +} + +static void map_clear_deltas(void) { + for (int y = 0; y < state->height; y++) + for (int x = 0; x < state->width; x++) { + Sprite *top = get_sprite(state->map[x + y * state->width]); + + for (; top; top = get_sprite(top->next)) + top->dx = top->dy = 0; + } +} + +static void solids_push(char c) { + state->solid[(int)c] = 1; +} +static void solids_clear(void) { + memset(&state->solid, 0, sizeof(state->solid)); +} + +static void legend_doodle_set(char kind, char *str) { + + int index = state->char_to_index[(int)kind]; + + // we don't want to increment if index 0 has already been assigned and this is it + if (index == 0 && !state->render->legend_doodled[(int)kind]) { + if (state->render->doodle_index_count >= state->legend_size) + legend_doodles_realloc(state->legend_size * 1.2f); + index = state->render->doodle_index_count++; + } + state->char_to_index[(int)kind] = index; + + state->render->legend_doodled[(int)kind] = 1; + Doodle *d = state->render->legend + index; + dbgf("bouta write to %lu + %d\n", state->render->legend, index); + + int px = 0, py = 0; + do { + switch (*str) { + case '\n': py++, px = 0; break; + case '.': px++; break; + case '\0': break; + default: { + int pi = char_to_palette_index(*str); + if (pi & (1 << 0)) doodle_pane_set_bit(d->palette0, px, py); + if (pi & (1 << 1)) doodle_pane_set_bit(d->palette1, px, py); + if (pi & (1 << 2)) doodle_pane_set_bit(d->palette2, px, py); + if (pi & (1 << 3)) doodle_pane_set_bit(d->palette3, px, py); + doodle_pane_set_bit(d->opacity, px, py); + px++; + } break; + } + } while (*str++); +} +static void legend_clear(void) { + state->render->doodle_index_count = 0; + memset(state->render->legend, 0, sizeof(Doodle) * state->legend_size); + memset(state->render->legend_resized, 0, sizeof(Doodle) * state->legend_size); + memset(&state->render->legend_doodled, 0, sizeof(state->render->legend_doodled)); + memset(&state->char_to_index, 0, sizeof(state->char_to_index)); +} +static void legend_prepare(void) { + if (state->width && state->height) + render_resize_legend(); +} + +static void push_table_set(char pusher, char pushes) { + push_table_set_bit(pusher, pushes); +} +static void push_table_clear(void) { + memset(state->push_table, 0, state->legend_size*state->legend_size/8); +} + +// Render errorbuf to game text. +static void render_errorbuf(void) { + int y = 0; + int x = 0; + for (int i = 0; i < sizeof(errorbuf); i++) { + if (errorbuf[i] == '\0') break; + if (errorbuf[i] == '\n' || x >= (SCREEN_SIZE_X / 8)) { + y++; + x = 0; + if (errorbuf[i] == '\n') continue; + } + if (y >= (SCREEN_SIZE_Y / 8)) break; + state->text_color[y][x] = errorbuf_color; + state->text_char [y][x] = errorbuf[i]; + x++; + } +} + +#if 0 +void text_add(char *str, int x, int y, uint32_t color); +Sprite *sprite_add(int x, int y, char kind); +Sprite *sprite_next(Sprite *s); +int sprite_get_x(Sprite *s); +int sprite_get_y(Sprite *s); +char sprite_get_kind(Sprite *s); +void sprite_set_x(Sprite *s, int x); +void sprite_set_y(Sprite *s, int y); +void sprite_set_kind(Sprite *s, char kind); + +void spritestack_clear(int x, int y); + +void solids_push(char c); +void solids_clear(); + +// setPushables, + +setBackground + +map: _makeTag(text => text), +bitmap: _makeTag(text => text), +tune: _makeTag(text => text), +#endif diff --git a/src/shared/sprig_engine/base_engine.h b/src/shared/sprig_engine/base_engine.h new file mode 100644 index 0000000..7003ec6 --- /dev/null +++ b/src/shared/sprig_engine/base_engine.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#if SPADE_EMBEDDED + static uint16_t color16(uint8_t r, uint8_t b, uint8_t g) { + r = (uint8_t)((float)((float)r / 255.0f) * 31.0f); + g = (uint8_t)((float)((float)g / 255.0f) * 31.0f); + b = (uint8_t)((float)((float)b / 255.0f) * 63.0f); + + // return ((r & 0xf8) << 8) + ((g & 0xfc) << 3) + (b >> 3); + return ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3); + } + + typedef uint16_t Color; +#else + #define color16 MFB_RGB + typedef uint32_t Color; +#endif + +#define ARR_COUNT(arr) (sizeof(arr) / sizeof(arr[0])) + +#define TEXT_CHARS_MAX_X (20) +#define TEXT_CHARS_MAX_Y (16) + +#define SPRITE_SIZE (16) +#define DOODLE_PANE_SIZE (SPRITE_SIZE*SPRITE_SIZE / 8) + +/** + * In-memory representation of a bitmap. + * + * A doodle has 5 "panes" that are packed arrays of bits. + */ +#define SCREEN_SIZE_X (160) +#define SCREEN_SIZE_Y (128) \ No newline at end of file diff --git a/src/shared/sprig_engine/engine.js b/src/shared/sprig_engine/engine.js new file mode 100644 index 0000000..a11b56d --- /dev/null +++ b/src/shared/sprig_engine/engine.js @@ -0,0 +1,219 @@ +let setTimeout, setInterval, clearInterval, clearTimeout; +const { + /* sprite interactions */ setSolids, setPushables, + /* see also: sprite.x +=, sprite.y += */ + + /* art */ setLegend, setBackground, + /* text */ addText, clearText, + + /* spawn sprites */ setMap, addSprite, + /* despawn sprites */ clearTile, /* sprite.remove() */ + + /* tile queries */ getGrid, getTile, getFirst, getAll, tilesWith, + /* see also: sprite.type */ + + /* map dimensions */ width, height, + + /* constructors */ bitmap, tune, map, color, + + /* input handling */ onInput, afterInput, + + /* how much sprite has moved since last onInput: sprite.dx, sprite.dy */ + + playTune, +} = (() => { +const exports = {}; +/* re-exports from C; bottom of module_native.c has notes about why these are in C */ +exports.setMap = map => native.setMap(map.trim()); +exports.addSprite = native.addSprite; +exports.getGrid = native.getGrid; +exports.getTile = native.getTile; +exports.tilesWith = native.tilesWith; +exports.clearTile = native.clearTile; +exports.getFirst = native.getFirst; +exports.getAll = native.getAll; +exports.width = native.width; +exports.height = native.height; +exports.setBackground = native.setBackground; +exports.playTune = (str, times) => { + native.piano_queue_song(str, times); + return { + end: () => native.piano_unqueue_song(str), + isPlaying: () => native.piano_is_song_queued(str) + } +} + +/* opts: x, y, color (all optional) */ +exports.addText = (str, opts={}) => { + const CHARS_MAX_X = 21; + const padLeft = Math.floor((CHARS_MAX_X - str.length)/2); + + for (const char of str.split('')) { + if (" !\"#%&\'()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\^_-`abcdefghijklmnopqrstuvwxyz|~¦§¨©¬®¯°±´¶·¸ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ÙÚÛÜÝÞßàáâãäåæçèéêëìíîïñòóôõö÷ùúûüýþÿĀāĂ㥹ĆćĊċČčĎĐđĒēĖėĘęĚěĞğĠġĦħĪīĮįİıŃńŇňŌōŒœŞşŨũŪūŮůŲųŴŵŶŷŸǍǎǏǐǑǒǓǔˆˇ˘˙˚˛˜˝ẀẁẂẃẄẅỲỳ†‡•…‰⁄™∂∅∏∑−√∞∫≈≠≤≥◊".indexOf(char) === -1) + console.log(`WARN: Character ${char} is no longer in supported in the Sprig editor.`); + } + + native.text_add( + str, + opts.color ?? [10, 10, 40], + opts.x ?? padLeft, + opts.y ?? 0 + ); +} + +exports.clearText = () => native.text_clear(); + + +exports.setLegend = (...bitmaps) => { + native.legend_clear(); + + for (const [key, bitmap] of bitmaps) { + const rows = bitmap.trim().split("\n").map(x => x.trim()) + const rowLengths = rows.map(x => x.length); + const isRect = rowLengths.every(val => val === rowLengths[0]) + if (!isRect) throw new Error(`Bitmap with key ${key} is not rectangular.`) + } + + for (const [charStr, bitmap] of bitmaps) { + native.legend_doodle_set(charStr, bitmap.trim()); + } + native.legend_prepare(); +}; + +exports.setSolids = solids => { + native.solids_clear(); + solids.forEach(native.solids_push); +}; + +exports.setPushables = pushTable => { + native.push_table_clear(); + for (const [pusher, pushesList] of Object.entries(pushTable)) + for (const pushes of pushesList) + native.push_table_set(pusher, pushes); +}; + +let afterInputs = []; +exports.afterInput = fn => (console.log('engine.js:afterInputs'), afterInputs.push(fn)); +// exports.afterInput = fn => afterInputs.push(fn); + +const button = { + pinToHandlers: { + "5": [], + "7": [], + "6": [], + "8": [], + "12": [], + "14": [], + "13": [], + "15": [], + }, + keyToPin: { + "w": "5", + "s": "7", + "a": "6", + "d": "8", + "i": "12", + "k": "14", + "j": "13", + "l": "15", + } +}; + +native.press_cb(pin => { + if (button.pinToHandlers[pin]) + button.pinToHandlers[pin].forEach(f => f()); + + afterInputs.forEach(f => f()); + + native.map_clear_deltas(); +}); + +{ + const timers = {}; + let id = 0; + let firstClearId = -1; + setTimeout = (fn, ms=10) => (timers[id] = { fn, ms }, id++); + setInterval = (fn, ms=10) => (timers[id] = { fn, ms, restartAt: ms }, id++); + clearTimeout = clearInterval = id => { + delete timers[id] + if (id === firstClearId + 1) firstClearId++; + }; + + native.frame_cb(dt => { + /* we'll never need to throw more than one error -ced */ + let errorForLater; + + for (let i = firstClearId + 1; i < id; i++) { + const tim = timers[i]; + if (!tim) continue; + + if (tim.ms <= 0) { + /* trigger their callback */ + try { + tim.fn(); + } catch (error) { + /* we'll never need to throw more than one error -ced */ + if (error && !errorForLater) errorForLater = error; + } + + /* restart intervals, clear timeouts */ + if (tim.restartAt !== undefined) { + tim.ms = tim.restartAt; + } else { + delete timers[i]; + if (i === firstClearId + 1) firstClearId++; + } + } + tim.ms -= dt; + } + + if (errorForLater) throw errorForLater; + }); +} + +exports.onInput = (key, fn) => { + const pin = button.keyToPin[key]; + + if (pin === undefined) + throw new Error(`the sprig doesn't have a "${key}" button!`); + + button.pinToHandlers[pin].push(fn); +}; + +function _makeTag(cb) { + return (strings, ...interps) => { + if (typeof strings === "string") { + throw new Error("Tagged template literal must be used like name`text`, instead of name(`text`)"); + } + const string = strings.reduce((p, c, i) => p + c + (interps[i] ?? ''), ''); + return cb(string); + } +} +exports.bitmap = _makeTag(text => text); +exports.tune = _makeTag(text => text); +exports.map = _makeTag(text => text); +exports.color = _makeTag(text => text); + +// .at polyfill +function at(n) { + // ToInteger() abstract op + n = Math.trunc(n) || 0; + // Allow negative indexing from the end + if (n < 0) n += this.length; + // OOB access is guaranteed to return undefined + if (n < 0 || n >= this.length) return undefined; + // Otherwise, this is just normal property access + return this[n]; +} + +const TypedArray = Reflect.getPrototypeOf(Int8Array); +for (const C of [Array, String, TypedArray]) { + Object.defineProperty(C.prototype, "at", + { value: at, + writable: true, + enumerable: false, + configurable: true }); +} + +return exports; +})(); diff --git a/src/shared/sprig_engine/native_magic_strings.h b/src/shared/sprig_engine/native_magic_strings.h new file mode 100644 index 0000000..942db1c --- /dev/null +++ b/src/shared/sprig_engine/native_magic_strings.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2022 Pico + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __NATIVE_MAGIC_STRINGS_H + #define __NATIVE_MAGIC_STRINGS_H + + #define MSTR_NATIVE_setMap "setMap" + #define MSTR_NATIVE_setBackground "setBackground" + #define MSTR_NATIVE_getFirst "getFirst" + #define MSTR_NATIVE_clearTile "clearTile" + #define MSTR_NATIVE_addSprite "addSprite" + #define MSTR_NATIVE_getTile "getTile" + #define MSTR_NATIVE_getGrid "getGrid" + #define MSTR_NATIVE_tilesWith "tilesWith" + #define MSTR_NATIVE_text_add "text_add" + #define MSTR_NATIVE_text_clear "text_clear" + + #define MSTR_NATIVE_width "width" + #define MSTR_NATIVE_height "height" + #define MSTR_NATIVE_getAll "getAll" + + #define MSTR_NATIVE_button_check "button_check" + + #define MSTR_NATIVE_map_clear_deltas "map_clear_deltas" + + #define MSTR_NATIVE_solids_push "solids_push" + #define MSTR_NATIVE_solids_clear "solids_clear" + + #define MSTR_NATIVE_push_table_set "push_table_set" + #define MSTR_NATIVE_push_table_clear "push_table_clear" + + #define MSTR_NATIVE_legend_doodle_set "legend_doodle_set" + #define MSTR_NATIVE_legend_clear "legend_clear" + #define MSTR_NATIVE_legend_prepare "legend_prepare" + + #define MSTR_NATIVE_press_cb "press_cb" + #define MSTR_NATIVE_frame_cb "frame_cb" + + #define MSTR_NATIVE_piano_queue_song "piano_queue_song" + #define MSTR_NATIVE_piano_unqueue_song "piano_unqueue_song" + #define MSTR_NATIVE_piano_is_song_queued "piano_is_song_queued" +#endif diff --git a/src/shared/sprig_engine/script.h b/src/shared/sprig_engine/script.h new file mode 100644 index 0000000..8ed1bad --- /dev/null +++ b/src/shared/sprig_engine/script.h @@ -0,0 +1,8 @@ +#pragma once +static char engine_script[] = + #ifdef SPADE_EMBEDDED + #include "build/engine.min.js.cstring" + #else + #include "build/engine.js.cstring" + #endif +; \ No newline at end of file diff --git a/src/shared/ui/errorbuf.h b/src/shared/ui/errorbuf.h new file mode 100644 index 0000000..d2b32a6 --- /dev/null +++ b/src/shared/ui/errorbuf.h @@ -0,0 +1,7 @@ +#pragma once + +#include "shared/sprig_engine/base_engine.h" + +extern char errorbuf[512]; // Buffer for error messages (frequently abused for printing anything) +extern Color errorbuf_color; // Color for error messages +static void fatal_error(void); // Call to handle fatal errors diff --git a/src/shared/ui/font.h b/src/shared/ui/font.h new file mode 100644 index 0000000..b24816d --- /dev/null +++ b/src/shared/ui/font.h @@ -0,0 +1,258 @@ +static uint8_t font_pixels[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xFF, // 00 + 0x00, 0x00, 0x22, 0x72, 0x22, 0x3E, 0x00, 0x00, // 01 + 0x00, 0x00, 0x12, 0x32, 0x7E, 0x32, 0x12, 0x00, // 02 + 0x7E, 0x81, 0xB9, 0xA5, 0xB9, 0xA5, 0xB9, 0x81, // 03 + 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, // 04 + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // 05 + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, // 06 + 0x00, 0x00, 0x3C, 0x42, 0x42, 0x7E, 0x00, 0x00, // 07 + 0x00, 0x10, 0x30, 0x7E, 0x30, 0x10, 0x00, 0x00, // 08 + 0x00, 0x08, 0x0C, 0x7E, 0x0C, 0x08, 0x00, 0x00, // 09 + 0x00, 0x10, 0x10, 0x10, 0x7C, 0x38, 0x10, 0x00, // 0A + 0x08, 0x1C, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x00, // 0B + 0x38, 0x30, 0x28, 0x08, 0x08, 0x08, 0x3E, 0x00, // 0C + 0x00, 0x00, 0x12, 0x32, 0x7E, 0x30, 0x10, 0x00, // 0D + 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, // 0E + 0x3E, 0x7C, 0x7C, 0x3E, 0x3E, 0x7C, 0xF8, 0xF8, // 0F + 0x38, 0x30, 0x28, 0x04, 0x04, 0x04, 0x04, 0x00, // 10 + 0x7F, 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x08, 0x00, // 11 + 0x00, 0x08, 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x7F, // 12 + 0x7E, 0x81, 0x9D, 0xA1, 0xB9, 0x85, 0x85, 0xB9, // 13 + 0x00, 0x3C, 0x42, 0x5A, 0x5A, 0x42, 0x3C, 0x00, // 14 + 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11, // 15 + 0x00, 0x7F, 0x22, 0x72, 0x27, 0x22, 0x7F, 0x00, // 16 + 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, // 17 + 0x00, 0x01, 0x09, 0x0D, 0x7F, 0x0D, 0x09, 0x01, // 18 + 0x00, 0x90, 0xB0, 0xFE, 0xB0, 0x90, 0x00, 0x00, // 19 + 0x00, 0x08, 0x7C, 0x06, 0x7C, 0x08, 0x00, 0x00, // 1A + 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, // 1B + 0x7E, 0x81, 0xA1, 0xA1, 0xA1, 0xA1, 0xBD, 0x81, // 1C + 0x7E, 0x81, 0xB9, 0xA5, 0xB9, 0xA5, 0xA5, 0x81, // 1D + 0x7E, 0x81, 0x99, 0xA1, 0xA1, 0xA1, 0x99, 0x81, // 1E + 0x00, 0x10, 0x3E, 0x60, 0x3E, 0x10, 0x00, 0x00, // 1F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 + 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x00, // 21 + 0x77, 0x33, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, // 22 + 0x36, 0x36, 0xFE, 0x6C, 0xFE, 0xD8, 0xD8, 0x00, // 23 + 0x18, 0x3E, 0x6C, 0x3E, 0x1B, 0x1B, 0x7E, 0x18, // 24 + 0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, // 25 + 0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00, // 26 + 0x1C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 27 + 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, // 28 + 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, // 29 + 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, // 2A + 0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, // 2B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x0C, 0x18, // 2C + 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, // 2D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, // 2E + 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, // 2F + 0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0x7C, 0x00, // 30 + 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, // 31 + 0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00, // 32 + 0xFC, 0x18, 0x30, 0x78, 0x0C, 0xCC, 0x78, 0x00, // 33 + 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00, // 34 + 0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, // 35 + 0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00, // 36 + 0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, // 37 + 0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00, // 38 + 0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00, // 39 + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, // 3A + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, // 3B + 0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00, // 3C + 0x00, 0x00, 0xFC, 0x00, 0xFC, 0x00, 0x00, 0x00, // 3D + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, // 3E + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, // 3F + 0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x78, 0x00, // 40 + 0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00, // 41 + 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, // 42 + 0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, // 43 + 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, // 44 + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00, // 45 + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00, // 46 + 0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3C, 0x00, // 47 + 0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00, // 48 + 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, // 49 + 0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, // 4A + 0xE6, 0x66, 0x6C, 0x70, 0x6C, 0x66, 0xE6, 0x00, // 4B + 0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, // 4C + 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00, // 4D + 0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, // 4E + 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, // 4F + 0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, // 50 + 0x78, 0xCC, 0xCC, 0xCC, 0xDC, 0x78, 0x1C, 0x00, // 51 + 0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0xE6, 0x00, // 52 + 0x7C, 0xC6, 0xF0, 0x3C, 0x0E, 0xC6, 0x7C, 0x00, // 53 + 0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, // 54 + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x00, // 55 + 0xCC, 0xCC, 0xCC, 0x78, 0x78, 0x30, 0x30, 0x00, // 56 + 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, // 57 + 0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00, // 58 + 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x78, 0x00, // 59 + 0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00, // 5A + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 5B + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 5C + 0x00, 0xFE, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, // 5D + 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, // 5E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // 5F + 0x3C, 0x42, 0x99, 0xA1, 0xA1, 0x99, 0x42, 0x3C, // 60 + 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, // 61 + 0xE0, 0x60, 0x7C, 0x66, 0x66, 0x66, 0xDC, 0x00, // 62 + 0x00, 0x00, 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x00, // 63 + 0x1C, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, // 64 + 0x00, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, // 65 + 0x38, 0x6C, 0x60, 0xF0, 0x60, 0x60, 0xF0, 0x00, // 66 + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, // 67 + 0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00, // 68 + 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0xFC, 0x00, // 69 + 0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, // 6A + 0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00, // 6B + 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, // 6C + 0x00, 0x00, 0xCC, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, // 6D + 0x00, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, // 6E + 0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, // 6F + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, // 70 + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E, // 71 + 0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0xF0, 0x00, // 72 + 0x00, 0x00, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x00, // 73 + 0x10, 0x30, 0x7C, 0x30, 0x30, 0x34, 0x18, 0x00, // 74 + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, // 75 + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, // 76 + 0x00, 0x00, 0xC6, 0xD6, 0xFE, 0xFE, 0x6C, 0x00, // 77 + 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, // 78 + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, // 79 + 0x00, 0x00, 0xFC, 0x98, 0x30, 0x64, 0xFC, 0x00, // 7A + 0x6C, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, // 7B + 0xCC, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, // 7C + 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, // 7D + 0x3C, 0x66, 0x66, 0x6C, 0x66, 0x66, 0x6C, 0xF0, // 7E + 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, // 7F + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x00, // 80 + 0xFF, 0xFF, 0xDD, 0x8D, 0xDD, 0xC1, 0xFF, 0xFF, // 81 + 0xFF, 0xFF, 0xED, 0xCD, 0x81, 0xCD, 0xED, 0xFF, // 82 + 0x81, 0x7E, 0x46, 0x5A, 0x46, 0x5A, 0x46, 0x7E, // 83 + 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, // 84 + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // 85 + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, // 86 + 0xFF, 0xFF, 0xC3, 0xBD, 0xBD, 0x81, 0xFF, 0xFF, // 87 + 0xFF, 0xEF, 0xCF, 0x81, 0xCF, 0xEF, 0xFF, 0xFF, // 88 + 0xFF, 0xF7, 0xF3, 0x81, 0xF3, 0xF7, 0xFF, 0xFF, // 89 + 0xFF, 0xEF, 0xEF, 0xEF, 0x83, 0xC7, 0xEF, 0xFF, // 8A + 0xF7, 0xE3, 0xC1, 0xF7, 0xF7, 0xF7, 0xF7, 0xFF, // 8B + 0xC7, 0xCF, 0xD7, 0xF7, 0xF7, 0xF7, 0xC1, 0xFF, // 8C + 0xFF, 0xFF, 0xED, 0xCD, 0x81, 0xCF, 0xEF, 0xFF, // 8D + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 8E + 0xC1, 0x83, 0x83, 0xC1, 0xC1, 0x83, 0x07, 0x07, // 8F + 0xC7, 0xCF, 0xD7, 0xFB, 0xFB, 0xFB, 0xFB, 0xFF, // 90 + 0x80, 0xF7, 0xE3, 0xD5, 0xF7, 0xF7, 0xF7, 0xFF, // 91 + 0xFF, 0xF7, 0xF7, 0xF7, 0xD5, 0xE3, 0xF7, 0x80, // 92 + 0x81, 0x7E, 0x62, 0x5E, 0x46, 0x7A, 0x7A, 0x46, // 93 + 0xFF, 0xC3, 0xBD, 0xA5, 0xA5, 0xBD, 0xC3, 0xFF, // 94 + 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE, // 95 + 0xFF, 0x80, 0xDD, 0x8D, 0xD8, 0xDD, 0x80, 0xFF, // 96 + 0xEE, 0xDD, 0xBB, 0x77, 0xEE, 0xDD, 0xBB, 0x77, // 97 + 0xFF, 0xFE, 0xF6, 0xF2, 0x80, 0xF2, 0xF6, 0xFE, // 98 + 0xFF, 0x6F, 0x4F, 0x01, 0x4F, 0x6F, 0xFF, 0xFF, // 99 + 0xFF, 0xF7, 0x83, 0xF9, 0x83, 0xF7, 0xFF, 0xFF, // 9A + 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, // 9B + 0x81, 0x7E, 0x5E, 0x5E, 0x5E, 0x5E, 0x42, 0x7E, // 9C + 0x81, 0x7E, 0x46, 0x5A, 0x46, 0x5A, 0x5A, 0x7E, // 9D + 0x81, 0x7E, 0x66, 0x5E, 0x5E, 0x5E, 0x66, 0x7E, // 9E + 0xFF, 0xEF, 0xC1, 0x9F, 0xC1, 0xEF, 0xFF, 0xFF, // 9F + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A0 + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xFF, 0xCF, 0xFF, // A1 + 0x88, 0xCC, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A2 + 0xC9, 0xC9, 0x01, 0x93, 0x01, 0x27, 0x27, 0xFF, // A3 + 0xE7, 0xC1, 0x93, 0xC1, 0xE4, 0xE4, 0x81, 0xE7, // A4 + 0xFF, 0x39, 0x33, 0xE7, 0xCF, 0x99, 0x39, 0xFF, // A5 + 0xC7, 0x93, 0xC7, 0x89, 0x23, 0x33, 0x89, 0xFF, // A6 + 0xE3, 0xF3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A7 + 0xE7, 0xCF, 0x9F, 0x9F, 0x9F, 0xCF, 0xE7, 0xFF, // A8 + 0x9F, 0xCF, 0xE7, 0xE7, 0xE7, 0xCF, 0x9F, 0xFF, // A9 + 0xFF, 0x99, 0xC3, 0x00, 0xC3, 0x99, 0xFF, 0xFF, // AA + 0xFF, 0xCF, 0xCF, 0x03, 0xCF, 0xCF, 0xFF, 0xFF, // AB + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xF3, 0xE7, // AC + 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, // AD + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xCF, 0xFF, // AE + 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3F, 0x7F, 0xFF, // AF + 0x83, 0x39, 0x31, 0x21, 0x09, 0x19, 0x83, 0xFF, // B0 + 0xCF, 0x8F, 0xCF, 0xCF, 0xCF, 0xCF, 0x03, 0xFF, // B1 + 0x87, 0x33, 0xF3, 0xC7, 0x9F, 0x33, 0x03, 0xFF, // B2 + 0x03, 0xE7, 0xCF, 0x87, 0xF3, 0x33, 0x87, 0xFF, // B3 + 0xE3, 0xC3, 0x93, 0x33, 0x01, 0xF3, 0xE1, 0xFF, // B4 + 0x03, 0x3F, 0x07, 0xF3, 0xF3, 0x33, 0x87, 0xFF, // B5 + 0xC7, 0x9F, 0x3F, 0x07, 0x33, 0x33, 0x87, 0xFF, // B6 + 0x03, 0x33, 0xF3, 0xE7, 0xCF, 0xCF, 0xCF, 0xFF, // B7 + 0x87, 0x33, 0x33, 0x87, 0x33, 0x33, 0x87, 0xFF, // B8 + 0x87, 0x33, 0x33, 0x83, 0xF3, 0xE7, 0x8F, 0xFF, // B9 + 0xFF, 0xFF, 0xCF, 0xCF, 0xFF, 0xCF, 0xCF, 0xFF, // BA + 0xFF, 0xFF, 0xCF, 0xCF, 0xFF, 0xCF, 0xCF, 0x9F, // BB + 0xE7, 0xCF, 0x9F, 0x3F, 0x9F, 0xCF, 0xE7, 0xFF, // BC + 0xFF, 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, // BD + 0x9F, 0xCF, 0xE7, 0xF3, 0xE7, 0xCF, 0x9F, 0xFF, // BE + 0x87, 0x33, 0xF3, 0xE7, 0xCF, 0xFF, 0xCF, 0xFF, // BF + 0x83, 0x39, 0x21, 0x21, 0x21, 0x3F, 0x87, 0xFF, // C0 + 0xCF, 0x87, 0x33, 0x33, 0x03, 0x33, 0x33, 0xFF, // C1 + 0x03, 0x99, 0x99, 0x83, 0x99, 0x99, 0x03, 0xFF, // C2 + 0xC3, 0x99, 0x3F, 0x3F, 0x3F, 0x99, 0xC3, 0xFF, // C3 + 0x07, 0x93, 0x99, 0x99, 0x99, 0x93, 0x07, 0xFF, // C4 + 0x01, 0x9D, 0x97, 0x87, 0x97, 0x9D, 0x01, 0xFF, // C5 + 0x01, 0x9D, 0x97, 0x87, 0x97, 0x9F, 0x0F, 0xFF, // C6 + 0xC3, 0x99, 0x3F, 0x3F, 0x31, 0x99, 0xC3, 0xFF, // C7 + 0x33, 0x33, 0x33, 0x03, 0x33, 0x33, 0x33, 0xFF, // C8 + 0x87, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0x87, 0xFF, // C9 + 0xE1, 0xF3, 0xF3, 0xF3, 0x33, 0x33, 0x87, 0xFF, // CA + 0x19, 0x99, 0x93, 0x8F, 0x93, 0x99, 0x19, 0xFF, // CB + 0x0F, 0x9F, 0x9F, 0x9F, 0x9D, 0x99, 0x01, 0xFF, // CC + 0x39, 0x11, 0x01, 0x29, 0x39, 0x39, 0x39, 0xFF, // CD + 0x39, 0x19, 0x09, 0x21, 0x31, 0x39, 0x39, 0xFF, // CE + 0xC7, 0x93, 0x39, 0x39, 0x39, 0x93, 0xC7, 0xFF, // CF + 0x03, 0x99, 0x99, 0x83, 0x9F, 0x9F, 0x0F, 0xFF, // D0 + 0x87, 0x33, 0x33, 0x33, 0x23, 0x87, 0xE3, 0xFF, // D1 + 0x03, 0x99, 0x99, 0x83, 0x93, 0x99, 0x19, 0xFF, // D2 + 0x83, 0x39, 0x0F, 0xC3, 0xF1, 0x39, 0x83, 0xFF, // D3 + 0x03, 0x4B, 0xCF, 0xCF, 0xCF, 0xCF, 0x87, 0xFF, // D4 + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x87, 0xFF, // D5 + 0x33, 0x33, 0x33, 0x87, 0x87, 0xCF, 0xCF, 0xFF, // D6 + 0x39, 0x39, 0x39, 0x29, 0x01, 0x11, 0x39, 0xFF, // D7 + 0x39, 0x39, 0x93, 0xC7, 0x93, 0x39, 0x39, 0xFF, // D8 + 0x33, 0x33, 0x33, 0x87, 0xCF, 0xCF, 0x87, 0xFF, // D9 + 0x01, 0x39, 0x73, 0xE7, 0xCD, 0x99, 0x01, 0xFF, // DA + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DB + 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, // DC + 0xFF, 0x01, 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, // DD + 0xEF, 0xC7, 0x93, 0x39, 0xFF, 0xFF, 0xFF, 0xFF, // DE + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // DF + 0xC3, 0xBD, 0x66, 0x5E, 0x5E, 0x66, 0xBD, 0xC3, // E0 + 0xFF, 0xFF, 0x87, 0xF3, 0x83, 0x33, 0x89, 0xFF, // E1 + 0x1F, 0x9F, 0x83, 0x99, 0x99, 0x99, 0x23, 0xFF, // E2 + 0xFF, 0xFF, 0x87, 0x33, 0x3F, 0x33, 0x87, 0xFF, // E3 + 0xE3, 0xF3, 0x83, 0x33, 0x33, 0x33, 0x89, 0xFF, // E4 + 0xFF, 0xFF, 0x87, 0x33, 0x03, 0x3F, 0x87, 0xFF, // E5 + 0xC7, 0x93, 0x9F, 0x0F, 0x9F, 0x9F, 0x0F, 0xFF, // E6 + 0xFF, 0xFF, 0x89, 0x33, 0x33, 0x83, 0xF3, 0x07, // E7 + 0x1F, 0x9F, 0x93, 0x89, 0x99, 0x99, 0x19, 0xFF, // E8 + 0xCF, 0xFF, 0x8F, 0xCF, 0xCF, 0xCF, 0x03, 0xFF, // E9 + 0xF3, 0xFF, 0xE3, 0xF3, 0xF3, 0x33, 0x33, 0x87, // EA + 0x1F, 0x9F, 0x99, 0x93, 0x87, 0x93, 0x19, 0xFF, // EB + 0x8F, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0x03, 0xFF, // EC + 0xFF, 0xFF, 0x33, 0x01, 0x01, 0x29, 0x39, 0xFF, // ED + 0xFF, 0xFF, 0x07, 0x33, 0x33, 0x33, 0x33, 0xFF, // EE + 0xFF, 0xFF, 0x87, 0x33, 0x33, 0x33, 0x87, 0xFF, // EF + 0xFF, 0xFF, 0x23, 0x99, 0x99, 0x83, 0x9F, 0x0F, // F0 + 0xFF, 0xFF, 0x89, 0x33, 0x33, 0x83, 0xF3, 0xE1, // F1 + 0xFF, 0xFF, 0x23, 0x89, 0x99, 0x9F, 0x0F, 0xFF, // F2 + 0xFF, 0xFF, 0x83, 0x3F, 0x87, 0xF3, 0x07, 0xFF, // F3 + 0xEF, 0xCF, 0x83, 0xCF, 0xCF, 0xCB, 0xE7, 0xFF, // F4 + 0xFF, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x89, 0xFF, // F5 + 0xFF, 0xFF, 0x33, 0x33, 0x33, 0x87, 0xCF, 0xFF, // F6 + 0xFF, 0xFF, 0x39, 0x29, 0x01, 0x01, 0x93, 0xFF, // F7 + 0xFF, 0xFF, 0x39, 0x93, 0xC7, 0x93, 0x39, 0xFF, // F8 + 0xFF, 0xFF, 0x33, 0x33, 0x33, 0x83, 0xF3, 0x07, // F9 + 0xFF, 0xFF, 0x03, 0x67, 0xCF, 0x9B, 0x03, 0xFF, // FA + 0x93, 0xFF, 0x87, 0xF3, 0x83, 0x33, 0x89, 0xFF, // FB + 0x33, 0xFF, 0x87, 0x33, 0x33, 0x33, 0x87, 0xFF, // FC + 0x33, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x89, 0xFF, // FD + 0xC3, 0x99, 0x99, 0x93, 0x99, 0x99, 0x93, 0x0F, // FE + 0x00, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x00, // FF +}; diff --git a/src/shared/version.h b/src/shared/version.h new file mode 100644 index 0000000..63f2224 --- /dev/null +++ b/src/shared/version.h @@ -0,0 +1,3 @@ +#pragma once + +#define SPADE_VERSION "1.0.0" diff --git a/src/shared/version.h.in b/src/shared/version.h.in new file mode 100644 index 0000000..4aec485 --- /dev/null +++ b/src/shared/version.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define SPADE_VERSION "@VERSION@" \ No newline at end of file diff --git a/src/version.json b/src/version.json new file mode 100644 index 0000000..688e939 --- /dev/null +++ b/src/version.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/tools/Untitled.png b/tools/Untitled.png new file mode 100644 index 0000000..1eecda6 Binary files /dev/null and b/tools/Untitled.png differ diff --git a/tools/hi.png b/tools/hi.png new file mode 100644 index 0000000..74fd464 Binary files /dev/null and b/tools/hi.png differ diff --git a/tools/output_data.h b/tools/output_data.h new file mode 100644 index 0000000..cadb2ee --- /dev/null +++ b/tools/output_data.h @@ -0,0 +1,93 @@ +unsigned char hi_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x80, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x03, 0x01, 0xf5, 0x4c, 0x00, 0x00, 0x03, + 0xf8, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x98, 0x5b, 0x4f, 0x53, + 0x41, 0x14, 0x46, 0xf9, 0x65, 0xfe, 0x13, 0x7f, 0x81, 0x0f, 0xfa, 0x66, + 0x62, 0x4c, 0x0c, 0x2f, 0xc6, 0x44, 0xd4, 0xa0, 0x46, 0x41, 0x08, 0x42, + 0xc1, 0x42, 0xa1, 0x40, 0x91, 0x52, 0x28, 0x50, 0x14, 0x10, 0xb9, 0x23, + 0xf7, 0x5b, 0x21, 0xd4, 0x52, 0xca, 0x55, 0xa5, 0x48, 0x69, 0x69, 0x29, + 0xfa, 0xd9, 0x49, 0x8e, 0x84, 0x6a, 0xa1, 0xe1, 0x05, 0x74, 0xad, 0x34, + 0xcd, 0xcc, 0x9c, 0x99, 0x33, 0xbb, 0x5d, 0xfb, 0xec, 0x73, 0xda, 0xbc, + 0x1f, 0xf0, 0x4f, 0x93, 0xc7, 0x57, 0x80, 0x60, 0xf8, 0xbf, 0x05, 0x5f, + 0xbf, 0x76, 0xcb, 0xbc, 0x9b, 0x06, 0x5c, 0x5e, 0xc1, 0x46, 0x52, 0x76, + 0x4f, 0x99, 0x73, 0x10, 0x7c, 0x55, 0x05, 0x4f, 0x8d, 0xcf, 0x66, 0x51, + 0x6e, 0x0d, 0x5a, 0x73, 0xf4, 0xd2, 0x12, 0xbe, 0xd3, 0xcb, 0x2b, 0xd8, + 0x32, 0x9a, 0x69, 0x3d, 0xfb, 0x45, 0xac, 0x85, 0x66, 0x2d, 0x5f, 0xe8, + 0x65, 0xbf, 0x07, 0x67, 0x5e, 0x88, 0x99, 0x05, 0xf9, 0x6f, 0x97, 0x35, + 0x82, 0xaf, 0x4c, 0x89, 0xfe, 0x63, 0xf9, 0xcd, 0xac, 0xdb, 0xa7, 0x0a, + 0x32, 0x25, 0xfa, 0xaa, 0x0a, 0xe6, 0x4a, 0xe5, 0x77, 0x30, 0x20, 0x18, + 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x11, 0x0c, 0xff, 0xae, 0xe0, + 0x78, 0x3c, 0xde, 0x58, 0xed, 0x4e, 0x26, 0x93, 0x6a, 0x47, 0x76, 0xf7, + 0x2a, 0x5f, 0xd5, 0xd6, 0x94, 0x37, 0xba, 0x1c, 0x9e, 0xd4, 0x51, 0x6a, + 0x7f, 0x3f, 0x5a, 0x55, 0xea, 0x74, 0x94, 0x37, 0x6a, 0xe4, 0xf0, 0x30, + 0x61, 0xe6, 0x6f, 0x6f, 0xee, 0x68, 0x44, 0x8d, 0xe3, 0xe3, 0xe3, 0x17, + 0x0f, 0x5f, 0x3b, 0x2a, 0x5c, 0x7a, 0xf9, 0xe7, 0x97, 0x35, 0xb2, 0x11, + 0xde, 0x74, 0xbe, 0x69, 0xb6, 0x97, 0xd5, 0xc7, 0x63, 0xf1, 0xd5, 0x40, + 0xc8, 0x1c, 0x52, 0x77, 0xf0, 0xc3, 0x48, 0x32, 0x79, 0xd4, 0xec, 0x6c, + 0x6b, 0xae, 0x6b, 0xab, 0xab, 0x6c, 0x8a, 0x1d, 0xc4, 0xb4, 0x9d, 0xda, + 0xda, 0xd7, 0xe3, 0xea, 0xd4, 0x79, 0xb4, 0xb6, 0xd3, 0xd3, 0x5d, 0x6f, + 0x77, 0xb7, 0x34, 0xb4, 0x6b, 0x5f, 0xa2, 0xca, 0x29, 0xaa, 0x33, 0x04, + 0x0f, 0x7d, 0x1c, 0xbb, 0x7f, 0xbb, 0x30, 0x91, 0xf8, 0x15, 0x53, 0x32, + 0x91, 0x34, 0x6b, 0x7c, 0x6d, 0x3d, 0x9f, 0x57, 0x56, 0xb5, 0x93, 0x50, + 0xb7, 0xef, 0xfd, 0xe0, 0xb2, 0x3f, 0xa0, 0xc6, 0xfe, 0xf7, 0xfd, 0x26, + 0x47, 0x6b, 0xf1, 0x63, 0x9b, 0xda, 0xa9, 0xd4, 0x71, 0xd9, 0x73, 0xfb, + 0xc9, 0x44, 0xb1, 0x15, 0x39, 0xb4, 0xe4, 0xd4, 0xf9, 0xc7, 0x86, 0x26, + 0x16, 0xe7, 0x96, 0x56, 0x96, 0x02, 0x3a, 0x89, 0xba, 0xd3, 0x13, 0x73, + 0x7a, 0x8d, 0x0c, 0xe8, 0xef, 0xec, 0x05, 0x75, 0x07, 0xfb, 0x46, 0xe7, + 0xa6, 0x17, 0xd6, 0x56, 0xc3, 0xda, 0x51, 0xdd, 0xe1, 0xfe, 0x4f, 0x8b, + 0xe9, 0xcf, 0x4f, 0x54, 0xe7, 0x8f, 0xea, 0xec, 0x12, 0x5d, 0x55, 0x52, + 0x67, 0x82, 0xfe, 0x3d, 0x52, 0xea, 0xdc, 0x8b, 0xec, 0x99, 0xb6, 0xaf, + 0xb5, 0x5b, 0x49, 0xaa, 0x68, 0x12, 0x87, 0x89, 0x06, 0xbb, 0x5b, 0xe9, + 0x59, 0x52, 0x58, 0x69, 0x82, 0xbe, 0x7b, 0xe3, 0x81, 0xa7, 0xb1, 0xc3, + 0xfc, 0x61, 0x39, 0x3f, 0xe3, 0xd7, 0xaa, 0xa6, 0xda, 0xd6, 0x0e, 0xcf, + 0x7b, 0xeb, 0x6c, 0x9a, 0x5c, 0x51, 0xe4, 0x30, 0x1f, 0xe9, 0xd5, 0x13, + 0x9b, 0xd2, 0x53, 0x79, 0x17, 0x8d, 0x1e, 0xf4, 0xf7, 0x0e, 0x2b, 0x56, + 0x8d, 0x2b, 0x79, 0xbb, 0xbc, 0xbd, 0xe3, 0x23, 0x53, 0x73, 0xd3, 0x8b, + 0xa6, 0xdb, 0xdb, 0x35, 0x40, 0x54, 0xb9, 0x46, 0x95, 0x9b, 0xe0, 0x1e, + 0x5f, 0xbf, 0x52, 0xc9, 0xea, 0xaa, 0xce, 0x28, 0x13, 0xc3, 0xa1, 0xf5, + 0x56, 0x57, 0xa7, 0xe2, 0x0b, 0x06, 0x42, 0x8f, 0xf2, 0x8b, 0x76, 0xb6, + 0xbe, 0xe8, 0x90, 0xb6, 0x57, 0x34, 0xde, 0xe6, 0x2e, 0x45, 0x3c, 0x3a, + 0x38, 0x31, 0x31, 0x3a, 0xad, 0xc1, 0xd9, 0xa9, 0xf9, 0x1e, 0xdf, 0x47, + 0xb3, 0xb6, 0xbb, 0xa3, 0x4f, 0xd9, 0x6d, 0x4e, 0xd2, 0xee, 0x7e, 0xb7, + 0x16, 0x5a, 0x57, 0xe6, 0xea, 0x0c, 0xaa, 0x4b, 0x8e, 0x74, 0x71, 0x53, + 0x95, 0x53, 0x62, 0x6a, 0xbb, 0x85, 0x59, 0xbf, 0xa6, 0x85, 0x82, 0x6b, + 0xdd, 0x9d, 0x7d, 0x44, 0x95, 0x6b, 0x54, 0xe7, 0x15, 0xac, 0x12, 0xaf, + 0x1c, 0x34, 0x7b, 0x9b, 0xae, 0x69, 0x28, 0xd6, 0xb1, 0xa1, 0xc9, 0x50, + 0x30, 0xac, 0x54, 0xd2, 0xeb, 0xc1, 0x9d, 0x67, 0x6a, 0x5b, 0xcb, 0xd3, + 0x11, 0x8f, 0xab, 0xb0, 0x68, 0x7b, 0x75, 0x37, 0xd7, 0xb7, 0xbc, 0x6f, + 0x7d, 0xe9, 0x1a, 0x15, 0x55, 0x4c, 0x66, 0x8e, 0x0e, 0x99, 0x7b, 0xcf, + 0x5a, 0x30, 0xac, 0x5b, 0x88, 0xb5, 0x56, 0x1f, 0x78, 0x6b, 0x63, 0x5b, + 0x6b, 0x95, 0xa7, 0x56, 0x51, 0x22, 0xaa, 0x5c, 0xa3, 0xca, 0x26, 0xf8, + 0x9d, 0xb7, 0xf7, 0xde, 0xcd, 0x02, 0x77, 0xbd, 0x37, 0xb2, 0x1b, 0xd1, + 0xf6, 0xf9, 0xb7, 0x0a, 0xcc, 0x0d, 0x7f, 0x66, 0x72, 0x3e, 0x18, 0x58, + 0xb5, 0x15, 0xd7, 0xba, 0x1b, 0xda, 0x4b, 0x9f, 0x56, 0x1d, 0x44, 0x63, + 0xd6, 0x12, 0x53, 0x76, 0xf4, 0x98, 0x50, 0xfe, 0xb2, 0xa6, 0x25, 0x7d, + 0x34, 0x76, 0x10, 0xd7, 0x27, 0xd4, 0xf3, 0x85, 0x52, 0x4f, 0x81, 0xee, + 0x7e, 0x8b, 0x68, 0x82, 0x9e, 0x14, 0x34, 0xc7, 0x2c, 0xd1, 0x23, 0x89, + 0xa6, 0xe9, 0xd6, 0x52, 0xfd, 0xba, 0xe1, 0xeb, 0xce, 0x37, 0x8d, 0x68, + 0x61, 0xad, 0xcd, 0xa5, 0xbb, 0xda, 0xaf, 0x0a, 0x76, 0x94, 0xd2, 0x2a, + 0x65, 0xbd, 0x4e, 0x68, 0x9e, 0x50, 0x88, 0xea, 0xfc, 0x51, 0x5d, 0xe8, + 0x67, 0x92, 0x1e, 0x1c, 0x4e, 0x86, 0x7b, 0xe6, 0x51, 0x3d, 0xf5, 0x59, + 0xb9, 0x7c, 0x0a, 0x8d, 0xeb, 0x51, 0x53, 0x77, 0xa3, 0xbf, 0xcd, 0x8c, + 0xc5, 0xe2, 0x44, 0x75, 0xc1, 0xa8, 0xf8, 0x1d, 0xcc, 0x1f, 0x1d, 0x80, + 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, 0x23, 0x18, + 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x04, + 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, + 0x08, 0x46, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, + 0x01, 0xc1, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, + 0x30, 0x20, 0x18, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, + 0x08, 0x06, 0x04, 0x03, 0x82, 0x11, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, + 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, + 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x46, 0x30, 0x5f, 0x01, 0x82, 0x01, + 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x46, 0x30, + 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x08, + 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, + 0x10, 0x8c, 0x60, 0x40, 0x30, 0x20, 0x18, 0x10, 0x0c, 0x08, 0x06, 0x04, + 0x03, 0x82, 0x11, 0x0c, 0x08, 0x06, 0x04, 0x03, 0x82, 0x01, 0xc1, 0x80, + 0x60, 0x40, 0x30, 0x82, 0x01, 0xc1, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18, + 0x10, 0x0c, 0x08, 0x06, 0x04, 0xff, 0x1f, 0xfc, 0x04, 0xd0, 0x2e, 0x1b, + 0x34, 0x16, 0xda, 0xd9, 0x88, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, + 0x44, 0xae, 0x42, 0x60, 0x82 +}; +unsigned int hi_png_len = 1073; diff --git a/tools/png2header.sh b/tools/png2header.sh new file mode 100755 index 0000000..92b39e9 --- /dev/null +++ b/tools/png2header.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +pngcrush + +if [ $? -ne 0 ]; then + echo "no pngcrush, install with your package manager" + exit 1 +fi + +pngcrush -q -rem alla -brute $1 $(basename $2 .h).png +xxd -i $(basename $2 .h).png > $2 \ No newline at end of file