11. Metapatterns
A "metapattern" is a pattern that references another pattern, and applies some transformations to it. After it applies the transformations, it behaves just like a pattern would.
The metapattern uses all bytes in the word as a single unit, rather than making between data + command.
After the first metapattern identifying byte comes the byte that holds the pattern reference. Encoded in binary, the pattern reference the location in the cell pool. This reference can either be a pattern or another metapattern.
The remaining 6 bytes are used as transformation commands that can be composed together. These transformation commands non-destructively alter the pattern in some way.
A transformation command byte is divided up into two nibbles. The lower (lefthand) nibble displays the command, the upper (righthand) nibble is the parameter.
Transformations may include:
left/right rotational shift: shift the entire pattern by some amount with wraparound.
shrink/expand: take a pattern and stretch/squash it by some factor.
invert: flips the bits of the pattern.
crop: takes only a portion of the pattern
repeat: repeats a segment of a pattern a certain number of times
reverse: reverses the pattern
mirror: creates a mirror image of the pattern
decimate: add some kind of noise/randomness to the pattern
static int metaprocess(trig_vm *vm, int pos,
uint32_t cmd,
uint32_t data,
uint32_t *cmdout,
uint32_t *dataout)
{
int pat;
uint32_t pcmd;
uint32_t pdat;
trig_cell *c;
pat = (cmd >> 8) & 0xFF;
if (pat < 1 || pat > 32) {
return empty(vm, pos, 0, 0);
}
pat--;
c = trig_vm_cell_get(vm, pat);
pcmd = c->cmd;
pdat = c->data;
if (pcmd & 8) {
if (pos == pat) return empty(vm, pos, 0, 0);
return metaprocess(vm, pos, pcmd, pdat, cmdout, dataout);
} else if (pcmd & 1) {
uint8_t ncmd;
uint8_t ndat;
uint8_t nib;
int n;
int update;
int next;
update = 0;
for (n = 0; n < 6; n++) {
if (n <= 1) {
nib = (cmd >> (16 + n*8)) & 0xFF;
} else {
nib = (data >> ((n - 2)*8)) & 0xFF;
}
ncmd = nib & 0xf;
ndat = (nib >> 4) & 0xf;
if (ncmd == 1) {
<<simple_commands>>
} else if (ncmd == 2) {
<<backward_shift>>
} else if (ncmd == 3) {
<<forward_shift>>
} else if (ncmd == 15) {
<<cellular_automata>>
}
}
next = pattern(vm, pos, pcmd, pdat);
if (update) {
<<update_the_cell>>
}
return next;
}
return empty(vm, pos, 0, 0);
}
static int metapattern(trig_vm *vm,
int pos,
uint32_t cmd,
uint32_t data)
{
uint32_t pcmd;
uint32_t pdat;
pcmd = 0;
pdat = 0;
return metaprocess(vm, pos, cmd, data, &pcmd, &pdat);
}
11.1. Parameterless Transformations
To save on bits, all commands that do not require any parameters in the data nib are thrown into command nib 1. These are known as "simple" commands. The data portion of the simple commands is used to tell which command to use.
At the moment:
0: invert
1: reverse
2: update
if (ndat == 0) {
<<command_invert>>
} else if (ndat == 1) {
<<reverse_command>>
} else if (ndat == 2) {
<<update_command>>
}
11.1.1. Inversion
Inversion (0) simply inverts the entire data bit. Anything that is a 1 becomes a 0, and vice versa.
pdat = ~pdat;
11.1.2. Reverse
Reversal (1) reverses the pattern. This requires knowing the size of the pattern, so both the command and data words of the pattern are needed.
pdat = reverse(pcmd, pdat);
static uint32_t reverse(uint32_t cmd, uint32_t dat);
Reversal does what you'd do for a reverse-in-place on a string, only with bits.
static uint32_t reverse(uint32_t cmd, uint32_t dat)
{
uint32_t out;
uint8_t sz;
uint8_t szd2;
int n;
sz = (cmd >> 16) & 0xFF;
szd2 = sz / 2;
out = dat;
for (n = 0; n < szd2; n++) {
int s1, s2;
int end;
end = sz - n - 1;
s1 = (dat >> n) & 1;
s2 = (dat >> end) & 1;
if (s1) {
out |= (1 << end);
} else {
out &= ~(1 << end);
}
if (s2) {
out |= (1 << n);
} else {
out &= ~(1 << n);
}
}
return out;
}
11.1.3. Update
The update
copies over the metapattern as it exists back
to the original pattern. This command will set an update
flag to be 1.
update = 1;
The cell should only be updated when the pattern reaches the end. This is checked by seeing if the return position differs.
if (next != pos) {
c->cmd = pcmd;
c->data = pdat;
}
11.2. 1-bit CA
1-dimensional cellular automata (15, all on) can be applied to the 32-bit pattern data from a rule. A rule is an 8-bit number. Command nibbles are only 4 bits, so to make this work, this command gobbles up the following byte, and reads all 8 bits as the parameter for rule.
This particular cellular automata implementation was adapted from rosetta code.
uint8_t rule;
uint32_t out;
int j;
rule = 0;
out = 0;
#define B(x) (1 << (x))
if (n < 5) {
n++; /* skips the next byte */
if (n <= 1) {
rule = (cmd >> (16 + n*8)) & 0xFF;
} else {
rule = (data >> ((n - 2)*8)) & 0xFF;
}
for (j = 0; j < 32; j++) {
if (rule & B(7 & (pdat>>(j-1) | pdat<<(32+1-j))))
out |= B(j);
}
pdat = out;
}
#undef B
11.3. WIP Shifts
ncmds 2 and 3 do rotational shifts of a pattern, with the ncmd being a value for how many times to shift.
Normally, these shifts are referred to as left/right shifts,
but this can be confusing with the little-endian binary
representation. Instead, the terms forward
and backward
will be used relative to how they are displayed. 2 will be
a forward (right) shift, and 3 will be a backward (left)
shift.
A rotational shift moves bits in a direction, and does wrap around. This operation to be specially built in order to compensate for pattern size. This operation does one shift at a time. It shifts the value, takes the shifted bit, and ORs it to the other side.
A forward shift is actually a left shift on the the layout (counter-intuitive riight?).
static uint32_t fshift_1(uint32_t n, uint32_t patsize);
First, the rotated bit is retrieved by ANDing the value at the end of the pattern position.
The value is then left shifted, masked, and then the rotated bit is OR'd into the first position.
static uint32_t fshift_1(uint32_t x, uint32_t patsize)
{
uint32_t out;
int bit;
bit = (x & (1 << (patsize - 1))) > 0;
out = (x << 1);
out |= bit;
if (patsize < 32) out &= (1 << patsize) - 1;
return out;
}
static uint32_t fshift(uint32_t cmd, uint32_t dat, int n);
static uint32_t fshift(uint32_t cmd, uint32_t dat, int n)
{
uint32_t out;
uint32_t sz;
int i;
sz = (cmd >> 16) & 0xFF;
out = dat;
for (i = 0; i < n; i++) out = fshift_1(out, sz);
return out;
}
{
pdat = fshift(pcmd, pdat, ndat);
}
A backward shift (right shift) is mirror of the forward shift. The rotated bit is the very first bit, and gets OR'd into the last bit position in the pattern.
static uint32_t bshift_1(uint32_t n, uint32_t patsize);
static uint32_t bshift_1(uint32_t x, uint32_t patsize)
{
uint32_t out;
int bit;
bit = (x & 1);
out = (x >> 1);
out |= (bit << (patsize - 1));
if (patsize < 32) out &= (1 << patsize) - 1;
return out;
}
static uint32_t bshift(uint32_t cmd, uint32_t dat, int n);
static uint32_t bshift(uint32_t cmd, uint32_t dat, int n)
{
uint32_t out;
uint32_t sz;
int i;
sz = (cmd >> 16) & 0xFF;
out = dat;
for (i = 0; i < n; i++) out = bshift_1(out, sz);
return out;
}
{
pdat = bshift(pcmd, pdat, ndat);
}
prev | home | next