Split kit and keys; KEYS GEN with MOTIF, 303, TURING, ARP
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
- genome(seed, salts) now has three parts with their own RNG streams: shared (steps, form, swing, arc, layback, fill odds, phase offset), kit (density, frame, accent cycle, free snare) and keys (scale/root, progression, motif, hocket, bass tone, note lengths, range). ROLL KEYS now renews the harmony too; ROLL KIT leaves the keys alone. - Keys generators share one signature and can fix note lengths: MOTIF (the old line), 303 (acid bass, answers in the rests, drone or silent pad), TURING (8/16-bit shift register, DRIFT flips bits, 16-bar window), ARP (triad/7th arpeggio, roots on kicks). - Pages: KIT GEN, KEYS GEN; ROLL shows KIT / KEYS; slots keep both gens. - Flash record v3 (old settings ignored). Bring-up golden checksums updated. - C port matches; host test and ASan/UBSan pass. Intended music change: every seed sounds different (genome split, new generators). Vectors regenerated with npm run vectors; the case grid now crosses all kit and keys generators, with TURING runs past its window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,17 +24,13 @@ The synth runs in poly mode on one channel, so bass, melody and pad share one vo
|
|||||||
|
|
||||||
## The genome
|
## The genome
|
||||||
|
|
||||||
`genome(seed)` picks, once per seed:
|
`genome(seed, salts)` picks, in three parts. Each part has its own random stream, so ROLL KIT and ROLL KEYS each renew one half only.
|
||||||
|
|
||||||
- **steps**: 16 most of the time, sometimes 12, 14 or 20 (bar length = steps × 16th)
|
| Part | Renewed by | Picks |
|
||||||
- **form**: AAAB, AABA, ABAB, AAAA, ABAC or ABAA over a 4-bar phrase
|
|---|---|---|
|
||||||
- **scale and root**: any of the 21 scales below, any root. The ROOT and SCALE pages override them; AUTO keeps the seed's pick.
|
| shared | SEED | **steps** (16 mostly; 12, 14 or 20), **form** (AAAB, AABA, ABAB, AAAA, ABAC, ABAA), **swing** (none to triplet feel), **arc** (flat, build, break), **layback** (snares sit late), melody/perc phase offset, fill odds |
|
||||||
- **progression**: one chord root per bar of the phrase (e.g. i–vi–iv–v)
|
| kit | SEED, ROLL KIT | kit density, **frame** (below), **accent cycle** (off, or every 3, 5 or 7 sixteenths across the bar line), free-snare settings |
|
||||||
- **frame**: where the main kick and snare live (below)
|
| keys | SEED, ROLL KEYS | **scale and root** (any of the 21 below; the ROOT and SCALE pages override them), **progression** (one chord root per bar, e.g. i–vi–iv–v), keys density, motif length, hocket mask, bass colour tone, note-length style, range |
|
||||||
- **swing**: none, light, medium, heavy or triplet feel
|
|
||||||
- **arc**: flat, build (bars 1–2 thin out) or break (bar 3 strips back)
|
|
||||||
- **accent cycle**: off, or an accent every 3, 5 or 7 sixteenths running across the bar line
|
|
||||||
- **motif length** (3–5 notes), **hocket mask**, **bass degree**, **melody phase offset**, **layback** (snares sit a little late), densities, fill odds
|
|
||||||
|
|
||||||
### Frames
|
### Frames
|
||||||
|
|
||||||
@@ -81,7 +77,7 @@ Bass, melody and pad follow the scale. Chords stack every other scale note, so t
|
|||||||
|
|
||||||
## Generators
|
## Generators
|
||||||
|
|
||||||
Selected on the GEN page. They only change the kit; the synth tracks are the same for all of them.
|
Kit generators, selected on the KIT GEN page.
|
||||||
|
|
||||||
- **EUCLID**: Euclidean rhythms. Kick E(k,n) with a rotation that keeps the one, hats and perc often on their own cycle length (polymeter).
|
- **EUCLID**: Euclidean rhythms. Kick E(k,n) with a rotation that keeps the one, hats and perc often on their own cycle length (polymeter).
|
||||||
- **SKELETON**: the frame, plus a few kick moves and hat patterns. The most "drummer-like".
|
- **SKELETON**: the frame, plus a few kick moves and hat patterns. The most "drummer-like".
|
||||||
@@ -89,6 +85,21 @@ Selected on the GEN page. They only change the kit; the synth tracks are the sam
|
|||||||
- **AUTOMATA**: a 1-D cellular automaton decides the hits inside the frame.
|
- **AUTOMATA**: a 1-D cellular automaton decides the hits inside the frame.
|
||||||
- **AMENIZER**: the four bars of the Amen break, velocities included, with its own humanising and chopping (below).
|
- **AMENIZER**: the four bars of the Amen break, velocities included, with its own humanising and chopping (below).
|
||||||
|
|
||||||
|
## Keys generators
|
||||||
|
|
||||||
|
Selected on the KEYS GEN page. All of them follow the bar's chord in the current scale and are split over BASS, MEL A, MEL B and PAD.
|
||||||
|
|
||||||
|
| Name | BASS | MEL A / B | PAD |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **MOTIF** | the chord root on the kicks, a colour tone on alternate kicks, an approach note into the next bar | a 3–5 note motif stated and answered, hocket split | the bar's triad |
|
||||||
|
| **303** | a 16-step acid line looped over the bar: gates, accents, octave jumps, slides; moves with the chord root | 1–2 note answers in the bass's rests | silent, or a drone on the key's root |
|
||||||
|
| **TURING** | the chord root wherever the shift register's bit 0 is set; another bit lifts it an octave | the register read as a scale degree, hocket split | the bar's triad |
|
||||||
|
| **ARP** | the chord root on every kick | the chord (triad or 7th) arpeggiated up, down, up-down or shuffled, alternating A / B | the arpeggio's chord |
|
||||||
|
|
||||||
|
- **303**: ENERGY opens more gates. DRIFT variants flip accents, slides and octaves. A slide is a note held into the next one; whether it glides is up to the synth's glide or legato setting.
|
||||||
|
- **TURING**: an 8- or 16-bit register stepping every 16th. DRIFT flips the bit coming round (0 = locked loop, a quarter of the knob ≈ one flip a bar). Flips stick for 16 bars, then fall away.
|
||||||
|
- **ARP**: 8ths at low density, 16ths at high, and 16ths in fill bars.
|
||||||
|
|
||||||
## Sections
|
## Sections
|
||||||
|
|
||||||
The form says which bars are A, B or C. B and C are transformations of A rather than new patterns, so the phrase hangs together. The transforms: kick answer, hat doubling, thin the first half, perc shift, ghost pickups, snare drag, drop the one, half-time switch.
|
The form says which bars are A, B or C. B and C are transformations of A rather than new patterns, so the phrase hangs together. The transforms: kick answer, hat doubling, thin the first half, perc shift, ghost pickups, snare drag, drop the one, half-time switch.
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ The Nokia 5110 has 84 × 48 pixels:
|
|||||||
| Page | Edit with the encoder | Push |
|
| Page | Edit with the encoder | Push |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **SEED** | scroll back through the last 16 seeds (`-1`, `-2` …; `*` = re-rolled) | load the shown seed |
|
| **SEED** | scroll back through the last 16 seeds (`-1`, `-2` …; `*` = re-rolled) | load the shown seed |
|
||||||
| **GEN** | EUCLID, SKELETON, SUBDIV, AUTOMATA, AMENIZER | done |
|
| **KIT GEN** | EUCLID, SKELETON, SUBDIV, AUTOMATA, AMENIZER | done |
|
||||||
|
| **KEYS GEN** | MOTIF, 303, TURING, ARP (see [Concept](concept.md#keys-generators)) | done |
|
||||||
| **ROOT** | AUTO, C … B. AUTO shows the seed's pick | done |
|
| **ROOT** | AUTO, C … B. AUTO shows the seed's pick | done |
|
||||||
| **SCALE** | AUTO, then the 21 scales (see [Concept](concept.md#scales)). AUTO shows the seed's pick | done |
|
| **SCALE** | AUTO, then the 21 scales (see [Concept](concept.md#scales)). AUTO shows the seed's pick | done |
|
||||||
| **BPM** | tempo | done |
|
| **BPM** | tempo | done |
|
||||||
| **SLOT** | 8 slots for seed + salts + generator (not root or scale) | push = load, long push = save |
|
| **SLOT** | 8 slots for seed + salts + kit and keys generators (not root or scale) | push = load, long push = save |
|
||||||
| **ROLL** | KIT or LINE | push re-rolls just that half (a new drum part over the same tune, or the other way round), then goes back to page select |
|
| **ROLL** | KIT or KEYS | push re-rolls just that half, then goes back to page select. KIT: a new drum part over the same keys. KEYS: new chords, scale (on AUTO), motif and patterns over the same drums |
|
||||||
| **LEARN** | — | push, press a kit track button, hit the pad on the sampler or drum machine. Push again to cancel. |
|
| **LEARN** | — | push, press a kit track button, hit the pad on the sampler or drum machine. Push again to cancel. |
|
||||||
|
|
||||||
## Simulator keys
|
## Simulator keys
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ Short record of why things are the way they are, so they don't get undone by acc
|
|||||||
- **Sections are transforms of A**, not new patterns. The phrase hangs together.
|
- **Sections are transforms of A**, not new patterns. The phrase hangs together.
|
||||||
- **Drift is continuous overlapping layers**, not steps. Wide knob spans used to do little. Drift adds displacement and weirdness at the top end.
|
- **Drift is continuous overlapping layers**, not steps. Wide knob spans used to do little. Drift adds displacement and weirdness at the top end.
|
||||||
- **AMENIZER keeps its own feel.** Its humanising and chops are tuned so that DRIFT past halfway isn't too extreme. The generic layers come in later and at half strength.
|
- **AMENIZER keeps its own feel.** Its humanising and chops are tuned so that DRIFT past halfway isn't too extreme. The generic layers come in later and at half strength.
|
||||||
|
- **Kit and keys are split (2026-10-11).** One seed drives both through their own generator (KIT GEN, KEYS GEN). The keys-only choices (progression, scale, motif) moved into the keys half so ROLL KEYS renews the harmony too. Every seed changed sound; vectors regenerated. ENERGY, DRIFT, bar length, form and swing stay shared, and bass may still lock to the kicks.
|
||||||
|
- **303 slides are overlaps only.** No portamento CCs: the synth channel is shared and poly, so a CC would bend every voice.
|
||||||
- **ROOT and SCALE are global and sticky (2026-10-11).** They survive SEED, ROLL and slot loads, so a seed can be fitted to an existing idea. AUTO lets the seed pick from all 21 scales, which changed every seed's sound (vectors regenerated). Slots don't store the key.
|
- **ROOT and SCALE are global and sticky (2026-10-11).** They survive SEED, ROLL and slot loads, so a seed can be fitted to an existing idea. AUTO lets the seed pick from all 21 scales, which changed every seed's sound (vectors regenerated). Slots don't store the key.
|
||||||
- **Integer-only engine** with a seeded RNG. This makes it deterministic and portable, and testable by golden vectors.
|
- **Integer-only engine** with a seeded RNG. This makes it deterministic and portable, and testable by golden vectors.
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ typedef struct { uint8_t used, ch, note; } pad_t; /* learned pad: ch 0..15 */
|
|||||||
/* Everything the sequencer reads. Core 0 writes it under g_live_lock; core 1 copies it under the same lock. */
|
/* Everything the sequencer reads. Core 0 writes it under g_live_lock; core 1 copies it under the same lock. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
sb_state state;
|
sb_state state;
|
||||||
int gen, energy, drift, bpm;
|
int gen, keys, energy, drift, bpm; /* gen = KIT GEN, keys = KEYS GEN */
|
||||||
int scale, root; /* ROOT / SCALE pages: -1 = AUTO (the seed picks) */
|
int scale, root; /* ROOT / SCALE pages: -1 = AUTO (the seed picks) */
|
||||||
uint8_t on[SB_NT], inv[SB_NT]; /* inv: flipped while the button is held */
|
uint8_t on[SB_NT], inv[SB_NT]; /* inv: flipped while the button is held */
|
||||||
pad_t learn[NKIT];
|
pad_t learn[NKIT];
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ static const char *const TRACK_NAME[SB_NT] = { "KICK","KICK 2","SNARE","GHOST","
|
|||||||
static const char *const NOTE_NAME[12] = { "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" };
|
static const char *const NOTE_NAME[12] = { "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" };
|
||||||
static const char *const GEN_NAME[SB_NGEN] = { "EUCLID","SKELETON","SUBDIV","AUTOMATA","AMENIZER" };
|
static const char *const GEN_NAME[SB_NGEN] = { "EUCLID","SKELETON","SUBDIV","AUTOMATA","AMENIZER" };
|
||||||
static const int8_t PANEL[12] = { 0, 1, 2, -1, 4, 5, 6, 3, 7, 8, 9, 10 }; /* screen map = button grid; -1 = RUN */
|
static const int8_t PANEL[12] = { 0, 1, 2, -1, 4, 5, 6, 3, 7, 8, 9, 10 }; /* screen map = button grid; -1 = RUN */
|
||||||
enum { PG_SEED, PG_GEN, PG_ROOT, PG_SCALE, PG_BPM, PG_SLOT, PG_ROLL, PG_LEARN, NPAGES };
|
enum { PG_SEED, PG_GEN, PG_KEYS, PG_ROOT, PG_SCALE, PG_BPM, PG_SLOT, PG_ROLL, PG_LEARN, NPAGES };
|
||||||
|
|
||||||
static slot_t slots[NSLOTS];
|
static slot_t slots[NSLOTS];
|
||||||
static sb_state history[16];
|
static sb_state history[16];
|
||||||
@@ -57,7 +57,7 @@ static void set_state(sb_state s){
|
|||||||
hist_sel = 0; mark_dirty();
|
hist_sel = 0; mark_dirty();
|
||||||
}
|
}
|
||||||
static void new_seed(void){ sb_state s = { get_rand_32(), {0, 0} }; set_state(s); }
|
static void new_seed(void){ sb_state s = { get_rand_32(), {0, 0} }; set_state(s); }
|
||||||
static void reroll(int l){ sb_state s = g_live.state; s.salts[l] = (s.salts[l] + 1) & 0xFFFF; set_state(s); msg(l ? "NEW LINE" : "NEW KIT", 700); }
|
static void reroll(int l){ sb_state s = g_live.state; s.salts[l] = (s.salts[l] + 1) & 0xFFFF; set_state(s); msg(l ? "NEW KEYS" : "NEW KIT", 700); }
|
||||||
|
|
||||||
static void apply_on(int tr, int v){ lock(); g_live.on[tr] = (uint8_t)v; unlock(); mark_dirty(); if(!sounds(tr)) send_cmd(CMD_RELEASE_TRACK, tr); }
|
static void apply_on(int tr, int v){ lock(); g_live.on[tr] = (uint8_t)v; unlock(); mark_dirty(); if(!sounds(tr)) send_cmd(CMD_RELEASE_TRACK, tr); }
|
||||||
static void set_inv(int tr, int v){ lock(); g_live.inv[tr] = (uint8_t)v; unlock(); if(!sounds(tr)) send_cmd(CMD_RELEASE_TRACK, tr); }
|
static void set_inv(int tr, int v){ lock(); g_live.inv[tr] = (uint8_t)v; unlock(); if(!sounds(tr)) send_cmd(CMD_RELEASE_TRACK, tr); }
|
||||||
@@ -68,6 +68,7 @@ static void enc_turn(int d){
|
|||||||
switch(page){
|
switch(page){
|
||||||
case PG_SEED: hist_sel = clampi(hist_sel + d, 0, nhist); break;
|
case PG_SEED: hist_sel = clampi(hist_sel + d, 0, nhist); break;
|
||||||
case PG_GEN: lock(); g_live.gen = (g_live.gen + d + SB_NGEN) % SB_NGEN; unlock(); mark_dirty(); break;
|
case PG_GEN: lock(); g_live.gen = (g_live.gen + d + SB_NGEN) % SB_NGEN; unlock(); mark_dirty(); break;
|
||||||
|
case PG_KEYS: lock(); g_live.keys = (g_live.keys + d + SB_NKEYS) % SB_NKEYS; unlock(); mark_dirty(); break;
|
||||||
case PG_ROOT: lock(); g_live.root = (g_live.root + 1 + d + 13) % 13 - 1; unlock(); mark_dirty(); break; /* AUTO, C .. B */
|
case PG_ROOT: lock(); g_live.root = (g_live.root + 1 + d + 13) % 13 - 1; unlock(); mark_dirty(); break; /* AUTO, C .. B */
|
||||||
case PG_SCALE: lock(); g_live.scale = (g_live.scale + 1 + d + SB_NSCALES + 1) % (SB_NSCALES + 1) - 1; unlock(); mark_dirty(); break; /* AUTO, MAJOR .. LOCRIAN */
|
case PG_SCALE: lock(); g_live.scale = (g_live.scale + 1 + d + SB_NSCALES + 1) % (SB_NSCALES + 1) - 1; unlock(); mark_dirty(); break; /* AUTO, MAJOR .. LOCRIAN */
|
||||||
case PG_BPM: set_bpm(g_live.bpm + d); break;
|
case PG_BPM: set_bpm(g_live.bpm + d); break;
|
||||||
@@ -88,13 +89,13 @@ static void enc_push(int hold){
|
|||||||
char t[24];
|
char t[24];
|
||||||
slots[slot_sel].used = 1; slots[slot_sel].seed = g_live.state.seed;
|
slots[slot_sel].used = 1; slots[slot_sel].seed = g_live.state.seed;
|
||||||
slots[slot_sel].salts[0] = g_live.state.salts[0]; slots[slot_sel].salts[1] = g_live.state.salts[1];
|
slots[slot_sel].salts[0] = g_live.state.salts[0]; slots[slot_sel].salts[1] = g_live.state.salts[1];
|
||||||
slots[slot_sel].gen = (uint8_t)g_live.gen; mark_dirty();
|
slots[slot_sel].gen = (uint8_t)g_live.gen; slots[slot_sel].keys = (uint8_t)g_live.keys; mark_dirty();
|
||||||
snprintf(t, sizeof t, "SAVED S%d", slot_sel + 1); msg(t, 900);
|
snprintf(t, sizeof t, "SAVED S%d", slot_sel + 1); msg(t, 900);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(slots[slot_sel].used){
|
if(slots[slot_sel].used){
|
||||||
sb_state s = { slots[slot_sel].seed, { slots[slot_sel].salts[0], slots[slot_sel].salts[1] } };
|
sb_state s = { slots[slot_sel].seed, { slots[slot_sel].salts[0], slots[slot_sel].salts[1] } };
|
||||||
lock(); g_live.gen = slots[slot_sel].gen; unlock();
|
lock(); g_live.gen = slots[slot_sel].gen; g_live.keys = slots[slot_sel].keys % SB_NKEYS; unlock();
|
||||||
set_state(s);
|
set_state(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,15 +150,16 @@ static void screen_text(char *label, char *value){
|
|||||||
if(hist_sel > 0) snprintf(n, sizeof n, " -%d", hist_sel);
|
if(hist_sel > 0) snprintf(n, sizeof n, " -%d", hist_sel);
|
||||||
snprintf(l, sizeof l, "SEED%s%s", n, (s->salts[0] || s->salts[1]) ? " *" : "");
|
snprintf(l, sizeof l, "SEED%s%s", n, (s->salts[0] || s->salts[1]) ? " *" : "");
|
||||||
hex8(v, s->seed); break; }
|
hex8(v, s->seed); break; }
|
||||||
case PG_GEN: strcpy(l, "GENERATOR"); strcpy(v, GEN_NAME[g_live.gen]); break;
|
case PG_GEN: strcpy(l, "KIT GEN"); strcpy(v, GEN_NAME[g_live.gen]); break;
|
||||||
|
case PG_KEYS: strcpy(l, "KEYS GEN"); strcpy(v, sb_keys_name(g_live.keys)); break;
|
||||||
case PG_ROOT: case PG_SCALE: { /* on AUTO, show the seed's own pick */
|
case PG_ROOT: case PG_SCALE: { /* on AUTO, show the seed's own pick */
|
||||||
sb_genome g; int sc = page == PG_SCALE, x = sc ? g_live.scale : g_live.root;
|
sb_genome g; int sc = page == PG_SCALE, x = sc ? g_live.scale : g_live.root;
|
||||||
if(x < 0){ sb_genome_make(g_live.state.seed, &g); x = sc ? g.scale : g.root; }
|
if(x < 0){ sb_genome_make(&g_live.state, &g); x = sc ? g.scale : g.root; }
|
||||||
snprintf(l, sizeof l, "%s%s", sc ? "SCALE" : "ROOT", (sc ? g_live.scale : g_live.root) < 0 ? " AUTO" : "");
|
snprintf(l, sizeof l, "%s%s", sc ? "SCALE" : "ROOT", (sc ? g_live.scale : g_live.root) < 0 ? " AUTO" : "");
|
||||||
strcpy(v, sc ? sb_scale_name(x) : NOTE_NAME[x]); break; }
|
strcpy(v, sc ? sb_scale_name(x) : NOTE_NAME[x]); break; }
|
||||||
case PG_BPM: strcpy(l, "TEMPO"); snprintf(v, sizeof v, "%d BPM", g_live.bpm); break;
|
case PG_BPM: strcpy(l, "TEMPO"); snprintf(v, sizeof v, "%d BPM", g_live.bpm); break;
|
||||||
case PG_SLOT: snprintf(l, sizeof l, "SLOT %d", slot_sel + 1); if(slots[slot_sel].used) hex8(v, slots[slot_sel].seed); else strcpy(v, "EMPTY"); break;
|
case PG_SLOT: snprintf(l, sizeof l, "SLOT %d", slot_sel + 1); if(slots[slot_sel].used) hex8(v, slots[slot_sel].seed); else strcpy(v, "EMPTY"); break;
|
||||||
case PG_ROLL: strcpy(l, "RE-ROLL"); strcpy(v, roll_sel ? "LINE" : "KIT"); break;
|
case PG_ROLL: strcpy(l, "RE-ROLL"); strcpy(v, roll_sel ? "KEYS" : "KIT"); break;
|
||||||
case PG_LEARN: {
|
case PG_LEARN: {
|
||||||
if(learn_mode == 2){ const char *n = TRACK_NAME[learn_tr]; char *o; strcpy(l, "LEARN "); o = l + 6; for(; *n; n++) if(*n != ' ') *o++ = *n; *o = 0; }
|
if(learn_mode == 2){ const char *n = TRACK_NAME[learn_tr]; char *o; strcpy(l, "LEARN "); o = l + 6; for(; *n; n++) if(*n != ' ') *o++ = *n; *o = 0; }
|
||||||
else strcpy(l, "LEARN");
|
else strcpy(l, "LEARN");
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ static void render_ahead(uint64_t now){
|
|||||||
sb_params p;
|
sb_params p;
|
||||||
int i;
|
int i;
|
||||||
snapshot(&L);
|
snapshot(&L);
|
||||||
p.gen = L.gen; p.energy = L.energy; p.drift = L.drift; p.fill = 0; p.scale = L.scale; p.root = L.root; /* no FILL button: bar-4 fills happen on their own */
|
p.gen = L.gen; p.keys = L.keys; p.energy = L.energy; p.drift = L.drift; p.fill = 0; p.scale = L.scale; p.root = L.root; /* no FILL button: bar-4 fills happen on their own */
|
||||||
sb_render_bar(&L.state, &p, bar, &rbar);
|
sb_render_bar(&L.state, &p, bar, &rbar);
|
||||||
for(i = 0; i < rbar.n; i++){
|
for(i = 0; i < rbar.n; i++){
|
||||||
enqueue(next_bar_tick + rbar.ev[i].t, 1, &rbar.ev[i]);
|
enqueue(next_bar_tick + rbar.ev[i].t, 1, &rbar.ev[i]);
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define MAGIC 0x53425831u /* "SBX1" */
|
#define MAGIC 0x53425831u /* "SBX1" */
|
||||||
#define VERSION 2 /* 2: ROOT / SCALE added, BANK gone. Older records are ignored. */
|
#define VERSION 3 /* 3: KEYS GEN. Older records are ignored. */
|
||||||
#define OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE)
|
#define OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE)
|
||||||
#define BUFSZ 512
|
#define BUFSZ 512
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t magic, version;
|
uint32_t magic, version;
|
||||||
uint32_t seed, salts[2];
|
uint32_t seed, salts[2];
|
||||||
int32_t gen, bpm, scale, root, mono_ch, mono_oct;
|
int32_t gen, keys, bpm, scale, root, mono_ch, mono_oct;
|
||||||
uint8_t on[SB_NT];
|
uint8_t on[SB_NT];
|
||||||
pad_t learn[NKIT];
|
pad_t learn[NKIT];
|
||||||
slot_t slots[NSLOTS];
|
slot_t slots[NSLOTS];
|
||||||
@@ -33,7 +33,7 @@ void storage_defaults(live_t *L, slot_t *slots){
|
|||||||
int t;
|
int t;
|
||||||
memset(L, 0, sizeof *L);
|
memset(L, 0, sizeof *L);
|
||||||
L->state.seed = 0x5EEDB0C5u;
|
L->state.seed = 0x5EEDB0C5u;
|
||||||
L->gen = 0; L->scale = -1; L->root = -1; L->energy = 64; L->drift = 32; L->bpm = 165;
|
L->gen = 0; L->keys = 0; L->scale = -1; L->root = -1; L->energy = 64; L->drift = 32; L->bpm = 165;
|
||||||
for(t = 0; t < SB_NT; t++) L->on[t] = 1;
|
for(t = 0; t < SB_NT; t++) L->on[t] = 1;
|
||||||
L->mono_ch = 1; L->mono_oct = 0; L->clock = 1;
|
L->mono_ch = 1; L->mono_oct = 0; L->clock = 1;
|
||||||
memset(slots, 0, sizeof(slot_t) * NSLOTS);
|
memset(slots, 0, sizeof(slot_t) * NSLOTS);
|
||||||
@@ -45,7 +45,7 @@ int storage_load(live_t *L, slot_t *slots){
|
|||||||
storage_defaults(L, slots);
|
storage_defaults(L, slots);
|
||||||
if(r->magic != MAGIC || r->version != VERSION || r->crc != crc(r)) return 0;
|
if(r->magic != MAGIC || r->version != VERSION || r->crc != crc(r)) return 0;
|
||||||
L->state.seed = r->seed; L->state.salts[0] = r->salts[0]; L->state.salts[1] = r->salts[1];
|
L->state.seed = r->seed; L->state.salts[0] = r->salts[0]; L->state.salts[1] = r->salts[1];
|
||||||
L->gen = r->gen; L->bpm = r->bpm; L->scale = r->scale; L->root = r->root; L->mono_ch = r->mono_ch; L->mono_oct = r->mono_oct;
|
L->gen = r->gen; L->keys = r->keys; L->bpm = r->bpm; L->scale = r->scale; L->root = r->root; L->mono_ch = r->mono_ch; L->mono_oct = r->mono_oct;
|
||||||
for(t = 0; t < SB_NT; t++) L->on[t] = r->on[t] ? 1 : 0;
|
for(t = 0; t < SB_NT; t++) L->on[t] = r->on[t] ? 1 : 0;
|
||||||
memcpy(L->learn, r->learn, sizeof L->learn);
|
memcpy(L->learn, r->learn, sizeof L->learn);
|
||||||
memcpy(slots, r->slots, sizeof r->slots);
|
memcpy(slots, r->slots, sizeof r->slots);
|
||||||
@@ -64,7 +64,7 @@ int storage_save(const live_t *L, const slot_t *slots){
|
|||||||
memset(r, 0, sizeof *r);
|
memset(r, 0, sizeof *r);
|
||||||
r->magic = MAGIC; r->version = VERSION;
|
r->magic = MAGIC; r->version = VERSION;
|
||||||
r->seed = L->state.seed; r->salts[0] = L->state.salts[0]; r->salts[1] = L->state.salts[1];
|
r->seed = L->state.seed; r->salts[0] = L->state.salts[0]; r->salts[1] = L->state.salts[1];
|
||||||
r->gen = L->gen; r->bpm = L->bpm; r->scale = L->scale; r->root = L->root; r->mono_ch = L->mono_ch; r->mono_oct = L->mono_oct;
|
r->gen = L->gen; r->keys = L->keys; r->bpm = L->bpm; r->scale = L->scale; r->root = L->root; r->mono_ch = L->mono_ch; r->mono_oct = L->mono_oct;
|
||||||
memcpy(r->on, L->on, sizeof r->on);
|
memcpy(r->on, L->on, sizeof r->on);
|
||||||
memcpy(r->learn, L->learn, sizeof r->learn);
|
memcpy(r->learn, L->learn, sizeof r->learn);
|
||||||
memcpy(r->slots, slots, sizeof r->slots);
|
memcpy(r->slots, slots, sizeof r->slots);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Settings in the last flash sector: slots, learned pads, BPM, generator, root, scale, synth channel and octave,
|
/* Settings in the last flash sector: slots, learned pads, BPM, kit and keys generators, root, scale, synth channel and octave,
|
||||||
plus the current seed and track on/off (as app.js keeps them). Erasing a sector stalls both cores for tens of
|
plus the current seed and track on/off (as app.js keeps them). Erasing a sector stalls both cores for tens of
|
||||||
milliseconds, so main.c only saves while stopped. */
|
milliseconds, so main.c only saves while stopped. */
|
||||||
#ifndef SEEDBOX_STORAGE_H
|
#ifndef SEEDBOX_STORAGE_H
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
|
||||||
typedef struct { uint8_t used, gen; uint32_t seed; uint32_t salts[2]; } slot_t;
|
typedef struct { uint8_t used, gen, keys; uint32_t seed; uint32_t salts[2]; } slot_t;
|
||||||
|
|
||||||
void storage_defaults(live_t *L, slot_t *slots);
|
void storage_defaults(live_t *L, slot_t *slots);
|
||||||
int storage_load(live_t *L, slot_t *slots); /* 1 if a valid record was found */
|
int storage_load(live_t *L, slot_t *slots); /* 1 if a valid record was found */
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ static const char *const SW_NAME[3] = { "SEED", "RUN", "PUSH" };
|
|||||||
/* engine self-test: golden cases from test/vectors (8 bars each) */
|
/* engine self-test: golden cases from test/vectors (8 bars each) */
|
||||||
typedef struct { uint32_t seed; int gen, energy, drift; uint32_t checksum; } golden_t;
|
typedef struct { uint32_t seed; int gen, energy, drift; uint32_t checksum; } golden_t;
|
||||||
static const golden_t GOLDEN[3] = {
|
static const golden_t GOLDEN[3] = {
|
||||||
{ 0x5EEDB0C5, 0, 64, 32, 0x4F5DE957 }, /* 5EEDB0C5_s0-0_g0_e64_d32 */
|
{ 0x5EEDB0C5, 0, 64, 32, 0xC055F3A7 }, /* 5EEDB0C5_s0-0_g0_k0_e64_d32 */
|
||||||
{ 0x5EEDB0C5, 3, 127, 0, 0x29C313A5 }, /* 5EEDB0C5_s0-0_g3_e127_d0, dense */
|
{ 0x5EEDB0C5, 3, 127,127, 0x1CF02038 }, /* 5EEDB0C5_s0-0_g3_k0_e127_d127, dense */
|
||||||
{ 0x5EEDB0C5, 4, 64, 32, 0x8C01C1F9 }, /* 5EEDB0C5_s0-0_g4_e64_d32, AMENIZER */
|
{ 0x5EEDB0C5, 4, 64, 32, 0x487CBA24 }, /* 5EEDB0C5_s0-0_g4_k0_e64_d32, AMENIZER */
|
||||||
};
|
};
|
||||||
static int engine_ok;
|
static int engine_ok;
|
||||||
static uint32_t engine_worst_us;
|
static uint32_t engine_worst_us;
|
||||||
@@ -35,7 +35,7 @@ static void engine_selftest(void){
|
|||||||
engine_ok = 1; engine_worst_us = 0;
|
engine_ok = 1; engine_worst_us = 0;
|
||||||
for(c = 0; c < 3; c++){
|
for(c = 0; c < 3; c++){
|
||||||
sb_state st = { GOLDEN[c].seed, {0, 0} };
|
sb_state st = { GOLDEN[c].seed, {0, 0} };
|
||||||
sb_params p = { GOLDEN[c].gen, GOLDEN[c].energy, GOLDEN[c].drift, 0, -1, -1 };
|
sb_params p = { GOLDEN[c].gen, SB_KEYS_MOTIF, GOLDEN[c].energy, GOLDEN[c].drift, 0, -1, -1 };
|
||||||
uint32_t h = SB_FNV_INIT;
|
uint32_t h = SB_FNV_INIT;
|
||||||
for(b = 0; b < 8; b++){
|
for(b = 0; b < 8; b++){
|
||||||
uint64_t t0 = time_us_64();
|
uint64_t t0 = time_us_64();
|
||||||
|
|||||||
@@ -99,17 +99,18 @@ static const int NUDGE_GRP[SB_NT] = {0,0,1,3,2,2,3,0,4,4,5};
|
|||||||
const char *sb_form_name(int form_idx){ return FORM_NAMES[form_idx]; }
|
const char *sb_form_name(int form_idx){ return FORM_NAMES[form_idx]; }
|
||||||
|
|
||||||
/* ---------- event lists ---------- */
|
/* ---------- event lists ---------- */
|
||||||
typedef struct { int16_t t, v, vel, a, tr; } ev_t;
|
typedef struct { int16_t t, v, vel, a, tr, len; } ev_t; /* len: keys generators' fixed length, 0 = the default rule */
|
||||||
typedef struct { int n; ev_t e[SB_MAX_EVENTS]; } evlist;
|
typedef struct { int n; ev_t e[SB_MAX_EVENTS]; } evlist;
|
||||||
|
|
||||||
static void push_tr(evlist *l, int t, int v, int vel, int a, int tr){
|
static void push_tr(evlist *l, int t, int v, int vel, int a, int tr){
|
||||||
ev_t *e;
|
ev_t *e;
|
||||||
if(l->n >= SB_MAX_EVENTS){ g_overflow = 1; return; }
|
if(l->n >= SB_MAX_EVENTS){ g_overflow = 1; return; }
|
||||||
e = &l->e[l->n++];
|
e = &l->e[l->n++];
|
||||||
e->t = (int16_t)t; e->v = (int16_t)v; e->vel = (int16_t)vel; e->a = (int16_t)a; e->tr = (int16_t)tr;
|
e->t = (int16_t)t; e->v = (int16_t)v; e->vel = (int16_t)vel; e->a = (int16_t)a; e->tr = (int16_t)tr; e->len = 0;
|
||||||
}
|
}
|
||||||
|
static void push_len(evlist *l, int t, int v, int vel, int tr, int len){ int n0 = l->n; push_tr(l, t, v, vel, 0, tr); if(l->n > n0) l->e[l->n - 1].len = (int16_t)len; }
|
||||||
static void push(evlist *l, int t, int v, int vel, int a){ push_tr(l, t, v, vel, a, 0); }
|
static void push(evlist *l, int t, int v, int vel, int a){ push_tr(l, t, v, vel, a, 0); }
|
||||||
static void push_ev(evlist *l, const ev_t *e){ push_tr(l, e->t, e->v, e->vel, e->a, e->tr); }
|
static void push_ev(evlist *l, const ev_t *e){ if(l->n >= SB_MAX_EVENTS){ g_overflow = 1; return; } l->e[l->n++] = *e; }
|
||||||
|
|
||||||
/* keep the events where cond holds; inside cond, e points at the current event */
|
/* keep the events where cond holds; inside cond, e points at the current event */
|
||||||
#define FILTER(l, cond) do{ int o_ = 0, i_; for(i_ = 0; i_ < (l)->n; i_++){ const ev_t *e = &(l)->e[i_]; if(cond) (l)->e[o_++] = *e; } (l)->n = o_; }while(0)
|
#define FILTER(l, cond) do{ int o_ = 0, i_; for(i_ = 0; i_ < (l)->n; i_++){ const ev_t *e = &(l)->e[i_]; if(cond) (l)->e[o_++] = *e; } (l)->n = o_; }while(0)
|
||||||
@@ -124,28 +125,35 @@ static void sort_tv(evlist *l){ /* stable, by (t, v) */
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- genome ---------- */
|
/* ---------- genome ---------- */
|
||||||
void sb_genome_make(uint32_t seed, sb_genome *g){
|
static uint32_t role_seed(const sb_state *st, int r){ return mix(mix(st->seed, 0x524F4C45u + (uint32_t)r), st->salts[r]); }
|
||||||
|
void sb_genome_make(const sb_state *st, sb_genome *g){
|
||||||
static const int ROT_W[4] = {4,2,2,1}, BASS_DEG[3] = {4,2,3}, ARC_W[3] = {3,2,2};
|
static const int ROT_W[4] = {4,2,2,1}, BASS_DEG[3] = {4,2,3}, ARC_W[3] = {3,2,2};
|
||||||
static const int ACC[5] = {0,3,5,3,7}, ACC_W[5] = {3,3,2,2,1};
|
static const int ACC[5] = {0,3,5,3,7}, ACC_W[5] = {3,3,2,2,1};
|
||||||
sb_rng r = rng(mix(seed, 0x47454E));
|
sb_rng r = rng(mix(st->seed, 0x47454E)), k, l;
|
||||||
int fi;
|
int fi;
|
||||||
|
/* shared */
|
||||||
g->steps = STEPS[pick_w(&r, STEPS_W, 4)];
|
g->steps = STEPS[pick_w(&r, STEPS_W, 4)];
|
||||||
fi = pick_w(&r, FORMS_W, 6); g->form_idx = fi; memcpy(g->form, FORMS[fi], sizeof g->form);
|
fi = pick_w(&r, FORMS_W, 6); g->form_idx = fi; memcpy(g->form, FORMS[fi], sizeof g->form);
|
||||||
g->scale = below(&r, SB_NSCALES); g->root = below(&r, 12);
|
g->fillP = 40 + below(&r, 80);
|
||||||
g->dens[0] = 40 + below(&r, 60); g->dens[1] = 30 + below(&r, 70);
|
|
||||||
g->fillP = 40 + below(&r, 80); g->monoLen = below(&r, 3); g->monoRange = 1 + below(&r, 2);
|
|
||||||
g->rot = pick_w(&r, ROT_W, 4);
|
g->rot = pick_w(&r, ROT_W, 4);
|
||||||
g->hocket = HOCKETS[below(&r, 8)];
|
|
||||||
g->bassDeg = BASS_DEG[below(&r, 3)];
|
|
||||||
g->prog_idx = below(&r, 10); memcpy(g->prog, PROGS[g->prog_idx], sizeof g->prog);
|
|
||||||
g->swing = SWINGS[pick_w(&r, SWING_W, 5)];
|
g->swing = SWINGS[pick_w(&r, SWING_W, 5)];
|
||||||
g->arc = pick_w(&r, ARC_W, 3);
|
g->arc = pick_w(&r, ARC_W, 3);
|
||||||
g->motifLen = 3 + below(&r, 3);
|
|
||||||
g->accentCycle = ACC[pick_w(&r, ACC_W, 5)];
|
|
||||||
g->layback = below(&r, 2);
|
g->layback = below(&r, 2);
|
||||||
g->frame = pick_w(&r, FRAME_W, 8); g->freeK = 2 + below(&r, 3); g->freeRot = below(&r, 16);
|
/* kit */
|
||||||
|
k = rng(mix(role_seed(st, 0), 0x4B4954));
|
||||||
|
g->dens[0] = 40 + below(&k, 60); g->dens[1] = 0;
|
||||||
|
g->accentCycle = ACC[pick_w(&k, ACC_W, 5)];
|
||||||
|
g->frame = pick_w(&k, FRAME_W, 8); g->freeK = 2 + below(&k, 3); g->freeRot = below(&k, 16);
|
||||||
|
/* keys */
|
||||||
|
l = rng(mix(role_seed(st, 1), 0x4B4559));
|
||||||
|
g->scale = below(&l, SB_NSCALES); g->root = below(&l, 12);
|
||||||
|
g->dens[1] = 30 + below(&l, 70);
|
||||||
|
g->monoLen = below(&l, 3); g->monoRange = 1 + below(&l, 2);
|
||||||
|
g->hocket = HOCKETS[below(&l, 8)];
|
||||||
|
g->bassDeg = BASS_DEG[below(&l, 3)];
|
||||||
|
g->prog_idx = below(&l, 10); memcpy(g->prog, PROGS[g->prog_idx], sizeof g->prog);
|
||||||
|
g->motifLen = 3 + below(&l, 3);
|
||||||
}
|
}
|
||||||
static uint32_t role_seed(const sb_state *st, int r){ return mix(mix(st->seed, 0x524F4C45u + (uint32_t)r), st->salts[r]); }
|
|
||||||
static int eu(int i, int k, int n, int rot){ int j = (i + rot) % n; return ((j * k) % n) < k; }
|
static int eu(int i, int k, int n, int rot){ int j = (i + rot) % n; return ((j * k) % n) < k; }
|
||||||
/* groups(steps): [s, min(4, steps-s)] for s = 0, 4, 8, ... */
|
/* groups(steps): [s, min(4, steps-s)] for s = 0, 4, 8, ... */
|
||||||
static int n_groups(int steps){ return (steps + 3) / 4; }
|
static int n_groups(int steps){ return (steps + 3) / 4; }
|
||||||
@@ -578,6 +586,136 @@ static void line_tracks(uint32_t rs, int variant, const sb_genome *g, int steps,
|
|||||||
one_at_a_time(bass);
|
one_at_a_time(bass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- keys generators ---------- */
|
||||||
|
typedef struct { uint32_t rs; int variant; const sb_genome *g; int steps, d, sec, pos; const evlist *kit; int fill, bar, drift; } keys_in;
|
||||||
|
static void keys_motif(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
|
||||||
|
line_tracks(in->rs, in->variant, in->g, in->steps, in->d, in->sec, in->pos, in->kit, in->fill, mel, bass, pad);
|
||||||
|
}
|
||||||
|
static void pad_chord(const sb_genome *g, const int *tones, int nt, evlist *pad){
|
||||||
|
uint8_t seen[128];
|
||||||
|
int i;
|
||||||
|
memset(seen, 0, sizeof seen);
|
||||||
|
for(i = 0; i < nt; i++){ int p = 48 + mod(deg_pitch(g, 0, tones[i]), 12); if(!seen[p]){ seen[p] = 1; push_tr(pad, 0, p, i == 0 ? 58 : 50, 0, SB_T_PAD); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct { int rank, off, up, acc, sl; } step303;
|
||||||
|
static void keys_303(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
|
||||||
|
static const int OFF[4] = {0,2,4,6}, OFF_W[4] = {10,2,3,1}, TONE[3] = {0,2,4};
|
||||||
|
const sb_genome *g = in->g;
|
||||||
|
sb_rng r = rng(mix(in->rs, 0x333033)), ar;
|
||||||
|
step303 pat[16];
|
||||||
|
uint8_t on[MAX_STEPS];
|
||||||
|
int c = chord_deg(g, in->pos), steps = in->steps, i, s, drone, thr;
|
||||||
|
mel->n = bass->n = pad->n = 0;
|
||||||
|
for(i = 0; i < 16; i++){
|
||||||
|
pat[i].rank = 0;
|
||||||
|
if(i > 0) pat[i].rank = below(&r, 128);
|
||||||
|
pat[i].off = OFF[pick_w(&r, OFF_W, 4)];
|
||||||
|
pat[i].up = chance(&r, P(24));
|
||||||
|
pat[i].acc = chance(&r, P(40));
|
||||||
|
pat[i].sl = chance(&r, P(28));
|
||||||
|
}
|
||||||
|
drone = chance(&r, P(64));
|
||||||
|
if(in->variant){
|
||||||
|
sb_rng vr = rng(mix(mix(in->rs, 0x5641u + (uint32_t)in->sec), (uint32_t)in->variant));
|
||||||
|
for(i = 0; i < 16; i++){
|
||||||
|
if(chance(&vr, P(20))) pat[i].acc = !pat[i].acc;
|
||||||
|
if(chance(&vr, P(16))) pat[i].sl = !pat[i].sl;
|
||||||
|
if(chance(&vr, P(16))) pat[i].up = !pat[i].up;
|
||||||
|
if(i > 0 && chance(&vr, P(16))) pat[i].rank = 127 - pat[i].rank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thr = clamp(in->d + 16, 0, 128);
|
||||||
|
for(s = 0; s < steps; s++) on[s] = (uint8_t)(pat[s % 16].rank < thr);
|
||||||
|
for(s = 0; s < steps; s++) if(on[s]){
|
||||||
|
const step303 *p = &pat[s % 16];
|
||||||
|
int nx = s + 1;
|
||||||
|
while(nx < steps && !on[nx]) nx++;
|
||||||
|
push_len(bass, s * TPS, bp(g, c + p->off) + (p->up ? 12 : 0), p->acc ? 120 : 80, SB_T_BASS, p->sl ? (nx - s) * TPS + 20 : TPS >> 1);
|
||||||
|
}
|
||||||
|
ar = rng(mix(in->rs, 0x414E53u + (uint32_t)in->sec));
|
||||||
|
for(s = 0; s < steps;){
|
||||||
|
int e2 = s, tr;
|
||||||
|
if(on[s]){ s++; continue; }
|
||||||
|
while(e2 < steps && !on[e2]) e2++;
|
||||||
|
if(e2 - s >= 2 && chance(&ar, P(in->d + 32))){
|
||||||
|
tr = ((g->hocket >> (s & 7)) & 1) ? SB_T_MELA : SB_T_MELB;
|
||||||
|
push_len(mel, s * TPS, mel_pitch(g, c + TONE[below(&ar, 3)]), 84, tr, TPS - 40);
|
||||||
|
if(e2 - s >= 3 && chance(&ar, P(64))) push_len(mel, (s + 1) * TPS, mel_pitch(g, c + TONE[below(&ar, 3)]), 72, tr, TPS - 40);
|
||||||
|
}
|
||||||
|
s = e2;
|
||||||
|
}
|
||||||
|
if(drone) push_tr(pad, 0, 48 + g->root, 52, 0, SB_T_PAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct { int L, flipP; uint32_t mask, fs; } turing_t;
|
||||||
|
static uint32_t rotl_bits(uint32_t v, int s, int L){ return s ? (((v << s) | (v >> (L - s))) & ((1u << L) - 1u)) : v; }
|
||||||
|
static uint32_t turing_step(const turing_t *T, uint32_t v, int k){
|
||||||
|
uint32_t top = (v >> (T->L - 1)) & 1u;
|
||||||
|
if(T->flipP && (int)(mix(T->fs, (uint32_t)k) >> 16) < T->flipP) top ^= 1u;
|
||||||
|
return ((v << 1) | top) & T->mask;
|
||||||
|
}
|
||||||
|
static void keys_turing(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
|
||||||
|
const sb_genome *g = in->g;
|
||||||
|
sb_rng r = rng(mix(in->rs, 0x545552));
|
||||||
|
turing_t T;
|
||||||
|
uint32_t reg;
|
||||||
|
int n = SCALE_LEN[g->scale], c = chord_deg(g, in->pos), from, to, k, s, j = 0, span, tones[3];
|
||||||
|
mel->n = bass->n = pad->n = 0;
|
||||||
|
T.L = chance(&r, P(64)) ? 8 : 16; T.mask = (1u << T.L) - 1u;
|
||||||
|
reg = sb_rng_next(&r) & T.mask;
|
||||||
|
if(!reg) reg = 1;
|
||||||
|
T.flipP = P(in->drift >> 2); T.fs = mix(in->rs, 0x464C50);
|
||||||
|
from = imax(0, in->bar - 16) * in->steps; to = in->bar * in->steps;
|
||||||
|
reg = rotl_bits(reg, from % T.L, T.L);
|
||||||
|
for(k = from; k < to; k++) reg = turing_step(&T, reg, k);
|
||||||
|
span = n * g->monoRange;
|
||||||
|
for(s = 0; s < in->steps; s++){
|
||||||
|
if((int)((reg >> 2) & 7u) * 18 < in->d){
|
||||||
|
int v = mel_pitch(g, c + (int)(((reg & 0xFFu) * (uint32_t)(span + 1)) >> 8));
|
||||||
|
push_tr(mel, s * TPS, v, 70 + (int)((reg >> 5) & 3u) * 10 + (s % 4 == 0 ? 12 : 0), 0, ((g->hocket >> (j & 7)) & 1) ? SB_T_MELA : SB_T_MELB);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if(reg & 1u) push_tr(bass, s * TPS, bp(g, c) + (((reg >> (T.L >> 1)) & 1u) ? 12 : 0), s == 0 ? 110 : 94, 0, SB_T_BASS);
|
||||||
|
reg = turing_step(&T, reg, to + s);
|
||||||
|
}
|
||||||
|
tones[0] = c; tones[1] = c + 2; tones[2] = c + 4;
|
||||||
|
pad_chord(g, tones, 3, pad);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void keys_arp(const keys_in *in, evlist *mel, evlist *bass, evlist *pad){
|
||||||
|
const sb_genome *g = in->g;
|
||||||
|
sb_rng r = rng(mix(in->rs, 0x415250));
|
||||||
|
int n = SCALE_LEN[g->scale], c = chord_deg(g, in->pos), i, o, seventh, dir, nch, chord[4], up[8], seq[16], nup = 0, nseq = 0;
|
||||||
|
int off = 0, rate, last0, s = 0, k = 0, nk = 0;
|
||||||
|
mel->n = bass->n = pad->n = 0;
|
||||||
|
seventh = chance(&r, P(64));
|
||||||
|
dir = below(&r, 4);
|
||||||
|
nch = seventh ? 4 : 3; chord[0] = c; chord[1] = c + 2; chord[2] = c + 4; chord[3] = c + 6;
|
||||||
|
for(o = 0; o < g->monoRange; o++) for(i = 0; i < nch; i++) up[nup++] = chord[i] + o * n;
|
||||||
|
if(dir == 1) for(i = nup - 1; i >= 0; i--) seq[nseq++] = up[i];
|
||||||
|
else {
|
||||||
|
for(i = 0; i < nup; i++) seq[nseq++] = up[i];
|
||||||
|
if(dir == 2) for(i = nup - 2; i > 0; i--) seq[nseq++] = up[i];
|
||||||
|
else if(dir == 3) for(i = nseq - 1; i > 0; i--){ int j = below(&r, i + 1), tmp = seq[i]; seq[i] = seq[j]; seq[j] = tmp; }
|
||||||
|
}
|
||||||
|
if(in->variant){ sb_rng vr = rng(mix(mix(in->rs, 0x5641u + (uint32_t)in->sec), (uint32_t)in->variant)); off = below(&vr, nseq); }
|
||||||
|
rate = in->d >= 64 ? 1 : 2; last0 = (n_groups(in->steps) - 1) * 4;
|
||||||
|
while(s < in->steps){
|
||||||
|
int stp = (in->fill && s >= last0) ? 1 : rate;
|
||||||
|
push_len(mel, s * TPS, mel_pitch(g, seq[(k + off) % nseq]), s % 4 == 0 ? 96 : 78, (k & 1) ? SB_T_MELB : SB_T_MELA, stp * TPS - 20);
|
||||||
|
k++; s += stp;
|
||||||
|
}
|
||||||
|
for(i = 0; i < in->kit->n; i++){
|
||||||
|
const ev_t *e = &in->kit->e[i];
|
||||||
|
if(e->v == 0 && e->t % TPS == 0){ push_tr(bass, e->t, bp(g, c), e->t == 0 ? 112 : 98, 0, SB_T_BASS); nk++; }
|
||||||
|
}
|
||||||
|
if(!nk) push_tr(bass, 0, bp(g, c), 104, 0, SB_T_BASS);
|
||||||
|
pad_chord(g, chord, nch, pad);
|
||||||
|
}
|
||||||
|
static const char *const KEYS_NAMES[SB_NKEYS] = {"MOTIF","303","TURING","ARP"};
|
||||||
|
const char *sb_keys_name(int keys){ return keys >= 0 && keys < SB_NKEYS ? KEYS_NAMES[keys] : "?"; }
|
||||||
|
|
||||||
static int kit_track(const ev_t *e){
|
static int kit_track(const ev_t *e){
|
||||||
if(e->v == 0) return (e->a || e->t == 0) ? SB_T_KICK : SB_T_KICK2;
|
if(e->v == 0) return (e->a || e->t == 0) ? SB_T_KICK : SB_T_KICK2;
|
||||||
if(e->v == 1) return (e->a || e->vel >= 90) ? SB_T_SNARE : SB_T_GHOST;
|
if(e->v == 1) return (e->a || e->vel >= 90) ? SB_T_SNARE : SB_T_GHOST;
|
||||||
@@ -640,14 +778,15 @@ static void mono(const mono_ctx *c, const evlist *list, int lenRule){
|
|||||||
for(k = 0; k < list->n; k++){
|
for(k = 0; k < list->n; k++){
|
||||||
const ev_t *cur = &list->e[k], *nx = k + 1 < list->n ? &list->e[k + 1] : 0;
|
const ev_t *cur = &list->e[k], *nx = k + 1 < list->n ? &list->e[k + 1] : 0;
|
||||||
int gap = (nx ? nx->t : c->barTicks) - cur->t, len, t0;
|
int gap = (nx ? nx->t : c->barTicks) - cur->t, len, t0;
|
||||||
if(lenRule == LEN_BASS) len = gap - 10;
|
if(cur->len > 0) len = cur->len; /* fixed by the keys generator */
|
||||||
|
else if(lenRule == LEN_BASS) len = gap - 10;
|
||||||
else if(g->monoLen == 0) len = imin(90, gap - 5);
|
else if(g->monoLen == 0) len = imin(90, gap - 5);
|
||||||
else if(g->monoLen == 1) len = imax(30, fdiv(gap, 2));
|
else if(g->monoLen == 1) len = imax(30, fdiv(gap, 2));
|
||||||
else len = (nx && nx->v != cur->v) ? gap + 20 : gap - 5;
|
else len = (nx && nx->v != cur->v) ? gap + 20 : gap - 5;
|
||||||
t0 = cur->t;
|
t0 = cur->t;
|
||||||
if(c->melShift && (cur->tr == SB_T_MELA || cur->tr == SB_T_MELB)) t0 = mod(t0 + c->melShift, c->barTicks);
|
if(c->melShift && (cur->tr == SB_T_MELA || cur->tr == SB_T_MELB)) t0 = mod(t0 + c->melShift, c->barTicks);
|
||||||
emit(c->out, clamp(groove(t0, g, 0, c->barTicks, c->swingBar) + nudge(c->barSeed, cur->t, NUDGE_GRP[cur->tr], c->micro), 0, c->barTicks - 1),
|
emit(c->out, clamp(groove(t0, g, 0, c->barTicks, c->swingBar) + nudge(c->barSeed, cur->t, NUDGE_GRP[cur->tr], c->micro), 0, c->barTicks - 1),
|
||||||
1, cur->v, cur->vel, imax(10, len - c->swingBar), cur->tr);
|
1, cur->v, cur->vel, imax(10, cur->len > 0 ? len : len - c->swingBar), cur->tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static void sort_out(sb_bar *out){ /* stable, by (t, tr, v) */
|
static void sort_out(sb_bar *out){ /* stable, by (t, tr, v) */
|
||||||
@@ -674,7 +813,7 @@ void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out)
|
|||||||
int dr, dVar, micro, wob, slip, disp, warp, e, arcD, kd, hd, warpBeat, melShift, swingBar, variants[2];
|
int dr, dVar, micro, wob, slip, disp, warp, e, arcD, kd, hd, warpBeat, melShift, swingBar, variants[2];
|
||||||
|
|
||||||
g_overflow = 0;
|
g_overflow = 0;
|
||||||
sb_genome_make(st->seed, &g);
|
sb_genome_make(st, &g);
|
||||||
sb_apply_key(&g, p);
|
sb_apply_key(&g, p);
|
||||||
if(amen){ g.steps = 16; g.swing = 0; g.layback = 0; }
|
if(amen){ g.steps = 16; g.swing = 0; g.layback = 0; }
|
||||||
pos = bar % PHRASE_BARS; phrase = bar / PHRASE_BARS; sec = g.form[pos]; barTicks = g.steps * TPS;
|
pos = bar % PHRASE_BARS; phrase = bar / PHRASE_BARS; sec = g.form[pos]; barTicks = g.steps * TPS;
|
||||||
@@ -725,7 +864,17 @@ void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out)
|
|||||||
if(disp && chance(&dRng, P(disp >> 1))) melShift = below(&dRng, 2) ? TPS : -TPS;
|
if(disp && chance(&dRng, P(disp >> 1))) melShift = below(&dRng, 2) ? TPS : -TPS;
|
||||||
swingBar = g.swing;
|
swingBar = g.swing;
|
||||||
if(wob){ int w = fdiv(wob * 40, 127); swingBar = clamp(g.swing + below(&dRng, 2 * w + 1) - w, 0, 90); }
|
if(wob){ int w = fdiv(wob * 40, 127); swingBar = clamp(g.swing + below(&dRng, 2 * w + 1) - w, 0, 90); }
|
||||||
line_tracks(role_seed(st, 1), variants[1], &g, g.steps, clamp(g.dens[1] + e - 64, 0, 127), sec, pos, &kit, fill, &mel, &bass, &pad);
|
{
|
||||||
|
keys_in ki;
|
||||||
|
ki.rs = role_seed(st, 1); ki.variant = variants[1]; ki.g = &g; ki.steps = g.steps; ki.d = clamp(g.dens[1] + e - 64, 0, 127);
|
||||||
|
ki.sec = sec; ki.pos = pos; ki.kit = &kit; ki.fill = fill; ki.bar = bar; ki.drift = dr;
|
||||||
|
switch(clamp(p->keys, 0, SB_NKEYS - 1)){
|
||||||
|
case SB_KEYS_303: keys_303(&ki, &mel, &bass, &pad); break;
|
||||||
|
case SB_KEYS_TURING: keys_turing(&ki, &mel, &bass, &pad); break;
|
||||||
|
case SB_KEYS_ARP: keys_arp(&ki, &mel, &bass, &pad); break;
|
||||||
|
default: keys_motif(&ki, &mel, &bass, &pad); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out->n = 0;
|
out->n = 0;
|
||||||
for(i = 0; i < kit.n; i++){
|
for(i = 0; i < kit.n; i++){
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
enum { SB_T_KICK, SB_T_KICK2, SB_T_SNARE, SB_T_GHOST, SB_T_HAT, SB_T_HAT16, SB_T_PERC,
|
enum { SB_T_KICK, SB_T_KICK2, SB_T_SNARE, SB_T_GHOST, SB_T_HAT, SB_T_HAT16, SB_T_PERC,
|
||||||
SB_T_BASS, SB_T_MELA, SB_T_MELB, SB_T_PAD };
|
SB_T_BASS, SB_T_MELA, SB_T_MELB, SB_T_PAD };
|
||||||
enum { SB_GEN_EUCLID, SB_GEN_SKELETON, SB_GEN_SUBDIV, SB_GEN_AUTOMATA, SB_GEN_AMENIZER, SB_NGEN };
|
enum { SB_GEN_EUCLID, SB_GEN_SKELETON, SB_GEN_SUBDIV, SB_GEN_AUTOMATA, SB_GEN_AMENIZER, SB_NGEN };
|
||||||
|
enum { SB_KEYS_MOTIF, SB_KEYS_303, SB_KEYS_TURING, SB_KEYS_ARP, SB_NKEYS };
|
||||||
|
|
||||||
/* hash and RNG */
|
/* hash and RNG */
|
||||||
uint32_t sb_mix(uint32_t a, uint32_t b);
|
uint32_t sb_mix(uint32_t a, uint32_t b);
|
||||||
@@ -38,11 +39,12 @@ typedef struct {
|
|||||||
int prog[4];
|
int prog[4];
|
||||||
int swing, arc, motifLen, accentCycle, layback, frame, freeK, freeRot;
|
int swing, arc, motifLen, accentCycle, layback, frame, freeK, freeRot;
|
||||||
} sb_genome;
|
} sb_genome;
|
||||||
void sb_genome_make(uint32_t seed, sb_genome *g);
|
typedef struct { uint32_t seed; uint32_t salts[2]; } sb_state;
|
||||||
|
/* shared part from the seed; kit and keys parts also take their salt (ROLL KIT / ROLL KEYS) */
|
||||||
|
void sb_genome_make(const sb_state *st, sb_genome *g);
|
||||||
const char *sb_form_name(int form_idx);
|
const char *sb_form_name(int form_idx);
|
||||||
|
|
||||||
typedef struct { uint32_t seed; uint32_t salts[2]; } sb_state;
|
typedef struct { int gen, keys, energy, drift, fill, scale, root; } sb_params; /* scale, root: -1 = AUTO (the seed picks) */
|
||||||
typedef struct { int gen, energy, drift, fill, scale, root; } sb_params; /* scale, root: -1 = AUTO (the seed picks) */
|
|
||||||
|
|
||||||
/* one output row: [t, lane, v, vel, len, tr] */
|
/* one output row: [t, lane, v, vel, len, tr] */
|
||||||
typedef struct { int16_t t, len; uint8_t lane, v, vel, tr; } sb_event;
|
typedef struct { int16_t t, len; uint8_t lane, v, vel, tr; } sb_event;
|
||||||
@@ -56,7 +58,8 @@ typedef struct {
|
|||||||
|
|
||||||
/* ROOT / SCALE pages: overrides the genome's scale and root when they're >= 0 (app.js applyKey). */
|
/* ROOT / SCALE pages: overrides the genome's scale and root when they're >= 0 (app.js applyKey). */
|
||||||
void sb_apply_key(sb_genome *g, const sb_params *p);
|
void sb_apply_key(sb_genome *g, const sb_params *p);
|
||||||
const char *sb_scale_name(int scale); /* screen name, at most 10 characters */
|
const char *sb_scale_name(int scale);
|
||||||
|
const char *sb_keys_name(int keys); /* MOTIF, 303, TURING, ARP */ /* screen name, at most 10 characters */
|
||||||
|
|
||||||
void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out);
|
void sb_render_bar(const sb_state *st, const sb_params *p, int bar, sb_bar *out);
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ static void check_primitives(const char *dir){
|
|||||||
sb_genome g;
|
sb_genome g;
|
||||||
int bad = 0, k;
|
int bad = 0, k;
|
||||||
n++;
|
n++;
|
||||||
sb_genome_make(seed, &g);
|
{
|
||||||
|
sb_state gs; jval *sl = json_get(v, "salts");
|
||||||
|
gs.seed = seed; gs.salts[0] = sl ? (uint32_t)num(sl->items[0]) : 0; gs.salts[1] = sl ? (uint32_t)num(sl->items[1]) : 0;
|
||||||
|
sb_genome_make(&gs, &g);
|
||||||
|
}
|
||||||
for(k = 0; k < gj->n; k++){
|
for(k = 0; k < gj->n; k++){
|
||||||
jval *f = gj->items[k];
|
jval *f = gj->items[k];
|
||||||
const char *key = f->key;
|
const char *key = f->key;
|
||||||
@@ -86,7 +90,7 @@ static void check_case(const jval *c){
|
|||||||
n++;
|
n++;
|
||||||
st.seed = hex(json_get(c, "seed"));
|
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]);
|
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.gen = (int)num(json_get(c, "gen")); p.keys = (int)num(json_get(c, "keys")); 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"));
|
p.scale = (int)num(json_get(c, "scale")); p.root = (int)num(json_get(c, "root"));
|
||||||
for(b = 0; b < bars->n; b++){
|
for(b = 0; b < bars->n; b++){
|
||||||
jval *jb = bars->items[b], *ev = json_get(jb, "events");
|
jval *jb = bars->items[b], *ev = json_get(jb, "events");
|
||||||
|
|||||||
31
sim/app.js
31
sim/app.js
@@ -18,16 +18,17 @@
|
|||||||
if(!Array.isArray(cfg.kit.learn) || cfg.kit.learn.length<7) cfg.kit.learn=[null,null,null,null,null,null,null];
|
if(!Array.isArray(cfg.kit.learn) || cfg.kit.learn.length<7) cfg.kit.learn=[null,null,null,null,null,null,null];
|
||||||
function saveCfg(){ store('cfg',cfg); }
|
function saveCfg(){ store('cfg',cfg); }
|
||||||
|
|
||||||
var app={ state:load('state',null)||{seed:0x5EEDB0C5,salts:[0,0]}, gen:load('gen',0), scale:load('scale',-1), root:load('root',-1), energy:load('energy',64), drift:load('drift',32), bpm:load('bpm',165),
|
var app={ state:load('state',null)||{seed:0x5EEDB0C5,salts:[0,0]}, gen:load('gen',0), keys:load('keys',0), scale:load('scale',-1), root:load('root',-1), energy:load('energy',64), drift:load('drift',32), bpm:load('bpm',165),
|
||||||
on:load('on',null), inv:[], fill:false, history:[], slots:load('slots',[null,null,null,null,null,null,null,null]),
|
on:load('on',null), inv:[], fill:false, history:[], slots:load('slots',[null,null,null,null,null,null,null,null]),
|
||||||
page:0, editing:false, histSel:0, slotSel:0, rollSel:0, learn:0, learnTr:-1 };
|
page:0, editing:false, histSel:0, slotSel:0, rollSel:0, learn:0, learnTr:-1 };
|
||||||
|
if(!(app.keys>=0 && app.keys<E.KEYS_NAMES.length)) app.keys=0;
|
||||||
if(!(app.scale>=-1 && app.scale<E.SCALE_NAMES.length)) app.scale=-1;
|
if(!(app.scale>=-1 && app.scale<E.SCALE_NAMES.length)) app.scale=-1;
|
||||||
if(!(app.root>=-1 && app.root<12)) app.root=-1;
|
if(!(app.root>=-1 && app.root<12)) app.root=-1;
|
||||||
if(!Array.isArray(app.on) || app.on.length!==NT){ app.on=[]; for(var i9=0;i9<NT;i9++) app.on.push(true); }
|
if(!Array.isArray(app.on) || app.on.length!==NT){ app.on=[]; for(var i9=0;i9<NT;i9++) app.on.push(true); }
|
||||||
for(var i0=0;i0<NT;i0++) app.inv.push(false); // inv: flipped while a button is held
|
for(var i0=0;i0<NT;i0++) app.inv.push(false); // inv: flipped while a button is held
|
||||||
var PAGES=['SEED','GEN','ROOT','SCALE','BPM','SLOT','ROLL','LEARN'];
|
var PAGES=['SEED','GEN','KEYS','ROOT','SCALE','BPM','SLOT','ROLL','LEARN'];
|
||||||
function params(fillNow){ return {gen:app.gen,energy:app.energy,drift:app.drift,fill:!!fillNow,scale:app.scale,root:app.root}; }
|
function params(fillNow){ return {gen:app.gen,keys:app.keys,energy:app.energy,drift:app.drift,fill:!!fillNow,scale:app.scale,root:app.root}; }
|
||||||
function save(){ store('state',app.state); store('gen',app.gen); store('scale',app.scale); store('root',app.root); store('energy',app.energy); store('drift',app.drift); store('bpm',app.bpm); }
|
function save(){ store('state',app.state); store('gen',app.gen); store('keys',app.keys); store('scale',app.scale); store('root',app.root); store('energy',app.energy); store('drift',app.drift); store('bpm',app.bpm); }
|
||||||
function setState(s){
|
function setState(s){
|
||||||
app.history.unshift({seed:app.state.seed,salts:app.state.salts.slice()}); if(app.history.length>16) app.history.pop();
|
app.history.unshift({seed:app.state.seed,salts:app.state.salts.slice()}); if(app.history.length>16) app.history.pop();
|
||||||
app.state={seed:s.seed>>>0,salts:[s.salts[0]|0,s.salts[1]|0]}; app.histSel=0; save(); dirty=true;
|
app.state={seed:s.seed>>>0,salts:[s.salts[0]|0,s.salts[1]|0]}; app.histSel=0; save(); dirty=true;
|
||||||
@@ -150,12 +151,13 @@
|
|||||||
|
|
||||||
/* ---------- actions ---------- */
|
/* ---------- actions ---------- */
|
||||||
function newSeed(){ setState({seed:rnd32(),salts:[0,0]}); }
|
function newSeed(){ setState({seed:rnd32(),salts:[0,0]}); }
|
||||||
function reroll(l){ var s={seed:app.state.seed,salts:app.state.salts.slice()}; s.salts[l]=(s.salts[l]+1)&0xFFFF; setState(s); msg(l?'NEW LINE':'NEW KIT',700); }
|
function reroll(l){ var s={seed:app.state.seed,salts:app.state.salts.slice()}; s.salts[l]=(s.salts[l]+1)&0xFFFF; setState(s); msg(l?'NEW KEYS':'NEW KIT',700); }
|
||||||
function encTurn(d){
|
function encTurn(d){
|
||||||
if(!app.editing){ app.page=(app.page+d+PAGES.length)%PAGES.length; app.learn=0; app.learnTr=-1; return; }
|
if(!app.editing){ app.page=(app.page+d+PAGES.length)%PAGES.length; app.learn=0; app.learnTr=-1; return; }
|
||||||
var pg=PAGES[app.page];
|
var pg=PAGES[app.page];
|
||||||
if(pg==='SEED') app.histSel=clamp(app.histSel+d,0,app.history.length);
|
if(pg==='SEED') app.histSel=clamp(app.histSel+d,0,app.history.length);
|
||||||
else if(pg==='GEN'){ app.gen=(app.gen+d+5)%5; save(); dirty=true; }
|
else if(pg==='GEN'){ app.gen=(app.gen+d+5)%5; save(); dirty=true; }
|
||||||
|
else if(pg==='KEYS'){ app.keys=mod(app.keys+d,E.KEYS_NAMES.length); save(); dirty=true; }
|
||||||
else if(pg==='ROOT'){ app.root=mod(app.root+1+d,13)-1; save(); dirty=true; } // AUTO, C .. B
|
else if(pg==='ROOT'){ app.root=mod(app.root+1+d,13)-1; save(); dirty=true; } // AUTO, C .. B
|
||||||
else if(pg==='SCALE'){ var ns=E.SCALE_NAMES.length+1; app.scale=mod(app.scale+1+d,ns)-1; save(); dirty=true; } // AUTO, MAJOR .. LOCRIAN
|
else if(pg==='SCALE'){ var ns=E.SCALE_NAMES.length+1; app.scale=mod(app.scale+1+d,ns)-1; save(); dirty=true; } // AUTO, MAJOR .. LOCRIAN
|
||||||
else if(pg==='BPM') setBpm(app.bpm+d);
|
else if(pg==='BPM') setBpm(app.bpm+d);
|
||||||
@@ -172,8 +174,8 @@
|
|||||||
if(!app.editing){ app.editing=true; return; }
|
if(!app.editing){ app.editing=true; return; }
|
||||||
if(pg==='SEED' && app.histSel>0) setState(app.history[app.histSel-1]);
|
if(pg==='SEED' && app.histSel>0) setState(app.history[app.histSel-1]);
|
||||||
if(pg==='SLOT'){
|
if(pg==='SLOT'){
|
||||||
if(hold){ app.slots[app.slotSel]={seed:app.state.seed,salts:app.state.salts.slice(),gen:app.gen}; store('slots',app.slots); msg('SAVED S'+(app.slotSel+1)); return; }
|
if(hold){ app.slots[app.slotSel]={seed:app.state.seed,salts:app.state.salts.slice(),gen:app.gen,keys:app.keys}; store('slots',app.slots); msg('SAVED S'+(app.slotSel+1)); return; }
|
||||||
var sl=app.slots[app.slotSel]; if(sl){ app.gen=sl.gen|0; setState(sl); }
|
var sl=app.slots[app.slotSel]; if(sl){ app.gen=sl.gen|0; app.keys=(sl.keys|0)%E.KEYS_NAMES.length; save(); setState(sl); }
|
||||||
}
|
}
|
||||||
if(pg==='ROLL') reroll(app.rollSel); // then back to page select
|
if(pg==='ROLL') reroll(app.rollSel); // then back to page select
|
||||||
app.editing=false;
|
app.editing=false;
|
||||||
@@ -299,7 +301,7 @@
|
|||||||
var ev=r.events.map(function(e){ return [e.t,e.lane,e.v,e.vel,e.len,e.tr]; });
|
var ev=r.events.map(function(e){ return [e.t,e.lane,e.v,e.vel,e.len,e.tr]; });
|
||||||
bars.push({bar:b,fill:r.fill,events:ev}); rows=rows.concat(ev);
|
bars.push({bar:b,fill:r.fill,events:ev}); rows=rows.concat(ev);
|
||||||
}
|
}
|
||||||
document.getElementById('vecOut').value=JSON.stringify({seed:'0x'+hex(app.state.seed),salts:app.state.salts,params:{gen:app.gen,energy:app.energy,drift:app.drift},
|
document.getElementById('vecOut').value=JSON.stringify({seed:'0x'+hex(app.state.seed),salts:app.state.salts,params:{gen:app.gen,keys:app.keys,energy:app.energy,drift:app.drift},
|
||||||
checksum:'0x'+hex(E.checksum(rows)),format:'[tick,lane,voice,vel,len,track]; all tracks are listed, on/off is applied at output',bars:bars});
|
checksum:'0x'+hex(E.checksum(rows)),format:'[tick,lane,voice,vel,len,track]; all tracks are listed, on/off is applied at output',bars:bars});
|
||||||
});
|
});
|
||||||
document.getElementById('vecCopy').addEventListener('click',function(){
|
document.getElementById('vecCopy').addEventListener('click',function(){
|
||||||
@@ -339,13 +341,14 @@
|
|||||||
function screenText(){
|
function screenText(){
|
||||||
var pg=PAGES[app.page], label='', value='';
|
var pg=PAGES[app.page], label='', value='';
|
||||||
if(pg==='SEED'){ var sh=app.histSel>0?app.history[app.histSel-1]:app.state; label='SEED'+(app.histSel>0?' -'+app.histSel:'')+((sh.salts[0]||sh.salts[1])?' *':''); value=hex(sh.seed); }
|
if(pg==='SEED'){ var sh=app.histSel>0?app.history[app.histSel-1]:app.state; label='SEED'+(app.histSel>0?' -'+app.histSel:'')+((sh.salts[0]||sh.salts[1])?' *':''); value=hex(sh.seed); }
|
||||||
else if(pg==='GEN'){ label='GENERATOR'; value=E.GEN_NAMES[app.gen]; }
|
else if(pg==='GEN'){ label='KIT GEN'; value=E.GEN_NAMES[app.gen]; }
|
||||||
|
else if(pg==='KEYS'){ label='KEYS GEN'; value=E.KEYS_NAMES[app.keys]; }
|
||||||
else if(pg==='ROOT' || pg==='SCALE'){ // on AUTO, show the seed's own pick
|
else if(pg==='ROOT' || pg==='SCALE'){ // on AUTO, show the seed's own pick
|
||||||
var sc=pg==='SCALE', x=sc?app.scale:app.root, gk=E.genome(app.state.seed);
|
var sc=pg==='SCALE', x=sc?app.scale:app.root, gk=E.genome(app.state.seed,app.state.salts);
|
||||||
label=(sc?'SCALE':'ROOT')+(x<0?' AUTO':''); if(x<0) x=sc?gk.scale:gk.root; value=sc?E.SCALE_NAMES[x]:E.NOTE_NAMES[x]; }
|
label=(sc?'SCALE':'ROOT')+(x<0?' AUTO':''); if(x<0) x=sc?gk.scale:gk.root; value=sc?E.SCALE_NAMES[x]:E.NOTE_NAMES[x]; }
|
||||||
else if(pg==='BPM'){ label='TEMPO'; value=app.bpm+' BPM'; }
|
else if(pg==='BPM'){ label='TEMPO'; value=app.bpm+' BPM'; }
|
||||||
else if(pg==='SLOT'){ label='SLOT '+(app.slotSel+1); value=app.slots[app.slotSel]?hex(app.slots[app.slotSel].seed):'EMPTY'; }
|
else if(pg==='SLOT'){ label='SLOT '+(app.slotSel+1); value=app.slots[app.slotSel]?hex(app.slots[app.slotSel].seed):'EMPTY'; }
|
||||||
else if(pg==='ROLL'){ label='RE-ROLL'; value=app.rollSel?'LINE':'KIT'; }
|
else if(pg==='ROLL'){ label='RE-ROLL'; value=app.rollSel?'KEYS':'KIT'; }
|
||||||
else if(pg==='LEARN'){ label= app.learn===2 ? 'LEARN '+E.TRACKS[KIT[app.learnTr]].name.replace(' ','') : 'LEARN';
|
else if(pg==='LEARN'){ label= app.learn===2 ? 'LEARN '+E.TRACKS[KIT[app.learnTr]].name.replace(' ','') : 'LEARN';
|
||||||
value= app.learn===0 ? 'PUSH' : app.learn===1 ? 'PICK TRACK' : (inPort?'HIT PAD':'NO INPUT'); }
|
value= app.learn===0 ? 'PUSH' : app.learn===1 ? 'PICK TRACK' : (inPort?'HIT PAD':'NO INPUT'); }
|
||||||
if(performance.now()<flashMsgUntil) value=flashMsg;
|
if(performance.now()<flashMsgUntil) value=flashMsg;
|
||||||
@@ -465,11 +468,11 @@
|
|||||||
if(curBar>=0){ var first=S.log[phrase*4]; if(first) curTick=tk-first.start; }
|
if(curBar>=0){ var first=S.log[phrase*4]; if(first) curTick=tk-first.start; }
|
||||||
}
|
}
|
||||||
mask=curMask();
|
mask=curMask();
|
||||||
var key=phrase+'|'+app.state.seed+'|'+app.state.salts.join('.')+'|'+app.gen+'|'+S.running;
|
var key=phrase+'|'+app.state.seed+'|'+app.state.salts.join('.')+'|'+app.gen+'|'+app.keys+'|'+app.scale+'|'+app.root+'|'+S.running;
|
||||||
if(dirty || key!==viewKey || (S.running && view && view.bars.some(function(b,i){ return S.log[phrase*4+i] && S.log[phrase*4+i].r!==b; }))){
|
if(dirty || key!==viewKey || (S.running && view && view.bars.some(function(b,i){ return S.log[phrase*4+i] && S.log[phrase*4+i].r!==b; }))){
|
||||||
view={bars:phraseBars(phrase)}; viewKey=key; dirty=false;
|
view={bars:phraseBars(phrase)}; viewKey=key; dirty=false;
|
||||||
var g=E.applyKey(E.genome(app.state.seed),params(false)), ds=E.describe(app.state,params(false));
|
var g=E.applyKey(E.genome(app.state.seed,app.state.salts),params(false)), ds=E.describe(app.state,params(false));
|
||||||
factsEl.innerHTML='<span>Seed <b>'+hex(app.state.seed)+'</b></span><span>Generator <b>'+E.GEN_NAMES[app.gen].toLowerCase()+'</b></span><span>Bar <b>'+(app.gen===4?16:g.steps)+' steps</b></span><span>Key <b>'+E.NOTE_NAMES[g.root]+' '+E.SCALE_NAMES[g.scale].toLowerCase()+(app.scale<0||app.root<0?' (auto)':'')+'</b></span>'+(ds?'<span><b>'+ds+'</b></span>':'');
|
factsEl.innerHTML='<span>Seed <b>'+hex(app.state.seed)+'</b></span><span>Kit <b>'+E.GEN_NAMES[app.gen].toLowerCase()+'</b></span><span>Keys <b>'+E.KEYS_NAMES[app.keys].toLowerCase()+'</b></span><span>Bar <b>'+(app.gen===4?16:g.steps)+' steps</b></span><span>Key <b>'+E.NOTE_NAMES[g.root]+' '+E.SCALE_NAMES[g.scale].toLowerCase()+(app.scale<0||app.root<0?' (auto)':'')+'</b></span>'+(ds?'<span><b>'+ds+'</b></span>':'');
|
||||||
}
|
}
|
||||||
var on=0; for(var t=0;t<NT;t++) if(sounds(t,mask)) on++;
|
var on=0; for(var t=0;t<NT;t++) if(sounds(t,mask)) on++;
|
||||||
posEl.innerHTML=(S.running && curBar>=0 ? '<span>Phrase <b>'+(phrase+1)+'</b></span><span>Bar <b>'+(curBar%4+1)+'/4</b></span>' : '<span>Stopped</span>')+
|
posEl.innerHTML=(S.running && curBar>=0 ? '<span>Phrase <b>'+(phrase+1)+'</b></span><span>Bar <b>'+(curBar%4+1)+'/4</b></span>' : '<span>Stopped</span>')+
|
||||||
|
|||||||
140
sim/engine.js
140
sim/engine.js
@@ -91,27 +91,36 @@ var Engine = (function(){
|
|||||||
var RULES=[30,45,54,57,60,73,90,105,110,122,126,150,182,225];
|
var RULES=[30,45,54,57,60,73,90,105,110,122,126,150,182,225];
|
||||||
var POLY_N=[5,7,9,11,12,6];
|
var POLY_N=[5,7,9,11,12,6];
|
||||||
|
|
||||||
function genome(seed){
|
/* Three parts, each from its own stream: shared (the bar and the song shape) comes from the seed alone;
|
||||||
var r=new Rng(mix(seed,0x47454E)), fi, g={};
|
kit and keys also take their half's salt, so ROLL KIT / ROLL KEYS renew just that half. */
|
||||||
|
function genome(seed,salts){
|
||||||
|
var sl=salts||[0,0], r=new Rng(mix(seed,0x47454E)), fi, g={};
|
||||||
|
// shared
|
||||||
g.steps=STEPS[r.pickW(STEPS_W)];
|
g.steps=STEPS[r.pickW(STEPS_W)];
|
||||||
fi=r.pickW(FORMS_W); g.form=FORMS[fi]; g.formName=FORM_NAMES[fi];
|
fi=r.pickW(FORMS_W); g.form=FORMS[fi]; g.formName=FORM_NAMES[fi];
|
||||||
g.scale=r.below(SCALES.length); g.root=r.below(12);
|
g.fillP=40+r.below(80);
|
||||||
g.dens=[40+r.below(60),30+r.below(70)];
|
|
||||||
g.fillP=40+r.below(80); g.monoLen=r.below(3); g.monoRange=1+r.below(2);
|
|
||||||
g.rot=r.pickW([4,2,2,1]); // phase offset (16ths) of the ambiguous tracks: melody, perc
|
g.rot=r.pickW([4,2,2,1]); // phase offset (16ths) of the ambiguous tracks: melody, perc
|
||||||
g.hocket=HOCKETS[r.below(HOCKETS.length)];
|
|
||||||
g.bassDeg=[4,2,3][r.below(3)];
|
|
||||||
// musical layer
|
|
||||||
g.prog=PROGS[r.below(PROGS.length)];
|
|
||||||
g.swing=SWINGS[r.pickW(SWING_W)];
|
g.swing=SWINGS[r.pickW(SWING_W)];
|
||||||
g.arc=r.pickW([3,2,2]);
|
g.arc=r.pickW([3,2,2]);
|
||||||
g.motifLen=3+r.below(3);
|
|
||||||
g.accentCycle=[0,3,5,3,7][r.pickW([3,3,2,2,1])];
|
|
||||||
g.layback=r.below(2);
|
g.layback=r.below(2);
|
||||||
g.frame=r.pickW(FRAME_W); g.freeK=2+r.below(3); g.freeRot=r.below(16);
|
// kit
|
||||||
|
var k=new Rng(mix(roleSeedOf(seed,sl,0),0x4B4954));
|
||||||
|
g.dens=[40+k.below(60),0];
|
||||||
|
g.accentCycle=[0,3,5,3,7][k.pickW([3,3,2,2,1])];
|
||||||
|
g.frame=k.pickW(FRAME_W); g.freeK=2+k.below(3); g.freeRot=k.below(16);
|
||||||
|
// keys
|
||||||
|
var l=new Rng(mix(roleSeedOf(seed,sl,1),0x4B4559));
|
||||||
|
g.scale=l.below(SCALES.length); g.root=l.below(12);
|
||||||
|
g.dens[1]=30+l.below(70);
|
||||||
|
g.monoLen=l.below(3); g.monoRange=1+l.below(2);
|
||||||
|
g.hocket=HOCKETS[l.below(HOCKETS.length)];
|
||||||
|
g.bassDeg=[4,2,3][l.below(3)];
|
||||||
|
g.prog=PROGS[l.below(PROGS.length)];
|
||||||
|
g.motifLen=3+l.below(3);
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
function roleSeed(st,r){ return mix(mix(st.seed,0x524F4C45+r),st.salts[r]); }
|
function roleSeedOf(seed,salts,r){ return mix(mix(seed,0x524F4C45+r),salts[r]); }
|
||||||
|
function roleSeed(st,r){ return roleSeedOf(st.seed,st.salts,r); }
|
||||||
function groups(steps){ var g=[],s; for(s=0;s<steps;s+=4) g.push([s,Math.min(4,steps-s)]); return g; }
|
function groups(steps){ var g=[],s; for(s=0;s<steps;s+=4) g.push([s,Math.min(4,steps-s)]); return g; }
|
||||||
function isBack(gi){ return (gi%2)===1; }
|
function isBack(gi){ return (gi%2)===1; }
|
||||||
function eu(i,k,n,rot){ var j=(i+rot)%n; return ((j*k)%n)<k; } // Bresenham Euclidean
|
function eu(i,k,n,rot){ var j=(i+rot)%n; return ((j*k)%n)<k; } // Bresenham Euclidean
|
||||||
@@ -363,7 +372,9 @@ var Engine = (function(){
|
|||||||
for(i=1;i<n;i++){ var stp=[-2,-1,1,2,-4,4][r.pickW([3,4,4,3,1,1])]; cont.push(cont[i-1]+stp); }
|
for(i=1;i<n;i++){ var stp=[-2,-1,1,2,-4,4][r.pickW([3,4,4,3,1,1])]; cont.push(cont[i-1]+stp); }
|
||||||
return {pos:pos,cont:cont,resp:r.below(3)}; // resp: how the second half answers (0 sequence up, 1 inversion, 2 displaced)
|
return {pos:pos,cont:cont,resp:r.below(3)}; // resp: how the second half answers (0 sequence up, 1 inversion, 2 displaced)
|
||||||
}
|
}
|
||||||
function lineTracks(rs,variant,g,steps,d,sec,pos,kit,fill){
|
function bassPitch(g,idx){ var p=degPitch(g,36,idx); while(p>47) p-=12; while(p<36) p+=12; return p; }
|
||||||
|
/* MOTIF: a motif stated on the bar's chord and answered, bass on the kicks with a colour tone, the triad on the pad. */
|
||||||
|
function keysMotif(rs,variant,g,steps,d,sec,pos,kit,fill){
|
||||||
var core=new Rng(mix(rs,0x4D4F+sec)), vr=new Rng(mix(mix(rs,0x5641+sec),variant));
|
var core=new Rng(mix(rs,0x4D4F+sec)), vr=new Rng(mix(mix(rs,0x5641+sec),variant));
|
||||||
var n=SCALES[g.scale].length, barT=steps*TPS, mel=[], bass=[], pad=[], i, j;
|
var n=SCALES[g.scale].length, barT=steps*TPS, mel=[], bass=[], pad=[], i, j;
|
||||||
var gs=groups(steps), last=gs[gs.length-1], kickList=[], M=makeMotif(rs,g), c=chordDeg(g,pos);
|
var gs=groups(steps), last=gs[gs.length-1], kickList=[], M=makeMotif(rs,g), c=chordDeg(g,pos);
|
||||||
@@ -399,7 +410,7 @@ var Engine = (function(){
|
|||||||
mel=oneAtATime(mel);
|
mel=oneAtATime(mel);
|
||||||
for(i=0;i<mel.length;i++) mel[i].tr=((g.hocket>>(i&7))&1)?T_MELA:T_MELB;
|
for(i=0;i<mel.length;i++) mel[i].tr=((g.hocket>>(i&7))&1)?T_MELA:T_MELB;
|
||||||
// bass: chord root on the kicks, the seed's colour tone on alternate kicks, an approach note into the next bar
|
// bass: chord root on the kicks, the seed's colour tone on alternate kicks, an approach note into the next bar
|
||||||
function bp(idx){ var p=degPitch(g,36,idx); while(p>47) p-=12; while(p<36) p+=12; return p; }
|
function bp(idx){ return bassPitch(g,idx); }
|
||||||
var nextC=chordDeg(g,pos+1);
|
var nextC=chordDeg(g,pos+1);
|
||||||
for(i=0;i<kickList.length;i++){
|
for(i=0;i<kickList.length;i++){
|
||||||
var t=kickList[i], idx=c;
|
var t=kickList[i], idx=c;
|
||||||
@@ -413,6 +424,91 @@ var Engine = (function(){
|
|||||||
return {mel:mel,bass:oneAtATime(bass),pad:pad};
|
return {mel:mel,bass:oneAtATime(bass),pad:pad};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- keys generators. Each gets (keys seed, phrase variant, genome, steps, keys density, section,
|
||||||
|
bar position, the bar's kit, fill, {bar, drift}) and returns {mel, bass, pad} sorted by time.
|
||||||
|
An event's optional len overrides the default note-length rule. ---------- */
|
||||||
|
function kickTimes(kit){ var a=[]; for(var i=0;i<kit.length;i++) if(kit[i].v===0 && kit[i].t%TPS===0) a.push(kit[i].t); return a; }
|
||||||
|
function padChord(g,tones){ var pad=[], seen={}; for(var i=0;i<tones.length;i++){ var p=48+mod(degPitch(g,0,tones[i]),12); if(!seen[p]){ seen[p]=1; pad.push({t:0,v:p,vel:i===0?58:50,tr:T_PAD}); } } return pad; }
|
||||||
|
|
||||||
|
/* 303: a 16-step acid line (gate rank, degree, octave, accent, slide per step) looped over the bar and moved to
|
||||||
|
each bar's chord root. ENERGY opens more gates; a DRIFT variant flips flags. Short answers fill the bass's rests;
|
||||||
|
the pad is silent or a drone on the key's root. Slides are overlaps: whether they glide is up to the synth. */
|
||||||
|
function keys303(rs,variant,g,steps,d,sec,pos,kit,fill,x){
|
||||||
|
var r=new Rng(mix(rs,0x333033)), c=chordDeg(g,pos), pat=[], i, s;
|
||||||
|
for(i=0;i<16;i++){
|
||||||
|
var rank=0; if(i>0) rank=r.below(128);
|
||||||
|
var off=[0,2,4,6][r.pickW([10,2,3,1])];
|
||||||
|
var up=r.chance(P(24)), acc=r.chance(P(40)), sl=r.chance(P(28));
|
||||||
|
pat.push({rank:rank,off:off,up:up,acc:acc,sl:sl});
|
||||||
|
}
|
||||||
|
var drone=r.chance(P(64));
|
||||||
|
if(variant){ var vr=new Rng(mix(mix(rs,0x5641+sec),variant));
|
||||||
|
for(i=0;i<16;i++){ if(vr.chance(P(20))) pat[i].acc=!pat[i].acc; if(vr.chance(P(16))) pat[i].sl=!pat[i].sl; if(vr.chance(P(16))) pat[i].up=!pat[i].up; if(i>0 && vr.chance(P(16))) pat[i].rank=127-pat[i].rank; } }
|
||||||
|
var thr=clamp(d+16,0,128), on=[], bass=[], mel=[];
|
||||||
|
for(s=0;s<steps;s++) on.push(pat[s%16].rank<thr);
|
||||||
|
for(s=0;s<steps;s++) if(on[s]){
|
||||||
|
var p=pat[s%16], nx=s+1; while(nx<steps && !on[nx]) nx++;
|
||||||
|
bass.push({t:s*TPS,v:bassPitch(g,c+p.off)+(p.up?12:0),vel:p.acc?120:80,tr:T_BASS,len:p.sl?(nx-s)*TPS+20:TPS>>1});
|
||||||
|
}
|
||||||
|
var ar=new Rng(mix(rs,0x414E53+sec));
|
||||||
|
for(s=0;s<steps;){
|
||||||
|
if(on[s]){ s++; continue; }
|
||||||
|
var e2=s; while(e2<steps && !on[e2]) e2++;
|
||||||
|
if(e2-s>=2 && ar.chance(P(d+32))){
|
||||||
|
var tr=((g.hocket>>(s&7))&1)?T_MELA:T_MELB; // the hocket split, by step
|
||||||
|
mel.push({t:s*TPS,v:melPitch(g,c+[0,2,4][ar.below(3)]),vel:84,tr:tr,len:TPS-40});
|
||||||
|
if(e2-s>=3 && ar.chance(P(64))) mel.push({t:(s+1)*TPS,v:melPitch(g,c+[0,2,4][ar.below(3)]),vel:72,tr:tr,len:TPS-40});
|
||||||
|
}
|
||||||
|
s=e2;
|
||||||
|
}
|
||||||
|
return {mel:mel,bass:bass,pad:drone?[{t:0,v:48+g.root,vel:52,tr:T_PAD}]:[]};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TURING: an 8- or 16-bit looping shift register, one step per 16th. DRIFT flips the bit coming round (0 = locked).
|
||||||
|
Whether absolute step k flips is a hash of (seed, k); the register is rebuilt over the last 16 bars, so flips
|
||||||
|
stick for 16 bars and any bar renders on its own. Melody = the register read as a scale degree; bass = the
|
||||||
|
chord root where bit 0 is set (another bit lifts it an octave); pad = the triad. */
|
||||||
|
function rotl(v,s,L){ return s?(((v<<s)|(v>>>(L-s)))&((1<<L)-1)):v; }
|
||||||
|
function keysTuring(rs,variant,g,steps,d,sec,pos,kit,fill,x){
|
||||||
|
var r=new Rng(mix(rs,0x545552)), L=r.chance(P(64))?8:16, mask=(1<<L)-1, base=r.next()&mask;
|
||||||
|
if(!base) base=1;
|
||||||
|
var n=SCALES[g.scale].length, c=chordDeg(g,pos), flipP=P(x.drift>>2), fs=mix(rs,0x464C50);
|
||||||
|
var from=Math.max(0,x.bar-16)*steps, to=x.bar*steps, reg=rotl(base,from%L,L), k, s, j=0;
|
||||||
|
function step(v,k){ var top=(v>>>(L-1))&1; if(flipP && (mix(fs,k)>>>16)<flipP) top^=1; return ((v<<1)|top)&mask; }
|
||||||
|
for(k=from;k<to;k++) reg=step(reg,k);
|
||||||
|
var span=n*g.monoRange, mel=[], bass=[];
|
||||||
|
for(s=0;s<steps;s++){
|
||||||
|
if(((reg>>>2)&7)*18<d){ mel.push({t:s*TPS,v:melPitch(g,c+(((reg&0xFF)*(span+1))>>>8)),vel:70+((reg>>>5)&3)*10+(s%4===0?12:0),tr:((g.hocket>>(j&7))&1)?T_MELA:T_MELB}); j++; }
|
||||||
|
if(reg&1) bass.push({t:s*TPS,v:bassPitch(g,c)+(((reg>>>(L>>1))&1)?12:0),vel:s===0?110:94,tr:T_BASS});
|
||||||
|
reg=step(reg,to+s);
|
||||||
|
}
|
||||||
|
return {mel:mel,bass:bass,pad:padChord(g,[c,c+2,c+4])};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ARP: the bar's chord (triad or 7th) arpeggiated up, down, up-down or in a fixed shuffle over 1-2 octaves,
|
||||||
|
8ths at low density and 16ths at high, alternating MEL A / B. Bass = the root on every kick; pad = the same chord. */
|
||||||
|
function keysArp(rs,variant,g,steps,d,sec,pos,kit,fill,x){
|
||||||
|
var r=new Rng(mix(rs,0x415250)), n=SCALES[g.scale].length, c=chordDeg(g,pos), i, o;
|
||||||
|
var seventh=r.chance(P(64)), dir=r.below(4), chord=seventh?[c,c+2,c+4,c+6]:[c,c+2,c+4], up=[], seq;
|
||||||
|
for(o=0;o<g.monoRange;o++) for(i=0;i<chord.length;i++) up.push(chord[i]+o*n);
|
||||||
|
if(dir===0) seq=up;
|
||||||
|
else if(dir===1) seq=up.slice().reverse();
|
||||||
|
else if(dir===2){ seq=up.slice(); for(i=up.length-2;i>0;i--) seq.push(up[i]); }
|
||||||
|
else { seq=up.slice(); for(i=seq.length-1;i>0;i--){ var j=r.below(i+1), tmp=seq[i]; seq[i]=seq[j]; seq[j]=tmp; } }
|
||||||
|
var off=0; if(variant) off=new Rng(mix(mix(rs,0x5641+sec),variant)).below(seq.length);
|
||||||
|
var rate=d>=64?1:2, gs=groups(steps), last0=gs[gs.length-1][0], mel=[], bass=[], s=0, k=0;
|
||||||
|
while(s<steps){
|
||||||
|
var stp=(fill && s>=last0)?1:rate;
|
||||||
|
mel.push({t:s*TPS,v:melPitch(g,seq[(k+off)%seq.length]),vel:s%4===0?96:78,tr:(k&1)?T_MELB:T_MELA,len:stp*TPS-20});
|
||||||
|
k++; s+=stp;
|
||||||
|
}
|
||||||
|
var kicks=kickTimes(kit);
|
||||||
|
for(i=0;i<kicks.length;i++) bass.push({t:kicks[i],v:bassPitch(g,c),vel:kicks[i]===0?112:98,tr:T_BASS});
|
||||||
|
if(!kicks.length) bass.push({t:0,v:bassPitch(g,c),vel:104,tr:T_BASS});
|
||||||
|
return {mel:mel,bass:bass,pad:padChord(g,chord)};
|
||||||
|
}
|
||||||
|
var KEYS=[keysMotif,keys303,keysTuring,keysArp], KEYS_NAMES=['MOTIF','303','TURING','ARP'];
|
||||||
|
|
||||||
function sortDedupe(ev){
|
function sortDedupe(ev){
|
||||||
ev.sort(function(a,b){ return a.t-b.t || a.v-b.v; });
|
ev.sort(function(a,b){ return a.t-b.t || a.v-b.v; });
|
||||||
var out=[], i;
|
var out=[], i;
|
||||||
@@ -467,7 +563,7 @@ var Engine = (function(){
|
|||||||
|
|
||||||
/* All tracks are rendered every bar; which ones sound is up to the player. */
|
/* All tracks are rendered every bar; which ones sound is up to the player. */
|
||||||
function renderBar(st,p,bar){
|
function renderBar(st,p,bar){
|
||||||
var g=applyKey(genome(st.seed),p), amen=p.gen===4; if(amen){ g.steps=16; g.swing=0; g.layback=0; } // the break: 16 steps, straight; drift adds the swing
|
var g=applyKey(genome(st.seed,st.salts),p), amen=p.gen===4; if(amen){ g.steps=16; g.swing=0; g.layback=0; } // the break: 16 steps, straight; drift adds the swing
|
||||||
var pos=bar%PHRASE_BARS, phrase=(bar/PHRASE_BARS)|0, sec=g.form[pos], barTicks=g.steps*TPS, i, j;
|
var pos=bar%PHRASE_BARS, phrase=(bar/PHRASE_BARS)|0, sec=g.form[pos], barTicks=g.steps*TPS, i, j;
|
||||||
var fr=new Rng(mix(mix(st.seed,0x46494C),phrase));
|
var fr=new Rng(mix(mix(st.seed,0x46494C),phrase));
|
||||||
var fill = !!p.fill || (!amen && pos===PHRASE_BARS-1 && fr.chance(P(g.fillP>>1)));
|
var fill = !!p.fill || (!amen && pos===PHRASE_BARS-1 && fr.chance(P(g.fillP>>1)));
|
||||||
@@ -508,7 +604,8 @@ var Engine = (function(){
|
|||||||
kit=sortDedupe(kit);
|
kit=sortDedupe(kit);
|
||||||
var melShift = disp && dRng.chance(P(disp>>1)) ? (dRng.below(2)?TPS:-TPS) : 0;
|
var melShift = disp && dRng.chance(P(disp>>1)) ? (dRng.below(2)?TPS:-TPS) : 0;
|
||||||
var swingBar = wob ? clamp(g.swing+dRng.below(2*fdiv(wob*40,127)+1)-fdiv(wob*40,127),0,90) : g.swing;
|
var swingBar = wob ? clamp(g.swing+dRng.below(2*fdiv(wob*40,127)+1)-fdiv(wob*40,127),0,90) : g.swing;
|
||||||
var lt=lineTracks(roleSeed(st,1),variants[1],g,g.steps,clamp(g.dens[1]+e-64,0,127),sec,pos,kit,fill), out=[];
|
var keysFn=KEYS[clamp(p.keys|0,0,KEYS.length-1)];
|
||||||
|
var lt=keysFn(roleSeed(st,1),variants[1],g,g.steps,clamp(g.dens[1]+e-64,0,127),sec,pos,kit,fill,{bar:bar,drift:dr}), out=[];
|
||||||
for(i=0;i<kit.length;i++){
|
for(i=0;i<kit.length;i++){
|
||||||
var tr=kitTrack(kit[i]), t=kit[i].t;
|
var tr=kitTrack(kit[i]), t=kit[i].t;
|
||||||
if(tr===T_PERC) t=(t+g.rot*TPS)%barTicks; // perc shares the melody's phase offset
|
if(tr===T_PERC) t=(t+g.rot*TPS)%barTicks; // perc shares the melody's phase offset
|
||||||
@@ -517,9 +614,9 @@ var Engine = (function(){
|
|||||||
out.push({t:clamp(groove(t,g,tr===T_SNARE,barTicks,swingBar)+nudge(barSeed,t,NUDGE_GRP[tr],micro)+(amen?amenFeel(krs,bar,t,kit[i].v,kit[i].a):0),0,barTicks-1),lane:0,v:kit[i].v,vel:kit[i].vel,len:len,tr:tr});
|
out.push({t:clamp(groove(t,g,tr===T_SNARE,barTicks,swingBar)+nudge(barSeed,t,NUDGE_GRP[tr],micro)+(amen?amenFeel(krs,bar,t,kit[i].v,kit[i].a):0),0,barTicks-1),lane:0,v:kit[i].v,vel:kit[i].vel,len:len,tr:tr});
|
||||||
}
|
}
|
||||||
function mono(list,lenRule){
|
function mono(list,lenRule){
|
||||||
for(var k=0;k<list.length;k++){ var nx=list[k+1], gap=(nx?nx.t:barTicks)-list[k].t, len=lenRule(gap,nx,list[k]);
|
for(var k=0;k<list.length;k++){ var nx=list[k+1], gap=(nx?nx.t:barTicks)-list[k].t, fixed=list[k].len!=null, len=fixed?list[k].len:lenRule(gap,nx,list[k]);
|
||||||
var t0=list[k].t; if(melShift && (list[k].tr===T_MELA || list[k].tr===T_MELB)) t0=mod(t0+melShift,barTicks);
|
var t0=list[k].t; if(melShift && (list[k].tr===T_MELA || list[k].tr===T_MELB)) t0=mod(t0+melShift,barTicks);
|
||||||
out.push({t:clamp(groove(t0,g,false,barTicks,swingBar)+nudge(barSeed,list[k].t,NUDGE_GRP[list[k].tr],micro),0,barTicks-1),lane:1,v:list[k].v,vel:list[k].vel,len:Math.max(10,len-swingBar),tr:list[k].tr}); }
|
out.push({t:clamp(groove(t0,g,false,barTicks,swingBar)+nudge(barSeed,list[k].t,NUDGE_GRP[list[k].tr],micro),0,barTicks-1),lane:1,v:list[k].v,vel:list[k].vel,len:Math.max(10,fixed?len:len-swingBar),tr:list[k].tr}); }
|
||||||
}
|
}
|
||||||
var melA=[], melB=[]; for(i=0;i<lt.mel.length;i++) (lt.mel[i].tr===T_MELA?melA:melB).push(lt.mel[i]);
|
var melA=[], melB=[]; for(i=0;i<lt.mel.length;i++) (lt.mel[i].tr===T_MELA?melA:melB).push(lt.mel[i]);
|
||||||
function melLen(gap,nx,cur){ return g.monoLen===0?Math.min(90,gap-5):g.monoLen===1?Math.max(30,gap>>1):((nx && nx.v!==cur.v)?gap+20:gap-5); }
|
function melLen(gap,nx,cur){ return g.monoLen===0?Math.min(90,gap-5):g.monoLen===1?Math.max(30,gap>>1):((nx && nx.v!==cur.v)?gap+20:gap-5); }
|
||||||
@@ -531,8 +628,9 @@ var Engine = (function(){
|
|||||||
}
|
}
|
||||||
var ROMAN=['i','ii','iii','iv','v','vi','vii'];
|
var ROMAN=['i','ii','iii','iv','v','vi','vii'];
|
||||||
function describe(st,p){
|
function describe(st,p){
|
||||||
var g=applyKey(genome(st.seed),p), s=[];
|
var g=applyKey(genome(st.seed,st.salts),p), s=[];
|
||||||
s.push(p.gen===4?'amen break':FRAMES[g.frame].n);
|
s.push(p.gen===4?'amen break':FRAMES[g.frame].n);
|
||||||
|
s.push('keys '+KEYS_NAMES[clamp(p.keys|0,0,KEYS.length-1)].toLowerCase());
|
||||||
s.push('chords '+g.prog.map(function(x){ return ROMAN[x%7]; }).join('–'));
|
s.push('chords '+g.prog.map(function(x){ return ROMAN[x%7]; }).join('–'));
|
||||||
if(p.gen!==4){
|
if(p.gen!==4){
|
||||||
s.push(g.swing?'swing '+Math.round(g.swing*100/TPS)+'%':'straight');
|
s.push(g.swing?'swing '+Math.round(g.swing*100/TPS)+'%':'straight');
|
||||||
@@ -548,6 +646,6 @@ var Engine = (function(){
|
|||||||
|
|
||||||
return {TPB:TPB,TPS:TPS,CLOCK_TICKS:CLOCK_TICKS,PHRASE_BARS:PHRASE_BARS,TRACKS:TRACKS,NT:NT,
|
return {TPB:TPB,TPS:TPS,CLOCK_TICKS:CLOCK_TICKS,PHRASE_BARS:PHRASE_BARS,TRACKS:TRACKS,NT:NT,
|
||||||
genome:genome,applyKey:applyKey,renderBar:renderBar,
|
genome:genome,applyKey:applyKey,renderBar:renderBar,
|
||||||
describe:describe,checksum:checksum,mix:mix,Rng:Rng,SCALE_NAMES:SCALE_NAMES,NOTE_NAMES:NOTE_NAMES,GEN_NAMES:GEN_NAMES};
|
describe:describe,checksum:checksum,mix:mix,Rng:Rng,SCALE_NAMES:SCALE_NAMES,NOTE_NAMES:NOTE_NAMES,GEN_NAMES:GEN_NAMES,KEYS_NAMES:KEYS_NAMES};
|
||||||
})();
|
})();
|
||||||
if(typeof module!=='undefined') module.exports=Engine;
|
if(typeof module!=='undefined') module.exports=Engine;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ textarea{width:100%;min-height:140px;font:400 11px/1.4 var(--f-mono);background:
|
|||||||
<p class="note" style="margin:8px 0 0">Landscape, laid out like the panel drawing: you sit at the bottom edge, cables leave from the back. The screen is drawn at roughly real size.</p>
|
<p class="note" style="margin:8px 0 0">Landscape, laid out like the panel drawing: you sit at the bottom edge, cables leave from the back. The screen is drawn at roughly real size.</p>
|
||||||
<p class="hint"><b>Track buttons:</b> tap = on ↔ off. Hold = flip it while held (drop a track out, or punch one in).<br>
|
<p class="hint"><b>Track buttons:</b> tap = on ↔ off. Hold = flip it while held (drop a track out, or punch one in).<br>
|
||||||
<b>Screen map / LEDs:</b> filled = on, hollow center = hit, empty = off, blinking = change waiting for the bar line. Map rows match the button rows.<br>
|
<b>Screen map / LEDs:</b> filled = on, hollow center = hit, empty = off, blinking = change waiting for the bar line. Map rows match the button rows.<br>
|
||||||
<b>Encoder pages:</b> SEED history · GEN · ROOT · SCALE (AUTO = the seed picks) · BPM · SLOT (push loads, hold push saves) · ROLL (re-roll kit or line) · LEARN (push, press a kit track, hit the pad).<br>
|
<b>Encoder pages:</b> SEED history · KIT GEN · KEYS GEN (MOTIF, 303, TURING, ARP) · ROOT · SCALE (AUTO = the seed picks) · BPM · SLOT (push loads, hold push saves) · ROLL (re-roll kit or keys) · LEARN (push, press a kit track, hit the pad).<br>
|
||||||
Keys: <kbd>Space</kbd> seed · <kbd>1</kbd>…<kbd>0</kbd> <kbd>-</kbd> tracks · <kbd>←</kbd><kbd>→</kbd> encoder · <kbd>Enter</kbd> push · <kbd>R</kbd> run</p>
|
Keys: <kbd>Space</kbd> seed · <kbd>1</kbd>…<kbd>0</kbd> <kbd>-</kbd> tracks · <kbd>←</kbd><kbd>→</kbd> encoder · <kbd>Enter</kbd> push · <kbd>R</kbd> run</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
/* The golden-vector case list. Shared by gen-vectors.js and check-vectors.js. */
|
/* The golden-vector case list. Shared by gen-vectors.js and check-vectors.js.
|
||||||
var SEEDS=[0x5EEDB0C5,0x00000001,0xDEADBEEF,0x12345678,0xC0FFEE00,0x0BADF00D];
|
keys = KEYS GEN (0 MOTIF, 1 303, 2 TURING, 3 ARP); scale/root -1 = AUTO; bars (optional) overrides BARS. */
|
||||||
var SETS=[[64,32],[0,0],[127,0],[127,127],[100,90],[40,64]]; // [energy, drift]
|
var SEEDS=[0x5EEDB0C5,0x00000001,0xDEADBEEF,0x00009AAB]; // bars of 14, 16, 12 and 20 steps
|
||||||
|
var SETS=[[64,32],[0,0],[127,127]]; // [energy, drift]
|
||||||
var BARS=8;
|
var BARS=8;
|
||||||
|
function base(seed,gen,keys,e,d){ return {seed:seed,salts:[0,0],gen:gen,keys:keys,energy:e,drift:d,fillBars:[],scale:-1,root:-1}; }
|
||||||
function cases(){
|
function cases(){
|
||||||
var out=[];
|
var out=[];
|
||||||
SEEDS.forEach(function(seed){ for(var gen=0;gen<5;gen++) SETS.forEach(function(ed){
|
// every kit generator against every keys generator
|
||||||
out.push({seed:seed,salts:[0,0],gen:gen,energy:ed[0],drift:ed[1],fillBars:[],scale:-1,root:-1});
|
SEEDS.forEach(function(seed){ for(var gen=0;gen<5;gen++) for(var keys=0;keys<4;keys++) SETS.forEach(function(ed){
|
||||||
|
out.push(base(seed,gen,keys,ed[0],ed[1]));
|
||||||
}); });
|
}); });
|
||||||
// salts (re-rolling one half of the seed) and FILL
|
// ROLL (salts) and FILL, per keys generator
|
||||||
[0,2,4].forEach(function(gen){
|
for(var keys=0;keys<4;keys++){
|
||||||
out.push({seed:0x5EEDB0C5,salts:[3,0],gen:gen,energy:64,drift:32,fillBars:[],scale:-1,root:-1});
|
var a=base(0x5EEDB0C5,0,keys,64,32); a.salts=[3,0]; out.push(a);
|
||||||
out.push({seed:0x5EEDB0C5,salts:[0,5],gen:gen,energy:64,drift:32,fillBars:[],scale:-1,root:-1});
|
var b=base(0x5EEDB0C5,2,keys,64,32); b.salts=[0,5]; out.push(b);
|
||||||
out.push({seed:0x5EEDB0C5,salts:[0,0],gen:gen,energy:80,drift:50,fillBars:[3,7],scale:-1,root:-1});
|
var c=base(0x5EEDB0C5,keys===3?4:1,keys,80,50); c.fillBars=[3,7]; out.push(c);
|
||||||
});
|
}
|
||||||
// ROOT / SCALE pages: [seed, gen, scale, root]; -1 = AUTO. Covers 5-, 6-, 7- and 8-note scales and each override alone.
|
// TURING over more than its 16-bar window: locked at drift 0, flipping at drift 64 and 127
|
||||||
[[0x5EEDB0C5,0,14,2],[0x5EEDB0C5,2,19,9],[0x5EEDB0C5,4,0,7],[0xDEADBEEF,1,18,-1],[0x12345678,3,-1,5],[0xC0FFEE00,0,7,11],[0x00000001,1,20,0]].forEach(function(k){
|
[0,64,127].forEach(function(d){ var t=base(0x12345678,1,2,64,d); t.bars=24; out.push(t); });
|
||||||
out.push({seed:k[0],salts:[0,0],gen:k[1],energy:64,drift:32,fillBars:[3],scale:k[2],root:k[3]});
|
// a long 303 and ARP run for the phrase variants
|
||||||
|
[1,3].forEach(function(keys){ var t=base(0xC0FFEE00,0,keys,90,100); t.bars=16; out.push(t); });
|
||||||
|
// ROOT / SCALE pages: [seed, gen, keys, scale, root]. Covers 5-, 6-, 7- and 8-note scales and each override alone.
|
||||||
|
[[0x5EEDB0C5,0,0,14,2],[0x5EEDB0C5,2,3,19,9],[0x5EEDB0C5,4,1,0,7],[0xDEADBEEF,1,2,18,-1],[0x12345678,3,0,-1,5],[0xC0FFEE00,0,1,7,11],[0x00000001,1,3,20,0]].forEach(function(k){
|
||||||
|
var c=base(k[0],k[1],k[2],64,32); c.fillBars=[3]; c.scale=k[3]; c.root=k[4]; out.push(c);
|
||||||
});
|
});
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ var dir=path.join(__dirname,'vectors'), fail=0, n=0;
|
|||||||
var prim=JSON.parse(fs.readFileSync(path.join(dir,'primitives.json'),'utf8'));
|
var prim=JSON.parse(fs.readFileSync(path.join(dir,'primitives.json'),'utf8'));
|
||||||
prim.mix.forEach(function(v){ n++; if(R.hex(E.mix(parseInt(v.a,16)>>>0,v.b))!==v.out){ fail++; console.log('mix',v.a,v.b,'differs'); } });
|
prim.mix.forEach(function(v){ n++; if(R.hex(E.mix(parseInt(v.a,16)>>>0,v.b))!==v.out){ fail++; console.log('mix',v.a,v.b,'differs'); } });
|
||||||
prim.rng.forEach(function(v){ n++; var r=new E.Rng(parseInt(v.seed,16)>>>0); for(var i=0;i<16;i++) if(R.hex(r.next())!==v.next[i]){ fail++; console.log('rng',v.seed,'step',i,'differs'); break; } });
|
prim.rng.forEach(function(v){ n++; var r=new E.Rng(parseInt(v.seed,16)>>>0); for(var i=0;i<16;i++) if(R.hex(r.next())!==v.next[i]){ fail++; console.log('rng',v.seed,'step',i,'differs'); break; } });
|
||||||
prim.genomes.forEach(function(v){ n++; if(JSON.stringify(E.genome(parseInt(v.seed,16)>>>0))!==JSON.stringify(v.genome)){ fail++; console.log('genome',v.seed,'differs'); } });
|
prim.genomes.forEach(function(v){ n++; if(JSON.stringify(E.genome(parseInt(v.seed,16)>>>0,v.salts))!==JSON.stringify(v.genome)){ fail++; console.log('genome',v.seed,'differs'); } });
|
||||||
fs.readdirSync(dir).filter(function(f){ return /^gen\d/.test(f); }).sort().forEach(function(f){
|
fs.readdirSync(dir).filter(function(f){ return /^gen\d/.test(f); }).sort().forEach(function(f){
|
||||||
JSON.parse(fs.readFileSync(path.join(dir,f),'utf8')).cases.forEach(function(c){
|
JSON.parse(fs.readFileSync(path.join(dir,f),'utf8')).cases.forEach(function(c){
|
||||||
n++;
|
n++;
|
||||||
var got=R.renderCase({seed:parseInt(c.seed,16)>>>0,salts:c.salts,gen:c.gen,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root},c.bars.length);
|
var got=R.renderCase({seed:parseInt(c.seed,16)>>>0,salts:c.salts,gen:c.gen,keys:c.keys,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root},c.bars.length);
|
||||||
if(got.checksum===c.checksum) return;
|
if(got.checksum===c.checksum) return;
|
||||||
fail++;
|
fail++;
|
||||||
for(var b=0;b<c.bars.length;b++){ var a=JSON.stringify(c.bars[b].events), g=JSON.stringify(got.bars[b].events);
|
for(var b=0;b<c.bars.length;b++){ var a=JSON.stringify(c.bars[b].events), g=JSON.stringify(got.bars[b].events);
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ var rngV=[]; [0,1,0x5EEDB0C5,0xDEADBEEF].forEach(function(s){ var r=new E.Rng(s)
|
|||||||
r=new E.Rng(s); for(i=0;i<16;i++) below.push(r.below(i+1));
|
r=new E.Rng(s); for(i=0;i<16;i++) below.push(r.below(i+1));
|
||||||
r=new E.Rng(s); for(i=0;i<16;i++) ch.push(r.chance(i*4096)?1:0);
|
r=new E.Rng(s); for(i=0;i<16;i++) ch.push(r.chance(i*4096)?1:0);
|
||||||
rngV.push({seed:R.hex(s>>>0),next:seq,below_1_to_16:below,chance_i_times_4096:ch}); });
|
rngV.push({seed:R.hex(s>>>0),next:seq,below_1_to_16:below,chance_i_times_4096:ch}); });
|
||||||
var genV=C.cases().filter(function(c,i,a){ return a.findIndex(function(d){ return d.seed===c.seed; })===i; })
|
var genV=C.cases().filter(function(c,i,a){ return a.findIndex(function(d){ return d.seed===c.seed && d.salts.join()===c.salts.join(); })===i; })
|
||||||
.map(function(c){ return {seed:R.hex(c.seed),genome:E.genome(c.seed>>>0)}; });
|
.map(function(c){ return {seed:R.hex(c.seed),salts:c.salts,genome:E.genome(c.seed>>>0,c.salts)}; });
|
||||||
fs.writeFileSync(path.join(dir,'primitives.json'),JSON.stringify({note:'mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed). genome keys are the JS field names.',mix:mixV,rng:rngV,genomes:genV},null,1)+'\n');
|
fs.writeFileSync(path.join(dir,'primitives.json'),JSON.stringify({note:'mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed, salts). genome keys are the JS field names.',mix:mixV,rng:rngV,genomes:genV},null,1)+'\n');
|
||||||
|
|
||||||
// 2. full renders, one file per generator
|
// 2. full renders, one file per generator
|
||||||
var byGen={}, index=[];
|
var byGen={}, index=[];
|
||||||
C.cases().forEach(function(c){
|
C.cases().forEach(function(c){
|
||||||
var out=R.renderCase(c,C.BARS), id=R.caseId(c);
|
var out=R.renderCase(c,C.BARS), id=R.caseId(c);
|
||||||
(byGen[c.gen]=byGen[c.gen]||[]).push({id:id,seed:R.hex(c.seed),salts:c.salts,gen:c.gen,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root,checksum:out.checksum,bars:out.bars.map(function(b){
|
(byGen[c.gen]=byGen[c.gen]||[]).push({id:id,seed:R.hex(c.seed),salts:c.salts,gen:c.gen,keys:c.keys,energy:c.energy,drift:c.drift,fillBars:c.fillBars,scale:c.scale,root:c.root,checksum:out.checksum,bars:out.bars.map(function(b){
|
||||||
return {bar:b.bar,sec:b.sec,fill:b.fill,barTicks:b.barTicks,events:b.events}; })});
|
return {bar:b.bar,sec:b.sec,fill:b.fill,barTicks:b.barTicks,events:b.events}; })});
|
||||||
index.push({id:id,checksum:out.checksum});
|
index.push({id:id,checksum:out.checksum});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ var E=require('../sim/engine.js');
|
|||||||
function hex(n){ return '0x'+('00000000'+(n>>>0).toString(16).toUpperCase()).slice(-8); }
|
function hex(n){ return '0x'+('00000000'+(n>>>0).toString(16).toUpperCase()).slice(-8); }
|
||||||
function renderCase(c,bars){
|
function renderCase(c,bars){
|
||||||
var st={seed:c.seed>>>0,salts:c.salts.slice()}, all=[], out=[];
|
var st={seed:c.seed>>>0,salts:c.salts.slice()}, all=[], out=[];
|
||||||
|
bars=c.bars||bars;
|
||||||
for(var b=0;b<bars;b++){
|
for(var b=0;b<bars;b++){
|
||||||
var r=E.renderBar(st,{gen:c.gen,energy:c.energy,drift:c.drift,fill:c.fillBars.indexOf(b)>=0,scale:c.scale,root:c.root},b);
|
var r=E.renderBar(st,{gen:c.gen,keys:c.keys,energy:c.energy,drift:c.drift,fill:c.fillBars.indexOf(b)>=0,scale:c.scale,root:c.root},b);
|
||||||
var ev=r.events.map(function(e){ return [e.t,e.lane,e.v,e.vel,e.len,e.tr]; });
|
var ev=r.events.map(function(e){ return [e.t,e.lane,e.v,e.vel,e.len,e.tr]; });
|
||||||
out.push({bar:b,sec:r.sec,fill:!!r.fill,barTicks:r.barTicks,events:ev}); all=all.concat(ev);
|
out.push({bar:b,sec:r.sec,fill:!!r.fill,barTicks:r.barTicks,events:ev}); all=all.concat(ev);
|
||||||
}
|
}
|
||||||
return {bars:out,checksum:hex(E.checksum(all))};
|
return {bars:out,checksum:hex(E.checksum(all))};
|
||||||
}
|
}
|
||||||
function caseId(c){ return hex(c.seed).slice(2)+'_s'+c.salts.join('-')+'_g'+c.gen+'_e'+c.energy+'_d'+c.drift+(c.fillBars.length?'_f'+c.fillBars.join('-'):'')+(c.scale>=0?'_sc'+c.scale:'')+(c.root>=0?'_r'+c.root:''); }
|
function caseId(c){ return hex(c.seed).slice(2)+'_s'+c.salts.join('-')+'_g'+c.gen+'_k'+c.keys+'_e'+c.energy+'_d'+c.drift+(c.fillBars.length?'_f'+c.fillBars.join('-'):'')+(c.scale>=0?'_sc'+c.scale:'')+(c.root>=0?'_r'+c.root:'')+(c.bars?'_b'+c.bars:''); }
|
||||||
module.exports={renderCase:renderCase,caseId:caseId,hex:hex};
|
module.exports={renderCase:renderCase,caseId:caseId,hex:hex};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"note": "mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed). genome keys are the JS field names.",
|
"note": "mix(a,b) hash, xorshift32 Rng (next, below(n), chance(p)) and genome(seed, salts). genome keys are the JS field names.",
|
||||||
"mix": [
|
"mix": [
|
||||||
{
|
{
|
||||||
"a": "0x00000000",
|
"a": "0x00000000",
|
||||||
@@ -270,6 +270,10 @@
|
|||||||
"genomes": [
|
"genomes": [
|
||||||
{
|
{
|
||||||
"seed": "0x5EEDB0C5",
|
"seed": "0x5EEDB0C5",
|
||||||
|
"salts": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
"genome": {
|
"genome": {
|
||||||
"steps": 14,
|
"steps": 14,
|
||||||
"form": [
|
"form": [
|
||||||
@@ -279,36 +283,40 @@
|
|||||||
1
|
1
|
||||||
],
|
],
|
||||||
"formName": "ABAB",
|
"formName": "ABAB",
|
||||||
"scale": 18,
|
"fillP": 110,
|
||||||
"root": 6,
|
"rot": 1,
|
||||||
|
"swing": 35,
|
||||||
|
"arc": 0,
|
||||||
|
"layback": 0,
|
||||||
"dens": [
|
"dens": [
|
||||||
81,
|
59,
|
||||||
37
|
77
|
||||||
],
|
],
|
||||||
"fillP": 47,
|
"accentCycle": 3,
|
||||||
"monoLen": 1,
|
"frame": 7,
|
||||||
"monoRange": 2,
|
"freeK": 4,
|
||||||
"rot": 0,
|
"freeRot": 3,
|
||||||
"hocket": 85,
|
"scale": 0,
|
||||||
|
"root": 5,
|
||||||
|
"monoLen": 0,
|
||||||
|
"monoRange": 1,
|
||||||
|
"hocket": 180,
|
||||||
"bassDeg": 2,
|
"bassDeg": 2,
|
||||||
"prog": [
|
"prog": [
|
||||||
0,
|
0,
|
||||||
3,
|
6,
|
||||||
0,
|
5,
|
||||||
4
|
4
|
||||||
],
|
],
|
||||||
"swing": 35,
|
"motifLen": 5
|
||||||
"arc": 1,
|
|
||||||
"motifLen": 5,
|
|
||||||
"accentCycle": 3,
|
|
||||||
"layback": 0,
|
|
||||||
"frame": 2,
|
|
||||||
"freeK": 3,
|
|
||||||
"freeRot": 5
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"seed": "0x00000001",
|
"seed": "0x00000001",
|
||||||
|
"salts": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
"genome": {
|
"genome": {
|
||||||
"steps": 16,
|
"steps": 16,
|
||||||
"form": [
|
"form": [
|
||||||
@@ -318,36 +326,40 @@
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"formName": "AABA",
|
"formName": "AABA",
|
||||||
"scale": 16,
|
"fillP": 102,
|
||||||
"root": 2,
|
|
||||||
"dens": [
|
|
||||||
80,
|
|
||||||
95
|
|
||||||
],
|
|
||||||
"fillP": 110,
|
|
||||||
"monoLen": 1,
|
|
||||||
"monoRange": 2,
|
|
||||||
"rot": 0,
|
"rot": 0,
|
||||||
|
"swing": 35,
|
||||||
|
"arc": 2,
|
||||||
|
"layback": 1,
|
||||||
|
"dens": [
|
||||||
|
53,
|
||||||
|
68
|
||||||
|
],
|
||||||
|
"accentCycle": 7,
|
||||||
|
"frame": 3,
|
||||||
|
"freeK": 2,
|
||||||
|
"freeRot": 15,
|
||||||
|
"scale": 19,
|
||||||
|
"root": 0,
|
||||||
|
"monoLen": 2,
|
||||||
|
"monoRange": 1,
|
||||||
"hocket": 90,
|
"hocket": 90,
|
||||||
"bassDeg": 3,
|
"bassDeg": 2,
|
||||||
"prog": [
|
"prog": [
|
||||||
0,
|
0,
|
||||||
|
2,
|
||||||
5,
|
5,
|
||||||
0,
|
4
|
||||||
5
|
|
||||||
],
|
],
|
||||||
"swing": 21,
|
"motifLen": 3
|
||||||
"arc": 1,
|
|
||||||
"motifLen": 3,
|
|
||||||
"accentCycle": 3,
|
|
||||||
"layback": 0,
|
|
||||||
"frame": 0,
|
|
||||||
"freeK": 2,
|
|
||||||
"freeRot": 1
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"seed": "0xDEADBEEF",
|
"seed": "0xDEADBEEF",
|
||||||
|
"salts": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
"genome": {
|
"genome": {
|
||||||
"steps": 12,
|
"steps": 12,
|
||||||
"form": [
|
"form": [
|
||||||
@@ -357,36 +369,169 @@
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"formName": "AAAA",
|
"formName": "AAAA",
|
||||||
"scale": 10,
|
"fillP": 80,
|
||||||
"root": 0,
|
"rot": 0,
|
||||||
|
"swing": 50,
|
||||||
|
"arc": 1,
|
||||||
|
"layback": 0,
|
||||||
"dens": [
|
"dens": [
|
||||||
91,
|
78,
|
||||||
66
|
78
|
||||||
],
|
],
|
||||||
"fillP": 40,
|
"accentCycle": 5,
|
||||||
|
"frame": 0,
|
||||||
|
"freeK": 3,
|
||||||
|
"freeRot": 14,
|
||||||
|
"scale": 5,
|
||||||
|
"root": 1,
|
||||||
"monoLen": 0,
|
"monoLen": 0,
|
||||||
"monoRange": 2,
|
"monoRange": 2,
|
||||||
"rot": 1,
|
|
||||||
"hocket": 170,
|
"hocket": 170,
|
||||||
"bassDeg": 4,
|
"bassDeg": 4,
|
||||||
"prog": [
|
"prog": [
|
||||||
0,
|
0,
|
||||||
1,
|
3,
|
||||||
0,
|
5,
|
||||||
6
|
6
|
||||||
],
|
],
|
||||||
"swing": 50,
|
"motifLen": 5
|
||||||
"arc": 2,
|
}
|
||||||
"motifLen": 4,
|
},
|
||||||
"accentCycle": 5,
|
{
|
||||||
|
"seed": "0x00009AAB",
|
||||||
|
"salts": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"genome": {
|
||||||
|
"steps": 20,
|
||||||
|
"form": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"formName": "ABAB",
|
||||||
|
"fillP": 60,
|
||||||
|
"rot": 1,
|
||||||
|
"swing": 0,
|
||||||
|
"arc": 0,
|
||||||
|
"layback": 1,
|
||||||
|
"dens": [
|
||||||
|
66,
|
||||||
|
42
|
||||||
|
],
|
||||||
|
"accentCycle": 7,
|
||||||
|
"frame": 6,
|
||||||
|
"freeK": 2,
|
||||||
|
"freeRot": 12,
|
||||||
|
"scale": 5,
|
||||||
|
"root": 3,
|
||||||
|
"monoLen": 0,
|
||||||
|
"monoRange": 2,
|
||||||
|
"hocket": 165,
|
||||||
|
"bassDeg": 2,
|
||||||
|
"prog": [
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
5,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"motifLen": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seed": "0x5EEDB0C5",
|
||||||
|
"salts": [
|
||||||
|
3,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"genome": {
|
||||||
|
"steps": 14,
|
||||||
|
"form": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"formName": "ABAB",
|
||||||
|
"fillP": 110,
|
||||||
|
"rot": 1,
|
||||||
|
"swing": 35,
|
||||||
|
"arc": 0,
|
||||||
"layback": 0,
|
"layback": 0,
|
||||||
|
"dens": [
|
||||||
|
89,
|
||||||
|
77
|
||||||
|
],
|
||||||
|
"accentCycle": 0,
|
||||||
|
"frame": 1,
|
||||||
|
"freeK": 3,
|
||||||
|
"freeRot": 12,
|
||||||
|
"scale": 0,
|
||||||
|
"root": 5,
|
||||||
|
"monoLen": 0,
|
||||||
|
"monoRange": 1,
|
||||||
|
"hocket": 180,
|
||||||
|
"bassDeg": 2,
|
||||||
|
"prog": [
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
5,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"motifLen": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seed": "0x5EEDB0C5",
|
||||||
|
"salts": [
|
||||||
|
0,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"genome": {
|
||||||
|
"steps": 14,
|
||||||
|
"form": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"formName": "ABAB",
|
||||||
|
"fillP": 110,
|
||||||
|
"rot": 1,
|
||||||
|
"swing": 35,
|
||||||
|
"arc": 0,
|
||||||
|
"layback": 0,
|
||||||
|
"dens": [
|
||||||
|
59,
|
||||||
|
33
|
||||||
|
],
|
||||||
|
"accentCycle": 3,
|
||||||
"frame": 7,
|
"frame": 7,
|
||||||
"freeK": 4,
|
"freeK": 4,
|
||||||
"freeRot": 3
|
"freeRot": 3,
|
||||||
|
"scale": 12,
|
||||||
|
"root": 11,
|
||||||
|
"monoLen": 2,
|
||||||
|
"monoRange": 1,
|
||||||
|
"hocket": 170,
|
||||||
|
"bassDeg": 3,
|
||||||
|
"prog": [
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"motifLen": 5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"seed": "0x12345678",
|
"seed": "0x12345678",
|
||||||
|
"salts": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
"genome": {
|
"genome": {
|
||||||
"steps": 16,
|
"steps": 16,
|
||||||
"form": [
|
"form": [
|
||||||
@@ -396,36 +541,40 @@
|
|||||||
1
|
1
|
||||||
],
|
],
|
||||||
"formName": "ABAB",
|
"formName": "ABAB",
|
||||||
"scale": 0,
|
"fillP": 43,
|
||||||
"root": 5,
|
"rot": 1,
|
||||||
|
"swing": 0,
|
||||||
|
"arc": 2,
|
||||||
|
"layback": 1,
|
||||||
"dens": [
|
"dens": [
|
||||||
50,
|
49,
|
||||||
83
|
72
|
||||||
],
|
],
|
||||||
"fillP": 111,
|
"accentCycle": 5,
|
||||||
|
"frame": 4,
|
||||||
|
"freeK": 2,
|
||||||
|
"freeRot": 5,
|
||||||
|
"scale": 10,
|
||||||
|
"root": 2,
|
||||||
"monoLen": 1,
|
"monoLen": 1,
|
||||||
"monoRange": 1,
|
"monoRange": 2,
|
||||||
"rot": 2,
|
"hocket": 165,
|
||||||
"hocket": 170,
|
"bassDeg": 2,
|
||||||
"bassDeg": 3,
|
|
||||||
"prog": [
|
"prog": [
|
||||||
0,
|
0,
|
||||||
0,
|
6,
|
||||||
5,
|
5,
|
||||||
4
|
4
|
||||||
],
|
],
|
||||||
"swing": 0,
|
"motifLen": 5
|
||||||
"arc": 1,
|
|
||||||
"motifLen": 5,
|
|
||||||
"accentCycle": 3,
|
|
||||||
"layback": 0,
|
|
||||||
"frame": 1,
|
|
||||||
"freeK": 3,
|
|
||||||
"freeRot": 9
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"seed": "0xC0FFEE00",
|
"seed": "0xC0FFEE00",
|
||||||
|
"salts": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
"genome": {
|
"genome": {
|
||||||
"steps": 16,
|
"steps": 16,
|
||||||
"form": [
|
"form": [
|
||||||
@@ -435,71 +584,32 @@
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"formName": "AABA",
|
"formName": "AABA",
|
||||||
"scale": 19,
|
"fillP": 115,
|
||||||
"root": 0,
|
"rot": 0,
|
||||||
"dens": [
|
"swing": 0,
|
||||||
42,
|
|
||||||
74
|
|
||||||
],
|
|
||||||
"fillP": 78,
|
|
||||||
"monoLen": 1,
|
|
||||||
"monoRange": 2,
|
|
||||||
"rot": 2,
|
|
||||||
"hocket": 210,
|
|
||||||
"bassDeg": 2,
|
|
||||||
"prog": [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
3
|
|
||||||
],
|
|
||||||
"swing": 21,
|
|
||||||
"arc": 1,
|
"arc": 1,
|
||||||
"motifLen": 3,
|
|
||||||
"accentCycle": 5,
|
|
||||||
"layback": 0,
|
"layback": 0,
|
||||||
"frame": 3,
|
|
||||||
"freeK": 3,
|
|
||||||
"freeRot": 12
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"seed": "0x0BADF00D",
|
|
||||||
"genome": {
|
|
||||||
"steps": 16,
|
|
||||||
"form": [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"formName": "AABA",
|
|
||||||
"scale": 9,
|
|
||||||
"root": 6,
|
|
||||||
"dens": [
|
"dens": [
|
||||||
46,
|
49,
|
||||||
85
|
47
|
||||||
],
|
],
|
||||||
"fillP": 92,
|
"accentCycle": 3,
|
||||||
"monoLen": 1,
|
"frame": 3,
|
||||||
|
"freeK": 4,
|
||||||
|
"freeRot": 9,
|
||||||
|
"scale": 12,
|
||||||
|
"root": 3,
|
||||||
|
"monoLen": 0,
|
||||||
"monoRange": 1,
|
"monoRange": 1,
|
||||||
"rot": 1,
|
"hocket": 180,
|
||||||
"hocket": 90,
|
"bassDeg": 4,
|
||||||
"bassDeg": 2,
|
|
||||||
"prog": [
|
"prog": [
|
||||||
0,
|
0,
|
||||||
3,
|
6,
|
||||||
0,
|
5,
|
||||||
4
|
4
|
||||||
],
|
],
|
||||||
"swing": 35,
|
"motifLen": 5
|
||||||
"arc": 2,
|
|
||||||
"motifLen": 3,
|
|
||||||
"accentCycle": 3,
|
|
||||||
"layback": 0,
|
|
||||||
"frame": 3,
|
|
||||||
"freeK": 2,
|
|
||||||
"freeRot": 9
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user