init
This commit is contained in:
parent
fcea56f838
commit
e3d2069400
18
CMakeLists.txt
Normal file
18
CMakeLists.txt
Normal file
@ -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/")
|
39
CMakePresets.json
Normal file
39
CMakePresets.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
|
21
src/CMakeLists.txt
Normal file
21
src/CMakeLists.txt
Normal file
@ -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"
|
||||
)
|
26
src/pc/CMakeLists.txt
Normal file
26
src/pc/CMakeLists.txt
Normal file
@ -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};
|
||||
)
|
210
src/pc/audio.c
Normal file
210
src/pc/audio.c
Normal file
@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Driver for the PC audio system. All actual sound generation code is in shared/audio/piano.c.
|
||||
*/
|
||||
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#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);
|
||||
}
|
365
src/pc/main.c
Normal file
365
src/pc/main.c
Normal file
@ -0,0 +1,365 @@
|
||||
#include <MiniFB.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#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;
|
||||
}
|
29
src/rpi/CMakeLists.txt
Normal file
29
src/rpi/CMakeLists.txt
Normal file
@ -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)
|
258
src/rpi/ST7735_TFT.h
Normal file
258
src/rpi/ST7735_TFT.h
Normal file
@ -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
|
65
src/rpi/audio.c
Normal file
65
src/rpi/audio.c
Normal file
@ -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);
|
||||
}
|
713
src/rpi/display.c
Normal file
713
src/rpi/display.c
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
264
src/rpi/main.c
Normal file
264
src/rpi/main.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
62
src/rpi/pico_extras_import.cmake
Normal file
62
src/rpi/pico_extras_import.cmake
Normal file
@ -0,0 +1,62 @@
|
||||
# This is a copy of <PICO_EXTRAS_PATH>/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)
|
73
src/rpi/pico_sdk_import.cmake
Normal file
73
src/rpi/pico_sdk_import.cmake
Normal file
@ -0,0 +1,73 @@
|
||||
# This is a copy of <PICO_SDK_PATH>/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})
|
2
src/shared/audio/audio.h
Normal file
2
src/shared/audio/audio.h
Normal file
@ -0,0 +1,2 @@
|
||||
void audio_init(void);
|
||||
void audio_try_push_samples(void);
|
175
src/shared/audio/parse_tune/gen_hash.c
Normal file
175
src/shared/audio/parse_tune/gen_hash.c
Normal file
@ -0,0 +1,175 @@
|
||||
// prints out a C file containing a hashmap of fnv1_hash'd tone string -> frequency
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 <stdint.h>\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:
|
||||
;
|
||||
}
|
||||
}
|
151
src/shared/audio/parse_tune/parse_tune.h
Normal file
151
src/shared/audio/parse_tune/parse_tune.h
Normal file
@ -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;
|
||||
}
|
105
src/shared/audio/parse_tune/test.c
Normal file
105
src/shared/audio/parse_tune/test.c
Normal file
@ -0,0 +1,105 @@
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
117
src/shared/audio/parse_tune/tone_map.h
Normal file
117
src/shared/audio/parse_tune/tone_map.h
Normal file
@ -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 <stdint.h>
|
||||
|
||||
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,
|
||||
};
|
222
src/shared/audio/piano.c
Normal file
222
src/shared/audio/piano.c
Normal file
@ -0,0 +1,222 @@
|
||||
// piano ties together our sample table, our note reader, and pico's audio buffer pool
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
21
src/shared/audio/piano.h
Normal file
21
src/shared/audio/piano.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
93
src/shared/hi.h
Normal file
93
src/shared/hi.h
Normal file
@ -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;
|
789
src/shared/sprig_engine/base_engine.c
Normal file
789
src/shared/sprig_engine/base_engine.c
Normal file
@ -0,0 +1,789 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
35
src/shared/sprig_engine/base_engine.h
Normal file
35
src/shared/sprig_engine/base_engine.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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)
|
219
src/shared/sprig_engine/engine.js
Normal file
219
src/shared/sprig_engine/engine.js
Normal file
@ -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;
|
||||
})();
|
60
src/shared/sprig_engine/native_magic_strings.h
Normal file
60
src/shared/sprig_engine/native_magic_strings.h
Normal file
@ -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
|
8
src/shared/sprig_engine/script.h
Normal file
8
src/shared/sprig_engine/script.h
Normal file
@ -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
|
||||
;
|
7
src/shared/ui/errorbuf.h
Normal file
7
src/shared/ui/errorbuf.h
Normal file
@ -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
|
258
src/shared/ui/font.h
Normal file
258
src/shared/ui/font.h
Normal file
@ -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
|
||||
};
|
3
src/shared/version.h
Normal file
3
src/shared/version.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define SPADE_VERSION "1.0.0"
|
3
src/shared/version.h.in
Normal file
3
src/shared/version.h.in
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define SPADE_VERSION "@VERSION@"
|
3
src/version.json
Normal file
3
src/version.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": "1.0.0"
|
||||
}
|
BIN
tools/Untitled.png
Normal file
BIN
tools/Untitled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
tools/hi.png
Normal file
BIN
tools/hi.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
93
tools/output_data.h
Normal file
93
tools/output_data.h
Normal file
@ -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;
|
11
tools/png2header.sh
Executable file
11
tools/png2header.sh
Executable file
@ -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
|
Loading…
Reference in New Issue
Block a user