Seedbox v0.1 + firmware, generic kit/synth, ROOT/SCALE pages
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
First commit of the repo. Contents beyond the v0.1 simulator and layout: - Full Pico 2 W firmware (firmware/app, drivers, engine port, host test). - BANK page and 404 mode A/B removed; learned pads play exactly what was learned. - 404/MiniFreak renamed to KIT/SYNTH throughout (jacks, code, sim, docs, layout labels; wiring rebuilt). - ROLL returns to page select after a re-roll; a freshly learned track turns on. - LCD contrast 66. - ROOT and SCALE pages: 21 scales, AUTO = the seed picks; global and sticky. Intended music change: the scale table grew from 8 to 21 and AUTO picks from all of them, so every seed's scale changed. Vectors regenerated with npm run vectors, plus new key-override cases. Flash record is now version 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2
firmware/test/.gitignore
vendored
Normal file
2
firmware/test/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
check_vectors
|
||||
check_vectors_san
|
||||
25
firmware/test/Makefile
Normal file
25
firmware/test/Makefile
Normal file
@@ -0,0 +1,25 @@
|
||||
# Host build of the engine and the vector checker. No SDK.
|
||||
# make test build and check against ../../test/vectors
|
||||
# make sanitize same, with ASan + UBSan
|
||||
CC ?= cc
|
||||
CFLAGS ?= -std=c99 -O2 -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wno-sign-conversion -Werror
|
||||
SRC = check_vectors.c json.c ../engine/engine.c
|
||||
HDR = json.h ../engine/engine.h
|
||||
VECTORS = ../../test/vectors
|
||||
|
||||
check_vectors: $(SRC) $(HDR)
|
||||
$(CC) $(CFLAGS) -o $@ $(SRC)
|
||||
|
||||
check_vectors_san: $(SRC) $(HDR)
|
||||
$(CC) $(CFLAGS) -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=all -o $@ $(SRC)
|
||||
|
||||
test: check_vectors
|
||||
./check_vectors $(VECTORS)
|
||||
|
||||
sanitize: check_vectors_san
|
||||
./check_vectors_san $(VECTORS)
|
||||
|
||||
clean:
|
||||
rm -f check_vectors check_vectors_san
|
||||
|
||||
.PHONY: test sanitize clean
|
||||
162
firmware/test/check_vectors.c
Normal file
162
firmware/test/check_vectors.c
Normal file
@@ -0,0 +1,162 @@
|
||||
/* Host test runner: checks firmware/engine against test/vectors (the same files check-vectors.js uses).
|
||||
Usage: check_vectors [vectors-dir] (default ../../test/vectors). Exit 0 = every check passes. */
|
||||
#define _POSIX_C_SOURCE 200809L /* strdup, opendir */
|
||||
#include "../engine/engine.h"
|
||||
#include "json.h"
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int fail, n, max_events;
|
||||
|
||||
static uint32_t hex(const jval *v){ return (uint32_t)strtoul(v->str, NULL, 16); }
|
||||
static long long num(const jval *v){ return v ? v->num : -999999; }
|
||||
|
||||
static void check_primitives(const char *dir){
|
||||
char path[1024];
|
||||
jval *root, *a;
|
||||
int i, j;
|
||||
snprintf(path, sizeof path, "%s/primitives.json", dir);
|
||||
root = json_parse_file(path);
|
||||
if(!root){ printf("cannot read %s\n", path); fail++; return; }
|
||||
|
||||
a = json_get(root, "mix");
|
||||
for(i = 0; i < a->n; i++){
|
||||
jval *v = a->items[i];
|
||||
uint32_t got = sb_mix(hex(json_get(v, "a")), (uint32_t)(int32_t)num(json_get(v, "b")));
|
||||
n++;
|
||||
if(got != hex(json_get(v, "out"))){ fail++; printf("mix %s %lld differs: got 0x%08X\n", json_get(v, "a")->str, num(json_get(v, "b")), got); }
|
||||
}
|
||||
|
||||
a = json_get(root, "rng");
|
||||
for(i = 0; i < a->n; i++){
|
||||
jval *v = a->items[i], *seq = json_get(v, "next"), *bl = json_get(v, "below_1_to_16"), *ch = json_get(v, "chance_i_times_4096");
|
||||
uint32_t seed = hex(json_get(v, "seed"));
|
||||
sb_rng r;
|
||||
n++;
|
||||
sb_rng_init(&r, seed);
|
||||
for(j = 0; j < seq->n; j++) if(sb_rng_next(&r) != hex(seq->items[j])){ fail++; printf("rng 0x%08X next step %d differs\n", seed, j); break; }
|
||||
n++;
|
||||
sb_rng_init(&r, seed);
|
||||
for(j = 0; j < bl->n; j++) if(sb_rng_below(&r, j + 1) != num(bl->items[j])){ fail++; printf("rng 0x%08X below step %d differs\n", seed, j); break; }
|
||||
n++;
|
||||
sb_rng_init(&r, seed);
|
||||
for(j = 0; j < ch->n; j++) if(sb_rng_chance(&r, j * 4096) != num(ch->items[j])){ fail++; printf("rng 0x%08X chance step %d differs\n", seed, j); break; }
|
||||
}
|
||||
|
||||
a = json_get(root, "genomes");
|
||||
for(i = 0; i < a->n; i++){
|
||||
jval *v = a->items[i], *gj = json_get(v, "genome");
|
||||
uint32_t seed = hex(json_get(v, "seed"));
|
||||
sb_genome g;
|
||||
int bad = 0, k;
|
||||
n++;
|
||||
sb_genome_make(seed, &g);
|
||||
for(k = 0; k < gj->n; k++){
|
||||
jval *f = gj->items[k];
|
||||
const char *key = f->key;
|
||||
#define INT(name) else if(!strcmp(key, #name)){ if(num(f) != g.name) bad = 1; }
|
||||
#define ARR(name, len) else if(!strcmp(key, #name)){ if(f->n != len) bad = 1; else for(j = 0; j < len; j++) if(num(f->items[j]) != g.name[j]) bad = 1; }
|
||||
if(!strcmp(key, "formName")){ if(strcmp(f->str, sb_form_name(g.form_idx))) bad = 1; }
|
||||
INT(steps) INT(scale) INT(root) INT(fillP) INT(monoLen) INT(monoRange) INT(rot) INT(hocket) INT(bassDeg)
|
||||
INT(swing) INT(arc) INT(motifLen) INT(accentCycle) INT(layback) INT(frame) INT(freeK) INT(freeRot)
|
||||
ARR(form, 4) ARR(prog, 4) ARR(dens, 2)
|
||||
else { printf("genome field %s is not checked by the C runner\n", key); bad = 1; }
|
||||
#undef INT
|
||||
#undef ARR
|
||||
if(bad){ printf("genome 0x%08X differs at %s\n", seed, key); break; }
|
||||
}
|
||||
if(bad) fail++;
|
||||
}
|
||||
json_free(root);
|
||||
}
|
||||
|
||||
static int cmp_str(const void *a, const void *b){ return strcmp(*(char *const *)a, *(char *const *)b); }
|
||||
|
||||
static void print_event(const char *tag, const sb_event *e){ printf(" %s [%d,%d,%d,%d,%d,%d]\n", tag, e->t, e->lane, e->v, e->vel, e->len, e->tr); }
|
||||
|
||||
static void check_case(const jval *c){
|
||||
jval *bars = json_get(c, "bars"), *salts = json_get(c, "salts"), *fb = json_get(c, "fillBars");
|
||||
sb_state st;
|
||||
sb_params p;
|
||||
sb_bar out;
|
||||
uint32_t h = SB_FNV_INIT;
|
||||
int b, i, firstBad = -1;
|
||||
n++;
|
||||
st.seed = hex(json_get(c, "seed"));
|
||||
st.salts[0] = (uint32_t)num(salts->items[0]); st.salts[1] = (uint32_t)num(salts->items[1]);
|
||||
p.gen = (int)num(json_get(c, "gen")); p.energy = (int)num(json_get(c, "energy")); p.drift = (int)num(json_get(c, "drift"));
|
||||
p.scale = (int)num(json_get(c, "scale")); p.root = (int)num(json_get(c, "root"));
|
||||
for(b = 0; b < bars->n; b++){
|
||||
jval *jb = bars->items[b], *ev = json_get(jb, "events");
|
||||
p.fill = 0;
|
||||
for(i = 0; i < fb->n; i++) if(num(fb->items[i]) == b) p.fill = 1;
|
||||
sb_render_bar(&st, &p, b, &out);
|
||||
if(out.n > max_events) max_events = out.n;
|
||||
for(i = 0; i < out.n; i++) h = sb_checksum_event(h, &out.ev[i]);
|
||||
if(firstBad >= 0) continue;
|
||||
if(out.overflow){ firstBad = b; printf("FAIL %s bar %d: event buffer overflow\n", json_get(c, "id")->str, b); continue; }
|
||||
if(out.sec != num(json_get(jb, "sec")) || out.fill != num(json_get(jb, "fill")) || out.barTicks != num(json_get(jb, "barTicks"))){
|
||||
firstBad = b;
|
||||
printf("FAIL %s bar %d: sec/fill/barTicks got %d/%d/%d want %lld/%lld/%lld\n", json_get(c, "id")->str, b,
|
||||
out.sec, out.fill, out.barTicks, num(json_get(jb, "sec")), num(json_get(jb, "fill")), num(json_get(jb, "barTicks")));
|
||||
continue;
|
||||
}
|
||||
for(i = 0; i < out.n || i < ev->n; i++){
|
||||
sb_event want = {0, 0, 0, 0, 0, 0};
|
||||
int have = i < ev->n, same;
|
||||
if(have){
|
||||
jval *row = ev->items[i];
|
||||
want.t = (int16_t)num(row->items[0]); want.lane = (uint8_t)num(row->items[1]); want.v = (uint8_t)num(row->items[2]);
|
||||
want.vel = (uint8_t)num(row->items[3]); want.len = (int16_t)num(row->items[4]); want.tr = (uint8_t)num(row->items[5]);
|
||||
}
|
||||
same = have && i < out.n && !memcmp(&want, &out.ev[i], sizeof want);
|
||||
if(!same){
|
||||
firstBad = b;
|
||||
printf("FAIL %s first difference in bar %d, event %d (got %d events, want %d)\n", json_get(c, "id")->str, b, i, out.n, ev->n);
|
||||
if(have) print_event("want", &want);
|
||||
if(i < out.n) print_event("got ", &out.ev[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(h != hex(json_get(c, "checksum")) || firstBad >= 0){
|
||||
fail++;
|
||||
if(firstBad < 0) printf("FAIL %s checksum 0x%08X want %s (events matched?)\n", json_get(c, "id")->str, h, json_get(c, "checksum")->str);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
const char *dir = argc > 1 ? argv[1] : "../../test/vectors";
|
||||
char *names[64];
|
||||
int nn = 0, i, k;
|
||||
DIR *d;
|
||||
struct dirent *de;
|
||||
|
||||
check_primitives(dir);
|
||||
|
||||
d = opendir(dir);
|
||||
if(!d){ printf("cannot open %s\n", dir); return 1; }
|
||||
while((de = readdir(d)) && nn < 64)
|
||||
if(!strncmp(de->d_name, "gen", 3) && de->d_name[3] >= '0' && de->d_name[3] <= '9') names[nn++] = strdup(de->d_name);
|
||||
closedir(d);
|
||||
qsort(names, (size_t)nn, sizeof names[0], cmp_str);
|
||||
if(!nn){ printf("no gen*.json in %s\n", dir); fail++; }
|
||||
|
||||
for(i = 0; i < nn; i++){
|
||||
char path[1024];
|
||||
jval *root, *cases;
|
||||
snprintf(path, sizeof path, "%s/%s", dir, names[i]);
|
||||
root = json_parse_file(path);
|
||||
if(!root){ printf("cannot read %s\n", path); fail++; continue; }
|
||||
cases = json_get(root, "cases");
|
||||
for(k = 0; k < cases->n; k++) check_case(cases->items[k]);
|
||||
json_free(root);
|
||||
free(names[i]);
|
||||
}
|
||||
printf("max events in a bar: %d (buffer %d)\n", max_events, SB_MAX_EVENTS);
|
||||
if(fail) printf("%d of %d checks FAILED\n", fail, n);
|
||||
else printf("all %d checks pass\n", n);
|
||||
return fail ? 1 : 0;
|
||||
}
|
||||
99
firmware/test/json.c
Normal file
99
firmware/test/json.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "json.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char *p;
|
||||
static int bad;
|
||||
|
||||
static void ws(void){ while(*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') p++; }
|
||||
static void *xcalloc(size_t n, size_t s){ void *r = calloc(n, s); if(!r){ fprintf(stderr, "out of memory\n"); exit(2); } return r; }
|
||||
|
||||
static char *parse_str(void){
|
||||
const char *s = ++p;
|
||||
char *out, *o;
|
||||
while(*p && *p != '"'){ if(*p == '\\' && p[1]) p++; p++; }
|
||||
if(*p != '"'){ bad = 1; return NULL; }
|
||||
out = o = xcalloc((size_t)(p - s) + 1, 1);
|
||||
for(; s < p; s++){ if(*s == '\\') s++; *o++ = *s; }
|
||||
p++;
|
||||
return out;
|
||||
}
|
||||
static jval *parse_val(void);
|
||||
static void add(jval *v, jval *item){
|
||||
v->items = realloc(v->items, sizeof(jval *) * (size_t)(v->n + 1));
|
||||
if(!v->items){ fprintf(stderr, "out of memory\n"); exit(2); }
|
||||
v->items[v->n++] = item;
|
||||
}
|
||||
static jval *parse_val(void){
|
||||
jval *v = xcalloc(1, sizeof *v);
|
||||
ws();
|
||||
if(*p == '{'){
|
||||
v->type = J_OBJ; p++; ws();
|
||||
if(*p == '}'){ p++; return v; }
|
||||
for(;;){
|
||||
char *k; jval *item;
|
||||
ws(); if(*p != '"'){ bad = 1; return v; }
|
||||
k = parse_str(); ws();
|
||||
if(*p != ':'){ bad = 1; free(k); return v; }
|
||||
p++;
|
||||
item = parse_val(); item->key = k; add(v, item);
|
||||
ws(); if(*p == ','){ p++; continue; }
|
||||
if(*p == '}'){ p++; return v; }
|
||||
bad = 1; return v;
|
||||
}
|
||||
}
|
||||
if(*p == '['){
|
||||
v->type = J_ARR; p++; ws();
|
||||
if(*p == ']'){ p++; return v; }
|
||||
for(;;){
|
||||
add(v, parse_val());
|
||||
ws(); if(*p == ','){ p++; continue; }
|
||||
if(*p == ']'){ p++; return v; }
|
||||
bad = 1; return v;
|
||||
}
|
||||
}
|
||||
if(*p == '"'){ v->type = J_STR; v->str = parse_str(); return v; }
|
||||
if(!strncmp(p, "true", 4)){ v->type = J_BOOL; v->num = 1; p += 4; return v; }
|
||||
if(!strncmp(p, "false", 5)){ v->type = J_BOOL; v->num = 0; p += 5; return v; }
|
||||
if(!strncmp(p, "null", 4)){ v->type = J_NULL; p += 4; return v; }
|
||||
if(*p == '-' || (*p >= '0' && *p <= '9')){
|
||||
char *end;
|
||||
v->type = J_NUM; v->num = strtoll(p, &end, 10);
|
||||
if(*end == '.' || *end == 'e' || *end == 'E') bad = 1; /* the vectors are integer-only */
|
||||
p = end; return v;
|
||||
}
|
||||
bad = 1; return v;
|
||||
}
|
||||
|
||||
jval *json_parse_file(const char *path){
|
||||
FILE *f = fopen(path, "rb");
|
||||
long n;
|
||||
char *buf;
|
||||
jval *v;
|
||||
if(!f) return NULL;
|
||||
fseek(f, 0, SEEK_END); n = ftell(f); fseek(f, 0, SEEK_SET);
|
||||
buf = xcalloc((size_t)n + 1, 1);
|
||||
if(fread(buf, 1, (size_t)n, f) != (size_t)n){ fclose(f); free(buf); return NULL; }
|
||||
fclose(f);
|
||||
p = buf; bad = 0;
|
||||
v = parse_val();
|
||||
ws();
|
||||
if(*p) bad = 1;
|
||||
free(buf);
|
||||
if(bad){ json_free(v); return NULL; }
|
||||
return v;
|
||||
}
|
||||
jval *json_get(const jval *o, const char *key){
|
||||
int i;
|
||||
if(!o || o->type != J_OBJ) return NULL;
|
||||
for(i = 0; i < o->n; i++) if(!strcmp(o->items[i]->key, key)) return o->items[i];
|
||||
return NULL;
|
||||
}
|
||||
jval *json_at(const jval *a, int i){ return (a && (a->type == J_ARR || a->type == J_OBJ) && i >= 0 && i < a->n) ? a->items[i] : NULL; }
|
||||
void json_free(jval *v){
|
||||
int i;
|
||||
if(!v) return;
|
||||
for(i = 0; i < v->n; i++) json_free(v->items[i]);
|
||||
free(v->items); free(v->str); free(v->key); free(v);
|
||||
}
|
||||
20
firmware/test/json.h
Normal file
20
firmware/test/json.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/* Minimal JSON reader for the host test runner (not used on the Pico). Integers only, no escapes beyond \" and \\. */
|
||||
#ifndef SEEDBOX_JSON_H
|
||||
#define SEEDBOX_JSON_H
|
||||
|
||||
typedef enum { J_NULL, J_BOOL, J_NUM, J_STR, J_ARR, J_OBJ } jtype;
|
||||
typedef struct jval {
|
||||
jtype type;
|
||||
long long num; /* J_NUM, J_BOOL */
|
||||
char *str; /* J_STR */
|
||||
char *key; /* set when the value sits inside an object */
|
||||
struct jval **items; /* J_ARR, J_OBJ */
|
||||
int n;
|
||||
} jval;
|
||||
|
||||
jval *json_parse_file(const char *path);
|
||||
jval *json_get(const jval *obj, const char *key); /* NULL if missing */
|
||||
jval *json_at(const jval *arr, int i);
|
||||
void json_free(jval *v);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user