(low resolution for reasons of computation cost)

I added a "disable texture resize" option to KF, which means you can access actual texture image pixels in OpenGL, which makes the "legendary colour palette" technique much easier (no need to convert the image to a linear palette in text format first).

colouring algorithm code:

vec3 colour()
{
if (getInterior()) return KFP_InteriorColor;
int w = textureSize(KFP_Texture, 0).x;
int h = textureSize(KFP_Texture, 0).y;
int N = to_int(floor(div(getN(), KFP_IterDiv)));
N += int(KFP_ColorOffset) * w * h / 1024;
int i = N % w;
int j = (N / w) % h;
return texelFetch(KFP_Texture, ivec2(i, j), 0).rgb;
}

(if you try this in latest released KF you will probably be disappointed, as the texture image is resized to match the output image size)

fractal perturbation maths summary

Here's an example of the kind of glitches that can happen (in this case, missing details in the right hand image with KF's default threshold), with a lower tolerance for glitches it takes much longer but the image is hopefully correct.

Test location in the Mandelbrot set taken from here deviantart.com/olbaid-st/art/D

Experimenting with colouring for : for each pixel, normalize a neighbourhood's smooth iteration count so the minimum is $0$ and the sum is $1$, then compute $E = \frac{ \sum x \log x }{ \sum 1 }$. Substitute $0$ for $0 \log 0$.

Without jitter I got some strange grid artifacts that looked really bad, especially when animated, with jitter the image is so noisy I need tons of samples to smooth it out, which also means I can't use fast-changing palettes in the exterior because the averaging turns them to mud.

This video is with 100 samples for entropy calculations to get each colour sample, and 100 colour samples per pixel.

Nearly finished kf-2.15.2 and zoomasm-3.0. Will probably release next week after final testing.

The main new feature in is being able to define your own colouring algorithms in OpenGL shader language (). The shader fragment can get raw computed data from KF, such as (smooth) iteration count and analytic distance estimates, and return a colour for the pixel.

There is also an emulation of the previous CPU colouring algorithm, which is modularized so you can use parts of it in your custom shaders.

The main new feature in is being able to load palettes from KF that use OpenGL GLSL colouring shader fragments, so you don't have to repeat work. This includes support for the list-of-colours palette and the other variables in KF's colour dialog.

works for distance estimates too, here are some nested units:


460 core

"TwoD.frag"

vec3 color(vec2 p, vec2 dx, vec2 dy)
{
Dual1cf c = dual1cf(complexf(p), 0);
Dual1cf z = dual1cf(complexf(0));
c.d[0] = mul(c.d[0], length(vec4(dx, dy)));
float Rr = 1000.0;
float R = 0;
float rr = 0.2117;
float r;
float sr = 0.08;
float s = 0;
for (int i = 0; i < 1000; ++i)
{
if (Rr <= length(z.x) && R == 0.0)
{
R = tanh(clamp(2.0 * length(z.x) * log(length(z.x)) / length(z.d[0]), 0.0, 4.0));
break;
}
else
if (i % 3 == 0 && rr <= length(z.x) && r == 0.0)
{
r = tanh(clamp(2.0 *length(z.x)/rr * log(length(z.x)/rr) / length(z.d[0]), 0.0, 4.0));
}
else
if (i % 6 == 0 && sr <= length(z.x) && s == 0.0)
{
s = tanh(clamp(2.0 * length(z.x)/sr * log(length(z.x)/sr) / length(z.d[0]), 0.0, 4.0));
}
}
if (length(z.x) <= Rr) return vec3(0.0);
if (0.0 < s && s < 0.5 && r < 0.5) return vec3(1.0, 1.0, 0.0);
if (0.0 < r && r < 0.5 && R < 0.5) return vec3(1.0, 0.0, 0.0);
if (0.0 < R && R < 0.5) return vec3(0.0, 0.0, 1.0);
return vec3(1.0);
}


left side, binary decomposition of every 3rd iteration before z->z^2+c (starting from 0) escapes a tiny radius (~0.21) (I think this radius is related to the derivative w.r.t. z of the periodic attractor somehow)

right side, binary decomposition of every 1th iteration before z escapes a larger radius (2) (this radius is the minimal escape radius for the quadratic Mandelbrot set)

the image is of the period 3 island, rotated 90 degrees from the usual view

wondering if this might possibly be useful for computing external angles: going to the cusp on the left is .(0) and on the right is .(011) (where 0 is light and 1 is dark); the other angles of the minibrot are tuned by .(011) and .(100) relative to the top level set... dunno if it's possible to get better than O(n^2) cost though...

Clive works too, but the latency is huge.

Got sound from Pd in UserLAnd TSVNC Xfce4 Debian Bullseye via Pulseaudio RTP VLC, all installed via f-Droid on an Android tablet. Using xvkbd to be able to type. Not yet tried patching, I expect it to be nightmarish...

Had to set latency really high and increase block size in Pd Portaudio settings to get a clean sound.

I got my fractal explorer working on my tablet. Was not easy. UI is a bit laggy and its hard to use as fingers obscure the screen. Maybe a stylus would help.

Starting to get back into practicing in the programming language after months of absence.

The tones (everything but percussion) in this one are mostly:


samphold(&s->sh[0], sin(pow(2.0, k) * cos(30 * twopi * 64 * t +
pow(2, 4 * cos(twopi * t)) * sin(ceil(pow(4, 1 + cos(twopi * t + 0.25 * twopi *
sin(4 * twopi * t)))) * 64 * twopi * t)) * pow(2.0, cos(twopi * 16 * t) * 2.0)),
wrap(pow(4, 1 + cos(16 * twopi * t)) * wrap(400 * 64 * t)));


so a bit of wave shaping (first sin()), phase modulation (inside of first cos()), strange-rhythm sequencing (the ceil() of a wiggly function) and bitcrush (using samphold() to lower sample rate). Two UGENs (for want of a better concept): a 16 bar phasor for the t value in [0..1), and the samphold; the rest is stateless maths.

The code is duplicated with minor modifications for the other channel, and there's some simple percussion, all fed through a resonant high pass filter for the bass drone and then a multiband compressor.

Figured out how to plot wakes implicitly.

Given a wake with parameter ray angles $s_-, s_+$, for each pixel $c$ in the image trace the dynamic rays at those angles towards the Julia set: if and only if they land together, then $c$ is in the wake.

An application of Theorem 2.5 from arxiv.org/abs/1709.09869 "A survey on MLC, Rigidity and related topics" by Anna Miriam Benini.

Previously I had been tracing the two parameter rays into a polygonal boundary and filling that using rasterization, to do: benchmark and compare the two methods in various scenarios.

Managed to fractalize a variant based on a sheared stack of toruses (such that the ends join up into a helix). Still has assumptions about the rise per revolution being small, but it turned out well. Rendered with "Raymond", my physically-inspired raytracer, using a material similar to water but more extreme in both index of refraction and absorption coefficients.

Render at 3840x2160 with 256 subframes took about 1.5 hours. Exported from FragM in EXR format, colour balance adjusted in Darktable, cropped/framed in GNU IMP.

If you need a lot of harmonics, it can be done in parallel (SIMD? FPGA?) by using the relations:

$$T_{2n} = T_n^2 - 1 T_{2n+1} = T_{n+1} T_n - x$$

For the second kind, I think you can use these ones:

$$U_{2n-1} = 2 T_n U_{n-1} U_{2n} = T_{2n} + x U_{2n-1}$$

(hope I didn't make any mistakes working that out)

Implemented bandlimited PWM via difference of two bandlimited saws (constructed by additive synthesis):


vec2 saw(float t)
{
float s = sin(t);
float c = cos(t);
float su = 1.0;
float sv = 2.0 * c;
float cu = 1.0;
float cv = c;
float sgn = 1.0;
int k = 1;
float sum = 0.0;
float dsum = 0.0;
while (k <= 16)
{
float term = sgn * su / float(k++);
float dterm = sgn * cu;
sum += term;
dsum += dterm;
sgn = -sgn;
float sw = 2.0 * c * sv - su; su = sv; sv = sw;
float cw = 2.0 * c * cv - cu; cu = cv; cv = cw;
}
return vec2(s * sum, dsum);
}


(This computes the derivative too for waveform plotting purposes.)

Experimenting with Chebyshev polynomials to do additive synthesis based on a single sine and cosine value pair.

Rough estimate 5 floating point arithmetic operations for each additional next harmonic, of which 3 are for the polynomial recurrence and 2 are for gain and accumulation. Compared to 3 flops and 1 sin() call for the obvious way. The sin() call way can do sparse harmonics much more easily though.

Figured out the single helix version, by realizing that the line through a point to the nearest point on the surface must lie on a plane containing the axis of the helix. There are three candidate points, conceptualized as the two neighbouring arcs above and below, and the arc opposite:


330 compatibility

providesColor
"MathUtils.frag"
"Complex.frag"
"DE-Raytracer.frag"

Helix

uniform float HelixD; slider[0.0,2.0,10.0]
uniform float HelixR; slider[0.0,1.0,10.0]
uniform float Helixr; slider[0.0,0.5,10.0]

uniform float time;

float DE(vec3 q)
{
q.z += time * 2.0 * PI * HelixD;
float dz = mod(q.z + HelixD * atan(q.y, q.x) + PI * HelixD, 2.0 * PI * HelixD) - PI * HelixD;
float xy = length(q.xy);
float d1 = length(vec2(xy - HelixR, dz - PI * HelixD));
float d2 = length(vec2(xy + HelixR, dz));
float d3 = length(vec2(xy - HelixR, dz + PI * HelixD));
return min(min(d1, d2), d3) - Helixr;
}

vec3 baseColor(vec3 q, vec3 n)
{
return vec3(0.5) + 0.5 * cross(n, normalize(vec3(-1.0, 1.0, -1.0)));
}


Implemented a double helix based on an idea from 's Knot.frag (not knighty's, the other one, based on forum posts by DarkBeam).

Not sure how to -ize it, wanted to turn it into a of helices of helices etc. Nor how to make it a single helix (I only managed to colour the two halves individually...).

I think each strand is an Archimedean Serpentine, but I'm not 100% sure on terminology..


330 compatibility

providesColor
"MathUtils.frag"
"Complex.frag"
"DE-Raytracer.frag"

Helix

uniform float HelixD; slider[0.0,2.0,10.0]
uniform float HelixR; slider[0.0,1.0,10.0]
uniform float Helixr; slider[0.0,0.5,10.0]

uniform float time;

float DE(vec3 q)
{
q.z += HelixD * time;
float t = (mod(q.z / HelixD + 0.5, 1.0) - 0.5) * 2.0 * PI;
q.xy *= mat2(cos(t), sin(t), -sin(t), cos(t));
q.z = 0;
float s = atan(HelixD / (2.0 * PI), HelixR);
q.yz *= mat2(cos(s), -sin(s), sin(s), cos(s));
return length(vec2(length(q.xy) - HelixR, q.z)) - Helixr;
}

vec3 baseColor(vec3 q, vec3 n)
{
q.z += HelixD * time;
float t = (mod(q.z / HelixD + 0.5, 1.0) - 0.5) * 2.0 * PI;
q.xy *= mat2(cos(t), sin(t), -sin(t), cos(t));
return vec3(0.5) + 0.5 * sign(q.x) * n;
}


rhythmic beeping

source code composed in mathr.co.uk/barry/v2/ , rendered to WAV using barry command line version, trimmed to first 2^22 samples using Audacity, encoded with LAME.


: audio { c t -- o }
t t 0x401 c 2 * - * 17 t 16 >> 3 & + >> 0x11 t 18 >> 0xF & 1 + * & <<
i8
;

RUN audio
1 -> increment
0 -> time


modulating PWM drone with echoed high pitched things and various fast kicks (with a bit of DC offset, sorry)

source code composed in mathr.co.uk/barry/v2/ , rendered to WAV using barry command line version.


: dup { x -- x x }
x x
;

: env { a t -- a }
a t ~ 0x7FFF & dup * 15 >> dup * 15 >> * 15 >>
;

: audio { c t | r s -- o }
t 15 >> 3 2 c * + * 7 & 6 % 1 + -> r
t 256 r + * 9 >> 0xFF & t 256 r - * 9 >> 0xFF & - 1 >>
r t t t * * * 0xFF & i8 i64 -> s
s 2 * 0xFF & i8 i64 t 0x4000 - env 0 >>
s 3 * 0xFF & i8 i64 t 0x7000 - env 1 >>
s 5 * 0xFF & i8 i64 t 0xA000 - env 2 >>
+ + t 14 >> 7 & 7 != *
r t * ~ 0x3FFF & dup * 15 >> dup * 15 >> 0x7F & 0x40 - t 20 >> 3 & 0 != *
+ t 19 >> 3 & 0 != *
+ 1 >>
i8
;

RUN audio
1 -> increment
0 -> time


(Previous one was 14th.)