<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>graeme. hello</title><link>https://graemephi.github.io/</link><description>Recent content on graeme. hello</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sun, 28 Sep 2025 19:44:32 +0100</lastBuildDate><atom:link href="https://graemephi.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Spectrogram Phases</title><link>https://graemephi.github.io/posts/spectrogram-phases/</link><pubDate>Sun, 28 Sep 2025 19:44:32 +0100</pubDate><guid>https://graemephi.github.io/posts/spectrogram-phases/</guid><description>&lt;p>There was &lt;a href="https://x.com/_lyraaaa_/status/1970803097686736916">a tweet recently&lt;/a> pointing out the apparent structurelessness of the phase data that is usually discarded when you compute audio spectrograms. People familiar with audio will know phase is important so this is pretty weird. So here&amp;rsquo;s quick post about what makes the phases so bad and some tricks to show there is structure to be found in them, but no promises any of it will be useful.&lt;/p>
&lt;p>I&amp;rsquo;m going to use &lt;a href="https://www.youtube.com/watch?v=YhRybYyrNaI">this track&lt;/a> for all the plots in this post. It has a bunch of big long chirps and even a big block of pink noise at the end. Don&amp;rsquo;t ask me. I think you can see someone turning the knob on a filter sweep as well.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/spectrogram-phases/01_hann.png" width="829" height="888" loading="lazy" />
&lt;/div>
&lt;p>The phases basically just looks like noise. By the way, it&amp;rsquo;s all grey, even though there is no grey in the color map, because I&amp;rsquo;m telling matplotlib to do the downsampling in color space. Averaging phases does the wrong thing. Phases are periodic and I&amp;rsquo;m too lazy to figure out a better way to downsample this data for display. A color off the color map is way less deceiving than a color on the color map, I think.&lt;/p>
&lt;h2 id="1">1&lt;/h2>
&lt;p>If you don&amp;rsquo;t window at all (actually a rectangular window) a bunch of structure appears in the phases. But we introduced a discontinuity at the boundaries so there&amp;rsquo;s loads of bogus high frequencies. Presumably some of this new phase structure is also bogus and just whatever it has to be to place a jump discontinuity between the first and last sample.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/spectrogram-phases/02_rectangular.png" width="829" height="888" loading="lazy" />
&lt;/div>
&lt;p>Here&amp;rsquo;s the big brain response to this, which I got from a Miller Puckette paper. This is for Hann (raised cosine) windows. You can observe, empirically, two things. First is that when you take the DFT of a pure tone then the phase of the whole thing jumps by &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>π&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\pi&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;/span>&lt;/span>&lt;/span> at the bin the tone is in. Second is that when you window this tone the phase now jumps twice, back and forth. So you say okay, if a bin is actually responding to a tone, then probably its adjacent bins have totally opposite phase. So, as complex numbers, they&amp;rsquo;re pointing in the opposite direction, and you can get a better estimate of the phase by flipping them and averaging. This is just&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">X &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>fft&lt;span style="color:#f92672">.&lt;/span>rfft(x &lt;span style="color:#f92672">*&lt;/span> window)
X[&lt;span style="color:#ae81ff">1&lt;/span>:&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">-=&lt;/span> X[:&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>] &lt;span style="color:#f92672">+&lt;/span> X[&lt;span style="color:#ae81ff">2&lt;/span>:]
&lt;/code>&lt;/pre>&lt;/div>&lt;p>because we don&amp;rsquo;t care about their magnitudes. In the context of phase vocoders this really works which is kind of fucked up, because if we actually now look at the phases it looks like nothing has changed.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/spectrogram-phases/03_hann_puckette.png" width="823" height="438" loading="lazy" />
&lt;/div>
&lt;p>But if we diff these two images we get this. So there&amp;rsquo;s definitely some legible structure in there.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/spectrogram-phases/04_hann_puckette_diff.png" width="823" height="438" loading="lazy" />
&lt;/div>
&lt;p>Another thing you can do is just taper the edges of the rectangular window, here I&amp;rsquo;m just doing 10 cosine samples at either end of a 8192 window. This kills off the high frequencies, which you can see by just eyeballing the spectrogram, but presumably still has all the problems rectangular windows have in terms of sidelobe fall off and whatever else.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/spectrogram-phases/05_almost_box.png" width="829" height="888" loading="lazy" />
&lt;/div>
&lt;p>It&amp;rsquo;s removed all those big streaks, so probably they really were bogus. Apparently this is called a &lt;a href="https://en.wikipedia.org/wiki/Window_function#Tukey_window">Tukey window&lt;/a>.&lt;/p>
&lt;h2 id="2">2&lt;/h2>
&lt;p>Phases are relative. The way to understand this is that if you slide a signal around in time all the phases update (at different rates, even), but audio sounds the same no matter when you hit play.&lt;/p>
&lt;p>I&amp;rsquo;m going to suggest defining phases relative to the phase in same bin in the last window, as phase deltas. I think you should be a little suspicious about this because we do actually care about the vertical phase structure and this will give every bin its own zero phase reference angle, making phases within a single window independent of each other. I don&amp;rsquo;t think this matters too much as its easy to recover a shared-reference phase in context, and in cases where the phase is not just noise I think the vertical structure is probably pretty stable from step to step. So let&amp;rsquo;s just do it. I&amp;rsquo;m using the Tukey window here.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/spectrogram-phases/06_diff_unwrap.png" width="823" height="438" loading="lazy" />
&lt;/div>
&lt;p>That&amp;rsquo;s a lot of structure. To get this I unwrapped the phases and computed deltas, both along the time dimension:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#75715e"># Xs : shape (window, bin)&lt;/span>
phases &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>angle(Xs)
phase_deltas &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>diff(np&lt;span style="color:#f92672">.&lt;/span>unwrap(phases, axis&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>), prepend&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>, axis&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The horizontal banding is pretty weird. This happens because higher bins' phases advance faster than lower bins, so their deltas are just bigger. We can get a normalized representation by dividing out the rate at which the sine under that bin advances:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">n &lt;span style="color:#f92672">=&lt;/span> len(window)
dt &lt;span style="color:#f92672">=&lt;/span> n &lt;span style="color:#f92672">//&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#75715e"># spectrogram step size&lt;/span>
normalized_frequencies &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>linspace(&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#f92672">/&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>, n&lt;span style="color:#f92672">//&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
phase_normalizer &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>angle(np&lt;span style="color:#f92672">.&lt;/span>exp(&lt;span style="color:#ae81ff">2&lt;/span>j &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>pi &lt;span style="color:#f92672">*&lt;/span> normalized_frequencies &lt;span style="color:#f92672">*&lt;/span> dt))
&lt;span style="color:#75715e"># ...&lt;/span>
normalized_phase_deltas &lt;span style="color:#f92672">=&lt;/span> phase_deltas &lt;span style="color:#f92672">/&lt;/span> phase_normalizer
&lt;/code>&lt;/pre>&lt;/div>&lt;p>And that looks like this. Please ignore the incorrect units.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/spectrogram-phases/07_normalized_diff_unwrap.png" width="823" height="438" loading="lazy" />
&lt;/div>
&lt;p>You can see chirps in the phase plot now. This is pretty closely related to &lt;a href="https://en.wikipedia.org/wiki/Instantaneous_phase_and_frequency">instantaneous frequency&lt;/a> but I don&amp;rsquo;t think I&amp;rsquo;ve ever seen it normalized this way. Someone must have.&lt;/p>
&lt;h2 id="end">End&lt;/h2>
&lt;p>I&amp;rsquo;m pretty dubious about this representation being of any use for machine learning. Maybe it would work if you never atan2 out the phase and kept it as a complex number (getting the phase delta is just a complex division). You definitely don&amp;rsquo;t want to be taking the derivate through anything that wraps the way an Euler angle does.&lt;/p>
&lt;p>I had a quick look at recent papers to see what ML people were doing here before yoloing this post out there. It doesn&amp;rsquo;t seem like anybody is using STFT phases. &lt;a href="https://github.com/facebookresearch/encodec">EnCodec&lt;/a>, at least, uses the real and imaginary parts of STFTs at multiple window sizes. That makes sense.&lt;/p></description></item><item><title>Twelve tones are inescapable</title><link>https://graemephi.github.io/posts/twelve-tones/</link><pubDate>Mon, 31 Mar 2025 12:00:00 +0000</pubDate><guid>https://graemephi.github.io/posts/twelve-tones/</guid><description>&lt;blockquote class="aside">Speculations. Nothing new just held up at a different angle than what you&amp;rsquo;ve
seen before.&lt;/blockquote>
&lt;p>I want to write about something interesting you can do with a single pentatonic scale. I&amp;rsquo;m just going to begin with what I noticed that got me thinking about this.&lt;/p>
&lt;p>Take &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> major pentatonic,&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C D E G A&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex"> \text{C D E G A}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C D E G A&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
And transpose it onto &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>G&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{G}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">G&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>.
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>G A &lt;/mtext>&lt;munder accentunder="true">&lt;mtext>B&lt;/mtext>&lt;mo stretchy="true">‾&lt;/mo>&lt;/munder>&lt;mtext> D E&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">
\text{G A } \underline{\text B} \text{ D E}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8833300000000001em;vertical-align:-0.20000000000000007em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">G A &lt;/span>&lt;/span>&lt;span class="mord underline">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.68333em;">&lt;span style="top:-2.84em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="underline-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">B&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20000000000000007em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord text">&lt;span class="mord"> D E&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
A new note popped out. The structure of the scale didn&amp;rsquo;t change but we needed a new note to represent it. If we didn&amp;rsquo;t know about &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>B&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text B&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">B&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, now we do.&lt;/p>
&lt;p>Now watch this. I&amp;rsquo;ve added the number of semitones between each note and color coded notes by the transposition they are introduced in. Start with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> major pentatonic:
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mstyle mathcolor="#0073e6">&lt;mtext>C D E G A&lt;/mtext>&lt;mspace linebreak="newline">&lt;/mspace>&lt;mstyle mathcolor="#000">&lt;mstyle mathsize="0.9em">&lt;mtext>2 2 3 2 3&lt;/mtext>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mrow>&lt;annotation encoding="application/x-tex"> \color{#0073e6}\text{C D E G A} \\
\color{#000}\small\text{2 2 3 2 3}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">C D E G A&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace newline" style="color:#0073e6;">&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.5799960000000001em;vertical-align:0em;">&lt;/span>&lt;span class="mord text sizing reset-size6 size5" style="color:#000;">&lt;span class="mord" style="color:#000;">2 2 3 2 3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
Transpose onto &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>E&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text E&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">E&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>:
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mstyle mathcolor="#0073e6">&lt;mtext>E &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>F♯ &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>G♯ &lt;/mtext>&lt;mstyle mathcolor="#0073e6">&lt;mtext>B &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>C♯&lt;/mtext>&lt;mspace linebreak="newline">&lt;/mspace>&lt;mstyle mathcolor="#000">&lt;mstyle mathsize="0.9em">&lt;mtext>2 2 3 2 3&lt;/mtext>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mrow>&lt;annotation encoding="application/x-tex">\color{#0073e6}\text{E }\color{#89ce00}\text{F♯ }\color{#89ce00}\text{G♯ }\color{#0073e6}\text{B }\color{#89ce00}\text{C♯} \\
\color{#000}\small\text{2 2 3 2 3} &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">E &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">F♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">G♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">B &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">C♯&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace newline" style="color:#89ce00;">&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.5799960000000001em;vertical-align:0em;">&lt;/span>&lt;span class="mord text sizing reset-size6 size5" style="color:#000;">&lt;span class="mord" style="color:#000;">2 2 3 2 3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>Rotate to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>G&lt;/mtext>&lt;mi mathvariant="normal">♯&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text G♯&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">G&lt;/span>&lt;/span>&lt;span class="mord">♯&lt;/span>&lt;/span>&lt;/span>&lt;/span>:
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mstyle mathcolor="#89ce00">&lt;mtext>G♯ &lt;/mtext>&lt;mstyle mathcolor="#0073e6">&lt;mtext>B &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>C♯ &lt;/mtext>&lt;mstyle mathcolor="#0073e6">&lt;mtext>E &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>F♯ &lt;/mtext>&lt;mspace linebreak="newline">&lt;/mspace>&lt;mstyle mathcolor="#000">&lt;mstyle mathsize="0.9em">&lt;mtext>3 2 3 2 2&lt;/mtext>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mrow>&lt;annotation encoding="application/x-tex">\color{#89ce00}\text{G♯ } \color{#0073e6}\text{B } \color{#89ce00}\text{C♯ } \color{#0073e6}\text{E } \color{#89ce00}\text{F♯ }\\
\color{#000}\small\text{3 2 3 2 2} &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">G♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">B &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">C♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">E &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">F♯ &lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace newline" style="color:#89ce00;">&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.5799960000000001em;vertical-align:0em;">&lt;/span>&lt;span class="mord text sizing reset-size6 size5" style="color:#000;">&lt;span class="mord" style="color:#000;">3 2 3 2 2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
Transpose back to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>:
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mstyle mathcolor="#0073e6">&lt;mtext>C &lt;/mtext>&lt;mstyle mathcolor="#e6308a">&lt;mtext>D♯ &lt;/mtext>&lt;mstyle mathcolor="#e6308a">&lt;mtext>F &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>G♯ &lt;/mtext>&lt;mstyle mathcolor="#e6308a">&lt;mtext>A♯&lt;/mtext>&lt;mspace linebreak="newline">&lt;/mspace>&lt;mstyle mathcolor="#000">&lt;mstyle mathsize="0.9em">&lt;mtext> 3 2 3 2 2&lt;/mtext>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mrow>&lt;annotation encoding="application/x-tex">\color{#0073e6}\text{C } \color{#e6308a}\text{D♯ } \color{#e6308a}\text{F } \color{#89ce00}\text{G♯ } \color{#e6308a}\text{A♯} \\
\color{#000}\small\text{ 3 2 3 2 2} &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">C &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#e6308a;">&lt;span class="mord" style="color:#e6308a;">D♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#e6308a;">&lt;span class="mord" style="color:#e6308a;">F &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">G♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#e6308a;">&lt;span class="mord" style="color:#e6308a;">A♯&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace newline" style="color:#e6308a;">&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.5799960000000001em;vertical-align:0em;">&lt;/span>&lt;span class="mord text sizing reset-size6 size5" style="color:#000;">&lt;span class="mord" style="color:#000;"> 3 2 3 2 2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>And we&amp;rsquo;ve got all of the notes in 2 transpositions:
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mstyle mathcolor="#0073e6">&lt;mtext>A &lt;/mtext>&lt;mstyle mathcolor="#e6308a">&lt;mtext>A♯ &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>B &lt;/mtext>&lt;mstyle mathcolor="#0073e6">&lt;mtext>C &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>C♯ &lt;/mtext>&lt;mstyle mathcolor="#0073e6">&lt;mtext>D &lt;/mtext>&lt;mstyle mathcolor="#e6308a">&lt;mtext>D♯ &lt;/mtext>&lt;mstyle mathcolor="#0073e6">&lt;mtext>E &lt;/mtext>&lt;mstyle mathcolor="#e6308a">&lt;mtext>F &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>F♯ &lt;/mtext>&lt;mstyle mathcolor="#0073e6">&lt;mtext>G &lt;/mtext>&lt;mstyle mathcolor="#89ce00">&lt;mtext>G♯ &lt;/mtext>&lt;mspace linebreak="newline">&lt;/mspace>&lt;mstyle mathcolor="#000">&lt;mstyle mathsize="0.9em">&lt;mtext>1 1 1 1 1 1 1 1 1 1 1 1&lt;/mtext>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mstyle>&lt;/mrow>&lt;annotation encoding="application/x-tex">
\color{#0073e6}\text{A }
\color{#e6308a}\text{A♯ }
\color{#89ce00}\text{B }
\color{#0073e6}\text{C }
\color{#89ce00}\text{C♯ }
\color{#0073e6}\text{D }
\color{#e6308a}\text{D♯ }
\color{#0073e6}\text{E }
\color{#e6308a}\text{F }
\color{#89ce00}\text{F♯ }
\color{#0073e6}\text{G }
\color{#89ce00}\text{G♯ } \\
\color{#000}\small\text{1 1 1 1 1 1 1 1 1 1 1 1}
&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">A &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#e6308a;">&lt;span class="mord" style="color:#e6308a;">A♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">B &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">C &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">C♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">D &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#e6308a;">&lt;span class="mord" style="color:#e6308a;">D♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">E &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#e6308a;">&lt;span class="mord" style="color:#e6308a;">F &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">F♯ &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#0073e6;">&lt;span class="mord" style="color:#0073e6;">G &lt;/span>&lt;/span>&lt;span class="mord text" style="color:#89ce00;">&lt;span class="mord" style="color:#89ce00;">G♯ &lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace newline" style="color:#89ce00;">&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.5799960000000001em;vertical-align:0em;">&lt;/span>&lt;span class="mord text sizing reset-size6 size5" style="color:#000;">&lt;span class="mord" style="color:#000;">1 1 1 1 1 1 1 1 1 1 1 1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>So that&amp;rsquo;s cool. Now what I would like to do is get an understanding of what is going on here that doesn&amp;rsquo;t rely on existing twelve-tone theory. Being able to lump pitch-classes into one of twelve ordered symbols that wrap around is doing a lot of work. So the rest of this post is about how much structure you need before pentatonic scales start implying the chromatic scale. Then we get to speculatin'. If this doesn&amp;rsquo;t sound interesting to you you can stop reading now.&lt;/p>
&lt;h2 id="string-theory">String theory&lt;/h2>
&lt;p>First I need to explain why the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>G&lt;/mtext>&lt;mi mathvariant="normal">♯&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text G♯&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">G&lt;/span>&lt;/span>&lt;span class="mord">♯&lt;/span>&lt;/span>&lt;/span>&lt;/span> onto &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> transposition is a totally normal thing to do. By rotating we get a mode, but why care about modes?&lt;/p>
&lt;p>So suppose you have a pair of strings. If you have a string instrument handy, you can try playing around with this. For pitches I will use the first two scales above, so one tuned to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and one tuned to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>E&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text E&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">E&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. We get the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>E&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text E&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">E&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> scales by placing the 2-2-3-2 pattern onto them.&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note blue" style="left: 0px;">C&lt;/div>
&lt;div class="note blue" style="left: 65.46px;">D&lt;/div>
&lt;div class="note blue" style="left: 123.78px;">E&lt;/div>
&lt;div class="note blue" style="left: 199.55px;">G&lt;/div>
&lt;div class="note blue" style="left: 243.24px;">A&lt;/div>
&lt;/div>&lt;/div>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint green" style="left: 300px;">&lt;/div>
&lt;div class="marker green" style="left: 600px;">&lt;/div>
&lt;div class="note green" style="left: 0px;">E&lt;/div>
&lt;div class="note green" style="left: 65.46px;">F♯&lt;/div>
&lt;div class="note green" style="left: 123.78px;">G♯&lt;/div>
&lt;div class="note green" style="left: 199.55px;">B&lt;/div>
&lt;div class="note green" style="left: 243.24px;">C♯&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Now we want to play one of the scales one the opposite string. Looking for the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>E&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text E&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">E&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> scale on the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> string we find the same notes here (up to octave equivalence, I went back and forth on this but I don&amp;rsquo;t think it matters):&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note green" style="left: 33.68px;">C♯&lt;/div>
&lt;div class="note green" style="left: 123.78px;">E&lt;/div>
&lt;div class="note green" style="left: 175.74px;">F♯&lt;/div>
&lt;div class="note green" style="left: 222.02px;">G♯&lt;/div>
&lt;div class="note green" style="left: 282.16px;">B&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Now we move the pattern we just found directly over to the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>E&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text E&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">E&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> string, and there we go, all the notes, no weird tricks, as promised.&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint green" style="left: 0px;">&lt;/div>
&lt;div class="midpoint green" style="left: 300px;">&lt;/div>
&lt;div class="marker green" style="left: 600px;">&lt;/div>
&lt;div class="note red" style="left: 33.68px;">F&lt;/div>
&lt;div class="note red" style="left: 123.78px;">G♯&lt;/div>
&lt;div class="note red" style="left: 175.74px;">A♯&lt;/div>
&lt;div class="note red" style="left: 222.02px;">C&lt;/div>
&lt;div class="note red" style="left: 282.16px;">D♯&lt;/div>
&lt;/div>&lt;/div>
&lt;p>This is pretty much immediate, right. You want to play the same scale on strings tuned to different pitches, and out it drops.&lt;/p>
&lt;p>Now, that got rid of symbolic manipulation and modes but I&amp;rsquo;m still relying on equal temperament. So I say it&amp;rsquo;s immediate but justifying 12-TET takes a lot of theory. There&amp;rsquo;s a gap here between experimenting with pentatonics on strings and reaching 12 tones that theory has already filled in.&lt;/p>
&lt;p>That&amp;rsquo;s easy enough to fix. We&amp;rsquo;ve got to redo the above with non 12-TET patterns. &lt;em>Pattern&lt;/em> because even scales are too much theory baggage for this part. To get away from any lingering chromaticism I&amp;rsquo;m going to dump down the fifth at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mn>3&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1/3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&amp;ndash;I&amp;rsquo;ll get to why even people who don&amp;rsquo;t care about &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>3&lt;/mn>&lt;mo>:&lt;/mo>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">3:2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">:&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;/span>&lt;/span>&lt;/span> ratios or, hell, harmony, will find it notable later&amp;ndash;and then fill in the empty space by placing nodes equally spaced between the gaps. This turns out to give a seven-limit tuning (exercise for the reader):&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note blue" style="left: 0.00px;">0&lt;/div>
&lt;div class="note blue" style="left: 66.67px;">1/9&lt;/div>
&lt;div class="note blue" style="left: 133.33px;">2/9&lt;/div>
&lt;div class="note blue" style="left: 200.00px;">1/3&lt;/div>
&lt;div class="note blue" style="left: 250.00px;">5/12&lt;/div>
&lt;/div>&lt;/div>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint green" style="left: 0px;">&lt;/div>
&lt;div class="midpoint green" style="left: 300px;">&lt;/div>
&lt;div class="marker green" style="left: 600px;">&lt;/div>
&lt;div class="note green" style="left: 0.00px;">0&lt;/div>
&lt;div class="note green" style="left: 66.67px;">1/9&lt;/div>
&lt;div class="note green" style="left: 133.33px;">2/9&lt;/div>
&lt;div class="note green" style="left: 200.00px;">1/3&lt;/div>
&lt;div class="note green" style="left: 250.00px;">5/12&lt;/div>
&lt;/div>&lt;/div>
&lt;p>These two patterns are like our &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>E&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text E&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">E&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> before, except now the green string is tuned to the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>2&lt;/mn>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mn>9&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">2/9&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord">9&lt;/span>&lt;/span>&lt;/span>&lt;/span> node of the blue. Find those green tones on the blue string, by ear, so I&amp;rsquo;m not going to pretend we have integer ratios for these:&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note green" style="left: 55.56px;">0.09&lt;/div>
&lt;div class="note green" style="left: 133.33px;">0.22&lt;/div>
&lt;div class="note green" style="left: 185.19px;">0.31&lt;/div>
&lt;div class="note green" style="left: 237.04px;">0.40&lt;/div>
&lt;div class="note green" style="left: 288.89px;">0.48&lt;/div>
&lt;/div>&lt;/div>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint green" style="left: 0px;">&lt;/div>
&lt;div class="midpoint green" style="left: 300px;">&lt;/div>
&lt;div class="marker green" style="left: 600px;">&lt;/div>
&lt;div class="note red" style="left: 55.56px;">0.09&lt;/div>
&lt;div class="note red" style="left: 133.33px;">0.22&lt;/div>
&lt;div class="note red" style="left: 185.19px;">0.31&lt;/div>
&lt;div class="note red" style="left: 237.04px;">0.40&lt;/div>
&lt;div class="note red" style="left: 288.89px;">0.48&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Placing the pattern on the green string without considering how it sounds made the red &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>G&lt;/mtext>&lt;mi mathvariant="normal">♯&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text G♯&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">G&lt;/span>&lt;/span>&lt;span class="mord">♯&lt;/span>&lt;/span>&lt;/span>&lt;/span> analogue. Now find it on the blue string, again by ear, so these produce the same pitches as on the green string,&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note red" style="left: 35.39px;">0.06&lt;/div>
&lt;div class="note red" style="left: 116.05px;">0.19&lt;/div>
&lt;div class="note red" style="left: 176.54px;">0.29&lt;/div>
&lt;div class="note red" style="left: 237.04px;">0.40&lt;/div>
&lt;div class="note red" style="left: 277.37px;">0.46&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Looking at the full gamut of notes we&amp;rsquo;ve generated gives&amp;hellip;&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note green up" style="left: 55.56px;">0.09&lt;/div>
&lt;div class="note green up" style="left: 133.33px;">0.22&lt;/div>
&lt;div class="note green up" style="left: 185.19px;">0.31&lt;/div>
&lt;div class="note green up" style="left: 237.04px;">0.40&lt;/div>
&lt;div class="note green up" style="left: 288.89px;">0.48&lt;/div>
&lt;div class="note red down" style="left: 35.39px;">0.06&lt;/div>
&lt;div class="note red down" style="left: 116.05px;">0.19&lt;/div>
&lt;div class="note red down" style="left: 176.54px;">0.29&lt;/div>
&lt;div class="note red down" style="left: 237.04px;">0.40&lt;/div>
&lt;div class="note red down" style="left: 277.37px;">0.46&lt;/div>
&lt;div class="note blue" style="left: 0.00px;">0&lt;/div>
&lt;div class="note blue" style="left: 66.67px;">1/9&lt;/div>
&lt;div class="note blue" style="left: 133.33px;">2/9&lt;/div>
&lt;div class="note blue" style="left: 200.00px;">1/3&lt;/div>
&lt;div class="note blue" style="left: 250.00px;">5/12&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Yeah. Compare to 12-TET:&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note green" style="left: 33.68px;">0.06&lt;/div>
&lt;div class="note green up" style="left: 123.78px;">0.21&lt;/div>
&lt;div class="note green" style="left: 175.74px;">0.29&lt;/div>
&lt;div class="note green up" style="left: 222.02px;">0.37&lt;/div>
&lt;div class="note green" style="left: 282.16px;">0.47&lt;/div>
&lt;div class="note red down" style="left: 0.00px;">0.00&lt;/div>
&lt;div class="note red" style="left: 95.46px;">0.16&lt;/div>
&lt;div class="note red" style="left: 150.51px;">0.25&lt;/div>
&lt;div class="note red down" style="left: 222.02px;">0.37&lt;/div>
&lt;div class="note red" style="left: 263.26px;">0.44&lt;/div>
&lt;div class="note blue up" style="left: 0.00px;">0.00&lt;/div>
&lt;div class="note blue" style="left: 65.46px;">0.11&lt;/div>
&lt;div class="note blue down" style="left: 123.78px;">0.21&lt;/div>
&lt;div class="note blue" style="left: 199.55px;">0.33&lt;/div>
&lt;div class="note blue" style="left: 243.24px;">0.41&lt;/div>
&lt;/div>&lt;/div>
&lt;p>One thing to watch out for is we didn&amp;rsquo;t start with the major pentatonic scale so we shouldn&amp;rsquo;t expect the nodes to correspond to nodes we found using the 12-TET scale. That is, they are not in the wrong position, or shifted over from where they ought to be. They just are where they are. But notice that even though we&amp;rsquo;ve lost the clear 12 tone structure, we&amp;rsquo;re still landing near 12-TET nodes. It&amp;rsquo;s the fifth that&amp;rsquo;s causing that.&lt;/p>
&lt;blockquote class="aside">&lt;p>It&amp;rsquo;s not hard to see why this clustering happens in principle. Once you have a fifth then you also have a fourth by the interval between it and the octave. A whole tone is then the interval between them. Stacking whole tones on top of the fifth will eventually produce a semitone relative to the fundamental.&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="note blue" style="left: 0px;">C&lt;/div>
&lt;div class="note green" style="left: 150.51px;">F&lt;/div>
&lt;div class="note green" style="left: 199.55px;">G&lt;/div>
&lt;div class="note red" style="left: 243.24px;">A&lt;/div>
&lt;div class="note red" style="left: 282.16px;">B&lt;/div>
&lt;div class="note red" style="left: 316.838px;">C♯&lt;/div>
&lt;div class="marker" style="left: 300px;">&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Once you&amp;rsquo;ve identified this thing as a semitone and try to unify it with other near-semitones&amp;ndash;by ear or mathematically&amp;ndash;you&amp;rsquo;re entering chromatic territory. So all the pieces are there once you have the fifth and the octave.&lt;/p>
&lt;p>Mathematically, this is equivalent to Pythagorean tuning. I&amp;rsquo;ve just motivated it differently. For whatever reason nowadays the way it&amp;rsquo;s presented is by stacking fifths and pointing out the comma you get after a comical number of fifths&amp;ndash;try to do it on a real string and you will see what I mean&amp;ndash;but slightly older textbooks like John Backus' &lt;em>The Acoustical Foundations of Music&lt;/em> (1969, pretty interesting) have the actual way it was done which was to alternate fifths upwards and fourths downwards. A fourth down followed by a fifth up directly gives you the whole tone.&lt;/p>
&lt;/blockquote>
&lt;p>To finish this bit off I want to look at applying this procedure to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C D E G A&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{C D E G A}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C D E G A&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> again but in Pythagorean tuning and with just intonation. Here&amp;rsquo;s 12-TET all on one string first, to compare with.&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note green" style="left: 33.68px;">0.06&lt;/div>
&lt;div class="note green up" style="left: 123.78px;">0.21&lt;/div>
&lt;div class="note green" style="left: 175.74px;">0.29&lt;/div>
&lt;div class="note green up" style="left: 222.02px;">0.37&lt;/div>
&lt;div class="note green" style="left: 282.16px;">0.47&lt;/div>
&lt;div class="note red down" style="left: 0.00px;">0.00&lt;/div>
&lt;div class="note red" style="left: 95.46px;">0.16&lt;/div>
&lt;div class="note red" style="left: 150.51px;">0.25&lt;/div>
&lt;div class="note red down" style="left: 222.02px;">0.37&lt;/div>
&lt;div class="note red" style="left: 263.26px;">0.44&lt;/div>
&lt;div class="note blue up" style="left: 0.00px;">0.00&lt;/div>
&lt;div class="note blue" style="left: 65.46px;">0.11&lt;/div>
&lt;div class="note blue down" style="left: 123.78px;">0.21&lt;/div>
&lt;div class="note blue" style="left: 199.55px;">0.33&lt;/div>
&lt;div class="note blue" style="left: 243.24px;">0.41&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Pythagorean:&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note green" style="left: 38.13px;">0.06&lt;/div>
&lt;div class="note green up" style="left: 125.93px;">0.21&lt;/div>
&lt;div class="note green" style="left: 178.60px;">0.30&lt;/div>
&lt;div class="note green up" style="left: 225.42px;">0.38&lt;/div>
&lt;div class="note green" style="left: 283.95px;">0.47&lt;/div>
&lt;div class="note red down" style="left: 8.08px;">0.01&lt;/div>
&lt;div class="note red" style="left: 100.56px;">0.17&lt;/div>
&lt;div class="note red" style="left: 156.06px;">0.26&lt;/div>
&lt;div class="note red down" style="left: 225.42px;">0.38&lt;/div>
&lt;div class="note red" style="left: 267.04px;">0.45&lt;/div>
&lt;div class="note blue up" style="left: 0.00px;">0&lt;/div>
&lt;div class="note blue" style="left: 66.67px;">1/9&lt;/div>
&lt;div class="note blue down" style="left: 125.93px;">0.21&lt;/div>
&lt;div class="note blue" style="left: 200.00px;">1/3&lt;/div>
&lt;div class="note blue" style="left: 244.44px;">11/27&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Just intonation:&lt;/p>
&lt;div class="string-container">&lt;div class="string-overflow">
&lt;div class="string">&lt;/div>
&lt;div class="midpoint blue" style="left: 0px;">&lt;/div>
&lt;div class="midpoint blue" style="left: 300px;">&lt;/div>
&lt;div class="marker blue" style="left: 600px;">&lt;/div>
&lt;div class="note green up" style="left: 24.00px;">0.04&lt;/div>
&lt;div class="note green up" style="left: 120.00px;">1/5&lt;/div>
&lt;div class="note green up" style="left: 173.33px;">0.29&lt;/div>
&lt;div class="note green up" style="left: 216.00px;">0.36&lt;/div>
&lt;div class="note green up" style="left: 280.00px;">0.47&lt;/div>
&lt;div class="note red down" style="left: 88.00px;">0.15&lt;/div>
&lt;div class="note red down" style="left: 139.20px;">0.23&lt;/div>
&lt;div class="note red down" style="left: 216.00px;">0.36&lt;/div>
&lt;div class="note red down" style="left: 258.67px;">0.43&lt;/div>
&lt;div class="note blue" style="left: 0.00px;">0&lt;/div>
&lt;div class="note blue" style="left: 66.67px;">1/9&lt;/div>
&lt;div class="note blue" style="left: 120.00px;">1/5&lt;/div>
&lt;div class="note blue" style="left: 200.00px;">1/3&lt;/div>
&lt;div class="note blue" style="left: 240.00px;">2/5&lt;/div>
&lt;div class="note red down" style="left: 292.80px;">0.49&lt;/div>
&lt;/div>&lt;/div>
&lt;p>Pythagorean tuning is doing a very good job at preserving structure we happen to already know is there. But if you didn&amp;rsquo;t know, then what it is giving you is a way of moving patterns from one string to another that mostly just works. Without it, scale correspondences between strings jump all over the place, without clear structure and with annoying discrepancies everywhere, which you can see in just intonation and my arbitrary pentatonic scale.&lt;/p>
&lt;p>To harp on just intonation a bit, I will quote acoustician &lt;a href="https://en.wikipedia.org/wiki/John_Backus_(acoustician)">John Backus&lt;/a>,&lt;/p>
&lt;blockquote>
&lt;p>Because of these difficulties, the just scale has never been of any practical use. Its theoretical attraction to individuals with numerological inclinations is extremely strong, however, so much so that it has even been called the “natural” scale, as though it had some fundamental basis in nature not possessed by other scales. It appears in practically every book dealing with the acoustics of music, where it has been given an emphasis it does not deserve.&lt;/p>
&lt;p>&lt;cite>John Backus, The Acoustical Foundations of Music, 1969&lt;/cite>&lt;/p>
&lt;/blockquote>
&lt;p>It turns out people tried really hard to make just intonation work during the Renaissance. Back then, Vincenzo Galilei in &lt;em>Dialogue on Ancient And Modern Music&lt;/em>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> did a similar thing to what I did above and systematically (and much more thoroughly than me) looked at how the intervals combine into near-misses. To what extent he is responsible for them abandoning just intonation, I don&amp;rsquo;t know, but he wanted everyone to move on to meantone and equal temperaments (&lt;a href="https://www.youtube.com/watch?v=XNvUPtFwZ8Y">sometimes both at once&lt;/a>), and they did. Another thing: it was a matter of debate among themselves whether they sang in just intonation or not, and they didn&amp;rsquo;t. This is pretty amusing to me as people still say the same thing. Galilei might have been the first person to actually check. If we&amp;rsquo;ve had 500 years of this, how many other things that I take for granted are just wrong? Do barbershop quartets really sing in just intonation? Wikipedia&amp;hellip;&lt;/p>
&lt;blockquote>
&lt;p>However, &amp;ldquo;In practice, it seems that most leads rely on an approximation of an equal-tempered scale for the melody, to which the other voices adjust vertically in just intonation.&amp;quot;&lt;a href="https://en.wikipedia.org/wiki/Barbershop_music#cite_note-Averill-5">[5]&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>Oh my god dude it keeps happening&lt;/p>
&lt;h2 id="this-post-part-two-whats-the-deal-with-fifths-anyway">This post part two: what&amp;rsquo;s the deal with fifths anyway?&lt;/h2>
&lt;p>For a long time now I&amp;rsquo;ve been pretty sure the usual story you hear that justifies the chromatic scale by going just intonation consonances &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo>→&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\rightarrow&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.36687em;vertical-align:0em;">&lt;/span>&lt;span class="mrel">→&lt;/span>&lt;/span>&lt;/span>&lt;/span> Pythagorean approximation &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo>→&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\rightarrow&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.36687em;vertical-align:0em;">&lt;/span>&lt;span class="mrel">→&lt;/span>&lt;/span>&lt;/span>&lt;/span> meantone etc. &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo>→&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\rightarrow&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.36687em;vertical-align:0em;">&lt;/span>&lt;span class="mrel">→&lt;/span>&lt;/span>&lt;/span>&lt;/span> equal temperament is bogus. Not just historically unsupported but, as a story we tell ourselves, it seems off, to me. On the other hand, throwing up your hands and declaring it all arbitrary and culturally contingent is denying a lot of the underlying structure that pops up everywhere.&lt;/p>
&lt;p>One reason I think pentatonic scales are important in motivating the 12 tone system is purely anthropological: they come along for the ride once you have anything resembling a scale and so are everywhere. But it&amp;rsquo;s not the case that what you end up with are pentatonic scales &lt;em>with a fifth&lt;/em>. Gamelan music is probably the best example where they dodged this and developed an entirely different approach to harmonic structure that uses a lot of inharmonic timbres. So transposing pentatonic scales alone isn&amp;rsquo;t enough to motivate twelve tones, even if you find my trick above convincing.&lt;/p>
&lt;p>Along similar lines, there&amp;rsquo;s an interesting 2024 preprint by &lt;a href="https://arxiv.org/abs/2408.12633">McBride et al.&lt;/a>, &lt;em>Melody predominates over harmony in the evolution of musical scales across 96 countries&lt;/em>, that points out folk melodies survive by ease of use: the whole tone to third interval range is in a goldilocks zone for singing. They focus on song but the same argument works for instruments too. It&amp;rsquo;s just hard to be mechanically precise about pitch until you invent a &lt;a href="https://en.wikipedia.org/wiki/Monochord">monochord&lt;/a> or something like it. Their point on top of that is that, once you gather them all up and analyse them, the scales you get out of this in practice are not particularly harmonic (consonant) in general.&lt;/p>
&lt;p>So what&amp;rsquo;s not bogus. Well, if you&amp;rsquo;re sufficiently serious about music for a long enough time you will eventually learn something about tone production.&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>Salviati&lt;/em>: A string which has been struck begins to vibrate and continues the motion as long as one hears the sound; these vibrations cause the immediately surrounding air to vibrate and quiver; then these ripples in the air expand far into space and strike not only all the strings of the same instrument but even those of neighboring instruments. Since that string which is tuned to unison with the one plucked is capable of vibrating with the same frequency, it acquires, at the first impulse, a slight oscillation; after receiving two, three, twenty, or more impulses, delivered at proper intervals, it finally accumulates a vibratory motion equal to that of the plucked string, as is clearly shown by equality of amplitude in their vibrations. This undulation expands through the air and sets into vibration not only strings, but also any other body which happens to have the same period as that of the plucked string. Accordingly if we attach to the side of an instrument small pieces of bristle or other flexible bodies, we shall observe that, when a spinet is sounded, only those pieces respond that have the same period as the string which has been struck; the remaining pieces do not vibrate in response to this string, nor do the former pieces respond to any other tone.&lt;/p>
&lt;p>&lt;cite>Galileo Galilei, Two New Sciences, 1638, trans. Henry Crew and Alfonso De Salvio&lt;/cite>&lt;/p>
&lt;/blockquote>
&lt;p>Yeah I&amp;rsquo;ve been rooting around old texts from before 12-TET got really locked in to see what they find salient about music. The oldest I&amp;rsquo;ve got is the &lt;em>Record of Music&lt;/em> which is dated to Warring States period China, so 200-400 BCE or so. They had proper Pythagorean twelve-tone tuning back then, too. I&amp;rsquo;m just going to quote from it, in full, starting from the very beginning, until we hit something relevant.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>The Roots of Music&lt;/strong>&lt;/p>
&lt;p>In all cases, the arising of music (yin) is born in the hearts of men. The movement of men&amp;rsquo;s hearts is made so by [external] things. They are touched off by things and move, thus they take shape in [human] sound (sheng). Sounds respond to each other, and thus give birth to change.&lt;/p>
&lt;p>&lt;cite>Scott Cook, &lt;em>Yue ji&lt;/em> - Record of Music. Introduction, translation, notes, and commentary. Asian Music, vol. 26 no. 2, 1995&lt;/cite>&lt;/p>
&lt;/blockquote>
&lt;p>Yup. And like all sufficiently old texts it comes with commentary from ~100 CE that shows they understood this to be talking about sympathetic resonance, like Galileo up there. (By the way a gong here is pretty much the root note of a pentatonic scale.)&lt;/p>
&lt;blockquote>
&lt;p>With musical instruments, when one plays a &lt;em>gong&lt;/em>, then many &lt;em>gong&lt;/em>&amp;rsquo;s respond. But this is not sufficient to be found enjoyable as music. For this reason, they are given to change and caused to be mixed together. The &lt;em>Yi Jing&lt;/em> says: &amp;lsquo;Alike sounds respond to each other, alike &lt;em>qi&lt;/em> seek each other.&amp;rsquo;&lt;/p>
&lt;p>&lt;cite>Zheng Xuan&lt;/cite>&lt;/p>
&lt;/blockquote>
&lt;p>Now, I need to point out that what follows in the translation I&amp;rsquo;ve got is 2 pages of modern-day translator&amp;rsquo;s commentary on just these tiny fragments alone. And it did not occur to them, at all, that this was about resonance. They focus entirely on the metaphysics, movement of men&amp;rsquo;s hearts or whatever. So my interpretation here is not necessarily self-evident, but, come on. The commentary is saying it directly: with musical instruments, when you play a &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>s respond!&lt;/p>
&lt;p>This is what is so special about fifths: the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>F&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text F&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">F&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> below and the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>G&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text G&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">G&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> above &lt;em>also&lt;/em> respond. For higher harmonics the effect of this falls off very rapidly. And, it does sound good, but it doesn&amp;rsquo;t resonate at its own pitch. You hear a harmonic, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>C&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> an octave up. Timbre-wise it&amp;rsquo;s more like a reverb. So, if you have a string instrument, whenever your open strings form unisons, octaves, and fifths against whatever note you&amp;rsquo;re playing, you get a richer sound that you can&amp;rsquo;t get without them. But as Zheng Xuan says, you need more than unisons to make music. &lt;a href="https://en.wikipedia.org/wiki/Ten-string_classical_guitar_of_Yepes">Musicians really want this&lt;/a>.&lt;/p>
&lt;p>At this point, if all you have are 3 strings tuned a fifth apart, say &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mtext>C&lt;/mtext>&lt;mn>3&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{C}_3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mtext> G&lt;/mtext>&lt;mn>3&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{ G}_3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord"> G&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mtext> D&lt;/mtext>&lt;mn>4&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{ D}_4&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord"> D&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">4&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, then finding &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mtext>G&lt;/mtext>&lt;mn>3&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text G_3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">G&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mtext>D&lt;/mtext>&lt;mn>3&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text D_3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">D&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> on the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mtext>C&lt;/mtext>&lt;mn>3&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text C_3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">C&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> string is exactly the first two steps of a Pythagorean tuning. Sympathetic resonance leads you right to it, even if your musical sense is focused exclusively on melodic structure and chords don&amp;rsquo;t interest you.&lt;/p>
&lt;p>Finally, while I do think this is extremely relevant to the historical development of instruments, you also shouldn&amp;rsquo;t ignore that Chinese text I quoted, which says, paraphrasing: when you hum the right way at things that resonate, they hum back. The significance of this to them will have been slightly different than its significance to us, right.&lt;/p>
&lt;blockquote class="aside">&lt;p>By the way, the music stuff out of ancient China is incredible. We know they used the chromatic scale because we keep digging up &lt;a href="https://en.wikipedia.org/wiki/Bianzhong">bronze bells&lt;/a> that come in sets tuned to it. And you can hit them different places to produce one of two tones, a third apart, and we have no idea how they found the shape that does that, and cannot yet reproduce it ourselves without copying. At this point, the oldest bells are a thousand years older than any written record of them using Pythagorean tuning to tune the things in the first place. This has led one professor of Chinese art &amp;lsquo;n&amp;rsquo; archaeology to say&lt;/p>
&lt;blockquote>
&lt;p>But the instruments that mattered in ancient China were bells, not
strings, and the theorists of a bell culture do not think in such terms. I am
proposing instead that, in a culture focused on bells, and at a time when
scales were a strictly aural phenomenon, the chromatic scale was discovered through transposition of the pentatonic scale. If in China the
chromatic scale was not a difficult idea, surely the reason is that the theorists who discovered it were not doing string-length calculations that
made transposition awkward. The chromatic scale as they conceived it
would have been an equal-semitone scale because there was no reason for
it to be anything else.&lt;/p>
&lt;p>&lt;cite>Robert Bagley, The Prehistory of Chinese Music Theory, Proceedings of the British Academy, Volume 131, 2004&lt;/cite>&lt;/p>
&lt;/blockquote>
&lt;p>But as I say above, I think you can get away from arithmetic on strings, too. (Also can you even build a bell forge without knowing a lot about ratios of lengths? Serious question. Surely there will be a lot of hoisting involved. It sounds positively Archimedean to me.)&lt;/p>
&lt;p>But in any case, the Pythagorean numerology only comes later, when you and the priests are trying to justify what you are doing. Which is totally fair, because if you can&amp;rsquo;t show that your music unites the harmony of the stars with the harmony of the state, what are we even doing here,&lt;/p>
&lt;/blockquote>
&lt;h2 id="end">End&lt;/h2>
&lt;p>This post ended up with its own folder in zotero. Man.&lt;/p>
&lt;p>But, okay. Here&amp;rsquo;s what I think. Forget all that stuff about the harmonic series and its consonances leading to the chromatic scale. Forget about the diatonic scales too, even. Say that stuff comes later, not before.&lt;/p>
&lt;p>Say instead sympathetic resonance between strings leads you to the &lt;em>procedure&lt;/em> of Pythagorean tuning, and it turns out you want to use it because it gives you nice way to organise all the pentatonic scales you play alongside the singers and guys with flutes. As a bonus, that procedural knowledge is something you can teach to just about anybody. You will still adjust your string tuning and intonation while playing a little bit to fit with what the other musicians are doing so you are not yet &lt;em>playing&lt;/em> the chromatic scale. But over time this provides a framework that eventually gets set in stone, due to the development of instruments that can only produce fixed pitches.&lt;/p>
&lt;p>As a nice just-so story I think this one is a lot more plausible. And I think it gets closer to what is actually important in music. But that&amp;rsquo;s just me&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>&lt;em>Vincenzo Galilei. Dialogue on Ancient and Modern Music. Translated by Claude V. Palisca. Music Theory Translation Series. New Haven: Yale University Press, 2003&lt;/em>.
&lt;br>
&lt;br>
The introduction to this book by the translator is worth reading if you&amp;rsquo;re interested in what is the deal, historically, with all these scales. After showing &lt;em>mathematically&lt;/em> the mess commas get you into, Galilei then does scale construction by ear, by the way.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>Generating Unique Random Numbers</title><link>https://graemephi.github.io/posts/unique-random-numbers/</link><pubDate>Fri, 27 Sep 2024 20:08:28 +0000</pubDate><guid>https://graemephi.github.io/posts/unique-random-numbers/</guid><description>&lt;blockquote class="aside">&lt;p>This post has two things:&lt;/p>
&lt;ol>
&lt;li>Two somewhat obscure non-linear invertible operations that work on only part of a register, and&lt;/li>
&lt;li>A fast stateless random permutation generator that uses them, and passes some statistical tests for RNGs you may have seen before&lt;/li>
&lt;/ol>
&lt;p>The ideas are pretty simple so the hope is you can also get an intuition for how to think about the problem, which is the best thing you can get out of this post.&lt;/p>
&lt;p>I got my intuition from these:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://burtleburtle.net/bob/rand/talksmall.html">Bob Jenkins' talk&lt;/a> on writing hash functions,&lt;/li>
&lt;li>&lt;a href="https://marc-b-reynolds.github.io/math/2017/10/13/IntegerBijections.html">Marc B Reynolds' post&lt;/a> on integer bijections (everything in this post is an integer bijection),&lt;/li>
&lt;li>Brent Burley&amp;rsquo;s &lt;a href="https://jcgt.org/published/0009/04/01/">Practical Hash-based Owen Scrambling&lt;/a>, highly recommend this one if you like path tracing,&lt;/li>
&lt;li>&lt;a href="https://andrew-helmer.github.io/permute/">Andrew Helmer&amp;rsquo;s blog post&lt;/a> about Andrew Kensler&amp;rsquo;s &lt;code>permute&lt;/code> in this &lt;a href="https://graphics.pixar.com/library/MultiJitteredSampling/paper.pdf">correlated multi-jitter paper&lt;/a>. Helmer&amp;rsquo;s post is where I first saw this idea, I think.&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>Random numbers that don&amp;rsquo;t repeat. Let me explain the basic idea first before I get into the weeds. We decide to generate unique random numbers in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mi>N&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[0, N)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>, here 8, by shuffling this list of numbers:
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>2&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>3&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>4&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>5&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>6&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>7&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(0,1,2,3,4,5,6,7)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">4&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">6&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">7&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
Adding the same number mod &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> to every element gives us back a new list with the same elements in a different order,
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>2&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>3&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>4&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>5&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>6&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>7&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>+&lt;/mo>&lt;mn>3&lt;/mn>&lt;mspace>&lt;/mspace>&lt;mspace width="1em"/>&lt;mrow>&lt;mi mathvariant="normal">m&lt;/mi>&lt;mi mathvariant="normal">o&lt;/mi>&lt;mi mathvariant="normal">d&lt;/mi>&lt;/mrow>&lt;mtext> &lt;/mtext>&lt;mtext> &lt;/mtext>&lt;mn>8&lt;/mn>&lt;mo>=&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>3&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>4&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>5&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>6&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>7&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>2&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(0,1,2,3,4,5,6,7) + 3 \mod 8 = (3,4,5,6,7,0,1,2)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">4&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">6&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">7&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mspace allowbreak">&lt;/span>&lt;span class="mspace" style="margin-right:1em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathrm">m&lt;/span>&lt;span class="mord mathrm">o&lt;/span>&lt;span class="mord mathrm">d&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">8&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">4&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">6&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">7&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
And so does multiplying by an odd number, mod &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>,
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>3&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>4&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>5&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>6&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>7&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>2&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>∗&lt;/mo>&lt;mn>5&lt;/mn>&lt;mspace>&lt;/mspace>&lt;mspace width="1em"/>&lt;mrow>&lt;mi mathvariant="normal">m&lt;/mi>&lt;mi mathvariant="normal">o&lt;/mi>&lt;mi mathvariant="normal">d&lt;/mi>&lt;/mrow>&lt;mtext> &lt;/mtext>&lt;mtext> &lt;/mtext>&lt;mn>8&lt;/mn>&lt;mo>=&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>7&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>4&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>6&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>3&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>5&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>2&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(3,4,5,6,7,0,1,2) * 5 \mod 8 = (7,4,1,6,3,0,5,2)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">4&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">6&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">7&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">∗&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mspace allowbreak">&lt;/span>&lt;span class="mspace" style="margin-right:1em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathrm">m&lt;/span>&lt;span class="mord mathrm">o&lt;/span>&lt;span class="mord mathrm">d&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">8&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">7&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">4&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">6&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
And this now looks pretty shuffled. So, take in the original numbers one at a time, apply these and other bijections the same way on each number, and out the other end we get a pseudorandom permutation without using any memory. We never need to construct the initial list.&lt;/p>
&lt;p>The operations we can use are any that can be undone (any shuffle can be re-sorted), so you hear people call them bijections and invertible functions interchangeably.&lt;/p>
&lt;p>I got interested in this after looking at the low discrepancy sequence stuff, but I brushed off the connection in &lt;a href="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/">my post&lt;/a> about it. With the tools there, its easy to generate numbers that don&amp;rsquo;t repeat but hard to make them random. That is, you could feed truly random bits in and you&amp;rsquo;d randomly select from a small set of random-looking sequences, which is not what we want. At the time, I didn&amp;rsquo;t know much about this problem, just that what I was doing didn&amp;rsquo;t work. This &lt;a href="https://www.ea.com/seed/news/constant-time-stateless-shuffling">EA Seed post of Alan Wolfe and William Donnelly&lt;/a> that uses Feistel networks got me interested again. The thing that really got me is that quality/speed trade-off they mention: what does it take to max out the quality? How can you tell you&amp;rsquo;ve reached it? Rather than answer these questions I made my own that always maxes the quality out.&lt;/p>
&lt;p>To do that you need some organising principle. Mine is this: use all your bits.&lt;/p>
&lt;p>What do I mean by this?&lt;/p>
&lt;p>In the above example we have 8 numbers we can do an add with, from which we get 3 bits worth of permutations. The multiply is 2 bits, we lose a bit due to the odd constraint. We won&amp;rsquo;t miss it for larger &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>. To choose these randomly we need 5 random bits, for 32 possible multiply-adds. That&amp;rsquo;s 32 different permutations we can get after one multiply-add, and, yes, any sequence of multiply-adds can be replaced by just one multiply-add, so out of these two operations we can&amp;rsquo;t get more than 32 permutations. No matter how many random bits we have with which to choose different madds, we&amp;rsquo;ll always end up with a sequence that can be reduced to a choice made with just 5 bits. Any more than that bits than that are wasted. We haven&amp;rsquo;t used them.&lt;/p>
&lt;p>There are &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>8&lt;/mn>&lt;mo stretchy="false">!&lt;/mo>&lt;mo>=&lt;/mo>&lt;mn>42320&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">8! = 42320&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">8&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">4&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">3&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span> permutations of length 8.&lt;/p>
&lt;h2 id="feistel-networks-entropy-and-so-on">Feistel networks, entropy, and so on&lt;/h2>
&lt;p>What Feistel networks do is give you non-linear bijections, and you can turn the crank on them to get as many permutations as you want. How many turns of the crank you need can be found in this &lt;a href="https://arxiv.org/abs/2106.06161">2022 Mitchell et al. paper &lt;/a> that adapts &lt;a href="https://dl.acm.org/doi/10.1145/2063384.2063405">philox&lt;/a> to generate permutations. Philox is (mostly) a Feistel network. To get good statistics, turns out it&amp;rsquo;s quite a lot: they want 24. This is the kind of round count you see in cryptography. What gives?&lt;/p>
&lt;p>In a Feistel network, you split each number into two halves, one half that keys a round function and another half that gets permuted with a xor. This means to know how one number is scrambled by the round function, you only need to know half the bits in each step, and &lt;em>this&lt;/em> means you get effectively half the entropy out of each round you could be getting (see this footnote if you are suspicious&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>). But this structure also makes it super easy to invert, which is very useful for encryption, right. For permutations of length 5, the function in the Mitchell paper will use 72 bits of entropy (and asks for some 700 bits that it just won&amp;rsquo;t use). You need 7 bits to index all 120 permutations!!&lt;/p>
&lt;p>This half-the-bits thing is important and is a good example of the kind of thinking you want to being doing when making these things, so, here. This is to illustrate something about bijections and the Feistel part isn&amp;rsquo;t super important so I&amp;rsquo;m not going to go in detail on that. Look:
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/unique-random-numbers/feistel.png" width="424" height="411" loading="lazy" />
&lt;/div>
&lt;/p>
&lt;p>This is a single round with the 8-bit input using a xor by some arbitrary function &lt;code>f&lt;/code> as the round function. One Feistel round permutes only the blue box to produce the purple box; the orange box remains unchanged in the output and the low bits reappear, unchanged, as the new high bits. That&amp;rsquo;s what I&amp;rsquo;m talking about in the previous paragraph. But if you stare at this a while you may realise there are only 16 ways that purple box can end up, and the only thing &lt;code>f&lt;/code> and &lt;code>seed&lt;/code> can do is make the choice more or less predictable. This is the important part: a better choice of &lt;code>f&lt;/code> can&amp;rsquo;t give you more permutations than that. If you only do this one thing, then each round you get at most 16x new permutations, and they&amp;rsquo;re the same new permutations regardless of &lt;code>f&lt;/code>. A bad choice of &lt;code>f&lt;/code> might fall short of all 16x, but ruling those out, the thing a better choice of &lt;code>f&lt;/code> is giving you is a more unpredictable order with respect to &lt;code>seed&lt;/code>.&lt;/p>
&lt;p>And that might sound good but for most &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> there are so many permutations we don&amp;rsquo;t care about the order. If the order you see permutations in matters, that&amp;rsquo;s bias. There are just too many permutations for it to be otherwise. I&amp;rsquo;ll get back to this point later.&lt;/p>
&lt;blockquote class="aside">&lt;p>I say a Feistel network will require too many bits, but it turns out to be really hard to use the minimal number of bits. Since I&amp;rsquo;ve brought this up, check this out. How many bits will the Fisher-Yates shuffle use? It&amp;rsquo;s the standard shuffle, and there used to be a little ecosystem of blog posts on how careless implementations could give bad statistics. My goal with this post is to match Fisher-Yates statistics-wise. (&lt;a href="https://en.wikipedia.org/wiki/Fisher-Yates_shuffle">Code from Wikipedia&lt;/a>, why not):&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">-- To shuffle an array a of n elements (indices 0..n-1):
for i from 0 to n−2 do
j ← random integer such that i ≤ j &amp;lt; n
exchange a[i] and a[j]
&lt;/code>&lt;/pre>&lt;p>On the first iteration, we generate a number between &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>, so &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\log_2 N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.93858em;vertical-align:-0.24414em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> bits are needed to describe the number of possible outcomes of the first iteration. The second is between &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">N,&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8777699999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;/span>&lt;/span>&lt;/span> that&amp;rsquo;s &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>N&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\log_2(N - 1)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> bits, and it&amp;rsquo;s smaller because we have one less possible values to choose from, right. Continue like this, then by the end we&amp;rsquo;ve used&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mtable rowspacing="0.24999999999999992em" columnalign="right left" columnspacing="0em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;/munderover>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>N&lt;/mi>&lt;mo>−&lt;/mo>&lt;mi>i&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;mi>N&lt;/mi>&lt;/munderover>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mi>i&lt;/mi>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;munderover>&lt;mo>∏&lt;/mo>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;mi>N&lt;/mi>&lt;/munderover>&lt;mi>i&lt;/mi>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mi>N&lt;/mi>&lt;mo stretchy="false">!&lt;/mo>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;annotation encoding="application/x-tex">
\begin{aligned}
\sum_{i=0}^{N - 2} \log_2(N - i) &amp;amp;=
\sum_{i=2}^{N} \log_2 i \\
&amp;amp;= \log_2\prod_{i=2}^N i \\
&amp;amp;= \log_2 N!
\end{aligned}
&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:8.31201em;vertical-align:-3.9060050000000004em;">&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-r">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:4.406005em;">&lt;span style="top:-6.406005em;">&lt;span class="pstrut" style="height:3.828336em;">&lt;/span>&lt;span class="mord">&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8283360000000004em;">&lt;span style="top:-1.872331em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.050005em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.300005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.277669em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">i&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3em;">&lt;span class="pstrut" style="height:3.828336em;">&lt;/span>&lt;span class="mord">&lt;/span>&lt;/span>&lt;span style="top:-0.5823309999999999em;">&lt;span class="pstrut" style="height:3.828336em;">&lt;/span>&lt;span class="mord">&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:3.9060050000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="col-align-l">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:4.406005em;">&lt;span style="top:-6.406005em;">&lt;span class="pstrut" style="height:3.828336em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8283360000000002em;">&lt;span style="top:-1.872331em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.050005em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.3000050000000005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.277669em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">i&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3em;">&lt;span class="pstrut" style="height:3.828336em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8283360000000002em;">&lt;span style="top:-1.872331em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.050005em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∏&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.3000050000000005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.277669em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">i&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-0.5823309999999999em;">&lt;span class="pstrut" style="height:3.828336em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:3.9060050000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>bits of entropy. Exactly enough bits for the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo stretchy="false">!&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">N!&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;/span>&lt;/span>&lt;/span> possible permutations: if you interpret the bits it draws as a number, then that number directly indexes into the big Lehmer-list in the sky of permutations of size &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>. So, assuming we could draw a fractional number of bits from an RNG, this shuffle is optimal in the number of bits it will use. Something that took me a while to realise is that the exact same reasoning applies to figuring out the minimum number of bits it would take to encode the next value in a permutation, given those you&amp;rsquo;ve already seen. :o&lt;/p>
&lt;p>We won&amp;rsquo;t get anywhere close to this lower bound, in both senses: for small &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> I&amp;rsquo;ll use too many bits, and for large &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>, well, nobody ever will get close to using enough.&lt;/p>
&lt;p>If all this talk of bits and entropy is lost on you: read David MacKay&amp;rsquo;s &lt;a href="https://www.inference.org.uk/itprnn/">Inference book&lt;/a>.&lt;/p>
&lt;/blockquote>
&lt;p>Anyway, all that&amp;rsquo;s why I went looking for other ways to map bits to bijections. My thinking is this: using cheaper non-linear bijections that operate on all the bits at once can produce more random lookin' permutations per unit time. The space of permutations that can be sampled &lt;em>at all&lt;/em> can expand up to twice as quickly by having no half-width bottlenecks.&lt;/p>
&lt;h2 id="two-invertible-operations">Two invertible operations&lt;/h2>
&lt;h4 id="1">1&lt;/h4>
&lt;p>Everyone into this junk knows odd multiplies are invertible and even multiplies aren&amp;rsquo;t. That&amp;rsquo;s wrong: even multiplies are perfectly invertible. It&amp;rsquo;s only after truncating the bits that don&amp;rsquo;t fit in the register that invertibility is lost. They don&amp;rsquo;t have invertibility &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>mod &lt;/mtext>&lt;msup>&lt;mn>2&lt;/mn>&lt;mi>b&lt;/mi>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{mod } 2^b&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.849108em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">mod &lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.849108em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">b&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, but here, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">b&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;/span> is probably small, and there is still plenty of register left over. So, don&amp;rsquo;t truncate: shift.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">a : value to permute
k : random bits, or constant, whatever
m = (1 &amp;lt;&amp;lt; bitwidth(a)) - 1 (mask for getting a back into range)
k_low_bits = (k &amp;amp; -k) - 1 (turn trailing zeros into a mask)
k += ((k &amp;amp; m) == 0) (fix up if k is 0)
ak = a * k (multiply)
a = ak + ((ak &amp;gt;&amp;gt; bits) &amp;amp; k_low_bits) (rotate)
&lt;/code>&lt;/pre>&lt;p>Spelled out: even multiplies can be split into two operations, an odd multiply and a left shift; the shift amount is the number of trailing zeros. To restore invertibility we could shift back to undo that shift, but this lands it back at an odd multiply so we&amp;rsquo;re still down a bit. Instead, do a rotate, by shifting the bits otherwise masked out into the low bits that were just zeroed out by the multiply.&lt;/p>
&lt;p>This is fine as is for small bit-widths, but if &lt;code>bitwidth(a)&lt;/code> is greater than the half the register width then &lt;code>k&lt;/code> can have so many trailing zeros that the multiply runs out of register. For &lt;code>bitwidth(a)&lt;/code> up at like 30 then it can only handle at most 2 trailing zeros. We don&amp;rsquo;t care about the missing bit that much in that case, so just detect when there are too many trailing zeros and turn it into a multiply by one:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">km = m &amp;amp; (~0u &amp;gt;&amp;gt; bits) (note, km == m when bits &amp;lt; regsiter width / 2)
k += ((k &amp;amp; km) == 0) (new fix up)
&lt;/code>&lt;/pre>&lt;p>This is getting a bit complicated. The up shot is we get an almost full-bit multiply and a non-linearity out of it. &amp;lsquo;Almost&amp;rsquo; because we still can&amp;rsquo;t multiply by zero. To underscore the point a bit, the shift amount is constant with respect to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> but the effective rotation amount depends on &lt;code>k&lt;/code>.&lt;/p>
&lt;p>If you choose &lt;code>k&lt;/code> randomly then the random rotates we get follow a geometric distribution: &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1/2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord">2&lt;/span>&lt;/span>&lt;/span>&lt;/span> chance the first bit is zero, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mn>4&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1/4&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord">4&lt;/span>&lt;/span>&lt;/span>&lt;/span> the first and second bit are both zero, &amp;hellip; So it&amp;rsquo;s usually 0, 1 or 2. That works well here because it means it just works for all &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>&amp;ndash;for small &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>, if we only have, say, 3 bits then we can only do rotates of 1 or 2 bits, so if we have a lot of larger rotates we aren&amp;rsquo;t getting the non-linearities we want. Likewise for large &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>, where we need to avoid shifting away bits at the opposite end, we don&amp;rsquo;t have to worry about that happening often either. But for other &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> we&amp;rsquo;d really like to get bits moving in different ways. So there&amp;rsquo;s room for improvement, maybe.&lt;/p>
&lt;h4 id="2">2&lt;/h4>
&lt;p>The second invertible operation I&amp;rsquo;d like to point out is actually a repeat from &lt;a href="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/">last time&lt;/a>, where it appeared in the Laine-Karras scramble (and I got it from the &lt;a href="https://jcgt.org/published/0009/04/01/">Brent Burley&lt;/a> paper originally). But this time I got to it by thinking: the above is using the low zero bits of &lt;code>k&lt;/code> as the shift amount. Can we swap our operands around and use the low zero bits of &lt;code>a&lt;/code> as the shift amount? Then we&amp;rsquo;d get a varying rotate per &lt;code>a&lt;/code>. But I couldn&amp;rsquo;t figure out a way to use that idea that was actually invertible. However in a moment of zen I realized the following is invertible:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">a ^= (a * k) &amp;lt;&amp;lt; 1
&lt;/code>&lt;/pre>&lt;p>Which is exactly equivalent to the multiplication by even constants in the nested uniform scramble.&lt;/p>
&lt;p>This is pretty interesting in that, if you look at it like a Feistel function, then rather than splitting up the permutation into equal sized classes indexed by half the bits, it splits it into classes indexed by the number of trailing zeros in &lt;code>a&lt;/code>. Not only are the classes shuffled independently, so values in one class stay in that class, each class is a different size. Within each, the permute is driven by the multiply. (Sorry if all this is a bit abstract all of a sudden).&lt;/p>
&lt;p>But in light of what I said before about Feistel networks I will use it like this, because the bits in &lt;code>k&lt;/code> are valuable:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">a ^= ((a * k) &amp;lt;&amp;lt; 1) ^ k
&lt;/code>&lt;/pre>&lt;p>So that&amp;rsquo;s two bijections. One is a little complicated code-wise but is really simple conceptually; an odd multiply followed by a rotate. It&amp;rsquo;s easy to see why it&amp;rsquo;s invertible. One is simple code-wise and is complicated conceptually; I don&amp;rsquo;t know what to call it. It&amp;rsquo;s a bit more subtle I think.&lt;/p>
&lt;h2 id="permute">permute&lt;/h2>
&lt;p>To use these, I need to define the problem. We want to &lt;strong>randomly sample permutations&lt;/strong>, uniformly and independently. For small &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>, this means sometimes sampling 0, 1, 2, 3, 4&amp;hellip; as a random permutation. So, that&amp;rsquo;s a stateless function like this, where &lt;code>seed&lt;/code> enumerates permutations in a pseudorandom order.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-c" data-lang="c">&lt;span style="color:#66d9ef">uint32_t&lt;/span> &lt;span style="color:#a6e22e">permute&lt;/span>(&lt;span style="color:#66d9ef">uint32_t&lt;/span> i, &lt;span style="color:#66d9ef">uint32_t&lt;/span> N, &lt;span style="color:#66d9ef">uint32_t&lt;/span> seed);
&lt;span style="color:#75715e">// Usage:
&lt;/span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">uint32_t&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> N; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;span style="color:#66d9ef">uint32_t&lt;/span> index &lt;span style="color:#f92672">=&lt;/span> permute(i, N, seed);
&lt;span style="color:#75715e">// ... use index
&lt;/span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Secondary goal is that it should just work. No need to seed it well. Cycle walking for non powers of two&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>The idea is that generating pseudorandom bits is expensive, so do it once at the start, and shift out the generated bits from a register as a source of entropy. Then use each bit of entropy at least once. I found the two bijections above weren&amp;rsquo;t enough on their own for good avalanche so I gray code between each use of entropy bits:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-c" data-lang="c">&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> &lt;span style="color:#a6e22e">hash32&lt;/span>(&lt;span style="color:#66d9ef">uint32_t&lt;/span> x) {
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
x &lt;span style="color:#f92672">*=&lt;/span> &lt;span style="color:#ae81ff">0x21f0aaad&lt;/span>;
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>;
x &lt;span style="color:#f92672">*=&lt;/span> &lt;span style="color:#ae81ff">0xd35a2d97&lt;/span>;
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>;
&lt;span style="color:#66d9ef">return&lt;/span> x;
}
&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> &lt;span style="color:#a6e22e">log2_floor&lt;/span>(&lt;span style="color:#66d9ef">uint32_t&lt;/span> a) {
&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">31u&lt;/span> &lt;span style="color:#f92672">-&lt;/span> _lzcnt_u32(a);
}
&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> &lt;span style="color:#a6e22e">log2_ceil&lt;/span>(&lt;span style="color:#66d9ef">uint32_t&lt;/span> a) {
&lt;span style="color:#66d9ef">return&lt;/span> log2_floor(a &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1u&lt;/span>) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1u&lt;/span>;
}
&lt;span style="color:#66d9ef">uint32_t&lt;/span> &lt;span style="color:#a6e22e">permute&lt;/span>(&lt;span style="color:#66d9ef">uint32_t&lt;/span> i, &lt;span style="color:#66d9ef">uint32_t&lt;/span> n, &lt;span style="color:#66d9ef">uint32_t&lt;/span> seed) {
&lt;span style="color:#66d9ef">uint32_t&lt;/span> bits &lt;span style="color:#f92672">=&lt;/span> log2_ceil(n);
&lt;span style="color:#66d9ef">uint32_t&lt;/span> mask &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#ae81ff">1u&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> bits) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1u&lt;/span>;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> multiplier_mask &lt;span style="color:#f92672">=&lt;/span> mask &lt;span style="color:#f92672">&amp;amp;&lt;/span> (&lt;span style="color:#f92672">~&lt;/span>&lt;span style="color:#ae81ff">0u&lt;/span> &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> bits);
&lt;span style="color:#66d9ef">uint32_t&lt;/span> index_seed &lt;span style="color:#f92672">=&lt;/span> (i &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> bits) &lt;span style="color:#f92672">^&lt;/span> n;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> state0 &lt;span style="color:#f92672">=&lt;/span> seed &lt;span style="color:#f92672">+&lt;/span> index_seed;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> state1 &lt;span style="color:#f92672">=&lt;/span> hash32(index_seed &lt;span style="color:#f92672">-&lt;/span> seed);
&lt;span style="color:#66d9ef">do&lt;/span> {
&lt;span style="color:#66d9ef">uint32_t&lt;/span> state &lt;span style="color:#f92672">=&lt;/span> state0;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> rounds &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>;
&lt;span style="color:#66d9ef">while&lt;/span> (rounds&lt;span style="color:#f92672">--&lt;/span>) {
&lt;span style="color:#66d9ef">uint32_t&lt;/span> p &lt;span style="color:#f92672">=&lt;/span> state;
&lt;span style="color:#66d9ef">do&lt;/span> {
&lt;span style="color:#66d9ef">uint32_t&lt;/span> q &lt;span style="color:#f92672">=&lt;/span> p;
p &lt;span style="color:#f92672">&amp;gt;&amp;gt;=&lt;/span> bits;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> r &lt;span style="color:#f92672">=&lt;/span> p &lt;span style="color:#f92672">^&lt;/span> state;
p &lt;span style="color:#f92672">&amp;gt;&amp;gt;=&lt;/span> bits;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> s &lt;span style="color:#f92672">=&lt;/span> p &lt;span style="color:#f92672">^&lt;/span> state;
p &lt;span style="color:#f92672">&amp;gt;&amp;gt;=&lt;/span> bits;
q &lt;span style="color:#f92672">&amp;amp;=&lt;/span> &lt;span style="color:#f92672">~&lt;/span>&lt;span style="color:#ae81ff">1u&lt;/span>;
q &lt;span style="color:#f92672">+=&lt;/span> ((q &lt;span style="color:#f92672">&amp;amp;&lt;/span> multiplier_mask) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0u&lt;/span>) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> q_lb &lt;span style="color:#f92672">=&lt;/span> (q &lt;span style="color:#f92672">&amp;amp;&lt;/span> (&lt;span style="color:#ae81ff">0u&lt;/span> &lt;span style="color:#f92672">-&lt;/span> q)) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1u&lt;/span>;
i &lt;span style="color:#f92672">^=&lt;/span> ((i &lt;span style="color:#f92672">*&lt;/span> p) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">^&lt;/span> p;
i &lt;span style="color:#f92672">^=&lt;/span> (i &lt;span style="color:#f92672">&amp;amp;&lt;/span> mask) &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;span style="color:#66d9ef">uint32_t&lt;/span> iqr &lt;span style="color:#f92672">=&lt;/span> (i &lt;span style="color:#f92672">*&lt;/span> q) &lt;span style="color:#f92672">+&lt;/span> r;
i &lt;span style="color:#f92672">=&lt;/span> iqr &lt;span style="color:#f92672">+&lt;/span> ((i &lt;span style="color:#f92672">^&lt;/span> (iqr &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> bits)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> q_lb);
i &lt;span style="color:#f92672">^=&lt;/span> (i &lt;span style="color:#f92672">&amp;amp;&lt;/span> mask) &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span>;
i &lt;span style="color:#f92672">^=&lt;/span> ((i &lt;span style="color:#f92672">*&lt;/span> s) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">^&lt;/span> s;
i &lt;span style="color:#f92672">^=&lt;/span> (i &lt;span style="color:#f92672">&amp;amp;&lt;/span> mask) &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">7&lt;/span>;
} &lt;span style="color:#66d9ef">while&lt;/span> (p);
state &lt;span style="color:#f92672">=&lt;/span> state1;
}
i &lt;span style="color:#f92672">&amp;amp;=&lt;/span> mask;
} &lt;span style="color:#66d9ef">while&lt;/span> (i &lt;span style="color:#f92672">&amp;gt;=&lt;/span> n);
&lt;span style="color:#66d9ef">return&lt;/span> i;
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>I kept it all within 32-bits to make it easier to analyze. It doesn&amp;rsquo;t work for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>&amp;gt;&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>31&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">N &amp;gt; 2^{31}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.72243em;vertical-align:-0.0391em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">&amp;gt;&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. If you want that, go to 64-bits. Replace the hash and add&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-c" data-lang="c">i &lt;span style="color:#f92672">^=&lt;/span> (i &lt;span style="color:#f92672">&amp;amp;&lt;/span> mask) &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>;
&lt;/code>&lt;/pre>&lt;/div>&lt;p>somewhere. One benefit of having no constants is it&amp;rsquo;s clear how to extend this thing to higher bit counts.&lt;/p>
&lt;h3 id="fiddly-detail">Fiddly detail&lt;/h3>
&lt;blockquote class="aside">&lt;p>The high bits in the index would otherwise go to waste so I use them as more seed bits, so each seed really defines a sequence of permutations per &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>, rather than just one permutation per &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>.&lt;/p>
&lt;p>The &lt;code>log2_ceil&lt;/code> is a little exotic in a non-systems language context but it is pretty much free here with &lt;code>lzcnt&lt;/code>, you probably want to pass it in precomputed if it or an equivalent instruction is not available, the alternatives really dent the speed of this thing. &lt;a href="https://stackoverflow.com/a/51351885">It&amp;rsquo;s from Travis Downs&lt;/a>.&lt;/p>
&lt;p>The hash function is one of those &lt;a href="https://nullprogram.com/blog/2018/07/31/">prospecting for hash functions&lt;/a> hashes. The way I use it, you can see you could also pull it out precomputed once for the whole permutation, if you really wanted to.&lt;/p>
&lt;p>As well as the add, I sneak in a xor of the original bits in middle of the multiply-rotate. Philox-y. And there&amp;rsquo;s always at least 1 bit of rotation.&lt;/p>
&lt;p>I don&amp;rsquo;t like the &lt;code>while (p)&lt;/code>, I&amp;rsquo;d rather use a fixed number of iterations derived from &lt;code>n&lt;/code>. As it is, larger &lt;code>n&lt;/code> shift bits out faster and stop earlier. But it will also stop early if the state becomes 0. It works better statistics-for-time this way, which I don&amp;rsquo;t really understand.&lt;/p>
&lt;/blockquote>
&lt;h3 id="justifications">Justifications&lt;/h3>
&lt;p>For &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> larger than 12, 32 bits is not enough seeds to index every permutation, so it&amp;rsquo;s fine to fall short of hitting every permutation, so long as the sampling is&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> unbiased.&lt;/p>
&lt;p>To be unbiased we need this: it doesn&amp;rsquo;t matter how you seed this thing. Consecutive seeds must produce permutations that are uncorrelated. It doesn&amp;rsquo;t take long before the space of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo stretchy="false">!&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">N!&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;/span>&lt;/span>&lt;/span> permutations it&amp;rsquo;s supposed to be sampling from is so vast that all &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>32&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{32}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> permutations are a vanishingly small subset. So, if nearby seeds are correlated, we already know we are sampling from a biased sample of all permutations. That&amp;rsquo;s why I use the seed directly in the first round: we could hash it, and this will probably give us better &lt;code>(state0, state1)&lt;/code> pairs to use (there are &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>64&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{64}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">6&lt;/span>&lt;span class="mord mtight">4&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> such pairs and we&amp;rsquo;ll use &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>32&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{32}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> of them), but there is no simple way to avoid having many mostly-zero states in one side of the pair; all we can control is what order we get them in.&lt;/p>
&lt;p>For that reason, I don&amp;rsquo;t think we can get away with using only 32 bits of state, even though we&amp;rsquo;ll only ever map to 32 bits worth of permutations. If we hash, we can use an invertible hash to guarantee each seed maps to unique hash bits. But this means we just shuffled the low-entropy seeds around. Or we use a non-invertible hash, and we know for sure some seeds map to the same permutation&amp;ndash;can&amp;rsquo;t be having it. Even if we pack this thing to the gills with intimidating constants, a low entropy seed will mean we land on a permutation near (in some sense) to the default permutation selected by the constants.&lt;/p>
&lt;p>Expanding the seed an extra 32 bits solves all this.&lt;/p>
&lt;p>To get an idea for why we really cannot have any correlations period, think about lexicographically sorting all the permutations and looking for correlations between nearby seeds. If &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>500&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">N = 500&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span> then for the first 10 values we have &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mrow>&lt;mo fence="true">(&lt;/mo>&lt;mfrac linethickness="0px">&lt;mn>500&lt;/mn>&lt;mn>10&lt;/mn>&lt;/mfrac>&lt;mo fence="true">)&lt;/mo>&lt;/mrow>&lt;mo>&amp;gt;&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>67&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">{500 \choose 10} &amp;gt; 2^{67}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">(&lt;/span>&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.32144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">5&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">&amp;gt;&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">6&lt;/span>&lt;span class="mord mtight">7&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> possible permutation-heads, so by the 11th value we expect the sorted permutations to be completely uncorrelated again.&lt;/p>
&lt;p>The restriction to 32-bit integers is somewhat artificial but it also enabled reasoning like the above that I&amp;rsquo;m not sure I&amp;rsquo;d have got to without it.&lt;/p>
&lt;h2 id="testing-these-things">Testing these things&lt;/h2>
&lt;p>A quick test that will fail most ideas you come up with is to generate permutations of size 8, each value is 3 bits so you only need 24 bits to represent an entire permutation. Generate a bunch, pack each into an int and compare the number of unique ints you get to unique numbers from a PRNG in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>8&lt;/mn>&lt;mo stretchy="false">!&lt;/mo>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[0, 8!)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">8&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Or math it out, but we&amp;rsquo;re all friends here. This fails &lt;code>permute&lt;/code> when using a single round.&lt;/p>
&lt;p>To get an idea for the behaviour with larger &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> you can make a &lt;a href="https://bost.ocks.org/mike/shuffle/compare.html">matrix diagram&lt;/a> that visualises how often a each input index gets mapped to each output index. This works but I found you could look good here while still being biased (this is true of avalanche as well). Instead, try this: look for correlations between adjacent indices. So, instead of counting &lt;code>(i, permute(i))&lt;/code> pairs, count &lt;code>(permute(i), permute(i + 1))&lt;/code> pairs. Somewhat interestingly, since these are always different, the pairs this gives you form a particular kind of permutation called a &lt;a href="https://en.wikipedia.org/wiki/Derangement">derangement&lt;/a>. With that said, here&amp;rsquo;s &lt;code>permute&lt;/code> with only 1 round, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>1024&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">N=1024&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">4&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>16&lt;/mn>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">16N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">6&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> consecutive seeds near 0:
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/unique-random-numbers/urn_seq_1.png" width="1024" height="1024" loading="lazy" />
&lt;/div>
&lt;blockquote class="aside">Scaled down by &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>2&lt;/mn>&lt;mo>⋅&lt;/mo>&lt;mn>16&lt;/mn>&lt;mi>N&lt;/mi>&lt;mo>⋅&lt;/mo>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>N&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;/mfrac>&lt;/mrow>&lt;annotation encoding="application/x-tex">2\cdot16N\cdot\frac{1}{(N-1)}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">⋅&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">6&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">⋅&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.365108em;vertical-align:-0.52em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.655em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mopen mtight">(&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;span class="mclose mtight">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.52em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> which puts the expected average at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0.5&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0.5&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">5&lt;/span>&lt;/span>&lt;/span>&lt;/span>. And fed through the &lt;code>BrGB&lt;/code> colour map from matplotlib which is white in the middle. It&amp;rsquo;s much more clear what&amp;rsquo;s going on compared to grayscale.&lt;/blockquote>
&lt;/p>
&lt;p>The zeros along the diagonal are expected, as it&amp;rsquo;s a derangement. But it&amp;rsquo;s not quite there yet, note that near the diagonal it is slightly darker and undersampled.
Here&amp;rsquo;s two rounds:
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/unique-random-numbers/urn_seq_2.png" width="1024" height="1024" loading="lazy" />
&lt;/div>
&lt;/p>
&lt;p>👍. These are all the seeds around zero&amp;ndash;all ones, all zeros, 31 ones, 31 zeros, that kind of junk.&lt;/p>
&lt;p>The last test I like is for small &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>, counting repeat permutations. Similar to the first test but more involved to actually implement. This is an interesting test because when we randomly sample permutations of length &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>13&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">13&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>, there are more than &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>32&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{32}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> permutations to sample but the number of samples before we expect to see a repeat is far less than &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>32&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{32}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Knuth in TAOCP somewhere has an approximate formula for this
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>Q&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>N&lt;/mi>&lt;mo stretchy="false">!&lt;/mo>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>∼&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo>+&lt;/mo>&lt;msqrt>&lt;mfrac>&lt;mrow>&lt;mi>π&lt;/mi>&lt;mi>N&lt;/mi>&lt;mo stretchy="false">!&lt;/mo>&lt;/mrow>&lt;mn>2&lt;/mn>&lt;/mfrac>&lt;/msqrt>&lt;mo>+&lt;/mo>&lt;mtext> &lt;/mtext>&lt;mo>…&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">Q(N!) \sim 1 + \sqrt{\frac{\pi N!}{2}} +\space\dots&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">Q&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">∼&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.72777em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.44em;vertical-align:-0.7634050000000001em;">&lt;/span>&lt;span class="mord sqrt">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.6765949999999998em;">&lt;span class="svg-align" style="top:-4.4em;">&lt;span class="pstrut" style="height:4.4em;">&lt;/span>&lt;span class="mord" style="padding-left:1em;">&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.37144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.6365950000000002em;">&lt;span class="pstrut" style="height:4.4em;">&lt;/span>&lt;span class="hide-tail" style="min-width:1.02em;height:2.48em;">&lt;svg width='400em' height='2.48em' viewBox='0 0 400000 2592' preserveAspectRatio='xMinYMin slice'>&lt;path d='M424,2478
c-1.3,-0.7,-38.5,-172,-111.5,-514c-73,-342,-109.8,-513.3,-110.5,-514
c0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5,25c-5.7,9.3,-9.8,16,-12.5,20
s-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13,-13s76,-122,76,-122s77,-121,77,-121
s209,968,209,968c0,-2,84.7,-361.7,254,-1079c169.3,-717.3,254.7,-1077.7,256,-1081
l0 -0c4,-6.7,10,-10,18,-10 H400000
v40H1014.6
s-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185
c-2,6,-10,9,-24,9
c-8,0,-12,-0.7,-12,-2z M1001 80
h400000v40h-400000z'/>&lt;/svg>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.7634050000000001em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mspace"> &lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.12em;vertical-align:0em;">&lt;/span>&lt;span class="minner">…&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
which is also &lt;a href="https://en.wikipedia.org/wiki/Birthday_problem#Average_number_of_people_to_get_at_least_one_shared_birthday">lurking on the Wikipedia article on the birthday problem&lt;/a>. From this you get that only after &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>&amp;gt;&lt;/mo>&lt;mn>21&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">N &amp;gt; 21&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.72243em;vertical-align:-0.0391em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">&amp;gt;&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span> or &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>22&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">22&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">2&lt;/span>&lt;/span>&lt;/span>&lt;/span> or so is it reasonable to map each seed to a unique permutation, as &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>Q&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>21&lt;/mn>&lt;mo stretchy="false">!&lt;/mo>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>≈&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>33&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">Q(21!) \approx 2^{33}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">Q&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">≈&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> but &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>Q&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>20&lt;/mn>&lt;mo stretchy="false">!&lt;/mo>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>≈&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>31&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">Q(20!)\approx 2^{31}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">Q&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mclose">!&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">≈&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>Between 13 and 20, in order to look statistically right, we need to be producing repeats&amp;ndash;even though there are more permutations than seeds!!&lt;/p>
&lt;p>This test is the hardest I&amp;rsquo;ve found to pass: an older function I had was a real slow thing with some Feistel rounds that looked otherwise good, and this test failed it at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>19&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">N = 19&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">9&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Unfailing it is how I got to the multiply-rotate. At the time I was looking at gaps, but &lt;a href="https://www.pcg-random.org/posts/birthday-test.html">M.E. O&amp;rsquo;Neill points out you can also count repeats&lt;/a> and tells you how many samples to take and how to compute p-values so let&amp;rsquo;s just use that. I&amp;rsquo;m going to dump a table on you now. Each row looks at consecutive seeds starting at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>N&lt;/th>
&lt;th>samples&lt;/th>
&lt;th>dupes&lt;/th>
&lt;th>expected&lt;/th>
&lt;th>unique_dupes&lt;/th>
&lt;th>p&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>16&lt;/td>
&lt;td>10&lt;/td>
&lt;td>10.32&lt;/td>
&lt;td>4&lt;/td>
&lt;td>0.54&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>31&lt;/td>
&lt;td>14&lt;/td>
&lt;td>13.42&lt;/td>
&lt;td>8&lt;/td>
&lt;td>0.63&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>70&lt;/td>
&lt;td>19&lt;/td>
&lt;td>16.80&lt;/td>
&lt;td>16&lt;/td>
&lt;td>0.75&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>6&lt;/td>
&lt;td>170&lt;/td>
&lt;td>21&lt;/td>
&lt;td>18.49&lt;/td>
&lt;td>18&lt;/td>
&lt;td>0.76&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>7&lt;/td>
&lt;td>449&lt;/td>
&lt;td>18&lt;/td>
&lt;td>19.38&lt;/td>
&lt;td>18&lt;/td>
&lt;td>0.44&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>1270&lt;/td>
&lt;td>16&lt;/td>
&lt;td>19.78&lt;/td>
&lt;td>16&lt;/td>
&lt;td>0.24&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>9&lt;/td>
&lt;td>3810&lt;/td>
&lt;td>13&lt;/td>
&lt;td>19.93&lt;/td>
&lt;td>13&lt;/td>
&lt;td>0.07&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>10&lt;/td>
&lt;td>12048&lt;/td>
&lt;td>14&lt;/td>
&lt;td>19.98&lt;/td>
&lt;td>14&lt;/td>
&lt;td>0.11&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>11&lt;/td>
&lt;td>39959&lt;/td>
&lt;td>19&lt;/td>
&lt;td>19.99&lt;/td>
&lt;td>19&lt;/td>
&lt;td>0.47&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>12&lt;/td>
&lt;td>138420&lt;/td>
&lt;td>19&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>19&lt;/td>
&lt;td>0.47&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>13&lt;/td>
&lt;td>499080&lt;/td>
&lt;td>20&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>20&lt;/td>
&lt;td>0.56&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>14&lt;/td>
&lt;td>1867387&lt;/td>
&lt;td>16&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>16&lt;/td>
&lt;td>0.22&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>15&lt;/td>
&lt;td>7232357&lt;/td>
&lt;td>19&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>19&lt;/td>
&lt;td>0.47&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>16&lt;/td>
&lt;td>28929425&lt;/td>
&lt;td>19&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>19&lt;/td>
&lt;td>0.47&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>17&lt;/td>
&lt;td>119279073&lt;/td>
&lt;td>12&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>12&lt;/td>
&lt;td>0.04&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>18&lt;/td>
&lt;td>506058246&lt;/td>
&lt;td>20&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>20&lt;/td>
&lt;td>0.56&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>19&lt;/td>
&lt;td>2205856754&lt;/td>
&lt;td>26&lt;/td>
&lt;td>20.00&lt;/td>
&lt;td>26&lt;/td>
&lt;td>0.92&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>20&lt;/td>
&lt;td>4294967295&lt;/td>
&lt;td>5&lt;/td>
&lt;td>3.79&lt;/td>
&lt;td>5&lt;/td>
&lt;td>0.82&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>21&lt;/td>
&lt;td>4294967295&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0.18&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0.99&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22&lt;/td>
&lt;td>4294967295&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0.01&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0.99&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Yeh: looks random. The p-value breaks down a bit towards the end there because we are taking so few (!!) samples, but whatever. The &lt;code>unique_dupes&lt;/code> column is just a sanity check that we aren&amp;rsquo;t getting the same repeated permutation.&lt;/p>
&lt;p>Counting repeats from all &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>32&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{32}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> permutations turns out to be a bit non-trivial. &lt;a href="https://graemephi.github.io/posts/unique-random-numbers/count.cpp">Code is here if you want&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;ve been less thorough with testing really large &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>. I dunno. Avalanche isn&amp;rsquo;t really a great test here, necessary but not sufficient type beat.&lt;/p>
&lt;h2 id="end">End&lt;/h2>
&lt;p>I&amp;rsquo;ve been sitting on this draft unpublished for a while now. I picked up some entropy intuition sometime around writing my low discrepancy noise post, I was trying to figure out why the permutations you can get out of xorshift and LCGs are so bad.&lt;/p>
&lt;p>It&amp;rsquo;s invertible, but I have no idea what the inverse function will look like.&lt;/p>
&lt;p>I was aiming for the performance to be around the same as Andrew Kensler&amp;rsquo;s permute, the correlated multi-jitter one. By the way, if you understood what I was talking about with the Feistel bits thing you can just eyeball it to get an idea of the permutations you&amp;rsquo;ll get out of it. My permute is about 2x slower, or 1.4x if you let the optimizer precompute the seed hash for you. Given the statistical quality I&amp;rsquo;m after I think this is alright, I can just throw this thing at people and not worry about them seeding it wrong or whatever. I think you can do better speed-wise but I don&amp;rsquo;t know how much better.&lt;/p>
&lt;p>And the small &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> thing. Why the focus on that? Couldn&amp;rsquo;t you just shuffle an array every time for those? Like, sure, maybe, but really my issue is: if you can&amp;rsquo;t nail the small &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> case where it&amp;rsquo;s easy to verify the statistics are good, why would you think large &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> is any better? Okay that&amp;rsquo;s it. ThaNKS for reading&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>This sidesteps the fact that over the permutation as a whole you get the full bit-width worth of bits of entropy. It&amp;rsquo;s only the path through state space taken by neighbouring values that are bit-deficient. For 8 bits, you&amp;rsquo;d have a 4 bits worth of round functions that each map 4 bits to 4 bits. 16 round functions times 16 possible outputs each is 8 bits total to describe the full permutation. Each round function is totally independent of what the other round functions are doing, and it&amp;rsquo;s only the number of bits required to encode the path a given value takes that is half that required to encode the entire permutation. Really weird.
&lt;br>
&lt;br>
This alone isn&amp;rsquo;t enough to explain the high round counts you see in practice. Interestingly, if you ask instead about the average number of bits that will be flipped then the required round counts come out close to what you see empirically&amp;hellip; but is that just a coincidence??&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>Rejection sampling is faster, and even fast on the GPU as that Michell paper points out. I think it&amp;rsquo;s better statistics-wise too; it just can&amp;rsquo;t be dropped in and used anywhere.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3" role="doc-endnote">
&lt;p>indistinguishable from an unbiased sample until we exhaust the seed space. Do you care about this distinction? Did I need to add it?&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>WTF Are Modular Forms</title><link>https://graemephi.github.io/posts/modular-forms/</link><pubDate>Mon, 25 Mar 2024 00:34:54 +0000</pubDate><guid>https://graemephi.github.io/posts/modular-forms/</guid><description>&lt;blockquote class="aside">&lt;p>This post is part of the &lt;a href="https://handmade.network/jam/learning-2024">HMN Learning Jam&lt;/a>. One weekend to fill your head with stuff and one weekend to dump it back out. I&amp;rsquo;ve spent a few more days here and there (that convinced me I could learn the rest of what I needed to know in a weekend) but otherwise that&amp;rsquo;s about how much time I&amp;rsquo;ve got in knowing about modular forms. This is a bit out of step from the rest of the submissions which are more programming focused but look: someone had to.&lt;/p>
&lt;p>As always happens I probably learned the most while I was writing this post and not when I was reading stuff.&lt;/p>
&lt;p>I&amp;rsquo;m gonna take a very non-standard approach that I&amp;rsquo;ve seen fragments of here and there in the Literature, so experts on this stuff know it all, but the path it takes is so irrelevant to what working with modular forms is &lt;em>actually like&lt;/em> that a mathematician would never be so cruel as to present it this way.&lt;/p>
&lt;p>However, I&amp;rsquo;m not a mathematician and am completely new to all this. From what I can tell, this path I&amp;rsquo;ll take here is closer to what was going on when modular forms first appeared in mathematics 200 years ago. It&amp;rsquo;s pretty far removed from modern day modular forms. But this means we get to see what modular forms &lt;em>are&lt;/em> in a certain naive sense that hopefully doesn&amp;rsquo;t require a degree in mathematics to understand.&lt;/p>
&lt;p>To get the most out of this, you want to know a a little linear algebra and be comfortable with complex numbers. You should also probably read the first half with a graphing calculator open. On the other hand, I&amp;rsquo;ve deliberately avoided some convenient mathematics notations/definitions that make maths inaccessible to many people, which makes life harder for me and probably people that know that stuff.&lt;/p>
&lt;p>The two books I found most useful for this are:&lt;/p>
&lt;ul>
&lt;li>Development of Mathematics in the 19th Century, Felix Klein,1928&lt;/li>
&lt;li>Complex Analysis, Elias M. Stein and Rami Shakarchi, 2003&lt;/li>
&lt;/ul>
&lt;p>The first is by the Klein bottle guy. He was pretty much there when modular forms became modular forms and is where I got this general approach from. He&amp;rsquo;s more brisk about it. Cool book, could not have understood any of this without it.&lt;/p>
&lt;p>Complex Analysis is a textbook for undergraduates. Goes slow in the right places. Really well written if you know how to read maths textbooks.&lt;/p>
&lt;p>Another book I&amp;rsquo;d like to recommend that you&amp;rsquo;ll probably have trouble discovering on your own is&lt;/p>
&lt;ul>
&lt;li>Elliptic Curves, Henry McKean &amp;amp; Victor Moll, 1999&lt;/li>
&lt;/ul>
&lt;p>This is in my preferred style for maths textbooks, which I have heard called &amp;ldquo;breezy&amp;rdquo; before. Some people figure out they&amp;rsquo;ve got a tiny audience and write conversationally directly to them, it&amp;rsquo;s great. No sweating on proofs, just big picture ideas from people fully in the &lt;a href="https://terrytao.wordpress.com/career-advice/theres-more-to-mathematics-than-rigour-and-proofs/">post-rigorous&lt;/a> phase of life, and they trust you have dryer references to fall back on if you need them. But it is squarely aimed at real deal mathematicians so beyond me for the most part.&lt;/p>
&lt;p>A cool resource is &lt;a href="https://www.youtube.com/watch?v=f8pjt9ivjjc&amp;amp;list=PL8yHsr3EFj51HisRtNyzHX-Xyg6I3Wl2F&amp;amp;pp=iAQB">Richard E Borcherds lecture series&lt;/a> on youtube if you want to see what it&amp;rsquo;s really about to a mathematician. He&amp;rsquo;s got a Fields medal in this stuff.&lt;/p>
&lt;p>Another cool video is this &lt;a href="https://www.youtube.com/watch?v=z7A_bSl8kIw">MathKiwi video&lt;/a> which in some sense starts where I leave off and has many good visualisations.&lt;/p>
&lt;p>To round this list out &lt;a href="https://mathoverflow.net/questions/24604/why-are-modular-forms-interesting">here&amp;rsquo;s a bunch of mathematicians saying what they find interesting about modular forms&lt;/a>. Check out how many different angles on them there are.&lt;/p>
&lt;/blockquote>
&lt;p>I can&amp;rsquo;t motivate why you&amp;rsquo;d want to know about modular forms. If you want to know this stuff, you want to know, and if you don&amp;rsquo;t, you don&amp;rsquo;t. Instead, I want to start at a basic number theory question and make a bee line for modular forms. Modular forms come from a few related ideas hanging together, so we&amp;rsquo;ll have to gather up those ideas first.&lt;/p>
&lt;p>Here&amp;rsquo;s our number theory question: given an integer &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>, how many solutions does
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>a&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>+&lt;/mo>&lt;msup>&lt;mi>b&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">n^2 = a^2 + b^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.9474379999999999em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>have? I start here because it makes the link to questions like these and Fermat&amp;rsquo;s Last Theorem clear, but I am going to say well clear of any weird factorisations and algebra that usually immediately follow this question, don&amp;rsquo;t worry.&lt;/p>
&lt;p>What a lot of number theorists like to do when encountering something new is to compute large tables of numbers, and here since you never have to look at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>a&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">a&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">b&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;/span> above &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span> you don&amp;rsquo;t have that many &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>a&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>b&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(a,b)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> pairs to check. Well, for a few &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>, anyway. You can get pretty far along crunching numbers. Then you stare at the numbers and hope for insight. &lt;a href="https://www.quantamagazine.org/elliptic-curve-murmurations-found-with-ai-take-flight-20240305/">This works surprisingly well&lt;/a>.&lt;/p>
&lt;p>But instead the way to modular forms is to draw the sucker. This turns it into the following question: on how many places does the circle
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>+&lt;/mo>&lt;msup>&lt;mi>y&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">n^2 = x^2 + y^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.9474379999999999em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.0585479999999998em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>lie on integer coordinates? Now instead of crunching numbers, you just draw a circle on a grid. Solutions are where the circle lies on a point where the grid lines intersect. That set of points of intersection over the entire &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">xy&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span> plane is called a lattice.&lt;/p>
&lt;p>The year is 1799 and we would like to solve this problem with a straightedge and compass. We dutifully draw the grid. We try it for one &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>, fill in the row in our table, try a second &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>, fill in the next row&amp;hellip; Number theorists love their tables. Eventually, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span> is too big for our compass. What do? We shrink the lattice. Instead of ever larger circles we ask about ever denser lattices. And instead of leaving this as just a convenience out in the world, we can make it real in the mathematics, and change the problem to looking for rational numbers that satisfy&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mfrac>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mfrac>&lt;mo>+&lt;/mo>&lt;mfrac>&lt;msup>&lt;mi>y&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mfrac>&lt;mo>=&lt;/mo>&lt;mn>1.&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">\frac{x^2}{n^2} + \frac{y^2}{n^2} = 1.&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.177108em;vertical-align:-0.686em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.491108em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.740108em;">&lt;span style="top:-2.9890000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.177108em;vertical-align:-0.686em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.491108em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.740108em;">&lt;span style="top:-2.9890000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>Play with this a bit in a graphing calculator and you&amp;rsquo;ll realise that once you&amp;rsquo;ve found a solution you can extend a line through it from the origin, and lattice points on that line are solutions for some other &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>. So something about the lattice is encoding information about the sums of two squares.&lt;/p>
&lt;p>With lattice we encountered the first term that comes up all the time when people talk about modular forms, and maybe see how it&amp;rsquo;s relevant to number theory, so we are making progress.&lt;/p>
&lt;p>So, that&amp;rsquo;s a circle. For this exposition I would like to encourage you to act like a mathematician and be willing to entertain idle questions. What about where an ellipse goes through a lattice point? We apply a similar trick as before, and get an axis-aligned ellipse by stretching &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">x&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">y&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span>, with &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>=&lt;/mo>&lt;mi>a&lt;/mi>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;msup>&lt;mi>y&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n^2 = ax^2 + by^2.&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.9474379999999999em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.0585479999999998em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>Instead of drawing the ellipse, we stretch the lattice in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">x&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">y&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and we are back to a circle. So we think of an ellipse as a transformed circle, and apply the inverse transform to the lattice instead, and this let&amp;rsquo;s us answer more questions with our compass. Very good.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/modular-forms/stretch.png" style="max-width: 120%; max-height: 120%;"/>
&lt;/div>
&lt;blockquote class="aside">I quickly made these in &lt;a href="https://www.tldraw.com/">tldraw&lt;/a> cause you really gotta be thinking in pictures to follow this and it&amp;rsquo;s just good manners to show the pictures. However I haven&amp;rsquo;t actually checked these are valid transforms. Sometimes even in a graphing calculator you&amp;rsquo;ll see what looks like an intersection and plug in the numbers and find it&amp;rsquo;s a near miss. So. No rigour here. Also I think the axis &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>a&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">a&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">b&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;/span> here should be sqrtd. Too late now.&lt;/blockquote>
&lt;p>What about an ellipse that isn&amp;rsquo;t aligned to the axis? Rotating the circle does nothing and rotating the lattice is the same as rotating the circle the other way. Instead, we have to shear the ellipse. What Gauss observed is that if we have &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>=&lt;/mo>&lt;mi>a&lt;/mi>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;mi>x&lt;/mi>&lt;mi>y&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>c&lt;/mi>&lt;msup>&lt;mi>y&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">n^2 = ax^2 + bxy + cy^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8641079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.9474379999999999em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.0585479999999998em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> then we can interpret this as tilting our lattice so it now tiles the plane with parallelograms, rather than squares. The way to get the lattice sheared just right is to set the sides of each parallelogram to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msqrt>&lt;mi>a&lt;/mi>&lt;/msqrt>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sqrt a&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.04em;vertical-align:-0.23972em;">&lt;/span>&lt;span class="mord sqrt">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8002800000000001em;">&lt;span class="svg-align" style="top:-3em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord mathdefault" style="padding-left:0.833em;">a&lt;/span>&lt;/span>&lt;span style="top:-2.76028em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="hide-tail" style="min-width:0.853em;height:1.08em;">&lt;svg width='400em' height='1.08em' viewBox='0 0 400000 1080' preserveAspectRatio='xMinYMin slice'>&lt;path d='M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z'/>&lt;/svg>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.23972em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msqrt>&lt;mi>c&lt;/mi>&lt;/msqrt>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sqrt c&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.04em;vertical-align:-0.23972em;">&lt;/span>&lt;span class="mord sqrt">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8002800000000001em;">&lt;span class="svg-align" style="top:-3em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord mathdefault" style="padding-left:0.833em;">c&lt;/span>&lt;/span>&lt;span style="top:-2.76028em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="hide-tail" style="min-width:0.853em;height:1.08em;">&lt;svg width='400em' height='1.08em' viewBox='0 0 400000 1080' preserveAspectRatio='xMinYMin slice'>&lt;path d='M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z'/>&lt;/svg>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.23972em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and the slope of the line to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mfrac>&lt;mi>b&lt;/mi>&lt;msqrt>&lt;mrow>&lt;mn>4&lt;/mn>&lt;mi>a&lt;/mi>&lt;mi>c&lt;/mi>&lt;/mrow>&lt;/msqrt>&lt;/mfrac>&lt;/mrow>&lt;annotation encoding="application/x-tex">\frac{b}{\sqrt{4ac}}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.4181079999999997em;vertical-align:-0.5379999999999999em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8801079999999999em;">&lt;span style="top:-2.5510085em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord sqrt mtight">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.912845em;">&lt;span class="svg-align" style="top:-3em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord mtight" style="padding-left:0.833em;">&lt;span class="mord mtight">4&lt;/span>&lt;span class="mord mathdefault mtight">a&lt;/span>&lt;span class="mord mathdefault mtight">c&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.872845em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="hide-tail mtight" style="min-width:0.853em;height:1.08em;">&lt;svg width='400em' height='1.08em' viewBox='0 0 400000 1080' preserveAspectRatio='xMinYMin slice'>&lt;path d='M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z'/>&lt;/svg>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.12715500000000002em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">b&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.5379999999999999em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. (I got this from Klein and verified it in a graphing calculator. Beats me. Visually it makes sense but I&amp;rsquo;m out of time to spell it out).&lt;/p>
&lt;p>And once again: we can read out integer solutions to this equation by drawing a circle of radius &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span> around the origin, and counting the lattice points it goes through.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/modular-forms/ellipse.png" style="max-width: 120%; max-height: 120%;"/>
&lt;/div>
&lt;p>The right hand side of that equation is a &lt;em>binary quadratic form&lt;/em> and number theorists talk about it as a function in its own right, &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;mi>a&lt;/mi>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;mi>x&lt;/mi>&lt;mi>y&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>c&lt;/mi>&lt;msup>&lt;mi>y&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex"> q(x,y) = ax^2 + bxy + cy^2.&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.9474379999999999em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.0585479999999998em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8641079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>And now we&amp;rsquo;ve landed on a form, so modular forms are appearing over the horizon. What&amp;rsquo;s a form? What makes thing form? It&amp;rsquo;s something polynomial-like that obeys
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>t&lt;/mi>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>t&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msup>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">P(tx) = t^kP(x)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.149108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8991079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
for some &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and, in 2 variables, and you can figure more variables out yourself, &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>t&lt;/mi>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>t&lt;/mi>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>t&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msup>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">P(tx, ty) = t^kP(x, y).&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.149108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8991079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>You see that? Linear scaling in the input becomes power scaling in the output. You can see this immediately for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">P(x)=x^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, where &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>t&lt;/mi>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>t&lt;/mi>&lt;mi>x&lt;/mi>&lt;msup>&lt;mo stretchy="false">)&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>t&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>t&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">P(tx) = (tx)^2 = t^2x^2 = t^2P(x)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">&lt;span class="mclose">)&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>. So &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>x&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">x^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> is a form. When we have two variables as in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">q(x,y)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>, then the exponents in each term need to sum to the same number, which gives you the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>. In &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">q(x,y)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> it&amp;rsquo;s &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>. That makes it quadratic. And it&amp;rsquo;s binary because we stop at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">y&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span> and don&amp;rsquo;t have a &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span>.&lt;/p>
&lt;p>You can imagine why this property might be interesting to people. Distance travelled at constant acceleration or whatever.&lt;/p>
&lt;p>Anyway, whatever modular forms are, we now know they are a kind of function, and we expect to see that relationship between its inputs and outputs pop up.&lt;/p>
&lt;p>To get to modularity we now want to look more at the lattice and other things we can do to it. The key observation is this: if we have a transformation that sends every lattice point to another lattice point on the original lattice, then with it we can generate more solutions to questions like those above, analogous to extending out the line through the origin we saw with the circle. Since we&amp;rsquo;re interested in integer solutions we&amp;rsquo;ll want transformations we can express with integers. The easiest to find are rotating the lattice by right angles, which give transformations like
&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>y&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mo>−&lt;/mo>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\begin{bmatrix}0 &amp;amp; -1 \\1 &amp;amp; 0\end{bmatrix}\begin{bmatrix}x \\ y\end{bmatrix}=\begin{bmatrix}-y \\ x\end{bmatrix}.&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="arraycolsep" style="width:0.5em;">&lt;/span>&lt;span class="arraycolsep" style="width:0.5em;">&lt;/span>&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">−&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">−&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
Another one is to shift everything over one lattice point; the translation &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\begin{bmatrix}1 &amp;amp; 1 \\ 0 &amp;amp; 1\end{bmatrix}.&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="arraycolsep" style="width:0.5em;">&lt;/span>&lt;span class="arraycolsep" style="width:0.5em;">&lt;/span>&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>If you apply that one to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">q&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;/span>&lt;/span>&lt;/span> you&amp;rsquo;ll get &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>q&lt;/mi>&lt;mo mathvariant="normal" lspace="0em" rspace="0em">′&lt;/mo>&lt;/msup>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;mi>a&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>y&lt;/mi>&lt;msup>&lt;mo stretchy="false">)&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>c&lt;/mi>&lt;msup>&lt;mi>y&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">q&amp;#x27;(x,y) = a(x+y)^2+b(x+y)y+cy^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.001892em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.751892em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">′&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">&lt;span class="mclose">)&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.008548em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and if you plot that sucker &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">= n^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.36687em;vertical-align:0em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> you&amp;rsquo;ll find it gives a new ellipse that intersects &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">q&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;/span>&lt;/span>&lt;/span> where &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">q&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;/span>&lt;/span>&lt;/span> goes through a lattice point, i.e. is an integer solution to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>q&lt;/mi>&lt;mo mathvariant="normal" lspace="0em" rspace="0em">′&lt;/mo>&lt;/msup>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">q&amp;#x27;(x,y) = n^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.001892em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.751892em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">′&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. And if you expand out the terms you&amp;rsquo;ll find yourself back on a binary quadratic form.&lt;/p>
&lt;p>You can apply these over and over with each other to get more elaborate transformations that stretch out the parallelograms. The thing they all have in common is they have determinant 1. We recall from 3blue1brown that the determinant is how a matrix will scale the area of the parallelogram, so what these matrices are doing is &lt;em>preserving&lt;/em> the area of each cell in the lattice. So we have two ways of seeing why these matrices and their compositions will let lattice points line up 1-to-1 before and after: firstly because we&amp;rsquo;ve built them out of simple individual steps that always do this (and it turns out any such integer &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>det&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">\det 1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mop">det&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span> matrix can be built this way), and secondly because we know the area never changes, so the density of the lattice is always the same.&lt;/p>
&lt;p>Mathematicians call these&amp;ndash;2x2 integer matrices with determinant 1&amp;ndash;unimodular. I have no idea why. Since you can multiply them together with abandon and always get back a unimodular matrix, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>det&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mi>A&lt;/mi>&lt;mi>B&lt;/mi>&lt;mo>=&lt;/mo>&lt;mi>det&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mi>A&lt;/mi>&lt;mo>⋅&lt;/mo>&lt;mi>det&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mi>B&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\det AB = \det A \cdot \det B&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mop">det&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">A&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.05017em;">B&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mop">det&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">A&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">⋅&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mop">det&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.05017em;">B&lt;/span>&lt;/span>&lt;/span>&lt;/span>, that makes these things collectively a group, the modular group.&lt;/p>
&lt;p>We&amp;rsquo;ve got modular. We&amp;rsquo;ve got forms. We can see what they each have to do with lattices and what lattices have to do with number theory. But still, they&amp;rsquo;re somewhat disconnected.&lt;/p>
&lt;p>To tie these things together we need complex numbers. We&amp;rsquo;ve been thinking about lattices on the plane this whole time, and now we think of each lattice point as &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;mo>=&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>i&lt;/mi>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z = x + iy&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.85396em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">i&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span>. And as usual with complex numbers we can now think of the action of multiplying by &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span> as rotating and scaling the complex plane, so &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span> sends the lattice point &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">(1,0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>) to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">(x, y&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span>).&lt;/p>
&lt;p>To line things up the same way mathematicians do, here&amp;rsquo;s another way to define a lattice. Take two complex numbers, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> (this is the notation everyone uses), and so long as they do not lie on the same line, we can generate a lattice with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>m&lt;/mi>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;mo>+&lt;/mo>&lt;mi>n&lt;/mi>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">m\omega_1 + n\omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.73333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, for integer &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>m&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">m, n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>, stepping out in steps of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> as &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>m&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">m&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;/span>&lt;/span>&lt;/span> increases and in steps of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> as &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span> increases.&lt;/p>
&lt;p>If you think about it a bit you can see a purely real &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and a purely imaginary &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> will give you a square lattice. Non-orthogonal &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>ω&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;/span>&lt;/span>&lt;/span> will give parallelograms.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/modular-forms/lattice.png" style="max-width: 120%; max-height: 120%;"/>
&lt;/div>
&lt;p>Now, in this context we&amp;rsquo;re interested in lattices with integer parts in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>ω&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and there are a lot of symmetries in the plane we want to get rid of, so applying a trick similar to shrinking the lattice to turn questions about integers into questions about rational numbers, we divide out the scale and imaginary part of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and substitute &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;mo separator="true">,&lt;/mo>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1, \omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mi>τ&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mi mathvariant="normal">/&lt;/mi>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">1, \tau = 1, \omega_2 / \omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8388800000000001em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.1132em;">τ&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. This is more like the lattices we&amp;rsquo;ve been talking about already to shear ellipses, where stepping out in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>m&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">m&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;/span>&lt;/span>&lt;/span> will step along the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">x&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span> axis. However, unlike before, we always step out in steps of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>, rather than &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msqrt>&lt;mi>a&lt;/mi>&lt;/msqrt>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sqrt a&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.04em;vertical-align:-0.23972em;">&lt;/span>&lt;span class="mord sqrt">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8002800000000001em;">&lt;span class="svg-align" style="top:-3em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord mathdefault" style="padding-left:0.833em;">a&lt;/span>&lt;/span>&lt;span style="top:-2.76028em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="hide-tail" style="min-width:0.853em;height:1.08em;">&lt;svg width='400em' height='1.08em' viewBox='0 0 400000 1080' preserveAspectRatio='xMinYMin slice'>&lt;path d='M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z'/>&lt;/svg>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.23972em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Compare that to the rational number variant of the sums of squares question we started on.&lt;/p>
&lt;p>Mathematicians loathe and fear the bottom half of the complex plane and so swap &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> freely to ensure &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>τ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\tau&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.1132em;">τ&lt;/span>&lt;/span>&lt;/span>&lt;/span> lies on the positive half-plane.&lt;/p>
&lt;p>So, now we want to think about a normalized lattice that has points in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">x&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span> axis in steps of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span> and into the upper half of the complex plane in steps of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>τ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\tau&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.1132em;">τ&lt;/span>&lt;/span>&lt;/span>&lt;/span>. We get &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>τ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\tau&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.1132em;">τ&lt;/span>&lt;/span>&lt;/span>&lt;/span> from our choice of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>.&lt;/p>
&lt;p>This next step I can&amp;rsquo;t justify all that well and found I didn&amp;rsquo;t understand properly when I came to write it. We have a point on the lattice, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span>. We would like to multiply it by a unimodular matrix and get back another point on the lattice. But we can&amp;rsquo;t multiply a 2x2 matrix by a single &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span>. We need a second term. It turns out we need homogeneous coordinates&amp;ndash;you know, like from video games&amp;ndash;and tack a one next to it. Then we get&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>a&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>b&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>c&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>d&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>z&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;mi>z&lt;/mi>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>a&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>c&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mo>+&lt;/mo>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>b&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>d&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.15999999999999992em" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mi>a&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mi>a&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>d&lt;/mi>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;/mrow>&lt;annotation encoding="application/x-tex">\begin{bmatrix}a &amp;amp; b \\ c &amp;amp; d\end{bmatrix}\begin{bmatrix}z \\ 1\end{bmatrix}=
z\begin{bmatrix}a \\ c\end{bmatrix} + \begin{bmatrix}b \\ d\end{bmatrix}=\begin{bmatrix}az + b \\ az +d \end{bmatrix}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">c&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="arraycolsep" style="width:0.5em;">&lt;/span>&lt;span class="arraycolsep" style="width:0.5em;">&lt;/span>&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">c&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.40003em;vertical-align:-0.95003em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">&lt;span class="delimsizing size3">[&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-c">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.45em;">&lt;span style="top:-3.61em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.4099999999999997em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.9500000000000004em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">&lt;span class="delimsizing size3">]&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
which gives us two new &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>ω&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\omega_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03588em;">ω&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and we scale that sucker back down to normalise back to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>τ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\tau&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.1132em;">τ&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and so we the action of these matrices is understood the same as projection matrices. Written directly, that&amp;rsquo;s &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mfrac>&lt;mrow>&lt;mi>a&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;mrow>&lt;mi>c&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>d&lt;/mi>&lt;/mrow>&lt;/mfrac>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\frac{az + b}{cz + d}.&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.14077em;vertical-align:-0.7693300000000001em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.37144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.7693300000000001em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> This seems truthy to me, in the sense of collapsing together structure as in our line-through-the-origin case, but I can&amp;rsquo;t explain it fully on it if you press me on it. Remember that here &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>a&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>b&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>c&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>d&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">a, b, c, d&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;/span> are all integers.&lt;/p>
&lt;p>&lt;em>Now&lt;/em> we come to modular forms proper. Before, forms were (implicitly) purely real, but now we see we&amp;rsquo;re knee deep in complex numbers. We had &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>t&lt;/mi>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>t&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msup>&lt;mi>P&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">P(tx) = t^kP(x).&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.149108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8991079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">P&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>These are forms with respect to linear scaling. What we want are forms with respect to the modular group. This means we&amp;rsquo;re going to have some function on the complex plane, and it&amp;rsquo;s inputs are going to be related to its outputs by some power scaling law, after those inputs have been acted on by the modular group.&lt;/p>
&lt;p>Let&amp;rsquo;s step back a bit. In this exposition, what we&amp;rsquo;re really interested in is the lattice. We want modular forms to tell us about the lattice; interest in modular forms in their own right comes later. The tack I&amp;rsquo;ve seen taken is to say what we&amp;rsquo;re really interested in are &lt;em>modular functions&lt;/em>, rather than &lt;em>forms&lt;/em>, &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>f&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mfrac>&lt;mrow>&lt;mi>a&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;mrow>&lt;mi>c&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>d&lt;/mi>&lt;/mrow>&lt;/mfrac>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;mi>f&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>z&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo separator="true">,&lt;/mo>&lt;mtext>  &lt;/mtext>&lt;mi>det&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mo stretchy="false">[&lt;/mo>&lt;mi>a&lt;/mi>&lt;mtext> &lt;/mtext>&lt;mi>b&lt;/mi>&lt;mo separator="true">;&lt;/mo>&lt;mi>c&lt;/mi>&lt;mtext> &lt;/mtext>&lt;mi>d&lt;/mi>&lt;mo stretchy="false">]&lt;/mo>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">f(\frac{az + b}{cz + d})= f(z),\ \ \det([a\ b; c\ d])= 1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.14077em;vertical-align:-0.7693300000000001em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.37144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.7693300000000001em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mspace"> &lt;/span>&lt;span class="mspace"> &lt;/span>&lt;span class="mop">det&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mspace"> &lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;span class="mpunct">;&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mspace"> &lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> which act more or less exactly like our lattice in that it doesn&amp;rsquo;t change after being acted on by unimodular matrices, but now for all &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span> in the complex plane. Upper half-plane. Whatever. The hope is that such functions can encode number-theoretic information we care about, the same way &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">q(x,y)=n^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> reveals an ellipse.&lt;/p>
&lt;p>They then relax this requirement, saying such functions are too hard to find or maybe uninteresting, and require instead &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>f&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mfrac>&lt;mrow>&lt;mi>a&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;mrow>&lt;mi>c&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>d&lt;/mi>&lt;/mrow>&lt;/mfrac>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>c&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>d&lt;/mi>&lt;msup>&lt;mo stretchy="false">)&lt;/mo>&lt;mi>k&lt;/mi>&lt;/msup>&lt;mi>f&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>z&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">f(\frac{az+b}{cz+d})=(cz+d)^k f(z).&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.14077em;vertical-align:-0.7693300000000001em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.37144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.7693300000000001em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.149108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;span class="mclose">&lt;span class="mclose">)&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8991079999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
That&amp;rsquo;s our modular form. So&amp;hellip; why this? Where&amp;rsquo;d &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>a&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>b&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">az+b&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">b&lt;/span>&lt;/span>&lt;/span>&lt;/span> go? And how to interpret &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>? The way I interpret this is projective geometry-wise, so if you know homogenous coordinates you&amp;rsquo;ll be used to being able to pile up a bunch of transforms then divide out &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>w&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">w&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.02691em;">w&lt;/span>&lt;/span>&lt;/span>&lt;/span> at the end to land back on a plane. That &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>c&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>d&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(cz + d)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> is saying under a unimodular transformation, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span> needs to be sent &lt;em>somewhere&lt;/em> on the lattice? (I think!) of points scaling by &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>c&lt;/mi>&lt;mi>z&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>d&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">cz+d&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord mathdefault">c&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;/span>&lt;/span>&lt;/span> is able to identify, projectively, as the same point. But, this is pretty handwavey and I think I can only get this idea across to people who already know projection matrices and homogeneous coordinates and all that, and I don&amp;rsquo;t really have an good intuition for how its working here as opposed to in Euclidian 3D.&lt;/p>
&lt;p>Naturally, mathematicians are even more restrictive about what kind of function &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>f&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">f&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;/span>&lt;/span>&lt;/span> can be, which I won&amp;rsquo;t bother with.&lt;/p>
&lt;p>Here&amp;rsquo;s the punchline. We don&amp;rsquo;t have any such &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>f&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">f&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;/span>&lt;/span>&lt;/span> yet and this post is not really going to go into them. My goal has been to get at why if you find such an &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>f&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">f&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;/span>&lt;/span>&lt;/span>, number theory tends to drop out of it, without definition overload. I&amp;rsquo;ll go over two kinds quickly.&lt;/p>
&lt;p>The first is the historical first, the lemniscate elliptic functions. They arose when Euler was investigating the physics of&amp;hellip; &lt;a href="https://www.jstor.org/stable/41133708">elastic ribbons&lt;/a>. They were particularly interested in this &lt;a href="https://www.desmos.com/calculator/d4nfqoor32">figure 8 curve&lt;/a>, called a &lt;a href="https://en.wikipedia.org/wiki/Lemniscate">lemniscate&lt;/a> (Latin for ribbon, naturally), which Gauss figured out a &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>sin&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sin&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.66786em;vertical-align:0em;">&lt;/span>&lt;span class="mop">sin&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>cos&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\cos&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mop">cos&lt;/span>&lt;/span>&lt;/span>&lt;/span> analogue to, and these turn out to be &amp;lsquo;doubly periodic&amp;rsquo; when extended to complex numbers in the same way as these lattices.&lt;/p>
&lt;p>The second is a much more direct construction that I think was come up with to nail this case, the &lt;a href="https://en.wikipedia.org/wiki/Weierstrass_elliptic_function">Weierstrass elliptic functions&lt;/a>. Conceptually, you just drop a divide by zero on every lattice point. This makes the lattice &amp;lsquo;puncture&amp;rsquo; the complex plane which I find weirdly satisfying. You&amp;rsquo;d like to be able to just do&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>f&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>z&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;munder>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>m&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;/munder>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>z&lt;/mi>&lt;mo>−&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>m&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>n&lt;/mi>&lt;mi>τ&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;msup>&lt;mo stretchy="false">)&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;/mfrac>&lt;/mrow>&lt;annotation encoding="application/x-tex">
f(z) = \sum_{m,n} \frac{1}{(z - (m + n\tau))^2}
&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.707553em;vertical-align:-1.386113em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.0500050000000003em;">&lt;span style="top:-1.8999949999999999em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">m&lt;/span>&lt;span class="mpunct mtight">,&lt;/span>&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.050005em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.386113em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.32144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.1132em;">τ&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mclose">&lt;span class="mclose">)&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.740108em;">&lt;span style="top:-2.9890000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.936em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>
and make it so that as &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;/span>&lt;/span>&lt;/span> nears a lattice point the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;mo>−&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>m&lt;/mi>&lt;mo>+&lt;/mo>&lt;mi>n&lt;/mi>&lt;mi>τ&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">z-(m + n\tau)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.04398em;">z&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.1132em;">τ&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> term for that point goes to zero, but this turns out not to converge. Fixing it is a nice trick and then showing it&amp;rsquo;s a modular form is also simple if you are comfortable whippin' sums. For an explanation of this I really recommend the Stein and Sharkachi book, it&amp;rsquo;s good and will occasionally repeat information that needs repeating, a rare skill among mathematicians.&lt;/p>
&lt;p>Putting these to work in real theorems and such is also another matter entirely. So this post hasn&amp;rsquo;t captured the character and feel of how modular forms are used at all. If you do have any skill in that, and you try to define modular forms, you start in weird places, and start talking about q-series (not the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>q&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">q&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">q&lt;/span>&lt;/span>&lt;/span>&lt;/span> above), and rational points on elliptic curves, and Fourier expansions of partition functions, and&lt;/p></description></item><item><title>Some low discrepancy noise functions</title><link>https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/</link><pubDate>Wed, 10 Aug 2022 00:00:00 +0000</pubDate><guid>https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/</guid><description>&lt;p>This post is about an attempt to generate blue noise at a point: no state, no offline training, just arithmetic on an index. Honestly I don&amp;rsquo;t have a good reason for wanting this but it&amp;rsquo;s probably been at the back of my mind for like 2 years&amp;ndash;it just seemed like it ought to be possible. It turns out to be doable in 1D and with a little lookup table we can push it to 2D.&lt;/p>
&lt;p>The kind of blue noise we&amp;rsquo;re going to get is the classic &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Blue_noise#Blue_noise">3dB per octave&lt;/a>&amp;quot;&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> blue noise, which might turn out to be not all that useful, who knows. But it will also be low discrepancy, and the tricks in this post to keep it that way all the way to 2D are pretty cool I think. The plan is to take a sequence we already know has good discrepancy, whiten its frequency spectrum, and then turn that into blue noise.&lt;/p>
&lt;h2 id="motivating-example-dither">Motivating example: dither&lt;/h2>
&lt;p>So: low discrepancy blue noise. Breaking that down:&lt;/p>
&lt;ul>
&lt;li>Low discrepancy: values the noise takes on are evenly spread out. Read on a bit if this is unclear.&lt;/li>
&lt;li>Blue: no or little low frequencies in the noise&amp;rsquo;s frequency spectrum.&lt;/li>
&lt;li>Noise: unpredictable.&lt;/li>
&lt;/ul>
&lt;p>A problem I had writing this post is that it&amp;rsquo;s not obvious why you&amp;rsquo;d even want noise with these properties, and the technical answer&amp;ndash;reduce variance&amp;ndash;is a bit mystifying. This was a good excuse for me to look at dithering a bit more closely. &lt;a href="https://bartwronski.com/2016/10/30/dithering-in-games-mini-series/">Bart Wronski&lt;/a> has a nice series of posts on dither if it&amp;rsquo;s new to you, but hopefully you can follow this either way.&lt;/p>
&lt;p>Here, we want to represent a 64x64 gray square, with a gray value of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0.5&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0.5&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">5&lt;/span>&lt;/span>&lt;/span>&lt;/span>, using only black and white pixels. If we know ahead of time that it is solid gray, we could use a checkerboard pattern as a way to approximate &amp;ldquo;gray&amp;rdquo;. But suppose we don&amp;rsquo;t, and we decide to choose whether to make a pixel black or white by drawing a random value in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[0,1)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> and comparing it to the source pixel; if the random value is less, black, if it&amp;rsquo;s greater, white. Since we&amp;rsquo;re using random values, we can do this multiple times and get a different dither pattern every time. Using some functions I&amp;rsquo;ll get into below, we get this.&lt;/p>
&lt;div class="img-flex" style="justify-content: space-around; width: 648px; left: unset; margin: unset;">
&lt;div style="flex: 0 1 0%;">PRNG&lt;/div>
&lt;div style="flex: 0 1 0%; white-space: nowrap;">Low discrepancy&lt;/div>
&lt;div style="flex: 0 1 0%;">Blue&lt;/div>
&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/dither-grid.png" width="648" height="870" loading="lazy" />
&lt;/div>
&lt;p>The PRNG is quite bad. By bad I mean like, to my eye, the third square seems to have too many white pixels. So, yes, it will give 1:1 white and black pixels on average, but that&amp;rsquo;s averaged over all the dithering you could ever do. That&amp;rsquo;s the problem: we don&amp;rsquo;t care about the quality of unrealized dither.&lt;/p>
&lt;p>The middle column, maybe you can convince yourself it looks different, I dunno. It&amp;rsquo;s subtle. If you take it upon yourself to count number of white pixels you&amp;rsquo;ll find that very close to half of them are white, in all four squares. That&amp;rsquo;s low discrepancy. The noise values we got covered &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[0,1)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> much more evenly than random noise. This gives us frequencies of black and white pixels that are always close to the frequencies of the dither we know would be ideal for solid gray.&lt;/p>
&lt;p>However, that&amp;rsquo;s not true over the entire square; there are clumps of black pixels and clumps of white pixels. That&amp;rsquo;s what blue noise addresses. Which you can see just looking at it, right. This gives &lt;em>spatial&lt;/em> frequencies of black and white pixels that better match the ideal dither.&lt;/p>
&lt;p>That&amp;rsquo;s two different kinds of frequency&amp;ndash;one in the histogram sense and another in the spectrum sense. I don&amp;rsquo;t quite know how independent they are. On the one hand, once you have some noise values, you can just rearrange them to get different frequency spectrums with the same discrepancy. On the other, if you want the noise to be low discrepancy &amp;lsquo;everywhere&amp;rsquo;, without any rearrangement, that rules out long lasting low frequency content. What you&amp;rsquo;ll find is the more samples you are willing to wait around for until a given run of draws becomes low discrepancy, the more freedom in shaping the spectrum you&amp;rsquo;ll have. Noise that is low discrepancy everywhere and at all scales has to be blue, though, I think.&lt;/p>
&lt;p>Back to the 64x64 squares. Generating a thousand each, the standard deviation of the number of white pixels in a random PRNG square is 32 (!). It&amp;rsquo;s less than 1 for both the low discrepancy versions. Counting just the top left quarter, at 32x32, it becomes 16, 12, and 2, for the PRNG, white and blue noise respectively. That&amp;rsquo;s what reducing variance means.&lt;/p>
&lt;h2 id="background">Background&lt;/h2>
&lt;p>The API that I want is a counter hash, so a function that takes in an index, hashes it, and spits out a noise value at that index. No state carries forward from index to index, every value is computed independently. Like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-c" data-lang="c">&lt;span style="color:#75715e">// integer index to 0.32 fixed point
&lt;/span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">uint32_t&lt;/span> &lt;span style="color:#a6e22e">noise&lt;/span>(&lt;span style="color:#66d9ef">uint32_t&lt;/span> index);
&lt;span style="color:#75715e">// usage
&lt;/span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">uint32_t&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">512&lt;/span>; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;span style="color:#75715e">// convert the noise to a float in [0, 1)
&lt;/span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> x &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">float&lt;/span>)noise(i) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>p&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32f&lt;/span>;
&lt;span style="color:#75715e">// ... do something with x
&lt;/span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>We&amp;rsquo;re going to follow &lt;a href="https://jcgt.org/published/0009/04/01/">Practical Hash-based Owen Scrambling&lt;/a> by taking a sequence we already know is low discrepancy and shuffling it. We&amp;rsquo;ll need the &lt;em>nested uniform scramble&lt;/em> from that paper, too. As for the sequence, the golden ratio sequence is &lt;a href="https://marc-b-reynolds.github.io/distribution/2020/01/24/Rank1Pre.html">very easy to compute&lt;/a> in the form I want, so we&amp;rsquo;ll use that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#f92672">import&lt;/span> np
i32 &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>int32
u32 &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>uint32
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">golden_ratio_sequence&lt;/span>(i: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
&lt;span style="color:#66d9ef">return&lt;/span> u32(i) &lt;span style="color:#f92672">*&lt;/span> u32(&lt;span style="color:#ae81ff">2654435769&lt;/span>) &lt;span style="color:#75715e"># 0.618... in 0.32 fixed point&lt;/span>
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">reverse_bits32&lt;/span>(x: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
x &lt;span style="color:#f92672">=&lt;/span> u32(x)
x &lt;span style="color:#f92672">=&lt;/span> ((x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x55555555&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span> ((x &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x55555555&lt;/span>)) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>))
x &lt;span style="color:#f92672">=&lt;/span> ((x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">2&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x33333333&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span> ((x &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x33333333&lt;/span>)) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">2&lt;/span>))
x &lt;span style="color:#f92672">=&lt;/span> ((x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">4&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x0F0F0F0F&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span> ((x &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x0F0F0F0F&lt;/span>)) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">4&lt;/span>))
x &lt;span style="color:#f92672">=&lt;/span> ((x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">8&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x00FF00FF&lt;/span>)) &lt;span style="color:#f92672">|&lt;/span> ((x &lt;span style="color:#f92672">&amp;amp;&lt;/span> u32(&lt;span style="color:#ae81ff">0x00FF00FF&lt;/span>)) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">8&lt;/span>))
x &lt;span style="color:#f92672">=&lt;/span> ( x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">16&lt;/span>) ) &lt;span style="color:#f92672">|&lt;/span> ( x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">16&lt;/span>))
&lt;span style="color:#66d9ef">return&lt;/span> x
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">nested_uniform_scramble&lt;/span>(x: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
x &lt;span style="color:#f92672">=&lt;/span> reverse_bits32(x)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">*&lt;/span> u32(&lt;span style="color:#ae81ff">0x6c50b47c&lt;/span>)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">*&lt;/span> u32(&lt;span style="color:#ae81ff">0xb82f1e52&lt;/span>)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">*&lt;/span> u32(&lt;span style="color:#ae81ff">0xc7afe638&lt;/span>)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">*&lt;/span> u32(&lt;span style="color:#ae81ff">0x8d22f6e6&lt;/span>)
&lt;span style="color:#66d9ef">return&lt;/span> reverse_bits32(x)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">okay_blue_noise&lt;/span>(i: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
&lt;span style="color:#66d9ef">return&lt;/span> golden_ratio_sequence(nested_uniform_scramble(i))
&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can get a reordering of any sequence you like by hashing the index with &lt;em>nested uniforms scramble&lt;/em>.&lt;/p>
&lt;p>It&amp;rsquo;s a bit intimidating, but the important thing about it is each hex constant there is an even number. In binary, multiplying by an even number sends bits to the left, so each xor permutes bits in a low-to-high direction. The authors wanted to permute bits high-to-low, and so reverse the bit order before and after. So, it&amp;rsquo;s a hash, where the action of each bit on the hash is constrained to only permute bits below itself. And the &lt;a href="http://burtleburtle.net/bob/rand/talksmall.html">xors are invertible&lt;/a>, meaning each input maps to a unique output.&lt;/p>
&lt;p>A consequence of this that can give you some intuition for what it does is that the high bit of the input is always preserved. So, as a counter hash, the first &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mi>n&lt;/mi>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.664392em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.664392em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> inputs map to &lt;code>u32&lt;/code>s below &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mi>n&lt;/mi>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.664392em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.664392em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. And since you know that holds for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mrow>&lt;mi>n&lt;/mi>&lt;mo>+&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{n+1}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;span class="mbin mtight">+&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> as well, you also get that &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mi>n&lt;/mi>&lt;/msup>&lt;mo separator="true">,&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mrow>&lt;mi>n&lt;/mi>&lt;mo>+&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msup>&lt;mo stretchy="false">]&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[2^n, 2^{n+1}]&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.664392em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;span class="mbin mtight">+&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;/span>&lt;/span>&lt;/span> maps to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mi>n&lt;/mi>&lt;/msup>&lt;mo separator="true">,&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mrow>&lt;mi>n&lt;/mi>&lt;mo>+&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msup>&lt;mo stretchy="false">]&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[2^n, 2^{n+1}]&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.664392em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;span class="mbin mtight">+&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;/span>&lt;/span>&lt;/span>. In these intervals, the shuffled sequence produces the same values as the underlying sequence, just in a different order. This doesn&amp;rsquo;t fully explain its behaviour as a hash but it&amp;rsquo;s enough for us to get going with.&lt;/p>
&lt;p>By the way, you can also think of the (fixed point) golden ratio sequence as a shuffle of the integers in &lt;code>u32&lt;/code>. They all appear exactly once.&lt;/p>
&lt;p>This has all been background, but before we get to the good stuff, let&amp;rsquo;s look at what we have:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#f92672">import&lt;/span> matplotlib.pyplot &lt;span style="color:#66d9ef">as&lt;/span> plt
plt&lt;span style="color:#f92672">.&lt;/span>rcParams[&lt;span style="color:#e6db74">&amp;#39;figure.figsize&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#ae81ff">9&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">spectrum&lt;/span>(seq):
S &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>fft&lt;span style="color:#f92672">.&lt;/span>rfft(&lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> seq &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>)
&lt;span style="color:#66d9ef">return&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>abs(S) &lt;span style="color:#f92672">/&lt;/span> len(seq)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">plots&lt;/span>(name: str, seq_u32):
seq &lt;span style="color:#f92672">=&lt;/span> seq_u32 &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>)
figure, (histogram_axis, spectrum_axis) &lt;span style="color:#f92672">=&lt;/span> plt&lt;span style="color:#f92672">.&lt;/span>subplots(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>)
histogram_axis&lt;span style="color:#f92672">.&lt;/span>hist(seq, &lt;span style="color:#ae81ff">128&lt;/span>)
histogram_axis&lt;span style="color:#f92672">.&lt;/span>set_xlabel(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>name&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> histogram: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(seq)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> points&amp;#34;&lt;/span>)
dft &lt;span style="color:#f92672">=&lt;/span> spectrum(seq)
spectrum_axis&lt;span style="color:#f92672">.&lt;/span>plot(dft)
spectrum_axis&lt;span style="color:#f92672">.&lt;/span>set_xlabel(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>name&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> spectrum: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>len(seq)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> points&amp;#34;&lt;/span>)
n &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">3333&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Bad histogram&amp;hellip;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#f92672">from&lt;/span> numpy.random &lt;span style="color:#f92672">import&lt;/span> default_rng
rng &lt;span style="color:#f92672">=&lt;/span> default_rng()
plots(&lt;span style="color:#e6db74">&amp;#34;PRNG&amp;#34;&lt;/span>, rng&lt;span style="color:#f92672">.&lt;/span>integers(&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0x1_0000_0000&lt;/span>, size&lt;span style="color:#f92672">=&lt;/span>n))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/PRNG.png" width="744" height="217" loading="lazy" />
&lt;/div>
&lt;p>Bad spectrum&amp;hellip;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">i &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(n)
plots(&lt;span style="color:#e6db74">&amp;#34;golden ratio sequence&amp;#34;&lt;/span>, golden_ratio_sequence(i))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/golden-ratio-sequence.png" width="744" height="217" loading="lazy" />
&lt;/div>
&lt;p>Noise with a flat histogram?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">plots(&lt;span style="color:#e6db74">&amp;#34;nested uniform scramble-shuffled grs&amp;#34;&lt;/span>, okay_blue_noise(i))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/nus-shuffled-grs.png" width="744" height="217" loading="lazy" />
&lt;/div>
&lt;p>This already has some low frequency attenuation. Well, if you drop the last bit reversal step and the golden ratio sequence and instead use it directly, not as a shuffle, it comes out even better. But my plan here is to clear the way by getting to white noise, and to use that as a base for more noise colours, so forget I said anything.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;h2 id="xorshift">Xorshift&lt;/h2>
&lt;p>We need more unpredictability. To that end, let&amp;rsquo;s look at using xorshift as a counter hash. Specifically, lets look at the low bits.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">xorshift&lt;/span>(x: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
x &lt;span style="color:#f92672">=&lt;/span> u32(x)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">13&lt;/span>)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">17&lt;/span>)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">5&lt;/span>)
&lt;span style="color:#66d9ef">return&lt;/span> x
i &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(&lt;span style="color:#ae81ff">16&lt;/span>)
print(i)
print(xorshift(i) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0b1111&lt;/span>)
&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
&lt;/code>&lt;/pre>&lt;p>Uh, hold on,&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">xorshift(i &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">64&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0b1111&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">[ 5 4 7 6 1 0 3 2 13 12 15 14 9 8 11 10]
&lt;/code>&lt;/pre>&lt;p>The lower 4 bits are a (not very random) permutation of the original 4-bit sequence!! The Xorshift* variants preserve this property and is at least superficially random-looking so let&amp;rsquo;s use that instead from now on (&lt;a href="https://gist.github.com/imneme/9b769cefccac1f2bd728596da3a856dd">constant from Melissa O&amp;rsquo;Neill&lt;/a>):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">xorshift_star&lt;/span>(x: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
x &lt;span style="color:#f92672">=&lt;/span> u32(x)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">13&lt;/span>)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">17&lt;/span>)
x &lt;span style="color:#f92672">^=&lt;/span> x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">5&lt;/span>)
x &lt;span style="color:#f92672">*=&lt;/span> u32(&lt;span style="color:#ae81ff">0x9e02ad0d&lt;/span>)
&lt;span style="color:#66d9ef">return&lt;/span> x
xorshift_star(i &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">64&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0b1111&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">[ 1 4 11 14 13 0 7 10 9 12 3 6 5 8 15 2]
&lt;/code>&lt;/pre>&lt;p>This works for powers of two up to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>16&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{16}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">1&lt;/span>&lt;span class="mord mtight">6&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. This isn&amp;rsquo;t a good way of generating permutations&amp;ndash;it can&amp;rsquo;t make all that many&amp;ndash;but we don&amp;rsquo;t need it to be good. &lt;a href="https://en.wikipedia.org/wiki/Linear_congruential_generator">LCGs&lt;/a> work this way, too.&lt;/p>
&lt;p>Now, because of this we can bestow xorshift with the high-bit-preserving property&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> of &lt;em>nested uniform scramble&lt;/em> by fixing the high bit and masking off any new bits placed above it. And we know that when we do this, every input still maps to a unique output, so long as we only scramble the lower 2 bytes. Using all 16 bits is way too random, so we can cap the number of bits we allow to be scrambled this way, and use the rest as a seed that selects a permutation. Looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">all_ones_below_high_bit&lt;/span>(x: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
x &lt;span style="color:#f92672">=&lt;/span> u32(x)
x &lt;span style="color:#f92672">|=&lt;/span> (x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">16&lt;/span>))
x &lt;span style="color:#f92672">|=&lt;/span> (x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">8&lt;/span>))
x &lt;span style="color:#f92672">|=&lt;/span> (x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">4&lt;/span>))
x &lt;span style="color:#f92672">|=&lt;/span> (x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">2&lt;/span>))
x &lt;span style="color:#f92672">|=&lt;/span> (x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>))
&lt;span style="color:#75715e"># this last shift: the high bit is not included&lt;/span>
&lt;span style="color:#66d9ef">return&lt;/span> x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">unfolded_masked_xorshift&lt;/span>(x: u32, cap_mask: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
mask &lt;span style="color:#f92672">=&lt;/span> all_ones_below_high_bit(x &lt;span style="color:#f92672">&amp;amp;&lt;/span> cap_mask)
upper &lt;span style="color:#f92672">=&lt;/span> x &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#f92672">~&lt;/span>mask
lower &lt;span style="color:#f92672">=&lt;/span> xorshift_star(x) &lt;span style="color:#f92672">&amp;amp;&lt;/span> mask
result &lt;span style="color:#f92672">=&lt;/span> upper &lt;span style="color:#f92672">+&lt;/span> lower
&lt;span style="color:#66d9ef">return&lt;/span> result
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Every bit we include in this scramble increases discrepancy&amp;ndash;bits can permute bits to their left, here. We can claw back a bit by treating the high bit of &lt;code>cap_mask&lt;/code> as a kind of sign bit and flipping all this logic to work backwards when its set.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">masked_xorshift&lt;/span>(x: u32, bits: u32 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
&lt;span style="color:#75715e"># all ones if (x &amp;amp; 0x100) == 0x100, all zeros otherwise&lt;/span>
sign_mask &lt;span style="color:#f92672">=&lt;/span> i32(x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">31&lt;/span> &lt;span style="color:#f92672">-&lt;/span> bits)) &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> i32(&lt;span style="color:#ae81ff">31&lt;/span>)
sign_mask &lt;span style="color:#f92672">=&lt;/span> u32(sign_mask)
cap_mask &lt;span style="color:#f92672">=&lt;/span> u32((&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> bits) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;span style="color:#66d9ef">return&lt;/span> unfolded_masked_xorshift(x &lt;span style="color:#f92672">^&lt;/span> sign_mask, cap_mask) &lt;span style="color:#f92672">^&lt;/span> sign_mask
&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is all pretty abstract. You can see what it does by plotting the difference with its input:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">i &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(&lt;span style="color:#ae81ff">1024&lt;/span>)
plt&lt;span style="color:#f92672">.&lt;/span>plot(i &lt;span style="color:#f92672">-&lt;/span> masked_xorshift(i))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/xor-diff.png" width="900" height="200" loading="lazy" />
&lt;/div>
&lt;p>As a shuffle, this shows you where elements are being moved to, relative to their original position. With &lt;code>bits = 8&lt;/code>, that distance is never more than 128. So, while this scramble doesn&amp;rsquo;t have all the nice properties of &lt;em>nested uniform scramble&lt;/em>, we do get a version of the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mi>n&lt;/mi>&lt;/msup>&lt;mo separator="true">,&lt;/mo>&lt;msup>&lt;mn>2&lt;/mn>&lt;mrow>&lt;mi>n&lt;/mi>&lt;mo>+&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msup>&lt;mo stretchy="false">]&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[2^n, 2^{n+1}]&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.664392em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;span class="mbin mtight">+&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;/span>&lt;/span>&lt;/span> bijection property that repeats inside every chunk of size 256. So discrepancy-wise it&amp;rsquo;s kind of bad, but we can bound the bad with &lt;code>bits&lt;/code>.&lt;/p>
&lt;p>Now we&amp;rsquo;ve got low discrepancy white noise:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">white_shuffle&lt;/span>(i: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
s &lt;span style="color:#f92672">=&lt;/span> i
s &lt;span style="color:#f92672">=&lt;/span> masked_xorshift(s)
s &lt;span style="color:#f92672">=&lt;/span> nested_uniform_scramble(s)
&lt;span style="color:#66d9ef">return&lt;/span> s
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">white&lt;/span>(i: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
&lt;span style="color:#66d9ef">return&lt;/span> golden_ratio_sequence(white_shuffle(i))
i &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(&lt;span style="color:#ae81ff">3333&lt;/span>)
plots(&lt;span style="color:#e6db74">&amp;#34;white&amp;#34;&lt;/span>, white(i))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/white.png" width="744" height="217" loading="lazy" />
&lt;/div>
&lt;p>Look closely at the lowest frequencies of the spectrum. There is a slight gradient away from zero. This is what &lt;code>bits&lt;/code> controls: the slope of that gradient. I found that at 11 bits it&amp;rsquo;s qualitatively pure white noise with no obvious correlations (&amp;hellip; aside from an usually flat histogram). But it&amp;rsquo;s just too much random. For example, the variance in the dither example really suffers. And variance is what we care about.&lt;/p>
&lt;p>Next: gotta eyeball it in 2D. You gotta.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">plt&lt;span style="color:#f92672">.&lt;/span>rcParams[&lt;span style="color:#e6db74">&amp;#39;image.cmap&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;gray&amp;#39;&lt;/span>
plt&lt;span style="color:#f92672">.&lt;/span>rcParams[&lt;span style="color:#e6db74">&amp;#39;image.interpolation&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;none&amp;#39;&lt;/span>
px &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#f92672">/&lt;/span>plt&lt;span style="color:#f92672">.&lt;/span>rcParams[&lt;span style="color:#e6db74">&amp;#39;figure.dpi&amp;#39;&lt;/span>]
n &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">512&lt;/span>
i &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(n&lt;span style="color:#f92672">*&lt;/span>n)
f, ax &lt;span style="color:#f92672">=&lt;/span> plt&lt;span style="color:#f92672">.&lt;/span>subplots(figsize&lt;span style="color:#f92672">=&lt;/span>(n&lt;span style="color:#f92672">*&lt;/span>px, n&lt;span style="color:#f92672">*&lt;/span>px)); ax&lt;span style="color:#f92672">.&lt;/span>axis(&lt;span style="color:#e6db74">&amp;#39;off&amp;#39;&lt;/span>)
ax&lt;span style="color:#f92672">.&lt;/span>imshow(white(i)&lt;span style="color:#f92672">.&lt;/span>reshape((n,n)) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/white2d-bad.png" width="512" height="512" loading="lazy" />
&lt;/div>
&lt;p>Ah. The edges. It&amp;rsquo;s bad. Okay, that&amp;rsquo;s our choice of &lt;code>bits&lt;/code>. It&amp;rsquo;s too small. But we don&amp;rsquo;t have to increase &lt;code>bits&lt;/code>, we can get away with doing another nested uniform scramble. This is kind of painful on x64, where bit reversal spews instructions everywhere, but, eh. There&amp;rsquo;s a trade-off between performance and discrepancy here and I completely lack any insight on it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">white_shuffle&lt;/span>(i: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
s &lt;span style="color:#f92672">=&lt;/span> i
s &lt;span style="color:#f92672">=&lt;/span> nested_uniform_scramble(s)
s &lt;span style="color:#f92672">=&lt;/span> masked_xorshift(s)
s &lt;span style="color:#f92672">=&lt;/span> nested_uniform_scramble(s)
&lt;span style="color:#66d9ef">return&lt;/span> s
f, ax &lt;span style="color:#f92672">=&lt;/span> plt&lt;span style="color:#f92672">.&lt;/span>subplots(figsize&lt;span style="color:#f92672">=&lt;/span>(n&lt;span style="color:#f92672">*&lt;/span>px, n&lt;span style="color:#f92672">*&lt;/span>px)); ax&lt;span style="color:#f92672">.&lt;/span>axis(&lt;span style="color:#e6db74">&amp;#39;off&amp;#39;&lt;/span>)
ax&lt;span style="color:#f92672">.&lt;/span>imshow(white(i)&lt;span style="color:#f92672">.&lt;/span>reshape((n,n)) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/white2d.png" width="512" height="512" loading="lazy" />
&lt;/div>
&lt;p>👍&lt;/p>
&lt;h2 id="blue-noise">Blue noise&lt;/h2>
&lt;p>Now, to transmute this into blue noise. Filtering the noise directly destroys low discrepancy, so no perfectly tuned cut off frequency and ripple here. But I think what I&amp;rsquo;m about to suggest might seem to come out of nowhere for some people. With that in mind, here&amp;rsquo;s what I was thinking about when I realised this: the &lt;em>unshuffled&lt;/em> golden ratio sequence has the property that&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">golden_ratio_sequence(i &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">-&lt;/span> golden_ratio_sequence(i)
&lt;/code>&lt;/pre>&lt;/div>&lt;p>In other words, you can go backwards from zero, and it&amp;rsquo;s the same, just inverted around &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0.5&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0.5&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">5&lt;/span>&lt;/span>&lt;/span>&lt;/span>. So what if we interleaved the forwards sequence with the backwards sequence? The two sequences would meet in the middle. Seems natural, right?&lt;/p>
&lt;p>This turns out to do something very specific in the frequency domain: it fills in the new frequency bins&amp;ndash;twice as many samples, twice as many bins&amp;ndash;with a copy of the frequency spectrum then high pass filters the whole thing. This is entirely due to the right hand side of the above equation, so when we use that form, it also works on the &lt;em>shuffled&lt;/em> sequence.&lt;/p>
&lt;p>But why!? If you&amp;rsquo;re comfortable with DSP this can be hand-waved as being the composition of three steps: zero-stuffing to &lt;a href="https://bartwronski.com/2021/02/15/bilinear-down-upsampling-pixel-grids-and-that-half-pixel-offset/">repeat the frequency spectrum&lt;/a>&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>, applying the simple low-pass filter &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">]&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[1, 1]&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and then reversing the frequency spectrum by inverting every second sample. This turns the low-pass filter we just applied into a high-pass filter.&lt;/p>
&lt;p>Even so, that last bit might need more explaining if you haven&amp;rsquo;t seen it before. This is directed at DSP people (don&amp;rsquo;t worry about it). First, in this context, &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;mo>−&lt;/mo>&lt;msub>&lt;mi>a&lt;/mi>&lt;mi>i&lt;/mi>&lt;/msub>&lt;mo>=&lt;/mo>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo>⋅&lt;/mo>&lt;msub>&lt;mi>a&lt;/mi>&lt;mi>i&lt;/mi>&lt;/msub>&lt;mo separator="true">,&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">1 - a_i=-1\cdot a_i,&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.72777em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.72777em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">⋅&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> because in 0.32 fixed point, we have &lt;code>0x1_0000_0000 - a[i] = 0 - a[i]&lt;/code>. From this we get that inverting every second sample is equivalent to point-wise multiplication by the signal&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">[&lt;/mo>&lt;mi>n&lt;/mi>&lt;mo stretchy="false">]&lt;/mo>&lt;mo>=&lt;/mo>&lt;mi mathvariant="normal">.&lt;/mi>&lt;mi mathvariant="normal">.&lt;/mi>&lt;mi mathvariant="normal">.&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1...&lt;/mn>&lt;mo>=&lt;/mo>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mi>π&lt;/mi>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex"> x[n] = ...-1,1,-1,1... = e^{i\pi n} &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8388800000000001em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8746639999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8746639999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>which is your frequency shift in the time domain&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup> operator set to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>π&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\pi&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;/span>&lt;/span>&lt;/span>, a half turn. This places all the negative frequencies into the positive part of spectrum, and because of the conjugate symmetry of real signal DFTs, they are the mirror image of the positive frequencies.&lt;/p>
&lt;p>DSP talk&amp;rsquo;s over. Intuitively, something like this &lt;em>should&lt;/em> happen&amp;ndash;low frequencies are slow movement, high frequencies are rapid movement. Forcing the sequence to repeatedly jump from one half to the other pushes all the frequencies higher.&lt;/p>
&lt;p>For &amp;lsquo;noise&amp;rsquo; this has the weird property that every second value is just the previous value flipped around &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0.5&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0.5&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">5&lt;/span>&lt;/span>&lt;/span>&lt;/span>. A gray code round can get rid of that&amp;ndash;rightward invertible action of the bits like before by the way, this is a scramble. Shift here is by 6 because reserving the high bits preserves the frequency spectrum. That&amp;rsquo;s where all the energy in the signal is.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">kronecker_sequence&lt;/span>(i: u32, a: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
&lt;span style="color:#66d9ef">return&lt;/span> u32(i) &lt;span style="color:#f92672">*&lt;/span> u32(a)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">blue&lt;/span>(i: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
s &lt;span style="color:#f92672">=&lt;/span> white_shuffle(i &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
b &lt;span style="color:#f92672">=&lt;/span> kronecker_sequence(s, &lt;span style="color:#ae81ff">2654435770&lt;/span>) &lt;span style="color:#75715e"># 0.31 fixed point golden ratio&lt;/span>
odd &lt;span style="color:#f92672">=&lt;/span> u32(i &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
b &lt;span style="color:#f92672">^=&lt;/span> (odd &lt;span style="color:#f92672">^&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>)) &lt;span style="color:#f92672">-&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>); &lt;span style="color:#75715e"># negate on odd indices&lt;/span>
b &lt;span style="color:#f92672">+=&lt;/span> odd
b &lt;span style="color:#f92672">^=&lt;/span> b &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">6&lt;/span>) &lt;span style="color:#75715e"># gray code round&lt;/span>
&lt;span style="color:#66d9ef">return&lt;/span> b
i &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(&lt;span style="color:#ae81ff">3333&lt;/span>)
plots(&lt;span style="color:#e6db74">&amp;#34;blue&amp;#34;&lt;/span>, blue(i))
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/blue.png" width="744" height="217" loading="lazy" />
&lt;/div>
&lt;h3 id="observations-without-narrative-flow">Observations without narrative flow&lt;/h3>
&lt;blockquote class="aside">We can get red noise by not doing the inversion step. This duplicates every element, which is bad, but we can scramble every odd element. (Technically, scrambling is a thing you do to the values of a low discrepancy sequence, and shuffling a thing a thing you do to their indexes. In fixed point they are kind of duals to one another, at least when you have a proper scramble, like &lt;em>nested uniform scramble&lt;/em> is.) This raises the spectre of a scrambled odd value colliding with an unscrambled even value. None of this is ideal from a discrepancy point of view, but whatever.&lt;/blockquote>
&lt;blockquote class="aside">There is another purely real rotation we can do: a quarter turn of the frequency spectrum, using &lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mi>π&lt;/mi>&lt;mi>n&lt;/mi>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;/msup>&lt;mo>=&lt;/mo>&lt;mo>…&lt;/mo>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo>…&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">e^{i\pi n/2} = \ldots -1,0,1,0,-1,0,1 \ldots&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.938em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.938em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;span class="mord mtight">/&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="minner">…&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.8388800000000001em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="minner">…&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>You can fill in the zeros with another sequence generated with another shuffled Kronecker sequence. &lt;a href="http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/">Martin Robert&amp;rsquo;s R2&lt;/a> works well. A quarter turn of red noise and a quarter turn of blue noise gets you green? and.. violet? I guess? noise? With the middle frequencies band-passed or cut. Unfortunately, it doesn&amp;rsquo;t extend to 2D using the technique I&amp;rsquo;m about to describe so I&amp;rsquo;m dropping it here. That it doesn&amp;rsquo;t work is interesting in its own right, I suppose.&lt;/blockquote>
&lt;blockquote class="aside">&lt;code>blue&lt;/code> doesn&amp;rsquo;t have equidistribution. That is, some inputs map to the same output. &lt;code>0&lt;/code> appears twice and who knows about &lt;code>0x8000_0000&lt;/code>. But it&amp;rsquo;s also lost an entire bit in the interleaving of two sequences. These are both length &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>32&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{32}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and they interleave to a sequence of length &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>33&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{33}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. We only have 32 bits to index it with. Rounding the constants to have a zero bit in the last place (and a one bit in the second last place) gives us a pair of sequences of length &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mn>31&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{31}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Now every value before the gray code round is even, but we know exactly what values we&amp;rsquo;re missing, the odd values, and what values we are double counting, the even values. I&amp;rsquo;m not bothered about that low bit, because we&amp;rsquo;re going to round it away anyway on conversion to float, and if you want integers, the high bits are better.&lt;/blockquote>
&lt;h2 id="2d-blue-noise">2D blue noise&lt;/h2>
&lt;p>Dumping it out in 2D doesn&amp;rsquo;t work, but the way it doesn&amp;rsquo;t work is instructive so let&amp;rsquo;s look at it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">spectrum_2d&lt;/span>(img):
dft &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>abs(np&lt;span style="color:#f92672">.&lt;/span>fft&lt;span style="color:#f92672">.&lt;/span>fftshift(np&lt;span style="color:#f92672">.&lt;/span>fft&lt;span style="color:#f92672">.&lt;/span>fft2(img)))
dft &lt;span style="color:#f92672">/=&lt;/span> dft&lt;span style="color:#f92672">.&lt;/span>shape[&lt;span style="color:#ae81ff">0&lt;/span>]
&lt;span style="color:#66d9ef">return&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>clip(dft, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">lookit&lt;/span>(image):
f, ax &lt;span style="color:#f92672">=&lt;/span> plt&lt;span style="color:#f92672">.&lt;/span>subplots(figsize&lt;span style="color:#f92672">=&lt;/span>(&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#f92672">*&lt;/span>image&lt;span style="color:#f92672">.&lt;/span>shape[&lt;span style="color:#ae81ff">0&lt;/span>]&lt;span style="color:#f92672">*&lt;/span>px, image&lt;span style="color:#f92672">.&lt;/span>shape[&lt;span style="color:#ae81ff">1&lt;/span>]&lt;span style="color:#f92672">*&lt;/span>px))
ax&lt;span style="color:#f92672">.&lt;/span>axis(&lt;span style="color:#e6db74">&amp;#39;off&amp;#39;&lt;/span>)
ax&lt;span style="color:#f92672">.&lt;/span>imshow(np&lt;span style="color:#f92672">.&lt;/span>hstack((image, spectrum_2d(image))))
n &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">384&lt;/span>
i &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(n&lt;span style="color:#f92672">*&lt;/span>n)&lt;span style="color:#f92672">.&lt;/span>reshape((n,n))
noise &lt;span style="color:#f92672">=&lt;/span> blue(i) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>)
lookit(noise)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/blue2d-bad.png" width="768" height="384" loading="lazy" />
&lt;/div>
&lt;p>It&amp;rsquo;s blue horizontally, but in no other direction. Okay. At this point, we can generate another noise image, transpose it so it&amp;rsquo;s blue vertically, and drop it on top, like&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">noise[x,y] &lt;span style="color:#f92672">+&lt;/span> more_noise[y,x]
&lt;/code>&lt;/pre>&lt;/div>&lt;p>and yeah it works but the spectrum isn&amp;rsquo;t that good and &lt;a href="https://en.wikipedia.org/wiki/Convolution_of_probability_distributions">the triangle distribution you get out of the sum&lt;/a> isn&amp;rsquo;t all that low-discrepancy either. If you don&amp;rsquo;t care about discrepancy, you could use some cheap PRNG here instead of low discrepancy noise and filter this right in a shader. Since you could design the filter to smooth out some of the anisotropy this method has, I think it could be pretty good.&lt;/p>
&lt;p>But to preserve low discrepancy we can decide that the issue is the path the noise takes over the image, and so let&amp;rsquo;s find a better path. I tried to find a way to do this with just arithmetic, and I got nowhere. But I did find a path that works that&amp;rsquo;s easy to compute offline, and it turns out we can tile the path. So this might be an okay compromise.&lt;/p>
&lt;p>No imaginative thinking here, horizontal path bad circular path good. This bins pixels into concentric rings, then pixels within each ring are sorted by their angle.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">spiral&lt;/span>(n: u32, lo: float, hi: float):
x, y &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>meshgrid(np&lt;span style="color:#f92672">.&lt;/span>linspace(lo, hi, n), np&lt;span style="color:#f92672">.&lt;/span>linspace(lo, hi, n))
&lt;span style="color:#75715e"># two sqrts: one for the distance, two to adjust for spirals closer to&lt;/span>
&lt;span style="color:#75715e"># the origin being tighter (think random sampling in a circle)&lt;/span>
xy &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>round(np&lt;span style="color:#f92672">.&lt;/span>sqrt(np&lt;span style="color:#f92672">.&lt;/span>sqrt(x&lt;span style="color:#f92672">*&lt;/span>x &lt;span style="color:#f92672">+&lt;/span> y&lt;span style="color:#f92672">*&lt;/span>y)) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>sqrt(n&lt;span style="color:#f92672">*&lt;/span>n &lt;span style="color:#f92672">+&lt;/span> n&lt;span style="color:#f92672">*&lt;/span>n))
&lt;span style="color:#75715e"># rescaled to a reasonable range that makes debugging possible without&lt;/span>
&lt;span style="color:#75715e"># a third eye&lt;/span>
angles &lt;span style="color:#f92672">=&lt;/span> (np&lt;span style="color:#f92672">.&lt;/span>arctan2(y, x) &lt;span style="color:#f92672">+&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>pi) &lt;span style="color:#f92672">/&lt;/span> (&lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>pi)
&lt;span style="color:#75715e"># sort by magnitudes then angles (I don&amp;#39;t know why lexsort is&lt;/span>
&lt;span style="color:#75715e"># little-endian), then invert the sort&lt;/span>
spiral &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>lexsort((angles&lt;span style="color:#f92672">.&lt;/span>flatten(), xy&lt;span style="color:#f92672">.&lt;/span>flatten()))&lt;span style="color:#f92672">.&lt;/span>argsort()
&lt;span style="color:#66d9ef">return&lt;/span> spiral&lt;span style="color:#f92672">.&lt;/span>reshape((n,n))
s &lt;span style="color:#f92672">=&lt;/span> spiral(n, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1.0&lt;/span>, &lt;span style="color:#ae81ff">1.0&lt;/span>)
noise &lt;span style="color:#f92672">=&lt;/span> blue(s) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>)
lookit(noise)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/blue2d-stillbad.png" width="768" height="384" loading="lazy" />
&lt;/div>
&lt;p>&amp;hellip; looks weird. Something to do with the origin. Don&amp;rsquo;t wanna think about it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">s &lt;span style="color:#f92672">=&lt;/span> spiral(n, &lt;span style="color:#ae81ff">2.0&lt;/span>, &lt;span style="color:#ae81ff">4.0&lt;/span>)
noise &lt;span style="color:#f92672">=&lt;/span> blue(s) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>)
lookit(noise)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/blue2d.png" width="768" height="384" loading="lazy" />
&lt;/div>
&lt;p>Nice. One reason this path is hard to replicate/approximate using only arithmetic on x and y is that it wraps at the edges in a nice symmetry breaking way that I don&amp;rsquo;t really understand:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">spiral(&lt;span style="color:#ae81ff">8&lt;/span>, &lt;span style="color:#ae81ff">2.0&lt;/span>, &lt;span style="color:#ae81ff">4.0&lt;/span>)
&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">[[ 0 2 1 6 10 20 19 32]
[ 4 3 7 12 11 21 34 33]
[ 5 8 14 13 23 22 35 47]
[ 9 16 15 25 24 37 36 48]
[18 17 27 26 39 38 49 56]
[30 29 28 41 40 51 50 57]
[31 44 43 42 53 52 59 58]
[46 45 55 54 62 61 60 63]]
&lt;/code>&lt;/pre>&lt;p>And here it is with a 64x64 path, tiled using the &lt;a href="https://en.wikipedia.org/wiki/Z-order_(curve)">z-order curve&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">left_shift_2&lt;/span>(x: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
x &lt;span style="color:#f92672">=&lt;/span> (x &lt;span style="color:#f92672">^&lt;/span> (x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0x0000ffff&lt;/span>
x &lt;span style="color:#f92672">=&lt;/span> (x &lt;span style="color:#f92672">^&lt;/span> (x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0x00ff00ff&lt;/span>
x &lt;span style="color:#f92672">=&lt;/span> (x &lt;span style="color:#f92672">^&lt;/span> (x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0x0f0f0f0f&lt;/span>
x &lt;span style="color:#f92672">=&lt;/span> (x &lt;span style="color:#f92672">^&lt;/span> (x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0x33333333&lt;/span>
x &lt;span style="color:#f92672">=&lt;/span> (x &lt;span style="color:#f92672">^&lt;/span> (x &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0x55555555&lt;/span>
&lt;span style="color:#66d9ef">return&lt;/span> u32(x)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">z_order&lt;/span>(x: u32, y: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
&lt;span style="color:#66d9ef">return&lt;/span> left_shift_2(x) &lt;span style="color:#f92672">+&lt;/span> (left_shift_2(y) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>))
tile_bits &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">6&lt;/span>
tile_n &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> tile_bits
tile_mask &lt;span style="color:#f92672">=&lt;/span> tile_n &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
tile_path &lt;span style="color:#f92672">=&lt;/span> spiral(tile_n, &lt;span style="color:#ae81ff">2.0&lt;/span>, &lt;span style="color:#ae81ff">4.0&lt;/span>)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">blue_2d&lt;/span>(x: u32, y: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
x_lo &lt;span style="color:#f92672">=&lt;/span> x &lt;span style="color:#f92672">&amp;amp;&lt;/span> tile_mask
y_lo &lt;span style="color:#f92672">=&lt;/span> y &lt;span style="color:#f92672">&amp;amp;&lt;/span> tile_mask
x_hi &lt;span style="color:#f92672">=&lt;/span> x &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> tile_bits
y_hi &lt;span style="color:#f92672">=&lt;/span> y &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> tile_bits
tile &lt;span style="color:#f92672">=&lt;/span> z_order(x_hi, y_hi)
i &lt;span style="color:#f92672">=&lt;/span> (tile &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> u32(&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#f92672">*&lt;/span>tile_bits)) &lt;span style="color:#f92672">+&lt;/span> tile_path[y_lo, x_lo]
&lt;span style="color:#66d9ef">return&lt;/span> blue(i)
x, y &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>meshgrid(np&lt;span style="color:#f92672">.&lt;/span>arange(n), np&lt;span style="color:#f92672">.&lt;/span>arange(n))
noise &lt;span style="color:#f92672">=&lt;/span> blue_2d(x, y) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>)
lookit(noise)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/blue2d-64.png" width="768" height="384" loading="lazy" />
&lt;/div>
&lt;p>Histogram&amp;rsquo;s still good:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">plt&lt;span style="color:#f92672">.&lt;/span>hist(noise&lt;span style="color:#f92672">.&lt;/span>flatten(), &lt;span style="color:#ae81ff">384&lt;/span>)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/blue2d-64-hist.png" width="900" height="200" loading="lazy" />
&lt;/div>
&lt;p>So that&amp;rsquo;s a 8KB lookup table that&amp;rsquo;s good for as much noise as you can index with an integer, and you can always &lt;a href="https://www.jcgt.org/published/0011/01/04/">get more constants&lt;/a> to replace the golden ratio with if you need more. (I&amp;rsquo;m not super satisfied with this, I wanted 0KB). Also some tiling artifacts where there is a too consistent &lt;em>lack&lt;/em> of correlation between adjacent pixels at tile boundaries, which is weird to think about. If you didn&amp;rsquo;t see it forget I said anything!!&lt;/p>
&lt;p>Part of the reason this works at all is that the quality of the noise that we&amp;rsquo;re threading through the image is not, spectrum-wise, all that good. With better noise, not only do you see the tiling clearly, the spectrum is really no better&amp;ndash;it&amp;rsquo;s determined by the spiral function in a way I don&amp;rsquo;t understand. So I&amp;rsquo;m not sure if this could be extended to 3D with just a path. But even if it does, for higher dimensions I doubt it would work all that well without huge lookup tables, because as the number of dimensions goes up the number of edge voxels increase faster than the number of interior voxels.&lt;/p>
&lt;h2 id="dither-revisited">Dither revisited&lt;/h2>
&lt;p>Since we started with dither, here&amp;rsquo;s some real dither. Usually for dither you want triangular noise rather than uniform. Triangular refers to the shape of the histogram. Alan Wolfe has &lt;a href="https://www.shadertoy.com/view/4t2SDh">a nice way of getting a triangle distribution out of uniform noise&lt;/a> that perfectly preserves discrepancy, so I&amp;rsquo;m going to use that. Hastily converted to python:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">uniform_to_triangle_dist&lt;/span>(x):
&lt;span style="color:#75715e"># From demofox @ https://www.shadertoy.com/view/4t2SDh&lt;/span>
x &lt;span style="color:#f92672">=&lt;/span> (x &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>) &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
orig &lt;span style="color:#f92672">=&lt;/span> x &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">2.0&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>
nz &lt;span style="color:#f92672">=&lt;/span> orig &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
x[&lt;span style="color:#f92672">~&lt;/span>nz] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>
x[nz] &lt;span style="color:#f92672">=&lt;/span> orig[nz] &lt;span style="color:#f92672">/&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>sqrt(np&lt;span style="color:#f92672">.&lt;/span>abs(orig[nz]))
x &lt;span style="color:#f92672">=&lt;/span> x &lt;span style="color:#f92672">-&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>sign(orig) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>
x &lt;span style="color:#f92672">=&lt;/span> (x &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>
&lt;span style="color:#66d9ef">return&lt;/span> x
&lt;/code>&lt;/pre>&lt;/div>&lt;p>An alternative would be to use &lt;a href="http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/">R2&lt;/a> to get another noise value to add to the first, you only need an extra multiply after all the shuffling to get it. Yet another alternative would be use more noise offset by half a tile to obscure the tiling artifacts.&lt;/p>
&lt;p>&lt;a href="https://gist.github.com/graemephi/3a90bc543aa974f7de04fa100c66bdc2#file-ldnoise-py-L278">Code&amp;rsquo;s here&lt;/a>. Quantized to 4 bits with 2 bits of dither. The top row is a gradient we&amp;rsquo;re dithering and what you get quantizing without dithering. Then it&amp;rsquo;s left: uniform noise dither, right: triangle noise dither. And top to bottom: random, this post&amp;rsquo;s white noise, this post&amp;rsquo;s blue noise, and last I&amp;rsquo;ve put known-good &lt;a href="http://momentsingraphics.de/BlueNoise.html">void and cluster blue noise&lt;/a>.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/dither-gradient.png" width="1024" height="500" loading="lazy" />
&lt;/div>
&lt;p>Yup, it dithers 👍 Not as good as a good texture, but that&amp;rsquo;s okay.&lt;/p>
&lt;p>The white noise&amp;hellip; I don&amp;rsquo;t know what I expected.&lt;/p>
&lt;h2 id="bonus">Bonus&lt;/h2>
&lt;p>The low bits of the spiral function have a blue spectrum:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">s &lt;span style="color:#f92672">=&lt;/span> (spiral(&lt;span style="color:#ae81ff">384&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.08&lt;/span>, &lt;span style="color:#ae81ff">0.08&lt;/span>) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">7&lt;/span>) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>
lookit(s)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/spiral-low-bits.png" width="768" height="384" loading="lazy" />
&lt;/div>
&lt;p>Which suggests maybe we can reverse the bits and get something interesting out?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">s &lt;span style="color:#f92672">=&lt;/span> u32(spiral(&lt;span style="color:#ae81ff">384&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.08&lt;/span>, &lt;span style="color:#ae81ff">0.08&lt;/span>))
s &lt;span style="color:#f92672">=&lt;/span> masked_xorshift(s, &lt;span style="color:#ae81ff">2&lt;/span>)
s &lt;span style="color:#f92672">^=&lt;/span> s &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
s &lt;span style="color:#f92672">=&lt;/span> reverse_bits32(s)
s &lt;span style="color:#f92672">=&lt;/span> nested_uniform_scramble(s)
lookit(s &lt;span style="color:#f92672">/&lt;/span> s&lt;span style="color:#f92672">.&lt;/span>max())
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/permuted-spiral.png" width="768" height="384" loading="lazy" />
&lt;/div>
&lt;p>Huh. Conceptually, this is just a reordering of pixels&amp;ndash;you could replace that divide by a shift to turn it into a true permutation. It&amp;rsquo;s not noise, though. I tried some things, like rotating some tiles, placing the origin of the spiral at the corners, adding random jitter to the magnitude and angles in &lt;code>spiral&lt;/code>, but nothing worked very well at getting rid of the obvious structure while maintaining the spectrum. This is interesting, though.&lt;/p>
&lt;h2 id="bonus-2">Bonus 2&lt;/h2>
&lt;p>I have no idea what is going on here.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">red&lt;/span>(i: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
s &lt;span style="color:#f92672">=&lt;/span> white_shuffle(i &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
r &lt;span style="color:#f92672">=&lt;/span> kronecker_sequence(s, &lt;span style="color:#ae81ff">2654435770&lt;/span>) &lt;span style="color:#75715e"># 0.31 fixed point golden ratio&lt;/span>
r[(i &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">^=&lt;/span> r[(i &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">6&lt;/span>)
&lt;span style="color:#66d9ef">return&lt;/span> r
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">red_2d&lt;/span>(x: u32, y: u32) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> u32:
&lt;span style="color:#75715e"># note the right shift--this isn&amp;#39;t z-order&lt;/span>
i &lt;span style="color:#f92672">=&lt;/span> left_shift_2(x) &lt;span style="color:#f92672">+&lt;/span> (left_shift_2(y) &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> u32(&lt;span style="color:#ae81ff">1&lt;/span>))
&lt;span style="color:#66d9ef">return&lt;/span> red(i)
noise &lt;span style="color:#f92672">=&lt;/span> red_2d(x, y) &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>ldexp(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>)
lookit(noise)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/red2d.png" width="768" height="384" loading="lazy" />
&lt;/div>
&lt;p>Histogram&amp;rsquo;s okay, too, but it&amp;rsquo;s pretty bad within the bins.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">plt&lt;span style="color:#f92672">.&lt;/span>hist(noise&lt;span style="color:#f92672">.&lt;/span>flatten(), &lt;span style="color:#ae81ff">384&lt;/span>)
&lt;/code>&lt;/pre>&lt;/div>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/some-low-discrepancy-noise-functions/red2d-hist.png" width="900" height="200" loading="lazy" />
&lt;/div>
&lt;h2 id="bottom-text">Bottom text&lt;/h2>
&lt;p>Here&amp;rsquo;s the &lt;a href="https://gist.github.com/graemephi/3a90bc543aa974f7de04fa100c66bdc2">code for this post&lt;/a> without all my words around it.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>Also known as &amp;ldquo;linear&amp;rdquo;. Who comes up with this stuff?&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>An interesting reason to use the golden ratio sequence is it turns out it&amp;rsquo;s easy to come up with more constants&amp;ndash;that is, aside from the golden ratio&amp;ndash;that let you generate multiple streams of correlated noise cheaply. R2 is one way, and there are others. But it&amp;rsquo;s a whole thing.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3" role="doc-endnote">
&lt;p>If it seems weird that I&amp;rsquo;m so singularly focused on this property of &lt;em>nested uniform scramble&lt;/em> it&amp;rsquo;s because I figured out &lt;code>masked_xorshift&lt;/code> before I found out about that paper, and I&amp;rsquo;m not sure I would have figured it out if I thought &lt;em>nested uniform scramble&lt;/em> was the model to follow.&lt;sup id="fnref:6">&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref">6&lt;/a>&lt;/sup>&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4" role="doc-endnote">
&lt;p>This fact is pretty elementary DSP-wise but Bart Wronski&amp;rsquo;s post was the only thing I could find that discusses it at a high level without assuming you are the sort of person who thinks that negative frequencies are a reasonable idea.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5" role="doc-endnote">
&lt;p>I told you not to worry about it!! But I can&amp;rsquo;t find any explanation of this that isn&amp;rsquo;t laden down with DSP nonsense so while I&amp;rsquo;m at it: the DFT gives you a representation of a signal as a sum of scaled-and-shifted sine waves, right? And you shift a signal around in time and all the sine waves change phase, in lockstep with each other. In the DFT sine waves are encoded as complex numbers&amp;ndash;a magnitude for the sine wave&amp;rsquo;s scale, and an angle, for its phase&amp;ndash;and it just so happens that multiplying a complex number by unit complex numbers only modifies the angle. So shifting signals &lt;em>in time&lt;/em> can be done in the frequency domain by multiplying the complex coefficient of each sine wave by &lt;code>exp(1j * carefully_chosen_shift_amount_per_sine_wave * 2 * pi)&lt;/code>. The shorter the wavelength, the larger the shift, because shorter waves complete more of their cycle per unit time.
&lt;br>
&lt;br>
That&amp;rsquo;s shifting in time. If you do the &lt;em>exact same thing&lt;/em> in the time domain&amp;ndash;multiply the time domain samples by those unit complex numbers&amp;ndash;you shift the frequency components around. That&amp;rsquo;s shifting in frequency. Okay? Okay.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:6" role="doc-endnote">
&lt;p>And the reason I keep italicizing &lt;em>nested uniform scramble&lt;/em> is because &lt;em>nested uniform scramble&lt;/em> is a nested uniform scramble. There are others, you see.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>Difference Decay</title><link>https://graemephi.github.io/posts/difference-decay/</link><pubDate>Wed, 29 Dec 2021 00:00:00 +0000</pubDate><guid>https://graemephi.github.io/posts/difference-decay/</guid><description>&lt;p>Here&amp;rsquo;s a variation on the &lt;a href="https://theorangeduck.com/page/spring-roll-call">damper&lt;/a> I keep coming up with uses for. I find my code that uses it a bit subtle and annoying to figure out, hence this post.&lt;/p>
&lt;p>That theorangeduck post is about springs, which could be an interesting extension to this, but this post is setting our sights lower.&lt;/p>
&lt;p>My first use for this was to clean up a noisy/unreliable clock by using a high resolution clock, taking the noisy clock&amp;rsquo;s drift as authoritative. But I think of it more generally, as combining two signals &lt;code>s&lt;/code> and &lt;code>n&lt;/code> to produce a third with the short-term character of &lt;code>s&lt;/code> but the long-term average of &lt;code>n&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-c++" data-lang="c++">&lt;span style="color:#66d9ef">float&lt;/span> &lt;span style="color:#a6e22e">update&lt;/span>(&lt;span style="color:#66d9ef">float&lt;/span> &lt;span style="color:#f92672">*&lt;/span>accumulator, &lt;span style="color:#66d9ef">float&lt;/span> dt, &lt;span style="color:#66d9ef">float&lt;/span> s, &lt;span style="color:#66d9ef">float&lt;/span> n)
{
&lt;span style="color:#66d9ef">float&lt;/span> acc &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">*&lt;/span>accumulator;
&lt;span style="color:#66d9ef">float&lt;/span> err &lt;span style="color:#f92672">=&lt;/span> s &lt;span style="color:#f92672">-&lt;/span> n;
acc &lt;span style="color:#f92672">+=&lt;/span> err;
acc &lt;span style="color:#f92672">*=&lt;/span> expf(&lt;span style="color:#f92672">-&lt;/span>dt);
&lt;span style="color:#66d9ef">float&lt;/span> x &lt;span style="color:#f92672">=&lt;/span> n &lt;span style="color:#f92672">+&lt;/span> acc;
acc &lt;span style="color:#f92672">-=&lt;/span> err;
&lt;span style="color:#f92672">*&lt;/span>accumulator &lt;span style="color:#f92672">=&lt;/span> acc;
&lt;span style="color:#66d9ef">return&lt;/span> x;
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>accumulator&lt;/code> is initialized to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span> and we expect &lt;code>dt&lt;/code> to be small and positive, so you can mentally substitute &lt;code>expf(-dt)&lt;/code> with &lt;code>1 - dt&lt;/code>.&lt;/p>
&lt;p>The easiest way to explain it is to work backwards. The &lt;code>+=&lt;/code>, &lt;code>-=&lt;/code> pair removes a delay term (&lt;code>err_prev&lt;/code> in the following), and is equivalent to:&lt;/p>
&lt;pre tabindex="0">&lt;code>err = s - n;
acc = (acc + (err - err_prev)) * expf(-dt);
err_prev = err;
x = n + acc;
&lt;/code>&lt;/pre>&lt;p>To see what this is doing, suppose we don&amp;rsquo;t apply the &lt;code>expf(-dt)&lt;/code> decay factor. Then, over all &lt;code>s&lt;/code> and &lt;code>n&lt;/code>, we get something like &lt;code>cumsum([0, diff(s - n)])&lt;/code>, which is a no-op. Written out,&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mtable rowspacing="0.24999999999999992em" columnalign="right left" columnspacing="0em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;msub>&lt;mtext>acc&lt;/mtext>&lt;mi>i&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;msub>&lt;mtext>acc&lt;/mtext>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;mo>+&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mi>i&lt;/mi>&lt;/msub>&lt;mo>−&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;mi>i&lt;/mi>&lt;/munderover>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mi>k&lt;/mi>&lt;/msub>&lt;mo>−&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;mi>i&lt;/mi>&lt;/munderover>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mi>k&lt;/mi>&lt;/msub>&lt;mo>−&lt;/mo>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;mi>i&lt;/mi>&lt;/munderover>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mi>i&lt;/mi>&lt;/msub>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;msub>&lt;mi>s&lt;/mi>&lt;mi>i&lt;/mi>&lt;/msub>&lt;mo>−&lt;/mo>&lt;msub>&lt;mi>n&lt;/mi>&lt;mi>i&lt;/mi>&lt;/msub>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;annotation encoding="application/x-tex">
\begin{aligned}
\text{acc}_i &amp;amp;= \text{acc}_{i-1} + (\text{err}_i - \text{err}_{i-1}) \\
&amp;amp;= \sum_{k=0}^i (\text{err}_k - \text{err}_{k-1}) \\
&amp;amp;= \sum_{k=0}^i \text{err}_k - \sum_{k=1}^i \text{err}_{k-1} \\
&amp;amp;= \text{err}_i \\
&amp;amp;= s_i - n_i
\end{aligned}
&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:11.327564em;vertical-align:-5.413782em;">&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-r">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:5.913782em;">&lt;span style="top:-8.885451em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">acc&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-6.413782em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;/span>&lt;/span>&lt;span style="top:-3em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;/span>&lt;/span>&lt;span style="top:-0.557887em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;/span>&lt;/span>&lt;span style="top:0.942113em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:5.413782em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="col-align-l">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:5.913782em;">&lt;span style="top:-8.885451em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">acc&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.311664em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.311664em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-6.413782em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8116690000000002em;">&lt;span style="top:-1.8478869999999998em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.0500049999999996em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.300005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.302113em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.3361079999999999em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8116690000000002em;">&lt;span style="top:-1.8478869999999998em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.0500049999999996em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.300005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.302113em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8116690000000002em;">&lt;span style="top:-1.8478869999999998em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.0500049999999996em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.300005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.302113em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.3361079999999999em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-0.557887em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:0.942113em;">&lt;span class="pstrut" style="height:3.811669em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">s&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:5.413782em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>so our value for &lt;code>x&lt;/code> would always equal the most recent &lt;code>s&lt;/code>. Likewise, if we zeroed out &lt;code>acc&lt;/code> completely, the damper would return &lt;code>n&lt;/code> every time.&lt;/p>
&lt;p>Applying the decay to &lt;code>acc&lt;/code> lets us decay &lt;em>old updates&lt;/em> to the difference between &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>s&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">s&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>. If &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>s&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">s&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span> grow at the different rates, this shrinks the gap between them and stops them drifting apart. But if &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span> updates less frequently or with jitter, this fills in the missing/incorrect detail with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>s&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">s&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;/span>&lt;/span>&lt;/span>.&lt;/p>
&lt;p>If you care about tuning the decay factor or having something more momentum than exponential decay, check out the &lt;a href="https://theorangeduck.com/page/spring-roll-call">theorangeduck post&lt;/a>. It&amp;rsquo;s good!! One thing to note is this damper doesn&amp;rsquo;t have any stability problems for large &lt;code>dt&lt;/code> that I&amp;rsquo;m aware of, you just lose more history.&lt;/p>
&lt;p>While retreading the maths for this post and trying to see if I could get it to look more intuitively damper-y, I noticed that if you scale the error difference term by the inverse of the decay to cancel out the first decay you get&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mtable rowspacing="0.24999999999999992em" columnalign="right left" columnspacing="0em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;msub>&lt;mtext>acc&lt;/mtext>&lt;mi>i&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;mo stretchy="false">[&lt;/mo>&lt;msub>&lt;mtext>acc&lt;/mtext>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;mo>+&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mi>i&lt;/mi>&lt;/msub>&lt;mo>−&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;mi>exp&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>d&lt;/mi>&lt;mi>t&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo stretchy="false">]&lt;/mo>&lt;mi>exp&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mo>−&lt;/mo>&lt;mi>d&lt;/mi>&lt;mi>t&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="true">&lt;mrow>&lt;mrow>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;msub>&lt;mtext>acc&lt;/mtext>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;mi>exp&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mo>−&lt;/mo>&lt;mi>d&lt;/mi>&lt;mi>t&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>+&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mi>i&lt;/mi>&lt;/msub>&lt;mo>−&lt;/mo>&lt;msub>&lt;mtext>err&lt;/mtext>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;annotation encoding="application/x-tex">
\begin{aligned}
\text{acc}_i &amp;amp; = [\text{acc}_{i-1}+(\text{err}_i - \text{err}_{i-1}) \exp(dt)] \exp(-dt) \\
&amp;amp;= \text{acc}_{i-1} \exp(-dt) +(\text{err}_i - \text{err}_{i-1})
\end{aligned}
&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:3.0000000000000004em;vertical-align:-1.2500000000000002em;">&lt;/span>&lt;span class="mord">&lt;span class="mtable">&lt;span class="col-align-r">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.7500000000000002em;">&lt;span style="top:-3.91em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">acc&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.41em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.2500000000000002em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="col-align-l">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.7500000000000002em;">&lt;span style="top:-3.91em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">acc&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.311664em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.311664em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">exp&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">exp&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-2.41em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">acc&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.311664em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">exp&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord mathdefault">d&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.31166399999999994em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord">&lt;span class="mord text">&lt;span class="mord">err&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.311664em;">&lt;span style="top:-2.5500000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.208331em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.2500000000000002em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>which looks similar to the simple damper:&lt;/p>
&lt;pre tabindex="0">&lt;code>x = lerp(x, g, 1.0f - expf(-dt))
= g + (x - g)*expf(-dt)
&lt;/code>&lt;/pre>&lt;p>Instead of repeatedly folding &lt;code>g&lt;/code> into &lt;code>x&lt;/code>, we&amp;rsquo;ve isolated &lt;code>x - g&lt;/code> as &lt;code>acc&lt;/code>, and we repeatedly fold updates to &lt;code>g&lt;/code> into &lt;code>acc&lt;/code>, which we decay. This idea of isolating the difference is what I was thinking about when I came up with this; it&amp;rsquo;s really another &lt;a href="https://graemephi.github.io/posts/dumb-tricks-with-phase-inversion">phase inversion trick&lt;/a>. So maybe applying this scaling factor is more correct?&lt;/p>
&lt;p>Anyway, I came up with this to fix a decades old visual stuttering issue in the rhythm game Etterna, a fork of Stepmania. They position objects on the screen by repeatedly querying the system audio API for it&amp;rsquo;s playback position. This turns out to work exactly as you&amp;rsquo;d hope on some hardware and APIs, and really not work at all on others. The game is visually stripped down enough that this looks like it has bad frame pacing issues, but the issue was entirely due to the reported audio position. There are variations here (figured out with &lt;a href="https://github.com/wolfpld/tracy">Tracy&lt;/a>):&lt;/p>
&lt;ul>
&lt;li>
&lt;p>WaveOut, interestingly, appears to directly report whatever the hardware driver tells it. This means it works great for some devices and terrible on others. I first hit this issue when a driver update for my USB sound card changed the behaviour here, introducing jitter you could &lt;em>see&lt;/em>. On the other hand, with my motherboard&amp;rsquo;s audio you instead see nice steady drift away from &lt;code>QueryPerformanceCounter&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>DirectSound appears to correct for drift and jitter, and this turns out to be bad. When you sample playback positions it looks to be in sync with wall time, but audio hardware &lt;em>does&lt;/em> drift, and DirectSound eventually corrects for this by jumping. Which means a visible jump in the game. Furthermore, it only reports a new sample position every 10ms, and at the point you are querying you have no idea how long ago the update to the position occurred. You basically end up with a few milliseconds of jitter, unless you&amp;rsquo;re running at very high frame rates.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ALSA and PulseAudio appear to have no way to query this, so you only know the time you submitted the last buffer. This is probably fine, and possibly none of this would have been a problem if the game never queried the other APIs in the first place. You can get a continuous estimate by extrapolating from the submission time, which I suspect is what you get in the good WaveOut case. Due to the architecture of the game, this ended up with the same unknown update time issue as DirectSound.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>And possibly more, but I stopped looking.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>So I wanted something that would eliminate jitter for all of these, without any parameter tuning, and not degrade the ideal &amp;lsquo;good driver under WaveOut&amp;rsquo; case in any way. This worked well.&lt;/p>
&lt;p>One thing here worth mentioning, where &lt;code>s&lt;/code> is high-quality time from &lt;code>QueryPerformanceCounter&lt;/code> or equivalent and &lt;code>n&lt;/code> is low-quality time from somewhere else, is that we only have samples &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>s&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>0&lt;/mn>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">s(t_0)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">n(t_1)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>. That is, we do not actually know the values of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>t&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">t&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.61508em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">t&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and we just assume &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>0&lt;/mn>&lt;/msub>&lt;mo>≈&lt;/mo>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">t_0 \approx t_1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.76508em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">≈&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.76508em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. For this problem in particular, you might want to take a third sample &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>s&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">s(t_2)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> and check that &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>s&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>−&lt;/mo>&lt;mi>s&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>0&lt;/mn>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">s(t_2) - s(t_0)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> is sufficiently small before using &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>t&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">n(t_1)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">t&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>, as your &lt;a href="https://en.wikipedia.org/wiki/Time_slice">time slice&lt;/a> can run out between sampling &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>s&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">s&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">s&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>.&lt;/p>
&lt;p>I wish I could say something about control theory here, since this kind of thing seems to be right in its wheelhouse. But I don&amp;rsquo;t really know any.&lt;/p></description></item><item><title>stb_ds: string interning</title><link>https://graemephi.github.io/posts/stb_ds-string-interning/</link><pubDate>Thu, 27 Aug 2020 00:00:00 +0000</pubDate><guid>https://graemephi.github.io/posts/stb_ds-string-interning/</guid><description>&lt;p>&lt;a href="https://github.com/nothings/stb">stb_ds&lt;/a> is a generic container library for C. It was probably from seeing the same technique in &lt;a href="https://github.com/pervognsen/bitwise">bitwise&lt;/a>, but I&amp;rsquo;ve had it at the back of my mind for a while now that one of the good use cases for stb_ds was easy string interning. But it&amp;rsquo;s not mentioned anywhere, not even in the &lt;a href="http://nothings.org/stb_ds">gentle introduction&lt;/a>! Well, the pieces are there, but how to put them together is a little subtle.&lt;/p>
&lt;p>By the way, string interning is when you keep at most one copy of a given string in memory, and use immutable references to such copies as your string value type. In return, you get &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>O&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">O(1)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.02778em;">O&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> string equality and you use less memory.&lt;/p>
&lt;p>You can do string interning with a single stb_ds hash table, and that&amp;rsquo;s it. I&amp;rsquo;m pretty sure stb_ds is designed to ensure that this is possible&amp;ndash;you can&amp;rsquo;t do it with most hash tables. Several features fitting together just right makes it work.&lt;/p>
&lt;p>First, table entries are stored contiguously in memory, and their order is stable, even when the hash table is resized. This is in constrast to other hash table implementations that store their data in sparse arrays. This isn&amp;rsquo;t just an implementation detail but a documented part of the API. For more on why you might want a hash table that does this, check out &lt;a href="https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html">this blog post from PyPy&lt;/a>.&lt;/p>
&lt;p>Second, stb_ds can hash strings for use as key types. Once you have string interning, you only need primitive type keys for your hash tables. But if you don&amp;rsquo;t have proper string keys before you have interning, you need to figure out what you&amp;rsquo;re doing. We don&amp;rsquo;t have to worry about it.&lt;/p>
&lt;p>Third, string hash tables can be configured to store their keys in a memory arena managed by the hash table. This means we won&amp;rsquo;t have to manage the intern pool memory at all. The arena gives us stable pointers and minimises the number of calls to the underlying allocator.&lt;/p>
&lt;p>And lastly, although stb_ds&amp;rsquo;s hash table maps keys to values, it&amp;rsquo;s possible to use it without specifying values at all. (And you don&amp;rsquo;t waste any space doing this.)&lt;/p>
&lt;p>All of these are useful in their own right! But check this out:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-C" data-lang="C">&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">struct&lt;/span> { &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>key; } Intern;
&lt;span style="color:#66d9ef">static&lt;/span> Intern &lt;span style="color:#f92672">*&lt;/span>interns &lt;span style="color:#f92672">=&lt;/span> NULL;
ptrdiff_t &lt;span style="color:#a6e22e">intern&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>str)
{
&lt;span style="color:#66d9ef">if&lt;/span> (str &lt;span style="color:#f92672">==&lt;/span> NULL) {
&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;
}
&lt;span style="color:#66d9ef">if&lt;/span> (interns &lt;span style="color:#f92672">==&lt;/span> NULL) {
sh_new_arena(interns);
}
ptrdiff_t result &lt;span style="color:#f92672">=&lt;/span> shgeti(interns, str);
&lt;span style="color:#66d9ef">if&lt;/span> (result &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>) {
shputs(interns, (Intern) { .key &lt;span style="color:#f92672">=&lt;/span> str });
result &lt;span style="color:#f92672">=&lt;/span> shlen(interns) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
}
&lt;span style="color:#66d9ef">return&lt;/span> result;
}
&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#a6e22e">from_intern&lt;/span>(ptrdiff_t handle)
{
&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>result &lt;span style="color:#f92672">=&lt;/span> NULL;
&lt;span style="color:#66d9ef">if&lt;/span> (handle &lt;span style="color:#f92672">&amp;gt;=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> handle &lt;span style="color:#f92672">&amp;lt;&lt;/span> shlen(interns)) {
result &lt;span style="color:#f92672">=&lt;/span> interns[handle].key;
}
&lt;span style="color:#66d9ef">return&lt;/span> result;
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Table entries go into the hash table as just a key, without a corresponding value. To get a value to use as a handle, we use their index in the table&amp;rsquo;s key storage. When we want to turn a handle back into &lt;code>char *&lt;/code>, we index off the &lt;code>interns&lt;/code> pointer.&lt;/p>
&lt;p>Since pointers to the interned strings are stable, you might want to hand out those pointers directly, instead of handles. On the other hand, pointer-sized handles are somewhat big on 64-bit systems, so there you might want to use a smaller handle type. On 64-bit, the above code is a bit worst-of-both-worlds, but I wanted to show the idea as cleanly as possible.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;p>If you&amp;rsquo;re using a version of stb_ds older than v0.65 the above code won&amp;rsquo;t work, as using &lt;code>shputs&lt;/code> will store the pointer in &lt;code>key&lt;/code> directly instead of a pointer to the copy it will allocate from the arena. Prior to v0.65, you had to manage the intern pool&amp;rsquo;s memory directly. That&amp;rsquo;s okay, because stb_ds exposes its internal memory arena functionality as part of the API. The idea is the same, just slightly less convenient.&lt;/p>
&lt;p>Also, I first call &lt;code>shgeti&lt;/code> there, but you really ought to be able to get away with just calling &lt;code>shputs&lt;/code> and calling it a day. If you use pointers instead of handles this works, because &lt;code>shputs&lt;/code> returns the inserted key. But I&amp;rsquo;m not convinced you can rely on that for future versions.&lt;/p>
&lt;p>And&amp;hellip; I think that&amp;rsquo;s it.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>If they&amp;rsquo;re smaller than pointer-sized, handles can overflow. I figure if I post code it should be working code that can be copy-pasted somewhere without blowing up some indeterminate time in the future. Although, I&amp;rsquo;m not keen on the behaviour it has with null strings and invalid handles. I feel like both of those indicate that bad things are happening, and returning null pointers is just adding fuel to the fire.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>deep sky object</title><link>https://graemephi.github.io/posts/deep-sky-object/</link><pubDate>Wed, 20 May 2020 00:00:00 +0000</pubDate><guid>https://graemephi.github.io/posts/deep-sky-object/</guid><description>&lt;p>A couple months ago I set &lt;a href="https://twitter.com/tiny_dso">@tiny_dso&lt;/a> running. It&amp;rsquo;s a twitter art bot, which I guess is less of an exciting thing nowadays, but it&amp;rsquo;s an idea I&amp;rsquo;ve wanted to do for at least a few years now: turn &lt;a href="https://twitter.com/tiny_star_field">@tiny_star_field&lt;/a>'s tweets into computer generated imitations of astrophotography.&lt;/p>
&lt;p>tiny_star_field is getting a bit intermittent nowadays, so I set the bot to work it&amp;rsquo;s way backwards through tiny_star_field's old tweets. I wanted to make it something you could follow for a long time and still be surprised by, rather than something you&amp;rsquo;d scroll down once and see everything it&amp;rsquo;s capable of. We&amp;rsquo;ll see if I succeeded there; I&amp;rsquo;m not sure. It&amp;rsquo;s hard to tweak the parameters for that kind of thing, especially since when I&amp;rsquo;m working on it I generate hundreds of images, which skews my perception of what&amp;rsquo;s happening too much or not enough.&lt;/p>
&lt;p>It&amp;rsquo;s been running for a while, so I can let it speak for itself:&lt;/p>
&lt;div class="img-flex">
&lt;img src="https://graemephi.github.io/posts/deep-sky-object/EXeYst0XkAITLkB.png" width="480" height="480" loading="lazy" />
&lt;img src="https://graemephi.github.io/posts/deep-sky-object/ERjccJlXYAEwypn.png" width="480" height="480" loading="lazy" />
&lt;img src="https://graemephi.github.io/posts/deep-sky-object/ESHpGWuW4AEAUlJ.png" width="480" height="480" loading="lazy" />
&lt;img src="https://graemephi.github.io/posts/deep-sky-object/ETJqBfgWoAIiTNk.png" width="480" height="480" loading="lazy" />
&lt;img src="https://graemephi.github.io/posts/deep-sky-object/EShIIcjXQAEHntm.png" width="480" height="480" loading="lazy" />
&lt;img src="https://graemephi.github.io/posts/deep-sky-object/EW0CH6_WkAQaKBw.png" width="480" height="480" loading="lazy" />
&lt;/div>
&lt;p>I don&amp;rsquo;t have it in me to write more about it with a coherent structure so I&amp;rsquo;m just going to dump some notes on the implementation here. I wrote most of the code a couple years ago, returned to it a couple times, and really just decided to get it running on twitter recently. There&amp;rsquo;s probably some stuff I intended to do that I&amp;rsquo;ve completely forgotten about, so this is mostly technical details I can read out of the code.&lt;/p>
&lt;hr>
&lt;p>The bot part that talks to twitter is just javascript that lives on &lt;a href="https://tiny-dso.glitch.me/">glitch&lt;/a>. The &lt;a href="https://github.com/graemephi/starfield">image generator&lt;/a> is an executable that spits pngs out of standard out.&lt;/p>
&lt;hr>
&lt;p>It&amp;rsquo;s written in ion, the language Per Vognsen created for &lt;a href="https://github.com/pervognsen/bitwise">bitwise&lt;/a>. It&amp;rsquo;s basically C99, except you can omit type names sometimes, you use &lt;code>.&lt;/code> instead of &lt;code>-&amp;gt;&lt;/code>, and there is some notion of modules. Oh, and it has out of order declarations. I think when I started writing this the language was fully 2 weeks old. It looked pretty cool and I didn&amp;rsquo;t want to use any libraries in this project&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>, so it fit my needs fine. As new and unfinished compilers go, it was pretty reliable. But it didn&amp;rsquo;t receive a whole lot of development past that. Coming back to the code to get the images onto twitter I found it had some bugs to work around, mostly to do with getting it to generate code that would compile on linux&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>I found tweaking the image generation intolerable if it took more than half a second or so, so I spent some time optimising it. That means multi-threading, vector instructions, and a lot of profiling. This wasn&amp;rsquo;t a problem with ion, because it can generate C code, and will take your word for it if you tell it some function or type exists. Also, it uses the C preprocessor to get line information to debuggers (and profilers). All in all, very nice. Good language.&lt;/p>
&lt;hr>
&lt;p>I learned early on that the easiest way to render a passable looking star was to place a single white pixel on an otherwise blank texture and just use &lt;a href="https://graemephi.github.io/posts/calculating-lod/">mipmaps&lt;/a> to filter it for rendering. Rotating a shrunken, very bright, pixel gets you endless variation on how the stars look.&lt;/p>
&lt;p>Pixel values fall in the range &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">]&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[0, 1]&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;/span>&lt;/span>&lt;/span> and, if you think on it, stars really ought to be much brighter than that. So this method works best if you just let your values overflow then adaptively crunch the image back down with post-processing. Most astrophotography has gone through reams of processing, anyway.&lt;/p>
&lt;hr>
&lt;p>All the &amp;ldquo;pixel shader&amp;rdquo;-like work is done using a pair of functions, &lt;code>pixel_iter_begin&lt;/code> and &lt;code>pixel_iter_next&lt;/code>. Rather than explaining what they do, here&amp;rsquo;s the code for drawing a texture:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-c" data-lang="c">func &lt;span style="color:#a6e22e">draw_tex&lt;/span>(dest: Image&lt;span style="color:#f92672">*&lt;/span>, target: Rect, tex: Tex&lt;span style="color:#f92672">*&lt;/span>) {
lod :&lt;span style="color:#f92672">=&lt;/span> compute_lod_level(dest.size, target.size, tex.size);
&lt;span style="color:#66d9ef">for&lt;/span> (it :&lt;span style="color:#f92672">=&lt;/span> pixel_iter_begin(dest, target); pixel_iter_next(&lt;span style="color:#f92672">&amp;amp;&lt;/span>it)) {
rgba :&lt;span style="color:#f92672">=&lt;/span> tex_lookup_lod(tex, it.npos, lod);
&lt;span style="color:#f92672">*&lt;/span>it.pixel &lt;span style="color:#f92672">=&lt;/span> color_blend(&lt;span style="color:#f92672">*&lt;/span>it.pixel, rgba);
}
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>This would work in C, but it&amp;rsquo;d look like &lt;code>for (PixelIter it = ...; pixel_iter_next(&amp;amp;it);) {} &lt;/code>. Note that trailing semicolon&amp;ndash;&lt;code>next&lt;/code> gets called before, not after, every iteration. Now, this way of iterating over pixels is very general and so pretty slow. It just makes doing pixel-by-pixel stuff extremely low friction to write code for. I think most programmers would reach for function pointers or generics to separate the pixel shading code from the iteration, but it&amp;rsquo;s not necessary, and a pain to actually use. It turns out that the iterator code was not performance critical at all, so the overhead didn&amp;rsquo;t matter much, and the implementation is also very naive.&lt;/p>
&lt;p>The pixel iterator also takes care of multi-threading. Here&amp;rsquo;s the definition of the &lt;code>Image&lt;/code> struct:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-c" data-lang="c">&lt;span style="color:#66d9ef">struct&lt;/span> Image {
pixels: Color&lt;span style="color:#f92672">*&lt;/span>;
size: int2;
wr: WritableRegion;
stride: &lt;span style="color:#66d9ef">int&lt;/span>;
offset: &lt;span style="color:#66d9ef">int&lt;/span>;
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>WritableRegion&lt;/code> allows the pixel iterator to clip the pixels being iterated over to the block the thread is responsible for rendering. The code that actually uses the pixel iterator doesn&amp;rsquo;t have to think about blocks or threads at all. It&amp;rsquo;s nice!&lt;/p>
&lt;p>The drawback is that if you want to draw into a side buffer before compositing into a destination buffer, then you end up allocating the full &lt;code>size&lt;/code> and only use a block in the middle somewhere. Avoiding that is what &lt;code>stride&lt;/code> and &lt;code>offset&lt;/code> are for. Usually, you&amp;rsquo;d sample an individual pixel like this:&lt;/p>
&lt;pre tabindex="0">&lt;code>sample := img.pixels[pos.x + pos.y*img.x];
&lt;/code>&lt;/pre>&lt;p>Instead, I do this:&lt;/p>
&lt;pre tabindex="0">&lt;code>sample := img.pixels[pos.x + pos.y*img.stride - img.offset];
&lt;/code>&lt;/pre>&lt;p>This way, you can sample an image by talking about coordinates in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;msup>&lt;mo stretchy="false">]&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">[0, 1]^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">&lt;span class="mclose">]&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> while the image only has storage allocated for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mfrac>&lt;mn>3&lt;/mn>&lt;mn>8&lt;/mn>&lt;/mfrac>&lt;mo separator="true">,&lt;/mo>&lt;mfrac>&lt;mn>4&lt;/mn>&lt;mn>8&lt;/mn>&lt;/mfrac>&lt;msup>&lt;mo stretchy="false">]&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">[\frac{3}{8}, \frac{4}{8}]^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.190108em;vertical-align:-0.345em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.6550000000000002em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">8&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.345em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.6550000000000002em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">8&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">4&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.345em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mclose">&lt;span class="mclose">]&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> (for example), and all it takes is an extra subtraction.&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>&lt;/p>
&lt;hr>
&lt;p>All the star colours are chosen by linearly interpolating between RGB values obtained by colour-picking from &lt;a href="https://en.wikipedia.org/wiki/Stellar_classification#/media/File:TernaryColorTmap.PNG">wikipedia&lt;/a>. They&amp;rsquo;re pretty close so I never had to do anything fancy involving splines or colour space transformations.&lt;/p>
&lt;hr>
&lt;p>Most of the look comes from blurring the entire image and layering the blurred and unblurred parts together in arbitrary ways. Think blend modes in image editors. It turns out it&amp;rsquo;s pretty easy to write a &lt;a href="https://fgiesen.wordpress.com/2012/07/30/fast-blurs-1/">fast&lt;/a> &lt;a href="https://fgiesen.wordpress.com/2012/08/01/fast-blurs-2/">blur&lt;/a>, and you don&amp;rsquo;t even have to think about how to vectorise it because everything is in 4 independent colour components already.&lt;/p>
&lt;p>The diffraction spikes are blurs, too. I think a lot of people jump to the fourier transform to do diffraction spikes but I couldn&amp;rsquo;t be bothered with that&amp;ndash;just take a box filter with a hole cut out of the middle and repeat it a few times. Same principle as using iterated box filters to approximate a gaussian blur, as in the links above, but non-separable this time, so you need to do vertical and horizontal passes both on the original image (i.e., not in series).&lt;/p>
&lt;p>If you look at actual diffraction spikes you can see different colours get diffracted more or less. I think this is probably the same principle as &lt;a href="https://en.wikipedia.org/wiki/Surface_wave#Ground_wave">ground waves&lt;/a>, where lower frequency radio waves diffract around the surface of the Earth more than higher frequency waves. So, I use different sizes of filter on each colour component; larger for larger wavelengths, I think, but you can only do so much in RGB.&lt;/p>
&lt;p>Honestly, though, the diffraction code is kind of terrible. Every time I think about it I think of a better way to implement it. For example, I haven&amp;rsquo;t vectorised it, because the memory accesses are different for each colour component. But to get diffractions at an angle I do a song and dance where I rotate a copy of the entire image to another buffer&amp;ndash;this would be a good time to rearrange the data into colour planes to make it vectorisable: multiple rows of a plane at once. I could even reuse the other blur code at that point, and implement the convolution &lt;code>(A - B)x&lt;/code> as &lt;code>Ax - Bx&lt;/code>. But I don&amp;rsquo;t! And I never will, now.&lt;/p>
&lt;hr>
&lt;p>The nebulas are a hot mess of noise functions, mostly &lt;a href="https://thebookofshaders.com/12/">cellular noise&lt;/a> with a healthy dose of &lt;a href="https://iquilezles.org/www/articles/warp/warp.htm">domain warping&lt;/a>. It all happens on a 2D plane; I didn&amp;rsquo;t want to think about 3D volumetric anything for this project.&lt;/p>
&lt;p>I do the cellular noise in fixed-point arithmetic. This was out of curiosity more anything else; the implementation comes pretty naturally from wanting to use a &lt;a href="https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=madd&amp;amp;techs=SSE2&amp;amp;expand=3505">certain SSE2 instruction&lt;/a>. I&amp;rsquo;d like to write more about it, but not in this post.&lt;/p>
&lt;hr>
&lt;p>By the way, &lt;a href="https://glitch.com/">glitch&lt;/a> is pretty cool. It spins up an instance of who-knows-what for you and clang is just sitting there, waiting to compile whatever you want. A very old version of clang. That doesn&amp;rsquo;t have the intrinsics I use. Well, it has gcc, too.&lt;/p>
&lt;p>If you&amp;rsquo;ve scrolled all the way down here and only now have decided you want the bot&amp;rsquo;s twitter, &lt;a href="https://twitter.com/tiny_dso">here you go&lt;/a>.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>Except &lt;a href="https://github.com/nothings/stb">stb_image_write.h&lt;/a> for the pngs. On Windows, I don&amp;rsquo;t write pngs, but render to a buffer managed by SDL. So they both have one dependency, but it&amp;rsquo;s a different dependency, I guess.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>I think most, if not all, of those bugs have been fixed in &lt;a href="https://github.com/uucidl/bitwise">this fork&lt;/a>.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3" role="doc-endnote">
&lt;p>You could also shunt the &lt;code>pixels&lt;/code> pointer off into no-mans-land and pray you get all the new weird boundary conditions right.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>Server-side KaTeX With Hugo: Part 2</title><link>https://graemephi.github.io/posts/server-side-katex-with-hugo-part-2/</link><pubDate>Sun, 19 Jan 2020 00:00:00 +0000</pubDate><guid>https://graemephi.github.io/posts/server-side-katex-with-hugo-part-2/</guid><description>&lt;p>So, despite saying I didn&amp;rsquo;t want to do this in my &lt;a href="https://graemephi.github.io/posts/static-katex-with-hugo/">last post&lt;/a> on this, &lt;a href="https://github.com/graemephi/kahugo">I went and forked Hugo&lt;/a>. Now rendering with KaTeX is way faster, and basically free when using &lt;code>hugo server&lt;/code>.&lt;/p>
&lt;p>This was more driven by curiosity than a desire to make things fast. At some point a couple weeks ago I was reminded of &lt;a href="https://bellard.org/quickjs/">QuickJS&lt;/a>, and from there it was a short series of small steps to my downfall. QuickJS really enabled this. It was easy to replace the javascript from my previous post with an executable that ran the qjs interpreter on KateX bytecode, and easy to then link it to a Haskell program that drove Pandoc as a library rather than using the command line. Having done &lt;em>that&lt;/em>, getting Goldmark, Hugo&amp;rsquo;s markdown processor, to render TeX using KaTeX was also a small amount of work.&lt;/p>
&lt;p>Rather than this post just being, &amp;ldquo;hey, I&amp;rsquo;m doing this now instead,&amp;rdquo; I guess I&amp;rsquo;ll talk a bit more about it.&lt;/p>
&lt;p>Goldmark is fairly extensible so shoe-horning in TeX-awareness just means telling Goldmark&amp;rsquo;s parser to call our code when it hits a &lt;code>$&lt;/code>: I put maths between &lt;code>$&lt;/code> and &lt;code>$$&lt;/code>, so &lt;code>$x$&lt;/code> gets rendered as &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">x&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span>. But taking responsibility for parsing any markdown yourself means you have to think about weird edge cases&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>Take links. Links in markdown have the following syntax: &lt;code>[foo](bar.tld)&lt;/code>. So, how should &lt;code>[$foo](bar.tld$)&lt;/code> be parsed? In my opinion, there is a correct answer here: that&amp;rsquo;s a link. I&amp;rsquo;ve found people citing some old RFC that prohibits $ in urls, but &lt;a href="https://en.wikipedia.org/wiki/$">they&amp;rsquo;re valid&lt;/a>. Anyone who writes &lt;code>[$](en.wikipedia.org/wiki/$)&lt;/code> wants that to be a link, and I can&amp;rsquo;t think of any exceptions.&lt;/p>
&lt;p>By the way, that $ in the previous paragraph also needs to be parsed as a $, and not the opening dollar of maths.&lt;/p>
&lt;p>So, those are links. What about &lt;code>[$[0, 1]$](url)&lt;/code>? There&amp;rsquo;s something satisfying about being able to link maths: &lt;a href="https://en.wikipedia.org/wiki/Unit_interval">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">]&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[0,1]&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/a>. So, that first example we want to not parse as TeX, and the second we do, and in order to support both we have to know if we are inside a link or not. Parsing!&lt;/p>
&lt;p>Thankfully, we have a working TeX-aware markdown processor already: Pandoc. After fixing up some minor differences between how Goldmark and Pandoc renders the HTML, Pandoc, with the old filter from last time, can &lt;a href="https://github.com/graemephi/goldmark-qjs-katex/blob/master/gen.go">generate a bunch of test cases&lt;/a>. I don&amp;rsquo;t follow Pandoc&amp;rsquo;s example in one place; Pandoc&amp;rsquo;s rule for allowing &lt;code>$[]$&lt;/code> inside links seems to be that the brackets must be balanced. I opted for requiring &lt;code>[&lt;/code> to appear before &lt;code>]&lt;/code> (which would otherwise close the link).&lt;/p>
&lt;p>Next, some threading stuff. Hugo uses goroutines, and the QuickJS runtime can only be used single-threaded. We don&amp;rsquo;t want to make each goroutine queue to access QuickJS, and we also want to keep our changes to Hugo to a minimum. From a C perspective, there&amp;rsquo;s a really obvious solution: give each thread its own QuickJS runtime using thread-local storage. But goroutines aren&amp;rsquo;t threads, and the Go scheduler wants to schedule goroutines to any thread it likes. This means that between two calls into QuickJS the goroutine can move thread, and whatever way we have of communicating with the QuickJS runtime from Go needs to be okay with this happening.&lt;/p>
&lt;p>What I decided to do was to just never allocate anything that would be passed back to Go, and have the Go code pass in memory instead. This makes the C code pure as far as Go is concerned, so we can use thread-local and not worry about the Go scheduler. It also means we don&amp;rsquo;t have to call free, which is always good.&lt;/p>
&lt;p>Last time, I reported that a single page with TeX took half a second to render. Now, my entire site takes 240ms. Without KaTeX, it&amp;rsquo;s around 150ms. I still find this a bit slower than it really ought to be, but I suppose for how little work it was, it&amp;rsquo;s pretty good.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>The &lt;a href="https://spec.commonmark.org/">Commonmark spec&lt;/a> is a great resource for all the markdown parsing gotchas you&amp;rsquo;ve never thought about before.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>Calculating LOD</title><link>https://graemephi.github.io/posts/calculating-lod/</link><pubDate>Tue, 31 Dec 2019 13:29:00 +0000</pubDate><guid>https://graemephi.github.io/posts/calculating-lod/</guid><description>&lt;p>Angelo Pesce (@kenpex) recently tweeted:&lt;/p>
&lt;!-- https://twitter.com/kenpex/status/1201946673091448832 -->
&lt;blockquote class="twitter-tweet">&lt;p lang="en" dir="ltr">This is a calibration poll. Please don't spoil the answer, RT appreciated - Do you know how a GPU knows which mips to fetch when a regular tex2D(tex,uv) is called with trilinear filtering?&lt;/p>— c0de517e/AngeloPesce (@kenpex) &lt;a href="https://twitter.com/kenpex/status/1201946673091448832?ref_src=twsrc%5Etfw">December 3, 2019&lt;/a>&lt;/blockquote>
&lt;p>I would have counted myself as &amp;lsquo;not sure&amp;rsquo;, but I had a pretty good idea what it would involve and I was also pretty sure I could figure it out. Instead of just being pretty sure about it I decided to test my intuitions against a GPU and made &lt;a href="https://www.shadertoy.com/view/3ldGR2">this shadertoy&lt;/a>.&lt;/p>
&lt;p>Most of the code is machinery to get a triangle up that we can rotate around in 3D space. I find it vaguely amusing to write a triangle rasteriser in a fragment shader, so that&amp;rsquo;s what I did. This post is just about LOD, so if you&amp;rsquo;re interested in how the rasteriser works I recommend starting with Fabien Giesen&amp;rsquo;s &lt;a href="https://fgiesen.wordpress.com/2013/02/06/%5Dthe-barycentric-conspirac/">The Barycentric Conspiracy&lt;/a>.&lt;/p>
&lt;p>So, mipmaps: we have a texture with a series of successively smaller mip levels, each filtered properly prior to rendering to avoid aliasing. In this post I&amp;rsquo;m just going to assume textures are always square, and always a power of two in size. These restrictions can be lifted but I don&amp;rsquo;t really see why you would, except maybe to save space when using very large non-square textures.&lt;/p>
&lt;h2 id="2d">2D&lt;/h2>
&lt;p>I&amp;rsquo;d already done this in 2D for &lt;a href="https://github.com/graemephi/starfield">a small project of mine&lt;/a>, so this coloured my intuition a bit.&lt;/p>
&lt;img src="https://graemephi.github.io/posts/calculating-lod/6.png" width="502" height="532" loading="lazy" />
&lt;p>I was just drawing textures in squares of different orientations and sizes&amp;ndash;it turns out an easy way to draw stars is to put a single very bright pixel in a big texture, and let mipmaps take care of the rest. So, here, we just have to do &lt;code>log2(texture_size / max(target_width, target_height))&lt;/code>, that is, the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\log_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.93858em;vertical-align:-0.24414em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> of the inverse of the amount of scaling applied to the texture. To see why, each mip has &lt;em>relative&lt;/em> sizes &lt;code>1/1, 1/2, 1/4, ..., 1/n&lt;/code>. We can get the relative size of the target rect by looking at its width and height, and we want to turn that relative size into an index into this list of sizes for which we have mips. So, we need the function &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mn>2&lt;/mn>&lt;mrow>&lt;mo>−&lt;/mo>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;/msup>&lt;mo>↦&lt;/mo>&lt;mo>−&lt;/mo>&lt;mi>n&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">2^{-n} \mapsto -n&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.782331em;vertical-align:-0.011em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.771331em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">−&lt;/span>&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">↦&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>. That&amp;rsquo;s &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\log_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.93858em;vertical-align:-0.24414em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>.&lt;/p>
&lt;p>Using &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mo>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mo>&lt;mn>2&lt;/mn>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\log_2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.93858em;vertical-align:-0.24414em;">&lt;/span>&lt;span class="mop">&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.20696799999999996em;">&lt;span style="top:-2.4558600000000004em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.24414em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> also takes care of the fractional part of mip selection, so we can do trilinear filtering. If you think about it this is somewhat interesting, because we&amp;rsquo;re blending in a larger mip onto a target we already know is too small to not alias (in general). But having a jump discontuinity between mips looks way worse than low levels of aliasing.&lt;/p>
&lt;h2 id="3d">3D&lt;/h2>
&lt;p>The main thing about 3D is that the LOD level can change across the surface of a triangle.&lt;/p>
&lt;img src="https://graemephi.github.io/posts/calculating-lod/stoy.png" width="477" height="284" loading="lazy" />
&lt;p>We need to think a bit more carefully about what&amp;rsquo;s happening. As we step along pixels in screen space, we also step along texels in texture space. While the former is constant, the latter steps vary in size due to perspective. Reasoning this way works in 2D, too, we just never had to: the constant, screen space step is our &lt;code>1&lt;/code> in the series of relative mip sizes, and the relative, texture space step size is the denominator up to &lt;code>n&lt;/code>. The question is how to get the step size at a given pixel, given that we shade each pixel independently of its neighbours. Now, I guess if you know the graphics pipeline quite well the answer to this jumps out at you, but I want to be able to justify why you would use the functions you do.&lt;/p>
&lt;p>So, in 2D, the texture space step size is constant, and we&amp;rsquo;re able to compute it by assuming we&amp;rsquo;re going to draw the entire texture and comparing its original size with its new width and height in screen space. Now, to get the step size at a point in 3D, we can approximate it by asking about a smaller part of the texture, say a 2x2 patch rather than the entire thing, at that point. To drive the point home here, in 2D we had&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mfrac>&lt;mrow>&lt;mi>T&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mtext mathvariant="bold">0&lt;/mtext>&lt;mo>+&lt;/mo>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>−&lt;/mo>&lt;mi>T&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mtext mathvariant="bold">0&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;mrow>&lt;mo fence="true">∥&lt;/mo>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo fence="true">∥&lt;/mo>&lt;/mrow>&lt;/mfrac>&lt;/mrow>&lt;annotation encoding="application/x-tex"> \frac{ T(\textbf{0} + \textbf{x} ) - T(\textbf{0}) }{ \left\| \textbf{x} \right\| }&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.363em;vertical-align:-0.936em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.427em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">∥&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">∥&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.13889em;">T&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">0&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">T&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">0&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.936em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>T&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">T&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">T&lt;/span>&lt;/span>&lt;/span>&lt;/span> being some transform and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;/mrow>&lt;annotation encoding="application/x-tex">\textbf{x}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.44444em;vertical-align:0em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> being one side of the texture, and we&amp;rsquo;re replacing it with&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mfrac>&lt;mrow>&lt;mi>T&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mtext mathvariant="bold">p&lt;/mtext>&lt;mo>+&lt;/mo>&lt;mi>h&lt;/mi>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>−&lt;/mo>&lt;mi>T&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mtext mathvariant="bold">p&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;mrow>&lt;mi>h&lt;/mi>&lt;mrow>&lt;mo fence="true">∥&lt;/mo>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo fence="true">∥&lt;/mo>&lt;/mrow>&lt;/mrow>&lt;/mfrac>&lt;/mrow>&lt;annotation encoding="application/x-tex"> \frac{ T(\textbf{p} + h\textbf{x} ) - T(\textbf{p}) }{ h\left\| \textbf{x} \right\| }&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:2.363em;vertical-align:-0.936em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.427em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">h&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="minner">&lt;span class="mopen delimcenter" style="top:0em;">∥&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose delimcenter" style="top:0em;">∥&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.13889em;">T&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">p&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault">h&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">−&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.13889em;">T&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">p&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.936em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>and making &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>h&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">h&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">h&lt;/span>&lt;/span>&lt;/span>&lt;/span> really small. So, we need two of these for the derivatives in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">x&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">y&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and you can get an approximation of them&amp;ndash;basically this same one here&amp;ndash;in OpenGL with &lt;code>dFdx()&lt;/code> and &lt;code>dFdy()&lt;/code>.&lt;/p>
&lt;p>In the shadertoy, this is all implemented like this.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="color:#75715e">// uv comes in as an interpolated input&lt;/span>
&lt;span style="color:#66d9ef">vec2&lt;/span> size &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">vec2&lt;/span>(textureSize(iChannel0, &lt;span style="color:#ae81ff">0&lt;/span>));
&lt;span style="color:#66d9ef">vec2&lt;/span> dx &lt;span style="color:#f92672">=&lt;/span> dFdx(uv &lt;span style="color:#f92672">*&lt;/span> size);
&lt;span style="color:#66d9ef">vec2&lt;/span> dy &lt;span style="color:#f92672">=&lt;/span> dFdy(uv &lt;span style="color:#f92672">*&lt;/span> size);
&lt;span style="color:#66d9ef">float&lt;/span> d &lt;span style="color:#f92672">=&lt;/span> max(length(dx), length(dy));
&lt;span style="color:#66d9ef">float&lt;/span> lod &lt;span style="color:#f92672">=&lt;/span> max(&lt;span style="color:#ae81ff">0.&lt;/span>, log2(d));
&lt;span style="color:#66d9ef">vec3&lt;/span> col &lt;span style="color:#f92672">=&lt;/span> textureLod(tex, p, lod).xyz;&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Eyeballing the results this seems to work, but if we take the difference between &lt;code>textureLod()&lt;/code> with our &lt;code>lod&lt;/code> value versus using &lt;code>texture()&lt;/code> and scale it up a bit to magnify errors, we get this.&lt;/p>
&lt;img src="https://graemephi.github.io/posts/calculating-lod/stoy2.png" width="477" height="284" loading="lazy" />
&lt;p>It should be black. The problem is obvious in retrospect, but rather than thinking too hard about it I had a look through the &lt;a href="https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#LODCalculation">DX11 Functional Specification&lt;/a>, which tells you to do this to the derivative vectors:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="color:#66d9ef">float&lt;/span> A &lt;span style="color:#f92672">=&lt;/span> dx.y &lt;span style="color:#f92672">*&lt;/span> dx.y &lt;span style="color:#f92672">+&lt;/span> dy.y &lt;span style="color:#f92672">*&lt;/span> dy.y;
&lt;span style="color:#66d9ef">float&lt;/span> B &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">2.0&lt;/span> &lt;span style="color:#f92672">*&lt;/span> (dx.x &lt;span style="color:#f92672">*&lt;/span> dx.y &lt;span style="color:#f92672">+&lt;/span> dy.x &lt;span style="color:#f92672">*&lt;/span> dy.y);
&lt;span style="color:#66d9ef">float&lt;/span> C &lt;span style="color:#f92672">=&lt;/span> dx.x &lt;span style="color:#f92672">*&lt;/span> dx.x &lt;span style="color:#f92672">+&lt;/span> dy.x &lt;span style="color:#f92672">*&lt;/span> dy.x;
&lt;span style="color:#66d9ef">float&lt;/span> F &lt;span style="color:#f92672">=&lt;/span> dx.x &lt;span style="color:#f92672">*&lt;/span> dy.y &lt;span style="color:#f92672">-&lt;/span> dy.x &lt;span style="color:#f92672">*&lt;/span> dx.y;
F &lt;span style="color:#f92672">=&lt;/span> F&lt;span style="color:#f92672">*&lt;/span>F;
&lt;span style="color:#66d9ef">float&lt;/span> p &lt;span style="color:#f92672">=&lt;/span> A &lt;span style="color:#f92672">-&lt;/span> C;
&lt;span style="color:#66d9ef">float&lt;/span> q &lt;span style="color:#f92672">=&lt;/span> A &lt;span style="color:#f92672">+&lt;/span> C;
&lt;span style="color:#66d9ef">float&lt;/span> t &lt;span style="color:#f92672">=&lt;/span> sqrt(p&lt;span style="color:#f92672">*&lt;/span>p &lt;span style="color:#f92672">+&lt;/span> B&lt;span style="color:#f92672">*&lt;/span>B);
dx.x &lt;span style="color:#f92672">=&lt;/span> sqrt(F &lt;span style="color:#f92672">*&lt;/span> (t&lt;span style="color:#f92672">+&lt;/span>p) &lt;span style="color:#f92672">/&lt;/span> (t &lt;span style="color:#f92672">*&lt;/span> (q&lt;span style="color:#f92672">+&lt;/span>t)));
dx.y &lt;span style="color:#f92672">=&lt;/span> sqrt(F &lt;span style="color:#f92672">*&lt;/span> (t&lt;span style="color:#f92672">-&lt;/span>p) &lt;span style="color:#f92672">/&lt;/span> (t &lt;span style="color:#f92672">*&lt;/span> (q&lt;span style="color:#f92672">+&lt;/span>t))) &lt;span style="color:#f92672">*&lt;/span> sign(B);
dy.x &lt;span style="color:#f92672">=&lt;/span> sqrt(F &lt;span style="color:#f92672">*&lt;/span> (t&lt;span style="color:#f92672">-&lt;/span>p) &lt;span style="color:#f92672">/&lt;/span> (t &lt;span style="color:#f92672">*&lt;/span> (q&lt;span style="color:#f92672">-&lt;/span>t))) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#f92672">-&lt;/span>sign(B);
dy.y &lt;span style="color:#f92672">=&lt;/span> sqrt(F &lt;span style="color:#f92672">*&lt;/span> (t&lt;span style="color:#f92672">+&lt;/span>p) &lt;span style="color:#f92672">/&lt;/span> (t &lt;span style="color:#f92672">*&lt;/span> (q&lt;span style="color:#f92672">-&lt;/span>t)));&lt;/code>&lt;/pre>&lt;/div>
&lt;h2 id="ellipses">Ellipses&lt;/h2>
&lt;p>The source it references only as [Heckbert 89] turns out to be a master&amp;rsquo;s thesis, &lt;em>Fundamentals of Texture Mapping and Image Warping&lt;/em>, which includes this in its appendices. The DX11 spec says it gives you a &amp;ldquo;proper orthogonal Jacobian matrix&amp;rdquo;, where proper is not a technical term. So, this is looking at the projection as a 2D transform from a point &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(x, y)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> in screen space to a point &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>u&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>v&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(u, v)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">u&lt;/span>&lt;span class="mpunct">,&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">v&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> in texture space. Now, it&amp;rsquo;s not linear, so you can&amp;rsquo;t represent it with a 2x2 matrix, but you can look at its derivative to get a linear approximation at a point. That&amp;rsquo;s the Jacobian, and we can construct it by just putting our derivative vectors in the columns of a matrix. But this Jacobian won&amp;rsquo;t be proper.&lt;/p>
&lt;p>Basically, the issue is that up until now I&amp;rsquo;ve been neglecting to consider off-axis scaling. To solve this, we want to look at how the unit circle is transformed, not just the axes. That is, we look at the ellipse formed by applying the Jacobian to the unit circle. Weirdly, a pretty good explanation of this idea can be found in the Wikipedia article on &lt;a href="https://en.wikipedia.org/wiki/Singular_value_decomposition#Intuitive_interpretations">singular value decomposition&lt;/a>. Anyway, here&amp;rsquo;s &lt;a href="https://www.shadertoy.com/view/ttc3DX">a shadertoy&lt;/a> showing how the above code modifies the axes:&lt;/p>
&lt;img src="https://graemephi.github.io/posts/calculating-lod/ellipse.png" width="640" height="360" loading="lazy" />
&lt;p>The right ellipse&amp;rsquo;s axes have been obtained by orthogonalising those on the left. The important thing is that both pairs of axes describe the same ellipse. The reason I went through the effort of creating that shadertoy, though, is because I wanted to check that putting those axes into the columns of two matrices would actually give you matrices that created the same ellipse. Because it&amp;rsquo;s a completely different transform, right, one of them isn&amp;rsquo;t even orthogonal! Of course, it does give you the same ellipse, and I guess the reason why that&amp;rsquo;s possible is obvious: each point on the plane is sent to a different place in either matrix, but the &lt;em>unit circle&lt;/em> traces out the same ellipse for both. To that end, I&amp;rsquo;ve coloured each point by its original position inside the unit circle.&lt;/p>
&lt;p>What this gives you is the maximum scaling in all directions, not just the directions the texture&amp;rsquo;s &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>x&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">x&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;/span>&lt;/span>&lt;/span> and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>y&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">y&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.625em;vertical-align:-0.19444em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">y&lt;/span>&lt;/span>&lt;/span>&lt;/span> axes have been mapped to in screen space.&lt;/p>
&lt;p>Now, applying that correction, we&amp;rsquo;re much closer:&lt;/p>
&lt;img src="https://graemephi.github.io/posts/calculating-lod/stoy3.png" width="477" height="284" loading="lazy" />
&lt;p>Still not perfect, though. I&amp;rsquo;m not sure what&amp;rsquo;s missing. DX11 allows for approximations here, and OpenGL&amp;rsquo;s specification doesn&amp;rsquo;t really say anything at all about it, so maybe that&amp;rsquo;s it. I&amp;rsquo;m really not sure.&lt;/p>
&lt;h2 id="blocks">Blocks&lt;/h2>
&lt;p>Finally, when I first wrote the rasteriser I did it the natural way, something like&lt;/p>
&lt;pre tabindex="0">&lt;code>if (inside_triangle) {
color = calculate_color()
write_pixel(color)
}
&lt;/code>&lt;/pre>&lt;p>But if you look at the shadertoy now, you&amp;rsquo;ll see it&amp;rsquo;s written as&lt;/p>
&lt;pre tabindex="0">&lt;code>color = calculate_color()
if (inside_triangle) {
write_pixel(color)
}
&lt;/code>&lt;/pre>&lt;p>which looks like it wastes a bunch of effort colouring pixels it never writes. The reason for this is that the &lt;code>dFdx&lt;/code> and &lt;code>dFdy&lt;/code> functions aren&amp;rsquo;t magic: they really do take an approximation of the derivative by subtracting neighbouring pixel values from each other in 2x2 blocks. Since we&amp;rsquo;re running the shader in parallel for each pixel in the block, different pixels in the block can branch in different directions. At that point, the GPU is missing neighbouring values for &lt;code>dFdx&lt;/code>/&lt;code>dFdy&lt;/code> to compute derivatives with. The derivative computation breaks down and we can&amp;rsquo;t sample mipmaps at all. On my machine, this gave a LOD value of &lt;code>0&lt;/code> which resulted in a seam of aliasing down the edges of triangles. At first I thought this was a bug in the inclusion test, but no, it&amp;rsquo;s just old fashioned &lt;a href="https://www.khronos.org/opengl/wiki/Non-Uniform_Control_Flow#Non-uniform_flow_control">non-uniform flow control&lt;/a>.&lt;/p>
&lt;h2 id="end">End&lt;/h2>
&lt;p>I&amp;rsquo;m kinda disappointed I wasn&amp;rsquo;t able to get the LOD computation exact even after going through DX11 and OpenGL specifications. And it &lt;em>is&lt;/em> just the LOD that&amp;rsquo;s wrong&amp;ndash;with fixed LOD, the rasteriser does trilinear filtering down to &lt;code>texelFetch()&lt;/code> just fine. Also, I&amp;rsquo;ve not talked about anisotropic filtering at all, which I&amp;rsquo;ve now discovered I know even less about than I thought I did. I&amp;rsquo;d like to know more about how it&amp;rsquo;s implemented on GPUs but it appears to be like, a trade secret, or something.&lt;/p>
&lt;p>I think writing this post took longer than writing the shadertoy.&lt;/p></description></item><item><title>Server-side KaTeX With Hugo</title><link>https://graemephi.github.io/posts/static-katex-with-hugo/</link><pubDate>Sun, 15 Dec 2019 18:01:49 +0000</pubDate><guid>https://graemephi.github.io/posts/static-katex-with-hugo/</guid><description>&lt;p>&lt;strong>Update&lt;/strong> (2020-01-19): &lt;a href="https://graemephi.github.io/posts/server-side-katex-with-hugo-part-2">I lasted some 3 weeks before gettng rid of this&lt;/a>. Also, I&amp;rsquo;ve changed the title from &amp;lsquo;static&amp;rsquo; to &amp;lsquo;server-side&amp;rsquo; which is more accurate.&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://gohugo.io/">Hugo&lt;/a> is a static website generator written in Go. &lt;a href="https://katex.org/">Katex&lt;/a> is a math typesetting library written in Javascript. This site uses them both.&lt;/p>
&lt;p>I keep javascript turned off on websites unless white-listed. I notice when websites fail to load without javascript and I notice when websites don&amp;rsquo;t require it at all. I&amp;rsquo;d like mine to be one of the latter, but also, I want to have proper maths typesetting. Now, usually the way you get maths onto the internet is you dump some LaTeX surrounded by $$ into a document, embed some random javascript file, and everything happens automatically. I don&amp;rsquo;t like that.&lt;/p>
&lt;p>What I want instead is for Hugo to typeset my maths. But I still want &lt;code>hugo server&lt;/code> to render everything properly, and I don&amp;rsquo;t want a complicated multi-stage build process, and I &lt;em>really&lt;/em> don&amp;rsquo;t want to maintain my own build of Hugo. Basically, I want to be able to dump the Hugo executable somewhere and have everything work when I type&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">cd blog
hugo
&lt;/code>&lt;/pre>&lt;p>Unfortunately, Hugo doesn&amp;rsquo;t really support Katex per se. It uses Markdown parsers that know to leave text surrounded by $$ alone, and Katex searches for them at page-load. These parsers are compiled into Hugo, and at no point can you intercept them to do further processing of your own.&lt;/p>
&lt;p>But Hugo supports &lt;a href="https://pandoc.org/">Pandoc&lt;/a>, and Pandoc is happy to pass you a copy of the AST to fiddle with before it actually renders anything. What&amp;rsquo;s more, Pandoc knows that $ means inline maths and $$ means display maths, so we can just read that information off of the AST and have Katex format it as inline or display appropriately.&lt;/p>
&lt;p>So, the outlines of a solution are starting to appear. We need to write a &lt;a href="https://pandoc.org/filters.html">Pandoc filter&lt;/a> that replaces LaTeX maths with Katex&amp;rsquo;s HTML typesetting.&lt;/p>
&lt;p>But there is a problem. There are two problems.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Hugo&amp;rsquo;s support of Pandoc is fairly limited. The command-line parameters it uses are hard-coded into Hugo, and they seem to just be whatever the guy who committed the PR adding Pandoc support needed (thanks to that guy, by the way. We couldn&amp;rsquo;t get even this far without him). We need to pass our own arguments to get Pandoc to filter anything.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Pandoc doesn&amp;rsquo;t support passing arguments to filters. We can tell Pandoc to execute &lt;code>node&lt;/code>, but we can&amp;rsquo;t tell Pandoc to execute &lt;code>node katex.js&lt;/code>. This seems to be a weird artifact of how Pandoc shells out to filters, at least on Windows.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>There is a simple, elegant, and generally terrible solution to both of these problems.&lt;/p>
&lt;p>The reason Hugo can call out to Pandoc, and the reason Pandoc can call out to Node, at all, is because they&amp;rsquo;re both in &lt;code>PATH&lt;/code>. So, we just have to arrange &lt;code>PATH&lt;/code> to our liking before running Hugo. I always run Hugo from VSCode, so I can do this on a per-project basis, and not contaminate the rest of my system with this, ah, workaround.&lt;/p>
&lt;p>I didn&amp;rsquo;t investigate the best way to do this; I suspect on Linux it would be a bit easier. But, on Windows, I didn&amp;rsquo;t want to think too hard about it, so I just compiled a couple C programs. Now I have the following folder structure:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">blog/
├── content/
├── ...
└── pandoc/
├── node_modules/
├── katex.exe
├── katex.js
└── pandoc.exe
&lt;/code>&lt;/pre>&lt;p>The first directory in &lt;code>PATH&lt;/code> whenever I run Hugo is &lt;code>path/to/blog/pandoc&lt;/code>. &lt;code>node_modules&lt;/code> contains only Katex, required by &lt;code>katex.js&lt;/code>, the pandoc filter. &lt;code>katex.exe&lt;/code> opens a pipe to &lt;code>node pandoc/katex.js&lt;/code> and &lt;code>pandoc.exe&lt;/code> opens a pipe to &lt;code>pandoc --katex --filter=katex&lt;/code>. Full paths to these are hard-coded into the executables, because I never learn.&lt;/p>
&lt;p>Then, in the front matter for a post that needs Katex, we use the Pandoc renderer:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-terminal" data-lang="terminal">---
title: &amp;quot;Static Katex With Hugo&amp;quot;
date: 2019-12-16T02:01:49Z
markup: pandoc
---
&lt;/code>&lt;/pre>&lt;p>The code for the executables and Pandoc filter are pretty rote. &lt;a href="https://gist.github.com/graemephi/7c60093f342b6dabf00d976492b6c91f">Here&amp;rsquo;s a gist, if you&amp;rsquo;re interested.&lt;/a>&lt;/p>
&lt;p>There&amp;rsquo;s one big problem with this. It&amp;rsquo;s slow. For every post with maths to typeset, we have to shell out to pandoc and then it has to shell out to Node. This takes around 500ms per page for me, as opposed to 40ms for other pages. Pandoc without a filter, by the way, takes about 80ms. I can live with this for single posts as I&amp;rsquo;m writing, but I suspect before long I won&amp;rsquo;t be able to stand how long a full build takes.&lt;/p>
&lt;p>That 500ms is mostly spent starting up Node and initialising Katex. So, one solution is to keep Node running and pass all the posts to a single instance. That doesn&amp;rsquo;t sound too hard to do myself, but my hope is Hugo will have made some progress on that themselves before I feel the need to&amp;ndash;there are now so many preprocessors written in javascript they&amp;rsquo;re already thinking about it, as far as I know. But what you really want is a LaTeX to HTML renderer written in Go.&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mi>i&lt;/mi>&lt;mi>π&lt;/mi>&lt;/mrow>&lt;/msup>&lt;mo>+&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo>=&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex"> e^{i\pi} + 1 = 0 &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.9579939999999999em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8746639999999999em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.03588em;">π&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p></description></item><item><title>The Discrete Fourier Transform, But With Triangles</title><link>https://graemephi.github.io/posts/triangle-dft/</link><pubDate>Sat, 14 Dec 2019 18:34:03 +0100</pubDate><guid>https://graemephi.github.io/posts/triangle-dft/</guid><description>&lt;p>Here&amp;rsquo;s something I&amp;rsquo;ve been wondering about lately: what happens if you replace all the sine waves in the fourier transform with triangle waves? If you are like me and would like to play around with such a thing, this post has a method to get a representation of a signal as scaled-and-shifted &lt;em>band-limited&lt;/em> triangle waves in &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>O&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>n&lt;/mi>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mi>n&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">O(n\log n)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.02778em;">O&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> time. This is fast enough that we can compute first and ask questions about what it&amp;rsquo;s good for later (I&amp;rsquo;ve yet to find anything). Actually, the stuff in this post will work for any periodic function, not just triangle waves, but this was the idea that motivated me to figure this out.&lt;/p>
&lt;p>To get more precise in what I&amp;rsquo;m trying to do here, here&amp;rsquo;s what I want. The DFT takes a signal made up of sine waves with amplitudes &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>a&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">a_k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.58056em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, phases &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>θ&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\theta_k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.84444em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.02778em;">θ&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and integer frequencies &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>, and produces a signal that has the value &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>a&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mi>i&lt;/mi>&lt;msub>&lt;mi>θ&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">a_ke^{i\theta_k}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.9991079999999999em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8491079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.02778em;">θ&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.3448em;">&lt;span style="top:-2.3487714285714287em;margin-left:-0.02778em;margin-right:0.07142857142857144em;">&lt;span class="pstrut" style="height:2.5em;">&lt;/span>&lt;span class="sizing reset-size3 size1 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15122857142857138em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>. I want a transform that does the same for triangle waves.&lt;/p>
&lt;p>I had a few bad starts on this&amp;ndash;for one thing, triangle waves are not orthogonal to each other at all&amp;ndash;but it all came together when I asked the following question: What is a cosine wave given as a sum of triangle waves?&lt;/p>
&lt;p>First, we need a definition of a triangle wave. With &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>K&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">K&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.07153em;">K&lt;/span>&lt;/span>&lt;/span>&lt;/span> being the number of harmonics and &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>m&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>2&lt;/mn>&lt;mi>k&lt;/mi>&lt;mo>+&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">m = 2k + 1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.77777em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>,&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;mrow>&lt;mi>K&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/munderover>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;msup>&lt;mi>m&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mfrac>&lt;mi>cos&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>2&lt;/mn>&lt;mi>π&lt;/mi>&lt;mi>m&lt;/mi>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex"> \text{tri}(x) = \sum_{k=0}^{K-1} \frac{1}{m^2} \cos(2\pi mx). &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:3.1304490000000005em;vertical-align:-1.302113em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8283360000000002em;">&lt;span style="top:-1.8478869999999998em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.0500049999999996em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.300005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.07153em;">K&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.302113em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.32144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.740108em;">&lt;span style="top:-2.9890000000000003em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">cos&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>I&amp;rsquo;ve adapted&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> this from the first of many definitions of a triangle wave on &lt;a href="https://en.wikipedia.org/wiki/Triangle_wave">Wikipedia&lt;/a>, where it&amp;rsquo;s presented without justification or citation. Personally, I don&amp;rsquo;t understand where it comes from, but it is pretty much the frequency domain represention of a triangle wave and that&amp;rsquo;s all we need. For the rest of this post, I&amp;rsquo;m going to leave &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>K&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">K&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.07153em;">K&lt;/span>&lt;/span>&lt;/span>&lt;/span> implicitly defined as the largest value it can take without introducing aliasing in its current context.&lt;/p>
&lt;p>For now, we just want to think about finding the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">X_k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> that gives us&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>cos&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>2&lt;/mn>&lt;mi>π&lt;/mi>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/munderover>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>k&lt;/mi>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex"> \cos(2\pi \textbf{x}) = \sum_{k=0}^{N-1} X_k\text{tri}(k\textbf{x}) &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mop">cos&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:3.1304490000000005em;vertical-align:-1.302113em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8283360000000002em;">&lt;span style="top:-1.8478869999999998em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.0500049999999996em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.300005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.302113em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>where &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">[&lt;/mo>&lt;mi>n&lt;/mi>&lt;mo stretchy="false">]&lt;/mo>&lt;mo>=&lt;/mo>&lt;mi>n&lt;/mi>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\textbf{x}[n] = n/N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mopen">[&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mclose">]&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Restricting ourselves to band-limited triangle waves gives us the fact that the lowest frequency of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>k&lt;/mi>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{tri}(k\textbf{x})&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> is &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Then &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{tri}(\textbf{x})&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">k = 1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span> is the only member of our series that is not orthogonal to &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>cos&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>2&lt;/mn>&lt;mi>π&lt;/mi>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\cos(2\pi \textbf{x})&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mop">cos&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">2&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>, so we know &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>X&lt;/mi>&lt;mn>1&lt;/mn>&lt;/msub>&lt;mo>=&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">X_1 = 1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>. But this introduces odd harmonics all the way up, and so the coefficient of the next odd triangle wave at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>3&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">k = 3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">3&lt;/span>&lt;/span>&lt;/span>&lt;/span> must use its lowest frequency to subtract out the first odd harmonic of the triangle wave at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">k = 1&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>, so &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>X&lt;/mi>&lt;mn>3&lt;/mn>&lt;/msub>&lt;mo>=&lt;/mo>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mn>9&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">X_3 = -1/9&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.30110799999999993em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">3&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord">9&lt;/span>&lt;/span>&lt;/span>&lt;/span>. This adds yet more harmonics for later triangle waves to clean up. We keep going like this until we&amp;rsquo;ve accounted for all &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> triangle waves.&lt;/p>
&lt;p>If that was too quick, here it is in pictures. The frequency domain representation is overlaid on top.&lt;/p>
&lt;img src="https://graemephi.github.io/posts/triangle-dft/cosine.png" width="714" height="625" loading="lazy" />
&lt;p>It&amp;rsquo;s not hard to see that this logic mostly works for arbitrary functions, too. We lose the orthogonality to kick off the process, but the whole point of the DFT is that sines and cosines form an orthogonal basis, so we only need orthogonality to the lowest frequency. As we go, we successively remove frequencies, so there is always a new lowest frequency to remove&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>Before we can implement this we also need to handle the phase of each frequency component. When we see &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>a&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mi>i&lt;/mi>&lt;msub>&lt;mi>θ&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">a_ke^{i\theta_k}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.9991079999999999em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">a&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8491079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.02778em;">θ&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.3448em;">&lt;span style="top:-2.3487714285714287em;margin-left:-0.02778em;margin-right:0.07142857142857144em;">&lt;span class="pstrut" style="height:2.5em;">&lt;/span>&lt;span class="sizing reset-size3 size1 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15122857142857138em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> at &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>k&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>, we need to align the phase of the lowest frequency of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>k&lt;/mi>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{tri}(kx)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>θ&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">\theta_k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.84444em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.02778em;">θ&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. We&amp;rsquo;re working in the frequency domain, so we need this thing from the shift theorem&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>n&lt;/mi>&lt;/msub>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mo>−&lt;/mo>&lt;mi>i&lt;/mi>&lt;mn>2&lt;/mn>&lt;mi>π&lt;/mi>&lt;mi>n&lt;/mi>&lt;mi mathvariant="normal">ℓ&lt;/mi>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex"> X_n e^{-i2\pi n\ell/N} &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.0879999999999999em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.151392em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.938em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">−&lt;/span>&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;span class="mord mtight">ℓ&lt;/span>&lt;span class="mord mtight">/&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>to apply a shift of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="normal">ℓ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\ell&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">ℓ&lt;/span>&lt;/span>&lt;/span>&lt;/span>. In our case, &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>X&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">X&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;/span>&lt;/span>&lt;/span> contains the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;mi mathvariant="normal">/&lt;/mi>&lt;msup>&lt;mi>m&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">1/m^2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">/&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">m&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> factors of the current triangle wave. We just need to know &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="normal">ℓ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\ell&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">ℓ&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Now, the lowest frequency of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>k&lt;/mi>&lt;mi>x&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{tri}(kx)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mord mathdefault">x&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> is &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">X_k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, so we want to choose &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="normal">ℓ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\ell&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">ℓ&lt;/span>&lt;/span>&lt;/span>&lt;/span> to satisfy&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>n&lt;/mi>&lt;/msub>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mo>−&lt;/mo>&lt;mi>i&lt;/mi>&lt;mn>2&lt;/mn>&lt;mi>π&lt;/mi>&lt;mi>n&lt;/mi>&lt;mi mathvariant="normal">ℓ&lt;/mi>&lt;mi mathvariant="normal">/&lt;/mi>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;/msup>&lt;mo>=&lt;/mo>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;msup>&lt;mi>e&lt;/mi>&lt;mrow>&lt;mi>i&lt;/mi>&lt;msub>&lt;mi>θ&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;/msup>&lt;mspace width="1em"/>&lt;mtext>if &lt;/mtext>&lt;mi>n&lt;/mi>&lt;mo>=&lt;/mo>&lt;mi>k&lt;/mi>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex"> X_n e^{-i2\pi n\ell/N} = X_k e^{i\theta_k} \quad \text{if } n = k.&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.0879999999999999em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.151392em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.938em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">−&lt;/span>&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mtight">2&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.03588em;">π&lt;/span>&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;span class="mord mtight">ℓ&lt;/span>&lt;span class="mord mtight">/&lt;/span>&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.0491079999999997em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">e&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8991079999999998em;">&lt;span style="top:-3.113em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">i&lt;/span>&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.02778em;">θ&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.3448em;">&lt;span style="top:-2.3487714285714287em;margin-left:-0.02778em;margin-right:0.07142857142857144em;">&lt;span class="pstrut" style="height:2.5em;">&lt;/span>&lt;span class="sizing reset-size3 size1 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15122857142857138em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:1em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">if &lt;/span>&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.69444em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>So&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi mathvariant="normal">ℓ&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;mo>=&lt;/mo>&lt;mo>−&lt;/mo>&lt;mfrac>&lt;mi>N&lt;/mi>&lt;mrow>&lt;mn>2&lt;/mn>&lt;mi>π&lt;/mi>&lt;/mrow>&lt;/mfrac>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mi>k&lt;/mi>&lt;/mfrac>&lt;msub>&lt;mi>θ&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex"> \ell_k = -\frac{N}{2\pi}\frac{1}{k} \theta_k. &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.84444em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">ℓ&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.04633em;vertical-align:-0.686em;">&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.36033em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.32144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.02778em;">θ&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>This part seemed obvious when I was just turning ideas in my head into code but now that I write it out like this I&amp;rsquo;m not so sure.&lt;/p>
&lt;p>This is all we need to find the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;/mrow>&lt;annotation encoding="application/x-tex">X_k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.83333em;vertical-align:-0.15em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> in&lt;/p>
&lt;p>&lt;span class="katex-display">&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>f&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>=&lt;/mo>&lt;munderover>&lt;mo>∑&lt;/mo>&lt;mrow>&lt;mi>k&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;mrow>&lt;mi>N&lt;/mi>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/munderover>&lt;mi mathvariant="normal">∣&lt;/mi>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;mi mathvariant="normal">∣&lt;/mi>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>k&lt;/mi>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo>+&lt;/mo>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mrow>&lt;mn>2&lt;/mn>&lt;mi>π&lt;/mi>&lt;/mrow>&lt;/mfrac>&lt;mi>arg&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>X&lt;/mi>&lt;mi>k&lt;/mi>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;mo stretchy="false">)&lt;/mo>&lt;mi mathvariant="normal">.&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex"> f(\textbf{x}) = \sum_{k=0}^{N-1} |X_k|\text{tri}(k\textbf{x} + \frac{1}{2\pi}\arg(X_k)). &lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10764em;">f&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:3.1304490000000005em;vertical-align:-1.302113em;">&lt;/span>&lt;span class="mop op-limits">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.8283360000000002em;">&lt;span style="top:-1.8478869999999998em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mrel mtight">=&lt;/span>&lt;span class="mord mtight">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.0500049999999996em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span>&lt;span class="mop op-symbol large-op">∑&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-4.300005em;margin-left:0em;">&lt;span class="pstrut" style="height:3.05em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.10903em;">N&lt;/span>&lt;span class="mbin mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.302113em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord">∣&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">∣&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03148em;">k&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:2.00744em;vertical-align:-0.686em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:1.32144em;">&lt;span style="top:-2.314em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">2&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">π&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.677em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.686em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">ar&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault" style="margin-right:0.07847em;">X&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.33610799999999996em;">&lt;span style="top:-2.5500000000000003em;margin-left:-0.07847em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.15em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;span class="mord">.&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/p>
&lt;p>Iterative algorithms spelled out in maths notation always feel a bit clunky to me, so rather than doing that, here&amp;rsquo;s some Python code to do it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4">&lt;code class="language-python" data-lang="python">&lt;span style="color:#f92672">import&lt;/span> np
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">cis&lt;/span>(t):
&lt;span style="color:#66d9ef">return&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>cos(t) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>j&lt;span style="color:#f92672">*&lt;/span>np&lt;span style="color:#f92672">.&lt;/span>sin(t)
&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">inverse_tri_sum_bandlimited&lt;/span>(x):
&lt;span style="color:#66d9ef">assert&lt;/span>(np&lt;span style="color:#f92672">.&lt;/span>isrealobj(x)) &lt;span style="color:#75715e"># real signals only. i&amp;#39;m lazy&lt;/span>
n &lt;span style="color:#f92672">=&lt;/span> len(x)
nyquist &lt;span style="color:#f92672">=&lt;/span> n &lt;span style="color:#f92672">//&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>
y &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>fft&lt;span style="color:#f92672">.&lt;/span>fft(x)
m &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(&lt;span style="color:#ae81ff">3&lt;/span>, nyquist, &lt;span style="color:#ae81ff">2&lt;/span>)
coefs &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">/&lt;/span> (m&lt;span style="color:#f92672">*&lt;/span>m)
&lt;span style="color:#66d9ef">for&lt;/span> k &lt;span style="color:#f92672">in&lt;/span> range(&lt;span style="color:#ae81ff">1&lt;/span>, nyquist&lt;span style="color:#f92672">//&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>):
&lt;span style="color:#75715e"># indices of the non-zero components of a triangle wave&lt;/span>
&lt;span style="color:#75715e"># with fundamental frequency k, not including the fundamental&lt;/span>
nz &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>arange(k&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>, nyquist, k&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>)
&lt;span style="color:#75715e"># construct the frequency domain of the triangle wave and subtract it off&lt;/span>
y[nz] &lt;span style="color:#f92672">-=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>abs(y[k]) &lt;span style="color:#f92672">*&lt;/span> coefs[:len(nz)] &lt;span style="color:#f92672">*&lt;/span> cis(nz &lt;span style="color:#f92672">*&lt;/span> (np&lt;span style="color:#f92672">.&lt;/span>angle(y[k]) &lt;span style="color:#f92672">/&lt;/span> k))
&lt;span style="color:#75715e"># fix up conjugate symmetry&lt;/span>
y[nyquist&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>:] &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>conj(y[nyquist&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>:&lt;span style="color:#ae81ff">0&lt;/span>:&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>])
&lt;span style="color:#66d9ef">return&lt;/span> y&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This makes use of the observation that once a frequency in &lt;code>y&lt;/code> has been zeroed out we never look at it again, so we can do the algorithm in-place. Also, band-limited triangle waves above half the nyquist frequency are just sine waves, so we don&amp;rsquo;t need to consider them at all.&lt;/p>
&lt;h2 id="some-graphs-to-think-about">Some Graphs To Think About&lt;/h2>
&lt;p>The nice thing about this algorithm is the result is in the same representation as given by the DFT. So, if the DFT is &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="script">F&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\mathcal{F}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathcal" style="margin-right:0.09931em;">F&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> and our triangle transform is &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="script">T&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\mathcal{T}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathcal" style="margin-right:0.25417em;">T&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>, then we can replace all the triangle waves in a function with sine waves by applying &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msup>&lt;mi mathvariant="script">F&lt;/mi>&lt;mrow>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;/mrow>&lt;/msup>&lt;mi mathvariant="script">T&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\mathcal{F}^{-1}\mathcal{T}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.8141079999999999em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord">&lt;span class="mord mathcal" style="margin-right:0.09931em;">F&lt;/span>&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">−&lt;/span>&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mord">&lt;span class="mord mathcal" style="margin-right:0.25417em;">T&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Right? So, this does what you might expect:&lt;/p>
&lt;img src="https://graemephi.github.io/posts/triangle-dft/tritri.png" width="600" height="400" loading="lazy" />
&lt;p>But look at this:&lt;/p>
&lt;img src="https://graemephi.github.io/posts/triangle-dft/tribad.png" width="600" height="400" loading="lazy" />
&lt;p>What gives? Well, this is a problem the DFT has, too. We&amp;rsquo;re getting a representation of the signal as a sum of triangle waves but &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mtext>tri&lt;/mtext>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>1.5&lt;/mn>&lt;mtext mathvariant="bold">x&lt;/mtext>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\text{tri}(1.5\textbf{x})&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord text">&lt;span class="mord">tri&lt;/span>&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">5&lt;/span>&lt;span class="mord text">&lt;span class="mord textbf">x&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> isn&amp;rsquo;t one of them. In the analogous case for the DFT this isn&amp;rsquo;t so bad because it ends up being represented by nearby sine waves. You get a nice exponential drop-off away from the true frequency. For triangle waves, you don&amp;rsquo;t have that at all. Now, this is without windowing, so there is also a big jump between the last and first sample that we probably ought to do something about. Maybe if we apply a triangular window&amp;hellip;&lt;/p>
&lt;img src="https://graemephi.github.io/posts/triangle-dft/tristillbad.png" width="600" height="400" loading="lazy" />
&lt;p>I don&amp;rsquo;t know what I expected.&lt;/p>
&lt;p>Anyway, the reason I did all this was to see if anything interesting happened when you messed around with it. For example, we can swap out &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="script">F&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\mathcal{F}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathcal" style="margin-right:0.09931em;">F&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="script">T&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\mathcal{T}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathcal" style="margin-right:0.25417em;">T&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> in the &lt;a href="https://en.wikipedia.org/wiki/Convolution_theorem">convolution theorem&lt;/a>. Here is a gaussian filter applied in the &amp;ldquo;triangle domain&amp;rdquo;, with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>σ&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sigma=2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">σ&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;/span>&lt;/span>&lt;/span> and with &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>σ&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>100&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sigma=100&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">σ&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mord">0&lt;/span>&lt;span class="mord">0&lt;/span>&lt;/span>&lt;/span>&lt;/span>. &lt;a href="https://commons.wikimedia.org/wiki/File:Mandrill_Albert_September_2015_Zoo_Berlin_(2).jpg">Image from Wikimedia&lt;/a>.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/triangle-dft/mandril.png" width="963" height="321" loading="lazy" />
&lt;/div>
&lt;p>Interesting! I don&amp;rsquo;t really have an intuition for what&amp;rsquo;s happening on for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>σ&lt;/mi>&lt;mo>=&lt;/mo>&lt;mn>2&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sigma = 2&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">σ&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;span class="mrel">=&lt;/span>&lt;span class="mspace" style="margin-right:0.2777777777777778em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.64444em;vertical-align:0em;">&lt;/span>&lt;span class="mord">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>. Larger values of &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>σ&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sigma&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.03588em;">σ&lt;/span>&lt;/span>&lt;/span>&lt;/span> look a bit more like what I expected.&lt;/p>
&lt;p>I also tried filtering audio this way. Removing high frequency triangle waves&amp;ndash;maybe I should give those a cute name like triquencies, because strictly speaking each one is multiple frequencies so I find it weird to refer to them that way&amp;ndash;introduces high harmonics of low frequency content in the signal. This sounds really nice if you have something with lots of harmonic material&amp;ndash;you get a nice wash of soft sawtooth buzz&amp;ndash;and pretty bad on something with sparse hamonics, like a plucked string. It would be nice to be able to use this for some audio effect, buuut &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi mathvariant="script">T&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">\mathcal{T}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord">&lt;span class="mord mathcal" style="margin-right:0.25417em;">T&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> isn&amp;rsquo;t linear, so you can&amp;rsquo;t use the usual methods to convolve small filters with long signals. Oh, well.&lt;/p>
&lt;h2 id="odds-and-ends">Odds and Ends&lt;/h2>
&lt;p>&lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>O&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>n&lt;/mi>&lt;mi>log&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;mi>n&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">O(n\log n)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.02778em;">O&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mop">lo&lt;span style="margin-right:0.01389em;">g&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.16666666666666666em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>. I didn&amp;rsquo;t spell out this claim, however the naive algorithm is &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>O&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;msup>&lt;mi>n&lt;/mi>&lt;mn>2&lt;/mn>&lt;/msup>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">O(n^2)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.064108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.02778em;">O&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.8141079999999999em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span>. But if we skip triangle wave frequencies that are zero then we only need to do &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>n&lt;/mi>&lt;mo stretchy="false">(&lt;/mo>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mn>2&lt;/mn>&lt;/mfrac>&lt;mo>+&lt;/mo>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mn>4&lt;/mn>&lt;/mfrac>&lt;mo>+&lt;/mo>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mn>6&lt;/mn>&lt;/mfrac>&lt;mo>+&lt;/mo>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mn>8&lt;/mn>&lt;/mfrac>&lt;mo>+&lt;/mo>&lt;mi mathvariant="normal">.&lt;/mi>&lt;mi mathvariant="normal">.&lt;/mi>&lt;mi mathvariant="normal">.&lt;/mi>&lt;mo>+&lt;/mo>&lt;mfrac>&lt;mn>1&lt;/mn>&lt;mi>n&lt;/mi>&lt;/mfrac>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">n(\frac{1}{2} + \frac{1}{4} + \frac{1}{6} + \frac{1}{8} + ... + \frac{1}{n})&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.190108em;vertical-align:-0.345em;">&lt;/span>&lt;span class="mord mathdefault">n&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.6550000000000002em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">2&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.345em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.190108em;vertical-align:-0.345em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.6550000000000002em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">4&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.345em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.190108em;vertical-align:-0.345em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.6550000000000002em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">6&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.345em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.190108em;vertical-align:-0.345em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.6550000000000002em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">8&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.345em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:0.66666em;vertical-align:-0.08333em;">&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mord">.&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;span class="mbin">+&lt;/span>&lt;span class="mspace" style="margin-right:0.2222222222222222em;">&lt;/span>&lt;/span>&lt;span class="base">&lt;span class="strut" style="height:1.190108em;vertical-align:-0.345em;">&lt;/span>&lt;span class="mord">&lt;span class="mopen nulldelimiter">&lt;/span>&lt;span class="mfrac">&lt;span class="vlist-t vlist-t2">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.845108em;">&lt;span style="top:-2.6550000000000002em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mathdefault mtight">n&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span style="top:-3.23em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="frac-line" style="border-bottom-width:0.04em;">&lt;/span>&lt;/span>&lt;span style="top:-3.394em;">&lt;span class="pstrut" style="height:3em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mtight">&lt;span class="mord mtight">1&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="vlist-s">​&lt;/span>&lt;/span>&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.345em;">&lt;span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;span class="mclose nulldelimiter">&lt;/span>&lt;/span>&lt;span class="mclose">)&lt;/span>&lt;/span>&lt;/span>&lt;/span> operations. Apparently, that series grows logarithmically.&lt;/p>
&lt;p>What about non-band-limited triangle waves? Well, it turns out the band-limited version is close enough you can just stick it in a non-linear least-squares solver and it&amp;rsquo;ll find you something near machine precision, despite the discontinuous Jacobian. But it&amp;rsquo;s just too slow for large problems to be all that interesting. Another thought is that if the problem is just that aliasing causes us to lose orthogonality, then can we not just upsample until the first &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>N&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">N&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.68333em;vertical-align:0em;">&lt;/span>&lt;span class="mord mathdefault" style="margin-right:0.10903em;">N&lt;/span>&lt;/span>&lt;/span>&lt;/span> band-limited triangle waves are effectively identical to non-band-limited? But when I tried this it would produce better results up to around 64x upsampling, and then stop, not even close to the least-squares method. I don&amp;rsquo;t really understand this at all! It could just be the problem is ill-defined but it seems unlikely to me.&lt;/p>
&lt;p>At the top I said this would work for any periodic function: the DFT of any signal can be used in place of the fourier series of the triangle wave. But I&amp;rsquo;m not so interested in trying out other exotic functions like square waves as they would all have the problems laid out here, just because of how the series of functions was constructed. By band-limiting each basis function, we get a nice understanding of how the signal spans the fourier basis once shifted and sampled. But by that same token, only the complex exponential results in a series of orthogonal functions when constructed this way.&lt;/p>
&lt;p>Now I can stop thinking about this.&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>Notably, I&amp;rsquo;ve swapped out the &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>sin&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\sin&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.66786em;vertical-align:0em;">&lt;/span>&lt;span class="mop">sin&lt;/span>&lt;/span>&lt;/span>&lt;/span> for &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>cos&lt;/mi>&lt;mo>⁡&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">\cos&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:0.43056em;vertical-align:0em;">&lt;/span>&lt;span class="mop">cos&lt;/span>&lt;/span>&lt;/span>&lt;/span>, which also makes &lt;span class="katex">&lt;span class="katex-mathml">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mo>−&lt;/mo>&lt;mn>1&lt;/mn>&lt;msup>&lt;mo stretchy="false">)&lt;/mo>&lt;mi>k&lt;/mi>&lt;/msup>&lt;/mrow>&lt;annotation encoding="application/x-tex">(-1)^k&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>&lt;span class="katex-html" aria-hidden="true">&lt;span class="base">&lt;span class="strut" style="height:1.099108em;vertical-align:-0.25em;">&lt;/span>&lt;span class="mopen">(&lt;/span>&lt;span class="mord">−&lt;/span>&lt;span class="mord">1&lt;/span>&lt;span class="mclose">&lt;span class="mclose">)&lt;/span>&lt;span class="msupsub">&lt;span class="vlist-t">&lt;span class="vlist-r">&lt;span class="vlist" style="height:0.849108em;">&lt;span style="top:-3.063em;margin-right:0.05em;">&lt;span class="pstrut" style="height:2.7em;">&lt;/span>&lt;span class="sizing reset-size6 size3 mtight">&lt;span class="mord mathdefault mtight" style="margin-right:0.03148em;">k&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span>&lt;/span> term you might be familiar with disappear. I do this because in the DFT, cosines with zero phase are represented by purely real frequency components. If we align our triangle wave with cosine, then our triangle wave is also made of purely real frequency components. This makes the discussion a little easier.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>This is basically &lt;a href="https://en.wikipedia.org/wiki/Matching_pursuit">matching pursuit&lt;/a> with a specific dictionary and a method to cheaply compute the next atom to remove.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>Dumb Tricks With Phase Inversion</title><link>https://graemephi.github.io/posts/dumb-tricks-with-phase-inversion/</link><pubDate>Sun, 02 Jun 2019 12:34:03 +0100</pubDate><guid>https://graemephi.github.io/posts/dumb-tricks-with-phase-inversion/</guid><description>&lt;p>Phase inversion takes a signal
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 54 18" width="54pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 54 18
L 54 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#peaaa2fd3e3)" d="M 2.454545 9
L 5.160344 7.124681
L 6.319971 6.545069
L 7.479599 6.207045
L 8.639227 6.150278
L 9.798855 6.380754
L 10.958482 6.863052
L 13.277738 8.210091
L 14.437366 8.79151
L 15.596994 9.099458
L 16.370079 9.088321
L 17.143164 8.878447
L 17.916249 8.469792
L 19.075877 7.541774
L 21.781675 5.054015
L 22.55476 4.662304
L 23.327845 4.567274
L 24.100931 4.814686
L 24.874016 5.407444
L 25.647101 6.29775
L 28.352899 9.956507
L 29.125984 10.46926
L 29.512527 10.540695
L 29.899069 10.474129
L 30.285612 10.265005
L 31.058697 9.431231
L 31.831782 8.128533
L 34.151038 3.664421
L 34.537581 3.224864
L 34.924123 2.964516
L 35.310666 2.906228
L 35.697208 3.06378
L 36.083751 3.440397
L 36.856836 4.805238
L 38.016464 7.901971
L 39.176092 10.989569
L 39.562634 11.72477
L 39.949177 12.217402
L 40.335719 12.424677
L 40.722262 12.31873
L 41.108805 11.88961
L 41.495347 11.14738
L 42.268432 8.868333
L 44.201145 2.038144
L 44.587688 1.234146
L 44.97423 0.818182
L 45.360773 0.840679
L 45.747316 1.32304
L 46.133858 2.253693
L 46.906943 5.240711
L 48.453114 12.560401
L 48.839656 13.831438
L 49.226199 14.606821
L 49.612742 14.796036
L 49.999284 14.352468
L 50.385827 13.28096
L 51.158912 9.548236
L 51.545455 7.163497
L 51.545455 7.163497
" style="fill:none;stroke:#cc0d1a;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="peaaa2fd3e3">
&lt;rect height="18" width="54" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
and flips it upside down
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 54 18" width="54pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 54 18
L 54 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p6eb91deae1)" d="M 2.454545 9
L 5.160344 10.875319
L 6.319971 11.454931
L 7.479599 11.792955
L 8.639227 11.849722
L 9.798855 11.619246
L 10.958482 11.136948
L 13.277738 9.789909
L 14.437366 9.20849
L 15.596994 8.900542
L 16.370079 8.911679
L 17.143164 9.121553
L 17.916249 9.530208
L 19.075877 10.458226
L 21.781675 12.945985
L 22.55476 13.337696
L 23.327845 13.432726
L 24.100931 13.185314
L 24.874016 12.592556
L 25.647101 11.70225
L 28.352899 8.043493
L 29.125984 7.53074
L 29.512527 7.459305
L 29.899069 7.525871
L 30.285612 7.734995
L 31.058697 8.568769
L 31.831782 9.871467
L 34.151038 14.335579
L 34.537581 14.775136
L 34.924123 15.035484
L 35.310666 15.093772
L 35.697208 14.93622
L 36.083751 14.559603
L 36.856836 13.194762
L 38.016464 10.098029
L 39.176092 7.010431
L 39.562634 6.27523
L 39.949177 5.782598
L 40.335719 5.575323
L 40.722262 5.68127
L 41.108805 6.11039
L 41.495347 6.85262
L 42.268432 9.131667
L 44.201145 15.961856
L 44.587688 16.765854
L 44.97423 17.181818
L 45.360773 17.159321
L 45.747316 16.67696
L 46.133858 15.746307
L 46.906943 12.759289
L 48.453114 5.439599
L 48.839656 4.168562
L 49.226199 3.393179
L 49.612742 3.203964
L 49.999284 3.647532
L 50.385827 4.71904
L 51.158912 8.451764
L 51.545455 10.836503
L 51.545455 10.836503
" style="fill:none;stroke:#33f2e6;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p6eb91deae1">
&lt;rect height="18" width="54" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
. Most people are introduced to it as a button you push in a DAW while troubleshooting phase problems, but with some thinking it&amp;rsquo;s capable of a lot more than that. Some digging in the audio DSP literature will uncover a stray sentence or two that acknowledges this, but I get the impression that it strikes the people who already know it as too obvious to be worth mentioning. The really nice thing about it is that it all works in a DAW&amp;ndash;you don&amp;rsquo;t need to break out Max/MSP or Reaktor to experiment with it.&lt;/p>
&lt;p>I&amp;rsquo;m pretty sure I originally got the basic idea for all this from &lt;a href="https://vladgsound.wordpress.com/">vladg/sound&lt;/a>.&lt;/p>
&lt;blockquote class="aside">My intuition for this is fairly algebraic so I&amp;rsquo;ll be leaning on that a little, but all you need to have is an intuition for what is happening when you route audio around in a DAW. That said, I will be using notation from digital signal processing that&amp;rsquo;s too useful to give up: &lt;code>x[n]&lt;/code> reads as the &lt;code>n&lt;/code>th sample of a signal &lt;code>x&lt;/code>. Then &lt;code>c[n] = a[n] + b[n]&lt;/code> describes &lt;code>c&lt;/code> by telling you how to compute the &lt;code>n&lt;/code>th sample of &lt;code>c&lt;/code> in terms of signals &lt;code>a&lt;/code> and &lt;code>b&lt;/code>. We could drop the &lt;code>[n]&lt;/code>, but this makes clear that we don&amp;rsquo;t need to know anything about &lt;code>a&lt;/code> or &lt;code>b&lt;/code> other than their &lt;code>n&lt;/code>th samples to compute the &lt;code>n&lt;/code>th sample of &lt;code>c&lt;/code>. For anything more complicated than this, I&amp;rsquo;ll use graphics.&lt;/blockquote>
&lt;h3 id="diffing">Diffing&lt;/h3>
&lt;p>Phase inversion negates a signal, &lt;code>y[n] = -x[n]&lt;/code>, so it turns addition (mixing) into subtraction.&lt;/p>
&lt;blockquote>
&lt;p>
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p12c0a407d9)" d="M 3.272727 18
L 4.818898 17.845997
L 5.849678 17.741868
L 6.880458 17.855032
L 8.426628 18.425267
L 9.457409 18.709453
L 9.972799 18.702718
L 10.488189 18.567014
L 11.518969 17.952004
L 13.06514 16.892645
L 13.58053 16.762056
L 14.09592 16.825713
L 14.61131 17.094047
L 15.64209 18.112505
L 16.67287 19.262823
L 17.188261 19.646872
L 17.703651 19.793136
L 18.219041 19.657607
L 18.734431 19.240425
L 19.765211 17.793954
L 20.795991 16.266843
L 21.311382 15.791182
L 21.826772 15.641467
L 22.342162 15.862589
L 22.857552 16.441138
L 23.888332 18.328225
L 24.919112 20.222477
L 25.434503 20.778483
L 25.949893 20.918844
L 26.465283 20.600297
L 26.980673 19.851828
L 28.011453 17.522566
L 29.042233 15.279856
L 29.557623 14.656688
L 30.073014 14.538279
L 30.588404 14.963911
L 31.103794 15.887203
L 32.134574 18.651331
L 33.165354 21.217699
L 33.680744 21.894009
L 34.196135 21.978618
L 34.711525 21.438309
L 35.226915 20.338218
L 37.288475 14.290754
L 37.803865 13.575329
L 38.319256 13.535371
L 38.834646 14.196227
L 39.350036 15.473029
L 41.411596 22.191119
L 41.926986 22.932112
L 42.442377 22.917582
L 42.957767 22.131613
L 43.473157 20.679516
L 45.534717 13.338901
L 46.050107 12.585222
L 46.565497 12.663164
L 47.080888 13.577831
L 47.596278 15.202935
L 49.142448 21.592114
L 49.657838 23.117561
L 50.173228 23.871713
L 50.688618 23.72224
L 51.204009 22.676109
L 51.719399 20.880997
L 53.265569 14.025774
L 53.780959 12.441158
L 54.296349 11.698141
L 54.811739 11.9265
L 55.32713 13.106045
L 55.84252 15.067407
L 57.38869 22.352921
L 57.90408 23.982944
L 58.41947 24.703785
L 58.93486 24.389972
L 59.450251 23.075968
L 59.965641 20.95301
L 61.511811 13.274362
L 62.027201 11.612473
L 62.542591 10.924272
L 63.057981 11.329249
L 63.573372 12.777742
L 64.604152 17.820624
L 65.634932 23.089597
L 66.150322 24.770043
L 66.665712 25.41577
L 67.181102 24.914856
L 67.696492 23.332948
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p12c0a407d9">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
+
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p4619e8d299)" d="M 3.272727 18
L 4.303508 14.234528
L 4.818898 12.817613
L 5.334288 11.879652
L 5.849678 11.493518
L 6.365068 11.681743
L 6.880458 12.415541
L 7.395848 13.618464
L 8.426628 16.938228
L 9.457409 20.451425
L 9.972799 21.894522
L 10.488189 22.96038
L 11.003579 23.565993
L 11.518969 23.671277
L 12.034359 23.281442
L 12.549749 22.445234
L 13.58053 19.809462
L 15.1267 15.377246
L 15.64209 14.290799
L 16.15748 13.563913
L 16.67287 13.247309
L 17.188261 13.353757
L 17.703651 13.858304
L 18.219041 14.70187
L 19.249821 17.040424
L 20.280601 19.510516
L 20.795991 20.52444
L 21.311382 21.276531
L 21.826772 21.712493
L 22.342162 21.808204
L 22.857552 21.57056
L 23.372942 21.035494
L 24.403722 19.33308
L 25.949893 16.480366
L 26.465283 15.782941
L 26.980673 15.313492
L 27.496063 15.100993
L 28.011453 15.150239
L 28.526843 15.442672
L 29.557623 16.585168
L 31.619184 19.343217
L 32.134574 19.777248
L 32.649964 20.030023
L 33.165354 20.091044
L 33.680744 19.967512
L 34.711525 19.272181
L 37.288475 16.953104
L 37.803865 16.719605
L 38.319256 16.615856
L 38.834646 16.640577
L 39.865426 17.014759
L 42.957767 18.694381
L 43.988547 18.80509
L 45.019327 18.631471
L 48.111668 17.714971
L 49.142448 17.648878
L 50.688618 17.792429
L 52.750179 18.025421
L 54.811739 17.992733
L 56.8733 17.940105
L 58.93486 18.126316
L 60.996421 18.283154
L 62.542591 18.141656
L 66.150322 17.552444
L 67.181102 17.632444
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#cc0d1a;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p4619e8d299">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
=
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p320c124c7d)" d="M 3.272727 18
L 4.303508 14.156411
L 4.818898 12.663609
L 5.334288 11.656456
L 5.849678 11.235386
L 6.365068 11.445756
L 6.880458 12.270572
L 7.395848 13.629849
L 8.426628 17.363495
L 9.457409 21.160878
L 9.972799 22.597239
L 10.488189 23.527394
L 11.003579 23.872996
L 11.518969 23.623281
L 12.034359 22.834729
L 13.06514 20.141969
L 14.09592 17.084997
L 14.61131 15.831926
L 15.1267 14.919721
L 15.64209 14.403304
L 16.15748 14.28292
L 16.67287 14.510132
L 17.188261 15.000628
L 19.765211 18.109491
L 20.795991 18.791283
L 22.857552 20.011698
L 23.888332 20.591713
L 24.403722 20.689369
L 24.919112 20.555522
L 25.434503 20.132505
L 25.949893 19.399209
L 26.980673 17.165319
L 28.011453 14.672804
L 28.526843 13.734837
L 29.042233 13.219134
L 29.557623 13.241856
L 30.073014 13.853625
L 30.588404 15.024997
L 31.619184 18.523505
L 32.649964 22.103026
L 33.165354 23.308743
L 33.680744 23.86152
L 34.196135 23.661183
L 34.711525 22.710489
L 35.226915 21.11931
L 37.288475 13.243858
L 37.803865 12.294934
L 38.319256 12.151227
L 38.834646 12.836804
L 39.350036 14.253993
L 41.411596 22.164595
L 41.926986 23.205633
L 42.442377 23.437028
L 42.957767 22.825995
L 43.473157 21.469213
L 45.534717 13.812567
L 46.050107 12.879496
L 46.565497 12.776185
L 47.080888 13.525376
L 47.596278 15.014732
L 49.657838 22.790513
L 50.173228 23.596116
L 50.688618 23.514669
L 51.204009 22.541863
L 51.719399 20.815029
L 53.265569 14.067047
L 53.780959 12.479096
L 54.296349 11.717791
L 54.811739 11.919234
L 55.32713 13.070634
L 55.84252 15.009995
L 57.38869 22.318484
L 57.90408 23.991093
L 58.41947 24.767633
L 58.93486 24.516287
L 59.450251 23.263612
L 59.965641 21.192338
L 61.511811 13.539017
L 62.027201 11.82922
L 62.542591 11.065928
L 63.057981 11.374104
L 63.573372 12.712357
L 64.088762 14.877851
L 65.634932 22.662657
L 66.150322 24.322487
L 66.665712 24.987951
L 67.181102 24.5473
L 67.696492 23.062357
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#e61ae6;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p320c124c7d">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
=
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p12c0a407d9)" d="M 3.272727 18
L 4.818898 17.845997
L 5.849678 17.741868
L 6.880458 17.855032
L 8.426628 18.425267
L 9.457409 18.709453
L 9.972799 18.702718
L 10.488189 18.567014
L 11.518969 17.952004
L 13.06514 16.892645
L 13.58053 16.762056
L 14.09592 16.825713
L 14.61131 17.094047
L 15.64209 18.112505
L 16.67287 19.262823
L 17.188261 19.646872
L 17.703651 19.793136
L 18.219041 19.657607
L 18.734431 19.240425
L 19.765211 17.793954
L 20.795991 16.266843
L 21.311382 15.791182
L 21.826772 15.641467
L 22.342162 15.862589
L 22.857552 16.441138
L 23.888332 18.328225
L 24.919112 20.222477
L 25.434503 20.778483
L 25.949893 20.918844
L 26.465283 20.600297
L 26.980673 19.851828
L 28.011453 17.522566
L 29.042233 15.279856
L 29.557623 14.656688
L 30.073014 14.538279
L 30.588404 14.963911
L 31.103794 15.887203
L 32.134574 18.651331
L 33.165354 21.217699
L 33.680744 21.894009
L 34.196135 21.978618
L 34.711525 21.438309
L 35.226915 20.338218
L 37.288475 14.290754
L 37.803865 13.575329
L 38.319256 13.535371
L 38.834646 14.196227
L 39.350036 15.473029
L 41.411596 22.191119
L 41.926986 22.932112
L 42.442377 22.917582
L 42.957767 22.131613
L 43.473157 20.679516
L 45.534717 13.338901
L 46.050107 12.585222
L 46.565497 12.663164
L 47.080888 13.577831
L 47.596278 15.202935
L 49.142448 21.592114
L 49.657838 23.117561
L 50.173228 23.871713
L 50.688618 23.72224
L 51.204009 22.676109
L 51.719399 20.880997
L 53.265569 14.025774
L 53.780959 12.441158
L 54.296349 11.698141
L 54.811739 11.9265
L 55.32713 13.106045
L 55.84252 15.067407
L 57.38869 22.352921
L 57.90408 23.982944
L 58.41947 24.703785
L 58.93486 24.389972
L 59.450251 23.075968
L 59.965641 20.95301
L 61.511811 13.274362
L 62.027201 11.612473
L 62.542591 10.924272
L 63.057981 11.329249
L 63.573372 12.777742
L 64.604152 17.820624
L 65.634932 23.089597
L 66.150322 24.770043
L 66.665712 25.41577
L 67.181102 24.914856
L 67.696492 23.332948
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p12c0a407d9">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
-
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p4b9a36d3ff)" d="M 3.272727 18
L 4.303508 21.765472
L 4.818898 23.182387
L 5.334288 24.120348
L 5.849678 24.506482
L 6.365068 24.318257
L 6.880458 23.584459
L 7.395848 22.381536
L 8.426628 19.061772
L 9.457409 15.548575
L 9.972799 14.105478
L 10.488189 13.03962
L 11.003579 12.434007
L 11.518969 12.328723
L 12.034359 12.718558
L 12.549749 13.554766
L 13.58053 16.190538
L 15.1267 20.622754
L 15.64209 21.709201
L 16.15748 22.436087
L 16.67287 22.752691
L 17.188261 22.646243
L 17.703651 22.141696
L 18.219041 21.29813
L 19.249821 18.959576
L 20.280601 16.489484
L 20.795991 15.47556
L 21.311382 14.723469
L 21.826772 14.287507
L 22.342162 14.191796
L 22.857552 14.42944
L 23.372942 14.964506
L 24.403722 16.66692
L 25.949893 19.519634
L 26.465283 20.217059
L 26.980673 20.686508
L 27.496063 20.899007
L 28.011453 20.849761
L 28.526843 20.557328
L 29.557623 19.414832
L 31.619184 16.656783
L 32.134574 16.222752
L 32.649964 15.969977
L 33.165354 15.908956
L 33.680744 16.032488
L 34.711525 16.727819
L 37.288475 19.046896
L 37.803865 19.280395
L 38.319256 19.384144
L 38.834646 19.359423
L 39.865426 18.985241
L 42.957767 17.305619
L 43.988547 17.19491
L 45.019327 17.368529
L 48.111668 18.285029
L 49.142448 18.351122
L 50.688618 18.207571
L 52.750179 17.974579
L 54.811739 18.007267
L 56.8733 18.059895
L 58.93486 17.873684
L 60.996421 17.716846
L 62.542591 17.858344
L 66.150322 18.447556
L 67.181102 18.367556
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#33f2e6;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p4b9a36d3ff">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
&lt;/p>
&lt;/blockquote>
&lt;p>The first thing to notice about this is if we add a signal to itself, phase inverted, we get silence.&lt;/p>
&lt;blockquote>
&lt;p>
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p5797117576)" d="M 3.272727 18
L 4.818898 7.930427
L 5.849678 2.95669
L 6.365068 1.306986
L 6.880458 0.308895
L 7.395848 0.001377
L 7.911238 0.396435
L 8.426628 1.478649
L 8.942019 3.205774
L 9.972799 8.302543
L 11.518969 18.44522
L 13.06514 28.435528
L 14.09592 33.283189
L 14.61131 34.854462
L 15.1267 35.76782
L 15.64209 35.98761
L 16.15748 35.505254
L 16.67287 34.33958
L 17.188261 32.536089
L 18.219041 27.319407
L 19.765211 17.109833
L 21.311382 7.204903
L 22.342162 2.486283
L 22.857552 0.994403
L 23.372942 0.166338
L 23.888332 0.034409
L 24.403722 0.603768
L 24.919112 1.85219
L 25.434503 3.730942
L 26.465283 9.064345
L 28.011453 19.33457
L 29.557623 29.148061
L 30.588404 33.734753
L 31.103794 35.146327
L 31.619184 35.888593
L 32.134574 35.932578
L 32.649964 35.276565
L 33.165354 33.94616
L 33.680744 31.993296
L 34.711525 26.546435
L 38.319256 4.006704
L 38.834646 2.05384
L 39.350036 0.723435
L 39.865426 0.067422
L 40.380816 0.111407
L 40.896206 0.853673
L 41.411596 2.265247
L 41.926986 4.291028
L 42.957767 9.848014
L 46.050107 29.833314
L 46.565497 32.269058
L 47.080888 34.14781
L 47.596278 35.396232
L 48.111668 35.965591
L 48.627058 35.833662
L 49.142448 35.005597
L 49.657838 33.513717
L 50.173228 31.416259
L 51.204009 25.752549
L 54.296349 5.834817
L 54.811739 3.463911
L 55.32713 1.66042
L 55.84252 0.494746
L 56.35791 0.01239
L 56.8733 0.23218
L 57.38869 1.145538
L 57.90408 2.716811
L 58.41947 4.884662
L 59.450251 10.651632
L 62.542591 30.489608
L 63.057981 32.794226
L 63.573372 34.521351
L 64.088762 35.603565
L 64.604152 35.998623
L 65.119542 35.691105
L 65.634932 34.693014
L 66.150322 33.04331
L 66.665712 30.806391
L 67.696492 24.93969
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#cc0d1a;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p5797117576">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
+
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#pa94d880bf0)" d="M 3.272727 18
L 4.818898 28.069573
L 5.849678 33.04331
L 6.365068 34.693014
L 6.880458 35.691105
L 7.395848 35.998623
L 7.911238 35.603565
L 8.426628 34.521351
L 8.942019 32.794226
L 9.972799 27.697457
L 11.518969 17.55478
L 13.06514 7.564472
L 14.09592 2.716811
L 14.61131 1.145538
L 15.1267 0.23218
L 15.64209 0.01239
L 16.15748 0.494746
L 16.67287 1.66042
L 17.188261 3.463911
L 18.219041 8.680593
L 19.765211 18.890167
L 21.311382 28.795097
L 22.342162 33.513717
L 22.857552 35.005597
L 23.372942 35.833662
L 23.888332 35.965591
L 24.403722 35.396232
L 24.919112 34.14781
L 25.434503 32.269058
L 26.465283 26.935655
L 28.011453 16.66543
L 29.557623 6.851939
L 30.588404 2.265247
L 31.103794 0.853673
L 31.619184 0.111407
L 32.134574 0.067422
L 32.649964 0.723435
L 33.165354 2.05384
L 33.680744 4.006704
L 34.711525 9.453565
L 38.319256 31.993296
L 38.834646 33.94616
L 39.350036 35.276565
L 39.865426 35.932578
L 40.380816 35.888593
L 40.896206 35.146327
L 41.411596 33.734753
L 41.926986 31.708972
L 42.957767 26.151986
L 46.050107 6.166686
L 46.565497 3.730942
L 47.080888 1.85219
L 47.596278 0.603768
L 48.111668 0.034409
L 48.627058 0.166338
L 49.142448 0.994403
L 49.657838 2.486283
L 50.173228 4.583741
L 51.204009 10.247451
L 54.296349 30.165183
L 54.811739 32.536089
L 55.32713 34.33958
L 55.84252 35.505254
L 56.35791 35.98761
L 56.8733 35.76782
L 57.38869 34.854462
L 57.90408 33.283189
L 58.41947 31.115338
L 59.450251 25.348368
L 62.542591 5.510392
L 63.057981 3.205774
L 63.573372 1.478649
L 64.088762 0.396435
L 64.604152 0.001377
L 65.119542 0.308895
L 65.634932 1.306986
L 66.150322 2.95669
L 66.665712 5.193609
L 67.696492 11.06031
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#33f2e6;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="pa94d880bf0">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
=
&lt;span class="svgi">&lt;svg height="36pt" version="1.1" viewBox="0 0 72 36" width="72pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 36
L 72 36
L 72 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p6acbd377c5)" d="M 3.272727 18
L 68.727273 18
L 68.727273 18
" style="fill:none;stroke:#777777;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p6acbd377c5">
&lt;rect height="36" width="72" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
&lt;/p>
&lt;/blockquote>
&lt;p>There is a vocals isolation trick that relies on this. That&amp;rsquo;s not what this post is about.&lt;/p>
&lt;p>What we&amp;rsquo;re interested in is signals before and after applying an audio effect. Some audio plugins have this built-in; Ableton&amp;rsquo;s EQ8 allows you to listen to each equaliser band in isolation. You could always do this yourself by flipping the phase of the equalised signal and mixing it back in with the original. Doing so in a DAW can be cumbersome, so when plugins do it for you, it can be very nice if this is all you want. But there is nothing stopping you taking the difference of a whole chain of effects, and you have to do it yourself for plugins that don&amp;rsquo;t have this feature.&lt;/p>
&lt;p>But there&amp;rsquo;s a consequence to this that is not obvious. We can add the difference signal to the dry signal to recreate the wet signal. Algebraically, &lt;code>b[n] = a[n] + (b[n] - a[n])&lt;/code>. &lt;em>This&lt;/em> is what this post is about. Whenever we have an effect in a signal chain, we can interpret the effect as &lt;em>mixing in&lt;/em> a difference signal to change the dry audio to the wet audio, and we are free to do any processing we want on the difference signal. Now, this is an interpretation, and it&amp;rsquo;s a wrong one. But we can have some fun with it.&lt;/p>
&lt;p>By the way, I&amp;rsquo;m trying to keep this post DAW-agnostic but at the end I describe how I do this in Ableton Live in a self-contained way that doesn&amp;rsquo;t involve six hundred sends. So, check that out if you&amp;rsquo;re confused how to actually do this in a DAW.&lt;/p>
&lt;h3 id="drywet-mixing">Dry/Wet Mixing&lt;/h3>
&lt;p>The simplest thing we can do with the difference signal idea is to implement dry/wet mixing. This is less useful for single effects, which have this built in, than it is for effect chains, which don&amp;rsquo;t.&lt;/p>
&lt;p>This is easy: turn the gain down on the difference signal. This will fade between from the wet to the dry signal. But if you&amp;rsquo;re willing to deal with a little algebra I think it&amp;rsquo;s worth exploring to what extent this is really dry/wet mixing, or something else. In dry/wet mixing, we mix two signals in such a way that the net gain sums to one,&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>c[n] = g*a[n] + (1 - g)*b[n]&lt;/code>,&lt;/p>
&lt;/blockquote>
&lt;p>where &lt;code>g&lt;/code> fades between &lt;code>a&lt;/code> when &lt;code>g&lt;/code> is 0 (-∞ dB) and &lt;code>b&lt;/code> when &lt;code>g&lt;/code> is 1 (0 dB). By multiplying out and regathering we get&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>c[n] = b[n] + g*(a[n] - b[n])&lt;/code>&lt;/p>
&lt;/blockquote>
&lt;p>so a dry/wet mix knob is directly equivalent to mixing in the difference signal. Note that &lt;code>a&lt;/code> and &lt;code>b&lt;/code> have swapped around; the phase of the difference signal has been inverted. In practice, you get to choose which way you want, which I&amp;rsquo;ll touch on later.&lt;/p>
&lt;p>This is no big deal. Putting an effect chain on a return track and balancing it with the source track will be easiser most of the time.&lt;/p>
&lt;h3 id="filters">Filters&lt;/h3>
&lt;p>This is well known among DSP people. A highpass filter can be made from a lowpass filter by subtracting the filtered signal from the original: &lt;code>hp[n] = x[n] - lp[n]&lt;/code>. It works the other way around, too, and with bandpass filters. If you think about this for a moment this is kind of obvious but may be surprising. If a lowpass filter removes high frequencies, then whatever is left are low frequencies, and subtracting those from the original signal will get rid of those low frequencies. But our differencing operation operates on single samples from each signal&amp;ndash;independent of any samples before or after&amp;ndash;and a single sample is just a number without any frequency information. So, given a lowpass filter, there is some highpass filter for which we can know the value of &lt;code>hp[n]&lt;/code> knowing only the two samples &lt;code>x[n]&lt;/code> and &lt;code>lp[n]&lt;/code>. Showing why this works mathematically isn&amp;rsquo;t hard&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> but something cool is happening here and I think it would be a mistake to dismiss it as obvious.&lt;/p>
&lt;p>With a little tinkering we can start making bandpass and bandstop filters the same way, and go on to shelving and bell filters. But all we&amp;rsquo;ve done so far is recreate things we can already do, and I want to get to the good stuff.&lt;/p>
&lt;h3 id="multiband-compression">Multiband Compression&lt;/h3>
&lt;p>The reason why you&amp;rsquo;d want to use the filters described above is that we can recover the original signal exactly by mixing them together again: &lt;code>x[n] = lp[n] + hp[n]&lt;/code>. We don&amp;rsquo;t have to think about how we parameterise the filters or anything because we&amp;rsquo;ve used our phase inversion trick to make one filter from the other. Now all that is left is to compress just one of those filtered signals, do the sum, and we have a multiband compressor. Building this out in a DAW can be tricky but, to be clear here, it lets you combine &lt;em>any&lt;/em> set of filter plugins and &lt;em>any&lt;/em> set of compressor plugins into a multiband compressor. That includes parametric equalisers, and doesn&amp;rsquo;t have the weird overlapping spectrums that throwing bandpass filters around everywhere does.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;p>To do this, you need to remove the frequencies you want to compress, compress the difference signal, then add it back to the filtered signal. Before, we were adding the difference signal to the dry signal. Now, we&amp;rsquo;re adding the processed difference signal to the wet signal. We could have used the dry signal; we have a choice here between &lt;code>a[n] + compress(b - a)[n]&lt;/code> and &lt;code>b[n] + compress(a - b)[n]&lt;/code>. For compression, I find the latter much easier to reason about. The opposite way still works, but you&amp;rsquo;re adding in a phase-inverted copy of compressed frequencies to the dry signal. I have no intuition for that.&lt;/p>
&lt;h3 id="compressors">Compressors&lt;/h3>
&lt;p>The above has a degree of conceptual elegance, which we&amp;rsquo;ll now dispense with. Let&amp;rsquo;s take the signal&lt;/p>
&lt;blockquote>
&lt;span class="svg">&lt;svg height="108pt" version="1.1" viewBox="0 0 475.2 108" width="475.2pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 108
L 475.2 108
L 475.2 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#pc18caf8bee)" d="M 21.6 54.435059
L 21.913498 23.977661
L 22.226996 72.290588
L 22.540493 76.054504
L 22.853991 34.583862
L 23.167489 26.106812
L 23.480987 61.982666
L 23.794485 76.347839
L 24.107983 61.339966
L 24.42148 65.942688
L 24.734978 78.172119
L 25.048476 67.411011
L 25.361974 32.599731
L 25.675472 40.653259
L 25.98897 35.147461
L 26.302467 23.722229
L 26.615965 25.131226
L 26.929463 54.728394
L 27.242961 73.773743
L 27.556459 68.859558
L 27.869956 68.356934
L 28.183454 80.034302
L 28.496952 77.007019
L 28.81045 56.672974
L 29.123948 49.283569
L 29.437446 55.115662
L 30.064441 29.791626
L 30.377939 27.520752
L 31.004935 50.613464
L 31.318433 54.085693
L 31.63193 65.464783
L 31.945428 72.567444
L 32.258926 74.56311
L 32.572424 68.121277
L 33.199419 62.335327
L 33.826415 42.772522
L 34.139913 36.432861
L 34.453411 34.6745
L 34.766909 34.183411
L 35.080406 39.051453
L 35.707402 62.42926
L 36.0209 67.495056
L 36.334398 69.437988
L 36.647896 72.397705
L 36.961393 72.106018
L 38.528882 34.855774
L 38.84238 31.16272
L 39.155878 35.683044
L 40.409869 68.979858
L 40.723367 72.24939
L 41.036865 71.623169
L 41.350363 68.787048
L 41.663861 63.81189
L 42.604354 41.60907
L 42.917852 36.190613
L 43.23135 33.689026
L 43.544848 35.961548
L 44.171843 53.823669
L 44.798839 68.780457
L 45.112337 72.661377
L 45.425835 73.239807
L 45.739332 69.828552
L 46.679826 47.12146
L 47.306821 32.449768
L 47.620319 32.433289
L 47.933817 38.145081
L 48.874311 65.285156
L 49.187808 70.62616
L 49.501306 72.593811
L 49.814804 71.473206
L 50.128302 67.557678
L 51.695791 34.262512
L 52.009289 33.829102
L 52.322787 38.850403
L 53.576778 70.9953
L 53.890276 73.340332
L 54.203774 72.190063
L 54.517271 67.440674
L 56.084761 31.79718
L 56.398258 34.153748
L 57.025254 51.343506
L 57.65225 67.717529
L 57.965747 71.65448
L 58.279245 72.455383
L 58.592743 70.166382
L 58.906241 64.861633
L 60.160232 36.772339
L 60.47373 33.545654
L 60.787228 35.481995
L 61.100726 42.24353
L 62.041219 68.052063
L 62.354717 72.229614
L 62.668215 73.117859
L 62.981713 70.565186
L 63.608708 56.735596
L 64.549202 33.527527
L 64.8627 32.197632
L 65.176197 36.826721
L 66.430189 69.535217
L 66.743687 72.201599
L 67.057184 71.746765
L 67.370682 68.193787
L 68.938171 35.112854
L 69.251669 33.873596
L 69.565167 37.929199
L 70.819158 69.981812
L 71.132656 72.789917
L 71.446154 72.298828
L 71.759652 68.340454
L 72.700145 45.013733
L 73.013643 37.029419
L 73.327141 32.441528
L 73.640639 33.614868
L 73.954136 40.200073
L 74.89463 66.387634
L 75.208128 70.835449
L 75.521626 72.223022
L 75.835123 70.495972
L 76.148621 65.800964
L 77.402612 38.085754
L 77.71611 34.185059
L 78.029608 35.112854
L 78.343106 41.010864
L 79.283599 66.687561
L 79.597097 71.349609
L 79.910595 72.808044
L 80.224093 70.93103
L 80.537591 65.586731
L 81.791582 34.804688
L 82.10508 32.380554
L 82.418578 35.945068
L 83.672569 68.426147
L 83.986067 71.600098
L 84.299565 71.707214
L 84.613062 68.747498
L 85.240058 55.385925
L 86.180552 36.231812
L 86.494049 34.102661
L 86.807547 37.174438
L 87.434543 53.822021
L 88.061538 68.805176
L 88.375036 72.157104
L 88.688534 72.252686
L 89.002032 68.996338
L 89.629028 54.840454
L 90.569521 33.324829
L 90.883019 33.351196
L 91.196517 38.988831
L 92.13701 65.029724
L 92.450508 69.932373
L 92.764006 71.840698
L 93.077504 70.687134
L 93.391001 66.598572
L 94.644993 39.450256
L 94.958491 34.977722
L 95.271988 34.928284
L 95.585486 39.931458
L 96.52598 65.276917
L 96.839478 70.387207
L 97.152975 72.399353
L 97.466473 71.156799
L 97.779971 66.516174
L 99.34746 32.790894
L 99.660958 35.254578
L 100.287954 51.945007
L 100.914949 67.246216
L 101.228447 70.912903
L 101.541945 71.563843
L 101.855443 69.190796
L 102.16894 63.973389
L 103.109434 43.238892
L 103.422932 37.429871
L 103.73643 34.517944
L 104.049927 36.600952
L 104.363425 43.204285
L 105.303919 67.579102
L 105.617417 71.417175
L 105.930914 72.099426
L 106.244412 69.518738
L 106.871408 56.223083
L 107.811901 34.38446
L 108.125399 33.293518
L 108.438897 37.940735
L 109.692888 68.955139
L 110.006386 71.372681
L 110.319884 70.774475
L 110.633382 67.249512
L 112.200871 35.912109
L 112.514369 34.936523
L 112.827866 39.010254
L 114.081858 69.355591
L 114.395356 71.891785
L 114.708853 71.262268
L 115.022351 67.325317
L 115.962845 45.129089
L 116.276343 37.673767
L 116.58984 33.422058
L 116.903338 34.758545
L 117.216836 41.230042
L 118.157329 66.025085
L 118.470827 70.131775
L 118.784325 71.310059
L 119.097823 69.515442
L 119.411321 64.909424
L 120.665312 38.682312
L 120.97881 35.104614
L 121.292308 36.215332
L 121.605806 42.011169
L 122.546299 66.292053
L 122.859797 70.599792
L 123.173295 71.837402
L 123.486792 69.891174
L 123.80029 64.658936
L 125.054282 35.587463
L 125.367779 33.448425
L 125.681277 37.06897
L 126.935269 67.911987
L 127.248766 70.81073
L 127.562264 70.759644
L 127.875762 67.791687
L 128.502758 54.997009
L 129.443251 37.016235
L 129.756749 36.319153
L 130.070247 23.44043
L 130.383745 73.510071
L 130.697242 42.154541
L 131.01074 29.643311
L 131.324238 72.394409
L 131.637736 83.984436
L 131.951234 53.873108
L 132.264731 70.850281
L 132.578229 74.243408
L 132.891727 70.543762
L 133.205225 43.604736
L 133.518723 28.094238
L 133.832221 45.16864
L 134.145718 43.184509
L 134.459216 39.524414
L 134.772714 50.425598
L 135.086212 66.605164
L 135.39971 68.622253
L 135.713208 62.149109
L 136.026705 63.617432
L 136.340203 77.613464
L 136.653701 69.91095
L 136.967199 47.409851
L 137.280697 50.087769
L 137.594194 53.822021
L 137.907692 39.873779
L 138.22119 34.857422
L 138.534688 34.466858
L 138.848186 46.276062
L 139.161684 48.515625
L 139.475181 53.227112
L 140.102177 70.909607
L 140.415675 67.453857
L 141.042671 63.907471
L 141.356168 59.225647
L 141.983164 43.642639
L 142.61016 38.154968
L 142.923657 37.240356
L 143.237155 42.69342
L 143.864151 59.71344
L 144.804644 70.301514
L 145.118142 68.422852
L 146.685631 40.015503
L 146.999129 37.677063
L 147.312627 39.425537
L 147.939623 49.372559
L 148.566618 64.855042
L 148.880116 68.202026
L 149.193614 68.726074
L 149.507112 68.165771
L 149.82061 64.653992
L 150.761103 44.432007
L 151.074601 39.173401
L 151.388099 36.363647
L 151.701597 38.250549
L 153.269086 68.477234
L 153.582583 69.390198
L 153.896081 67.157227
L 154.523077 57.243164
L 155.46357 40.341797
L 155.777068 37.874817
L 156.090566 39.13385
L 156.404064 43.840393
L 157.344557 64.668823
L 157.658055 68.338806
L 157.971553 69.50061
L 158.285051 67.879028
L 158.598549 63.264771
L 159.85254 38.402161
L 160.166038 36.460876
L 160.479536 39.267334
L 162.047025 68.55304
L 162.360522 68.991394
L 162.67402 66.868835
L 162.987518 62.473755
L 164.241509 40.089661
L 164.555007 37.828674
L 164.868505 39.501343
L 165.182003 44.585266
L 166.122496 64.690247
L 166.435994 68.121277
L 166.749492 68.965027
L 167.06299 67.124268
L 167.376488 62.813232
L 168.630479 40.439026
L 168.943977 37.879761
L 169.257475 39.267334
L 169.570972 44.115601
L 170.511466 64.301331
L 170.824964 67.890564
L 171.138462 68.892517
L 171.451959 67.297302
L 171.765457 63.264771
L 173.019448 40.859253
L 173.332946 38.036316
L 173.646444 39.049805
L 173.959942 43.647583
L 174.900435 63.833313
L 175.213933 67.62854
L 175.527431 68.869446
L 175.840929 67.476929
L 176.154427 63.627319
L 177.408418 41.277832
L 177.721916 38.204407
L 178.035414 38.889954
L 178.348911 43.227356
L 179.289405 63.365295
L 179.602903 67.346741
L 179.916401 68.787048
L 180.229898 67.608765
L 180.543396 63.97998
L 181.797388 41.709595
L 182.110885 38.400513
L 182.424383 38.766357
L 182.737881 42.825256
L 183.991872 67.03363
L 184.30537 68.686523
L 184.618868 67.727417
L 184.932366 64.314514
L 186.499855 38.644409
L 186.813353 38.66748
L 187.126851 42.441284
L 188.380842 66.710632
L 188.69434 68.567871
L 189.007837 67.834534
L 189.321335 64.616089
L 190.888824 38.911377
L 191.202322 38.591675
L 191.51582 42.088623
L 192.769811 66.377747
L 193.083309 68.426147
L 193.396807 67.908691
L 193.710305 64.90448
L 195.277794 39.657898
L 195.591292 39.280518
L 195.90479 42.306152
L 197.158781 64.439758
L 197.472279 66.908386
L 197.785776 67.10614
L 198.099274 64.988525
L 198.72627 55.412292
L 199.353266 45.424072
L 199.666763 41.707947
L 199.980261 40.17865
L 200.293759 41.750793
L 200.607257 45.94812
L 201.54775 62.979675
L 201.861248 66.173401
L 202.174746 67.120972
L 202.488244 65.744934
L 202.801742 62.241394
L 204.055733 42.777466
L 204.369231 40.409363
L 204.682729 41.012512
L 204.996226 44.473206
L 206.250218 65.342834
L 206.563716 66.995728
L 206.877213 66.344788
L 207.190711 63.47406
L 208.7582 40.954834
L 209.071698 40.570862
L 209.385196 43.205933
L 210.012192 54.131836
L 210.639187 64.289795
L 210.952685 66.652954
L 211.266183 66.718872
L 211.579681 64.525452
L 212.206676 55.025024
L 212.833672 45.308716
L 213.14717 41.757385
L 213.460668 40.424194
L 213.774165 42.159485
L 214.087663 46.419434
L 215.028157 63.067017
L 215.341655 66.082764
L 215.655152 66.868835
L 215.96865 65.36261
L 216.282148 61.806335
L 217.536139 42.770874
L 217.849637 40.607117
L 218.163135 41.375061
L 218.476633 44.934631
L 219.730624 65.304932
L 220.044122 66.788086
L 220.35762 65.998718
L 220.671118 63.062073
L 222.238607 41.075134
L 222.552104 40.882324
L 222.865602 43.650879
L 223.492598 54.51416
L 224.119594 64.322754
L 224.433091 66.489807
L 224.746589 66.418945
L 225.060087 64.126648
L 225.687083 54.70697
L 226.314078 45.244446
L 226.627576 42.091919
L 226.941074 41.254761
L 227.254572 43.144958
L 227.881567 52.389954
L 228.508563 62.017273
L 228.822061 64.818787
L 229.135559 65.654297
L 229.449057 64.444702
L 229.762554 61.369629
L 231.016546 44.278748
L 231.330044 42.327576
L 231.643541 42.70166
L 231.957039 45.404297
L 233.21103 63.513611
L 233.524528 65.342834
L 233.838026 65.133545
L 234.151524 62.94342
L 234.77852 54.318054
L 235.405515 45.822876
L 235.719013 43.062561
L 236.032511 42.296265
L 236.346009 43.932678
L 236.973004 52.480591
L 237.6 61.939819
L 237.913498 50.125671
L 238.226996 86.072388
L 238.853991 25.360291
L 239.480987 80.060669
L 240.107983 24.061707
L 240.42148 53.874756
L 240.734978 67.574158
L 241.048476 60.094116
L 241.361974 35.631958
L 241.675472 49.283569
L 241.98897 68.526672
L 242.302467 66.31842
L 242.615965 45.532837
L 242.929463 70.787659
L 243.242961 67.364868
L 243.869956 36.708069
L 244.496952 56.318665
L 245.123948 34.928284
L 245.437446 53.657227
L 245.750943 52.839844
L 246.064441 43.74646
L 246.377939 52.668457
L 246.691437 67.083069
L 247.004935 66.743591
L 247.318433 62.674805
L 247.63193 61.218018
L 247.945428 62.8396
L 248.258926 58.278076
L 248.572424 50.188293
L 248.885922 47.309326
L 249.199419 47.200562
L 249.512917 45.069763
L 249.826415 41.063599
L 250.139913 44.201294
L 250.453411 52.594299
L 250.766909 56.938293
L 251.080406 57.740845
L 251.707402 67.261047
L 252.0209 64.434814
L 252.334398 60.019958
L 252.647896 58.220398
L 252.961393 55.466675
L 253.588389 44.603394
L 253.901887 43.013123
L 254.215385 44.20459
L 254.528882 44.133728
L 254.84238 47.485657
L 255.469376 58.983398
L 256.409869 65.135193
L 256.723367 64.095337
L 257.977358 47.907532
L 258.290856 44.029907
L 258.604354 42.836792
L 258.917852 44.412231
L 259.544848 50.550842
L 260.171843 59.901306
L 260.485341 62.819824
L 260.798839 63.754211
L 261.112337 63.582825
L 261.425835 61.982666
L 262.993324 44.425415
L 263.306821 43.624512
L 263.620319 45.409241
L 264.247315 52.727783
L 264.874311 60.970825
L 265.187808 63.488892
L 265.501306 63.938782
L 265.814804 62.854431
L 266.128302 60.290222
L 267.382293 45.595459
L 267.695791 44.089233
L 268.009289 44.298523
L 268.322787 46.538086
L 269.576778 62.05188
L 269.890276 63.85968
L 270.203774 63.762451
L 270.517271 61.877197
L 271.144267 54.627869
L 271.771263 47.035767
L 272.084761 44.670959
L 272.398258 44.001892
L 272.711756 45.181824
L 273.025254 48.036072
L 274.279245 62.950012
L 274.592743 63.953613
L 274.906241 63.154358
L 275.219739 60.647827
L 276.47373 45.934937
L 276.787228 44.160095
L 277.100726 44.303467
L 277.414224 46.26947
L 278.041219 53.990112
L 278.668215 61.653076
L 278.981713 63.569641
L 279.29521 63.750916
L 279.608708 62.193604
L 280.235704 55.155212
L 280.8627 47.57959
L 281.176197 45.0401
L 281.489695 44.036499
L 281.803193 44.969238
L 282.116691 47.629028
L 283.370682 62.628662
L 283.68418 63.815186
L 283.997678 63.243347
L 284.311176 61.016968
L 285.878665 44.446838
L 286.192163 44.267212
L 286.50566 45.967896
L 287.132656 53.383667
L 287.759652 61.161987
L 288.073149 63.297729
L 288.386647 63.737732
L 288.700145 62.42926
L 289.013643 59.601379
L 290.267634 45.534485
L 290.581132 44.489685
L 290.89463 45.236206
L 291.208128 47.605957
L 292.462119 61.504761
L 292.775617 62.864319
L 293.089115 62.648438
L 293.402612 60.888428
L 294.029608 54.229065
L 294.656604 47.582886
L 294.970102 45.636658
L 295.283599 45.173584
L 295.597097 46.33374
L 295.910595 48.930908
L 297.164586 62.05188
L 297.478084 62.862671
L 297.791582 62.074951
L 298.10508 59.830444
L 299.359071 46.742432
L 299.672569 45.321899
L 299.986067 45.453735
L 300.299565 47.184082
L 300.92656 53.945618
L 301.553556 60.731873
L 301.867054 62.491882
L 302.180552 62.71106
L 302.494049 61.359741
L 303.121045 55.163452
L 303.748041 48.331055
L 304.061538 46.09314
L 304.375036 45.22467
L 304.688534 45.969543
L 305.002032 48.212402
L 306.256023 61.519592
L 306.569521 62.71106
L 306.883019 62.335327
L 307.196517 60.461609
L 308.764006 45.631714
L 309.077504 45.359802
L 309.391001 46.67981
L 310.017997 52.960144
L 310.644993 59.995239
L 310.958491 62.094727
L 311.271988 62.714355
L 311.585486 61.756897
L 311.898984 59.410217
L 313.152975 46.627075
L 313.466473 45.392761
L 313.779971 45.710815
L 314.093469 47.577942
L 314.720464 54.38562
L 315.34746 60.893372
L 315.660958 62.460571
L 315.974456 62.495178
L 316.287954 61.000488
L 316.914949 54.726746
L 317.541945 48.106934
L 317.855443 46.048645
L 318.16894 45.366394
L 318.482438 46.27771
L 318.795936 48.632629
L 320.049927 61.603638
L 320.363425 62.617126
L 320.676923 62.056824
L 320.990421 60.067749
L 322.244412 47.299438
L 322.55791 45.847595
L 322.871408 45.880554
L 323.184906 47.385132
L 323.811901 53.441345
L 324.438897 59.683777
L 324.752395 61.455322
L 325.065893 61.868958
L 325.37939 60.899963
L 325.692888 58.686768
L 326.94688 47.248352
L 327.260377 46.262878
L 327.573875 46.648499
L 327.887373 48.392029
L 328.514369 54.500977
L 329.141364 60.285278
L 329.454862 61.651428
L 329.76836 61.616821
L 330.081858 60.229248
L 330.708853 54.533936
L 331.335849 48.609558
L 331.649347 46.809998
L 331.962845 46.274414
L 332.276343 47.12146
L 332.58984 49.235779
L 333.843832 60.834045
L 334.157329 61.748657
L 334.470827 61.269104
L 334.784325 59.467896
L 336.351814 46.52655
L 336.665312 46.445801
L 336.97881 47.721313
L 337.605806 53.337524
L 338.232801 59.461304
L 338.546299 61.250977
L 338.859797 61.702515
L 339.173295 60.773071
L 339.486792 58.619202
L 340.740784 47.363708
L 341.054282 46.386475
L 341.367779 46.757263
L 341.681277 48.453003
L 342.308273 54.451538
L 342.935269 60.153442
L 343.248766 61.517944
L 343.562264 61.508057
L 343.875762 60.150146
L 344.502758 54.565247
L 345.129753 48.769409
L 345.443251 47.014343
L 345.756749 54.983826
L 346.070247 49.567017
L 346.383745 70.973877
L 346.697242 34.241089
L 347.01074 48.395325
L 347.324238 54.973938
L 347.637736 84.618896
L 348.264731 26.723145
L 348.891727 83.315369
L 349.205225 42.159485
L 349.832221 55.110718
L 350.145718 61.155396
L 350.459216 33.422058
L 351.086212 52.404785
L 351.39971 58.024292
L 351.713208 42.17926
L 352.340203 64.892944
L 352.653701 63.291138
L 352.967199 51.816467
L 353.280697 54.939331
L 353.594194 61.600342
L 353.907692 55.216187
L 354.22119 42.337463
L 354.848186 53.14801
L 355.161684 46.602356
L 355.475181 43.898071
L 356.102177 59.294861
L 356.415675 55.344727
L 356.729173 54.791016
L 357.042671 62.526489
L 357.356168 64.868225
L 357.669666 58.40332
L 357.983164 54.411987
L 358.296662 55.247498
L 358.61016 55.273865
L 358.923657 47.714722
L 359.237155 45.463623
L 359.550653 48.657349
L 359.864151 50.252563
L 360.177649 48.441467
L 360.491147 50.786499
L 360.804644 56.473572
L 361.118142 58.927368
L 361.43164 59.133362
L 361.745138 59.952393
L 362.058636 61.620117
L 362.372134 59.423401
L 362.685631 55.610046
L 363.626125 49.054504
L 363.939623 46.546326
L 364.25312 47.109924
L 365.507112 55.970947
L 365.82061 59.397034
L 366.134107 60.527527
L 366.761103 60.166626
L 367.074601 58.734558
L 367.701597 52.442688
L 368.015094 50.152039
L 368.64209 47.179138
L 368.955588 47.182434
L 369.582583 51.623657
L 370.836575 60.834045
L 371.150073 60.509399
L 371.46357 59.357483
L 371.777068 57.531555
L 372.717562 49.421997
L 373.03106 47.968506
L 373.344557 47.38678
L 373.658055 47.754272
L 373.971553 49.53241
L 375.225544 59.790894
L 375.539042 60.832397
L 375.85254 60.431946
L 376.166038 58.879578
L 376.793033 53.947266
L 377.420029 48.827087
L 377.733527 47.594421
L 378.047025 47.446106
L 378.360522 48.352478
L 378.67402 50.318481
L 379.614514 58.469238
L 379.928012 60.160034
L 380.241509 60.763184
L 380.555007 60.110596
L 380.868505 58.273132
L 382.122496 48.364014
L 382.435994 47.385132
L 382.749492 47.604309
L 383.06299 48.942444
L 383.689985 53.99176
L 384.316981 59.120178
L 384.630479 60.456665
L 384.943977 60.6297
L 385.257475 59.612915
L 385.88447 54.886597
L 386.511466 49.7005
L 386.824964 48.092102
L 387.138462 47.541687
L 387.451959 48.167908
L 387.765457 49.848816
L 389.019448 59.232239
L 389.332946 60.09906
L 389.646444 59.876587
L 389.959942 58.619202
L 390.586938 54.016479
L 391.213933 49.497803
L 391.527431 48.250305
L 391.840929 48.011353
L 392.154427 48.838623
L 392.467925 50.598633
L 393.721916 59.509094
L 394.035414 60.075989
L 394.348911 59.548645
L 394.662409 58.00946
L 395.916401 49.038025
L 396.229898 48.083862
L 396.543396 48.181091
L 396.856894 49.318176
L 397.48389 53.823669
L 398.110885 58.507141
L 398.424383 59.781006
L 398.737881 60.000183
L 399.051379 59.131714
L 399.678374 54.97229
L 400.30537 50.227844
L 400.618868 48.665588
L 400.932366 48.02948
L 401.245864 48.457947
L 401.559361 49.881775
L 402.813353 58.985046
L 403.126851 59.940857
L 403.440348 59.812317
L 403.753846 58.632385
L 404.380842 54.171387
L 405.007837 49.667542
L 405.321335 48.385437
L 405.634833 48.077271
L 405.948331 48.841919
L 406.261829 50.527771
L 407.51582 59.369019
L 407.829318 59.988647
L 408.142816 59.518982
L 408.456313 58.060547
L 409.710305 49.192932
L 410.023803 48.197571
L 410.3373 48.232178
L 410.650798 49.304993
L 411.277794 53.683594
L 411.90479 58.343994
L 412.218287 59.655762
L 412.531785 59.930969
L 412.845283 59.12677
L 413.472279 55.094238
L 414.099274 50.390991
L 414.412772 48.810608
L 414.72627 48.120117
L 415.039768 48.48761
L 415.353266 49.847168
L 416.920755 59.830444
L 417.234253 59.754639
L 417.54775 58.657104
L 418.174746 54.31311
L 418.801742 49.855408
L 419.115239 48.604614
L 419.428737 48.324463
L 419.742235 49.059448
L 420.055733 50.672791
L 421.309724 58.831787
L 421.623222 59.431641
L 421.93672 59.059204
L 422.250218 57.7771
L 423.504209 49.749939
L 423.817707 48.805664
L 424.131205 48.767761
L 424.444702 49.677429
L 425.071698 53.548462
L 425.698694 57.790283
L 426.012192 59.041077
L 426.325689 59.359131
L 426.639187 58.711487
L 426.952685 57.218445
L 428.206676 49.397278
L 428.520174 48.719971
L 428.833672 48.965515
L 429.14717 50.122375
L 429.774165 54.237305
L 430.401161 58.233582
L 430.714659 59.215759
L 431.028157 59.242126
L 431.341655 58.325867
L 431.96865 54.484497
L 432.595646 50.386047
L 432.909144 49.123718
L 433.222642 48.719971
L 433.536139 49.252258
L 433.849637 50.644775
L 435.103628 58.591187
L 435.417126 59.303101
L 435.730624 59.032837
L 436.044122 57.872681
L 437.611611 48.925964
L 437.925109 48.810608
L 438.238607 49.603271
L 438.865602 53.325989
L 439.492598 57.56781
L 439.806096 58.889465
L 440.119594 59.296509
L 440.433091 58.74939
L 440.746589 57.346985
L 442.000581 49.581848
L 442.314078 48.815552
L 442.627576 48.975403
L 442.941074 50.028442
L 443.56807 54.013184
L 444.195065 58.02594
L 444.508563 59.087219
L 444.822061 59.205872
L 445.135559 58.396729
L 445.762554 54.688843
L 446.38955 50.595337
L 446.703048 49.280273
L 447.016546 48.797424
L 447.330044 49.227539
L 447.643541 50.511292
L 448.897533 58.413208
L 449.21103 59.197632
L 449.524528 59.037781
L 449.838026 57.963318
L 450.465022 54.023071
L 451.092017 50.150391
L 451.405515 49.06604
L 451.719013 48.843567
L 452.032511 49.550537
L 452.346009 51.050171
L 453.6 58.630737
L 453.6 58.630737
" style="fill:none;stroke:#e61ae6;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="pc18caf8bee">
&lt;rect height="108" width="475.2" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
&lt;/blockquote>
&lt;p>and use a compressor to flatten its peaks.&lt;/p>
&lt;blockquote>
&lt;span class="svg">&lt;svg height="108pt" version="1.1" viewBox="0 0 475.2 108" width="475.2pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 108
L 475.2 108
L 475.2 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p629da2a526)" d="M 21.6 54.453186
L 21.913498 31.101746
L 22.226996 66.760071
L 22.540493 68.671692
L 22.853991 40.888916
L 23.167489 35.254578
L 23.480987 59.451416
L 23.794485 69.487427
L 24.107983 59.166321
L 24.42148 62.539673
L 24.734978 71.189758
L 25.048476 63.600952
L 25.361974 38.446655
L 25.675472 44.155151
L 25.98897 39.890259
L 26.302467 31.584595
L 26.615965 33.746704
L 27.242961 68.312439
L 27.556459 64.927551
L 27.869956 64.710022
L 28.183454 73.681458
L 28.496952 71.553955
L 28.81045 56.066528
L 29.123948 50.315186
L 29.437446 54.876709
L 30.064441 34.455322
L 30.377939 32.876587
L 31.004935 51.251221
L 31.318433 54.070862
L 31.63193 63.508667
L 31.945428 69.531921
L 32.258926 71.346313
L 32.572424 66.003662
L 33.199419 61.190002
L 34.139913 38.532349
L 34.453411 36.872864
L 34.766909 36.348816
L 35.080406 40.612061
L 35.707402 61.621765
L 36.0209 66.267334
L 36.334398 68.103149
L 36.647896 70.876648
L 36.961393 70.677246
L 38.528882 36.043945
L 38.84238 32.522278
L 39.155878 36.736084
L 40.409869 68.274536
L 40.723367 71.423767
L 41.036865 70.86676
L 41.350363 68.174011
L 41.663861 63.434509
L 42.604354 42.030945
L 42.917852 36.777283
L 43.23135 34.307007
L 43.544848 36.495483
L 43.858345 43.845337
L 44.485341 62.271057
L 44.798839 68.417908
L 45.112337 72.236206
L 45.425835 72.804749
L 45.739332 69.50061
L 46.679826 47.245056
L 47.306821 32.800781
L 47.620319 32.767822
L 47.933817 38.387329
L 48.874311 65.143433
L 49.187808 70.426758
L 49.501306 72.363098
L 49.814804 71.272156
L 50.128302 67.412659
L 51.695791 34.435547
L 52.009289 33.988953
L 52.322787 38.967407
L 53.576778 70.891479
L 53.890276 73.228271
L 54.203774 72.079651
L 54.517271 67.366516
L 56.084761 31.896057
L 56.398258 34.239441
L 57.025254 51.353394
L 57.65225 67.677979
L 57.965747 71.59845
L 58.279245 72.407593
L 58.592743 70.112
L 58.906241 64.827026
L 60.160232 36.813538
L 60.47373 33.591797
L 60.787228 35.519897
L 61.100726 42.271545
L 62.041219 68.03064
L 62.354717 72.198303
L 62.668215 73.083252
L 62.981713 70.533875
L 63.608708 56.730652
L 64.549202 33.542358
L 64.8627 32.220703
L 65.176197 36.841553
L 66.430189 69.523682
L 66.743687 72.195007
L 67.057184 71.728638
L 67.370682 68.183899
L 68.938171 35.11615
L 69.251669 33.891724
L 69.565167 37.934143
L 70.819158 69.970276
L 71.132656 72.793213
L 71.446154 72.290588
L 71.759652 68.330566
L 72.700145 45.015381
L 73.013643 37.037659
L 73.327141 32.451416
L 73.640639 33.623108
L 73.954136 40.198425
L 74.89463 66.377747
L 75.208128 70.828857
L 75.521626 72.209839
L 75.835123 70.491028
L 76.148621 65.792725
L 77.402612 38.085754
L 77.71611 34.185059
L 78.029608 35.121094
L 78.343106 41.012512
L 79.283599 66.690857
L 79.597097 71.343018
L 79.910595 72.799805
L 80.224093 70.93103
L 80.537591 65.581787
L 81.791582 34.804688
L 82.10508 32.380554
L 82.418578 35.945068
L 83.672569 68.421204
L 83.986067 71.59845
L 84.299565 71.702271
L 84.613062 68.754089
L 85.240058 55.382629
L 86.180552 36.221924
L 86.494049 34.109253
L 86.807547 37.171143
L 87.434543 53.831909
L 88.061538 68.808472
L 88.375036 72.150513
L 88.688534 72.255981
L 89.002032 68.99469
L 89.629028 54.838806
L 90.569521 33.321533
L 90.883019 33.349548
L 91.196517 38.985535
L 92.13701 65.026428
L 92.450508 69.932373
L 92.764006 71.837402
L 93.077504 70.69043
L 93.391001 66.603516
L 94.644993 39.445312
L 94.958491 34.97937
L 95.271988 34.93158
L 95.585486 39.924866
L 96.52598 65.276917
L 96.839478 70.388855
L 97.152975 72.401001
L 97.466473 71.156799
L 97.779971 66.522766
L 99.34746 32.794189
L 99.660958 35.249634
L 100.287954 51.949951
L 100.914949 67.252808
L 101.228447 70.907959
L 101.541945 71.557251
L 101.855443 69.189148
L 102.16894 63.971741
L 103.109434 43.227356
L 103.422932 37.428223
L 103.73643 34.514648
L 104.049927 36.604248
L 104.363425 43.204285
L 105.303919 67.577454
L 105.617417 71.415527
L 105.930914 72.10437
L 106.244412 69.51709
L 106.871408 56.224731
L 107.811901 34.377869
L 108.125399 33.298462
L 108.438897 37.947327
L 109.692888 68.960083
L 110.006386 71.372681
L 110.319884 70.782715
L 110.633382 67.246216
L 112.200871 35.915405
L 112.514369 34.93158
L 112.827866 39.006958
L 114.081858 69.353943
L 114.395356 71.891785
L 114.708853 71.258972
L 115.022351 67.328613
L 115.962845 45.129089
L 116.276343 37.667175
L 116.58984 33.413818
L 116.903338 34.768433
L 117.216836 41.221802
L 118.157329 66.023438
L 118.470827 70.133423
L 118.784325 71.306763
L 119.097823 69.518738
L 119.411321 64.914368
L 120.665312 38.685608
L 120.97881 35.101318
L 121.292308 36.213684
L 121.605806 42.011169
L 122.546299 66.292053
L 122.859797 70.596497
L 123.173295 71.840698
L 123.486792 69.892822
L 123.80029 64.658936
L 125.054282 35.585815
L 125.367779 33.450073
L 125.681277 37.080505
L 126.935269 67.915283
L 127.248766 70.807434
L 127.562264 70.756348
L 127.875762 67.798279
L 128.502758 55.000305
L 129.443251 36.99646
L 129.756749 36.282898
L 130.070247 33.512695
L 130.383745 67.300598
L 130.697242 45.81134
L 131.01074 36.922302
L 131.324238 67.10614
L 131.637736 73.917114
L 131.951234 53.909363
L 132.264731 65.578491
L 132.578229 68.15094
L 132.891727 65.735046
L 133.205225 46.505127
L 133.518723 35.093079
L 133.832221 47.469177
L 134.145718 45.87561
L 134.459216 43.011475
L 134.772714 51.251221
L 135.086212 63.797058
L 135.39971 65.496094
L 135.713208 60.479736
L 136.026705 61.73053
L 136.340203 73.177185
L 136.653701 67.055054
L 136.967199 48.545288
L 137.280697 50.735413
L 137.594194 53.848389
L 137.907692 42.007874
L 138.22119 37.625977
L 138.534688 37.171143
L 138.848186 47.301086
L 139.161684 49.199524
L 139.475181 53.327637
L 140.102177 69.044128
L 140.415675 66.033325
L 141.042671 62.954956
L 141.356168 58.74115
L 141.983164 44.512756
L 142.61016 39.364563
L 142.923657 38.453247
L 143.237155 43.484436
L 143.864151 59.350891
L 144.804644 69.398438
L 145.118142 67.676331
L 146.685631 40.597229
L 146.999129 38.323059
L 147.312627 39.9776
L 147.939623 49.53241
L 148.566618 64.51886
L 148.880116 67.786743
L 149.193614 68.304199
L 149.507112 67.783447
L 149.82061 64.372192
L 150.761103 44.651184
L 151.074601 39.494751
L 151.388099 36.713013
L 151.701597 38.552124
L 153.269086 68.254761
L 153.582583 69.172668
L 153.896081 66.971008
L 154.523077 57.206909
L 155.46357 40.496704
L 155.777068 38.051147
L 156.090566 39.277222
L 156.404064 43.935974
L 157.344557 64.584778
L 157.658055 68.230042
L 157.971553 69.38855
L 158.285051 67.786743
L 158.598549 63.2005
L 159.85254 38.49115
L 160.166038 36.55481
L 160.479536 39.339844
L 162.047025 68.490417
L 162.360522 68.935364
L 162.67402 66.822693
L 162.987518 62.439148
L 164.241509 40.132507
L 164.555007 37.878113
L 164.868505 39.539246
L 165.182003 44.609985
L 166.122496 64.665527
L 166.435994 68.088318
L 166.749492 68.93042
L 167.06299 67.099548
L 167.376488 62.800049
L 168.630479 40.452209
L 168.943977 37.90448
L 169.257475 39.282166
L 169.570972 44.127136
L 170.511466 64.279907
L 170.824964 67.87738
L 171.138462 68.879333
L 171.451959 67.289062
L 171.765457 63.256531
L 173.019448 40.870789
L 173.332946 38.037964
L 173.646444 39.059692
L 173.959942 43.66571
L 174.900435 63.828369
L 175.213933 67.625244
L 175.527431 68.861206
L 175.840929 67.463745
L 176.154427 63.624023
L 177.408418 41.277832
L 177.721916 38.206055
L 178.035414 38.894897
L 178.348911 43.225708
L 179.289405 63.360352
L 179.602903 67.338501
L 179.916401 68.782104
L 180.229898 67.615356
L 180.543396 63.978333
L 181.797388 41.707947
L 182.110885 38.412048
L 182.424383 38.769653
L 182.737881 42.815369
L 183.991872 67.036926
L 184.30537 68.688171
L 184.618868 67.734009
L 184.932366 64.30957
L 186.499855 38.639465
L 186.813353 38.66748
L 187.126851 42.446228
L 188.380842 66.715576
L 188.69434 68.571167
L 189.007837 67.822998
L 189.321335 64.617737
L 190.888824 38.906433
L 191.202322 38.598267
L 191.51582 42.091919
L 192.769811 66.372803
L 193.083309 68.434387
L 193.396807 67.907043
L 193.710305 64.897888
L 195.277794 39.66449
L 195.591292 39.282166
L 195.90479 42.3078
L 197.158781 64.447998
L 197.472279 66.910034
L 197.785776 67.099548
L 198.099274 64.991821
L 198.72627 55.418884
L 199.353266 45.415833
L 199.666763 41.716187
L 199.980261 40.183594
L 200.293759 41.754089
L 200.607257 45.944824
L 201.54775 62.979675
L 201.861248 66.175049
L 202.174746 67.127563
L 202.488244 65.743286
L 202.801742 62.241394
L 204.055733 42.777466
L 204.369231 40.414307
L 204.682729 41.00592
L 204.996226 44.468262
L 206.250218 65.336243
L 206.563716 66.99408
L 206.877213 66.346436
L 207.190711 63.483948
L 208.7582 40.954834
L 209.071698 40.56427
L 209.385196 43.205933
L 210.012192 54.133484
L 210.639187 64.296387
L 210.952685 66.641418
L 211.266183 66.717224
L 211.579681 64.5271
L 212.206676 55.023376
L 212.833672 45.300476
L 213.14717 41.752441
L 213.460668 40.429138
L 213.774165 42.161133
L 214.087663 46.417786
L 215.028157 63.070312
L 215.341655 66.07782
L 215.655152 66.860596
L 215.96865 65.377441
L 216.282148 61.807983
L 217.536139 42.770874
L 217.849637 40.605469
L 218.163135 41.378357
L 218.476633 44.931335
L 219.730624 65.296692
L 220.044122 66.789734
L 220.35762 66.00531
L 220.671118 63.057129
L 222.238607 41.066895
L 222.552104 40.880676
L 222.865602 43.649231
L 223.492598 54.515808
L 224.119594 64.314514
L 224.433091 66.491455
L 224.746589 66.414001
L 225.060087 64.125
L 225.687083 54.708618
L 226.314078 45.246094
L 226.627576 42.091919
L 226.941074 41.259705
L 227.254572 43.149902
L 227.881567 52.388306
L 228.508563 62.028809
L 228.822061 64.808899
L 229.135559 65.646057
L 229.449057 64.443054
L 229.762554 61.361389
L 231.016546 44.267212
L 231.330044 42.320984
L 231.643541 42.708252
L 231.957039 45.402649
L 233.21103 63.513611
L 233.524528 65.34613
L 233.838026 65.136841
L 234.151524 62.938477
L 234.77852 54.316406
L 235.405515 45.821228
L 235.719013 43.060913
L 236.032511 42.296265
L 236.346009 43.934326
L 236.973004 52.482239
L 237.6 61.934875
L 237.913498 50.096008
L 238.226996 78.183655
L 238.540493 50.924927
L 238.853991 34.2724
L 239.167489 50.506348
L 239.480987 72.516357
L 240.107983 32.306396
L 240.42148 53.914307
L 240.734978 64.039307
L 241.048476 58.569763
L 241.361974 40.046814
L 241.675472 50.377808
L 241.98897 65.295044
L 242.302467 63.693237
L 242.615965 47.259888
L 242.929463 67.506592
L 243.242961 64.846802
L 243.869956 39.699097
L 244.496952 55.954468
L 245.123948 37.690247
L 245.437446 53.708313
L 245.750943 52.993103
L 246.064441 45.045044
L 246.377939 52.828308
L 246.691437 65.565308
L 247.004935 65.334595
L 247.318433 61.758545
L 247.63193 60.497864
L 247.945428 61.98761
L 248.258926 57.887512
L 248.572424 50.521179
L 248.885922 47.874573
L 249.199419 47.739441
L 249.512917 45.745422
L 249.826415 41.997986
L 250.139913 44.880249
L 250.453411 52.691528
L 250.766909 56.760315
L 251.080406 57.523315
L 251.707402 66.542542
L 252.0209 63.881104
L 252.334398 59.720032
L 252.647896 58.0177
L 252.961393 55.394165
L 253.588389 44.993958
L 253.901887 43.448181
L 254.215385 44.577026
L 254.528882 44.494629
L 254.84238 47.703186
L 255.469376 58.825195
L 256.409869 64.833618
L 256.723367 63.836609
L 257.977358 48.034424
L 258.290856 44.234253
L 258.604354 43.051025
L 258.917852 44.585266
L 259.544848 50.605225
L 260.171843 59.807373
L 260.485341 62.691284
L 260.798839 63.620728
L 261.112337 63.454285
L 261.425835 61.883789
L 262.993324 44.5177
L 263.306821 43.725037
L 263.620319 45.486694
L 264.247315 52.731079
L 264.874311 60.926331
L 265.187808 63.42627
L 265.501306 63.872864
L 265.814804 62.786865
L 266.128302 60.245728
L 267.382293 45.64325
L 267.695791 44.143616
L 268.009289 44.339722
L 268.322787 46.571045
L 269.576778 62.022217
L 269.890276 63.823425
L 270.203774 63.727844
L 270.517271 61.854126
L 271.144267 54.624573
L 271.771263 47.047302
L 272.084761 44.690735
L 272.398258 44.02002
L 272.711756 45.195007
L 273.025254 48.044312
L 274.279245 62.940125
L 274.592743 63.943726
L 274.906241 63.142822
L 275.219739 60.637939
L 276.47373 45.946472
L 276.787228 44.176575
L 277.100726 44.306763
L 277.414224 46.281006
L 278.041219 53.985168
L 278.668215 61.644836
L 278.981713 63.559753
L 279.29521 63.744324
L 279.608708 62.187012
L 280.235704 55.161804
L 280.8627 47.589478
L 281.176197 45.043396
L 281.489695 44.043091
L 281.803193 44.972534
L 282.116691 47.63562
L 283.370682 62.63031
L 283.68418 63.815186
L 283.997678 63.235107
L 284.311176 61.012024
L 285.878665 44.441895
L 286.192163 44.270508
L 286.50566 45.969543
L 287.132656 53.390259
L 287.759652 61.165283
L 288.073149 63.304321
L 288.386647 63.727844
L 288.700145 62.427612
L 289.013643 59.606323
L 290.267634 45.537781
L 290.581132 44.488037
L 290.89463 45.24115
L 291.208128 47.609253
L 292.462119 61.506409
L 292.775617 62.859375
L 293.089115 62.641846
L 293.402612 60.893372
L 294.029608 54.219177
L 294.656604 47.589478
L 294.970102 45.638306
L 295.283599 45.162048
L 295.597097 46.335388
L 295.910595 48.924316
L 297.164586 62.05188
L 297.478084 62.861023
L 297.791582 62.078247
L 298.10508 59.840332
L 299.359071 46.747375
L 299.672569 45.31366
L 299.986067 45.465271
L 300.299565 47.180786
L 300.92656 53.952209
L 301.553556 60.738464
L 301.867054 62.495178
L 302.180552 62.70282
L 302.494049 61.358093
L 303.121045 55.1651
L 303.748041 48.326111
L 304.061538 46.094788
L 304.375036 45.22467
L 304.688534 45.972839
L 305.002032 48.212402
L 306.256023 61.511353
L 306.569521 62.712708
L 306.883019 62.338623
L 307.196517 60.471497
L 308.764006 45.630066
L 309.077504 45.366394
L 309.391001 46.689697
L 310.017997 52.966736
L 310.644993 60.000183
L 310.958491 62.091431
L 311.271988 62.712708
L 311.585486 61.758545
L 311.898984 59.416809
L 313.152975 46.632019
L 313.466473 45.394409
L 313.779971 45.70752
L 314.093469 47.582886
L 314.720464 54.390564
L 315.34746 60.896667
L 315.660958 62.467163
L 315.974456 62.495178
L 316.287954 60.995544
L 316.914949 54.716858
L 317.541945 48.113525
L 317.855443 46.043701
L 318.16894 45.359802
L 318.482438 46.282654
L 318.795936 48.639221
L 320.049927 61.611877
L 320.363425 62.612183
L 320.676923 62.056824
L 320.990421 60.064453
L 322.244412 47.299438
L 322.55791 45.847595
L 322.871408 45.880554
L 323.184906 47.380188
L 323.811901 53.447937
L 324.438897 59.690369
L 324.752395 61.453674
L 325.065893 61.878845
L 325.37939 60.891724
L 325.692888 58.68512
L 326.94688 47.254944
L 327.260377 46.256287
L 327.573875 46.651794
L 327.887373 48.385437
L 328.514369 54.50592
L 329.141364 60.280334
L 329.454862 61.643188
L 329.76836 61.623413
L 330.081858 60.225952
L 330.708853 54.53064
L 331.335849 48.611206
L 331.649347 46.811646
L 331.962845 46.274414
L 332.276343 47.124756
L 332.58984 49.237427
L 333.843832 60.838989
L 334.157329 61.750305
L 334.470827 61.269104
L 334.784325 59.477783
L 336.038316 47.937195
L 336.351814 46.523254
L 336.665312 46.447449
L 336.97881 47.722961
L 337.605806 53.344116
L 338.232801 59.471191
L 338.546299 61.247681
L 338.859797 61.704163
L 339.173295 60.771423
L 339.486792 58.624146
L 340.740784 47.367004
L 341.054282 46.388123
L 341.367779 46.755615
L 341.681277 48.448059
L 342.308273 54.448242
L 342.935269 60.153442
L 343.248766 61.517944
L 343.562264 61.508057
L 343.875762 60.146851
L 344.502758 54.560303
L 345.129753 48.766113
L 345.443251 46.997864
L 345.756749 54.950867
L 346.070247 50.643127
L 346.383745 66.890259
L 346.697242 39.292053
L 347.01074 49.899902
L 347.324238 54.728394
L 347.637736 75.693604
L 348.264731 34.107605
L 348.891727 75.912781
L 349.205225 45.025269
L 349.832221 54.868469
L 350.145718 59.621155
L 350.459216 37.652344
L 351.086212 52.704712
L 351.39971 57.292603
L 351.713208 44.235901
L 352.340203 63.162598
L 352.653701 61.882141
L 352.967199 52.134521
L 353.280697 54.814087
L 353.594194 60.600037
L 353.907692 55.067871
L 354.22119 43.759644
L 354.848186 53.233704
L 355.161684 47.381836
L 355.475181 44.918152
L 356.102177 58.807068
L 356.415675 55.22937
L 356.729173 54.726746
L 357.042671 61.839294
L 357.356168 64.037659
L 357.669666 58.080322
L 357.983164 54.392212
L 358.296662 55.1651
L 358.61016 55.203003
L 358.923657 48.098694
L 359.237155 45.95636
L 359.550653 48.957275
L 359.864151 50.453613
L 360.177649 48.716675
L 360.491147 50.941406
L 360.804644 56.363159
L 361.118142 58.704895
L 361.43164 58.91748
L 361.745138 59.720032
L 362.058636 61.335022
L 362.372134 59.228943
L 362.685631 55.55896
L 363.626125 49.209412
L 363.939623 46.763855
L 364.25312 47.292847
L 365.507112 55.933044
L 365.82061 59.278381
L 366.134107 60.397339
L 366.761103 60.049622
L 367.074601 58.645569
L 367.701597 52.467407
L 368.328592 48.767761
L 368.64209 47.269775
L 368.955588 47.271423
L 369.582583 51.651672
L 370.836575 60.763184
L 371.150073 60.445129
L 371.46357 59.314636
L 371.777068 57.501892
L 372.717562 49.458252
L 373.03106 48.013
L 373.344557 47.429626
L 373.658055 47.790527
L 373.971553 49.557129
L 375.225544 59.757935
L 375.539042 60.802734
L 375.85254 60.397339
L 376.166038 58.863098
L 376.793033 53.94397
L 377.420029 48.835327
L 377.733527 47.617493
L 378.047025 47.472473
L 378.360522 48.36731
L 378.67402 50.330017
L 379.614514 58.46759
L 379.928012 60.141907
L 380.241509 60.743408
L 380.555007 60.09906
L 380.868505 58.259949
L 382.122496 48.378845
L 382.435994 47.38678
L 382.749492 47.614197
L 383.06299 48.942444
L 383.689985 53.993408
L 384.316981 59.115234
L 384.630479 60.446777
L 384.943977 60.628052
L 385.257475 59.604675
L 385.88447 54.888245
L 386.511466 49.70874
L 386.824964 48.10199
L 387.138462 47.543335
L 387.451959 48.167908
L 387.765457 49.85376
L 389.019448 59.228943
L 389.332946 60.09082
L 389.646444 59.866699
L 389.959942 58.619202
L 390.586938 54.014832
L 391.213933 49.506042
L 391.527431 48.255249
L 391.840929 48.011353
L 392.154427 48.840271
L 392.467925 50.608521
L 393.721916 59.510742
L 394.035414 60.074341
L 394.348911 59.537109
L 394.662409 58.006165
L 395.916401 49.041321
L 396.229898 48.090454
L 396.543396 48.182739
L 396.856894 49.319824
L 397.48389 53.825317
L 398.110885 58.510437
L 398.424383 59.774414
L 398.737881 59.998535
L 399.051379 59.131714
L 399.678374 54.968994
L 400.30537 50.226196
L 400.618868 48.665588
L 400.932366 48.034424
L 401.245864 48.454651
L 401.559361 49.881775
L 402.813353 58.985046
L 403.126851 59.930969
L 403.440348 59.809021
L 403.753846 58.632385
L 404.380842 54.169739
L 405.007837 49.679077
L 405.321335 48.377197
L 405.634833 48.077271
L 405.948331 48.832031
L 406.261829 50.531067
L 407.51582 59.373962
L 407.829318 59.990295
L 408.142816 59.518982
L 408.456313 58.058899
L 409.710305 49.197876
L 410.023803 48.199219
L 410.3373 48.237122
L 410.650798 49.303345
L 411.277794 53.677002
L 411.90479 58.334106
L 412.218287 59.64917
L 412.531785 59.927673
L 412.845283 59.128418
L 413.472279 55.090942
L 414.099274 50.384399
L 414.412772 48.805664
L 414.72627 48.125061
L 415.039768 48.482666
L 415.353266 49.847168
L 416.920755 59.827148
L 417.234253 59.766174
L 417.54775 58.663696
L 418.174746 54.318054
L 418.801742 49.863647
L 419.115239 48.611206
L 419.428737 48.324463
L 419.742235 49.06604
L 420.369231 52.867859
L 420.996226 57.353577
L 421.309724 58.835083
L 421.623222 59.429993
L 421.93672 59.05426
L 422.250218 57.775452
L 423.504209 49.749939
L 423.817707 48.805664
L 424.131205 48.767761
L 424.444702 49.674133
L 425.071698 53.546814
L 425.698694 57.795227
L 426.012192 59.037781
L 426.325689 59.364075
L 426.639187 58.713135
L 426.952685 57.218445
L 428.206676 49.398926
L 428.520174 48.715027
L 428.833672 48.965515
L 429.14717 50.128967
L 429.774165 54.234009
L 430.401161 58.228638
L 430.714659 59.209167
L 431.028157 59.238831
L 431.341655 58.327515
L 431.96865 54.481201
L 432.595646 50.386047
L 432.909144 49.118774
L 433.222642 48.723267
L 433.536139 49.252258
L 433.849637 50.641479
L 435.103628 58.594482
L 435.417126 59.301453
L 435.730624 59.032837
L 436.044122 57.875977
L 437.611611 48.927612
L 437.925109 48.80896
L 438.238607 49.609863
L 438.865602 53.324341
L 439.492598 57.569458
L 439.806096 58.882874
L 440.119594 59.303101
L 440.433091 58.74939
L 440.746589 57.350281
L 442.000581 49.583496
L 442.314078 48.820496
L 442.627576 48.973755
L 442.941074 50.03009
L 443.56807 54.014832
L 444.195065 58.030884
L 444.508563 59.088867
L 444.822061 59.20752
L 445.135559 58.388489
L 445.762554 54.695435
L 446.38955 50.603577
L 446.703048 49.285217
L 447.016546 48.789185
L 447.330044 49.224243
L 447.643541 50.514587
L 448.897533 58.414856
L 449.21103 59.200928
L 449.524528 59.031189
L 449.838026 57.964966
L 450.465022 54.032959
L 451.092017 50.153687
L 451.405515 49.062744
L 451.719013 48.848511
L 452.032511 49.553833
L 452.346009 51.055115
L 453.6 58.634033
L 453.6 58.634033
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p629da2a526">
&lt;rect height="108" width="475.2" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
&lt;/blockquote>
&lt;p>This sounds dreadful, but we can subtract it from the original signal to produce&lt;/p>
&lt;blockquote>
&lt;span class="svg">&lt;svg height="108pt" version="1.1" viewBox="0 0 475.2 108" width="475.2pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 108
L 475.2 108
L 475.2 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p0c8c92a352)" d="M 21.6 53.980225
L 21.913498 46.877563
L 22.226996 59.535461
L 22.540493 61.38446
L 22.853991 47.698242
L 23.167489 44.848938
L 23.480987 56.53949
L 23.794485 60.857117
L 24.107983 56.165405
L 24.42148 57.406311
L 24.734978 60.984009
L 25.048476 57.806763
L 25.361974 48.156372
L 25.675472 50.494812
L 25.98897 49.262146
L 26.302467 46.131042
L 26.615965 45.381226
L 26.929463 54.214233
L 27.242961 59.462952
L 27.556459 57.940247
L 27.869956 57.650208
L 28.183454 60.352844
L 28.496952 59.462952
L 28.81045 54.611389
L 29.123948 52.976624
L 29.437446 54.232361
L 30.064441 49.346191
L 30.377939 48.639221
L 31.004935 53.36554
L 31.318433 54.00824
L 31.63193 55.962708
L 31.945428 57.035522
L 32.258926 57.220093
L 32.572424 56.112671
L 33.199419 55.142029
L 33.826415 52.600891
L 34.139913 51.908752
L 34.453411 51.791748
L 34.766909 51.837891
L 35.080406 52.447632
L 35.707402 54.805847
L 36.0209 55.22113
L 36.647896 55.524353
L 36.961393 55.435364
L 37.588389 54.163147
L 37.901887 53.817078
L 38.528882 52.796997
L 38.84238 52.628906
L 39.155878 52.958496
L 40.096372 54.365845
L 40.409869 54.711914
L 40.723367 54.819031
L 41.350363 54.606445
L 41.977358 54.079102
L 42.604354 53.583069
L 43.23135 53.370483
L 43.544848 53.472656
L 44.798839 54.364197
L 45.425835 54.425171
L 46.05283 54.182922
L 47.306821 53.645691
L 47.933817 53.759399
L 48.874311 54.146667
L 49.501306 54.214233
L 50.4418 54.07251
L 51.695791 53.831909
L 52.636284 53.950562
L 53.576778 54.112061
L 54.517271 54.075806
L 56.084761 53.902771
L 59.533237 53.993408
L 61.100726 53.975281
L 63.29521 54.013184
L 66.116691 54.011536
L 129.756749 54.034607
L 130.070247 43.927734
L 130.383745 60.216064
L 130.697242 50.354736
L 131.01074 46.71936
L 131.324238 59.291565
L 131.637736 64.073914
L 131.951234 53.962097
L 132.264731 59.27179
L 132.578229 60.09082
L 132.891727 58.810364
L 133.205225 51.106201
L 133.518723 46.996216
L 133.832221 51.707703
L 134.145718 51.294067
L 134.459216 50.516235
L 134.772714 53.16449
L 135.086212 56.80481
L 135.39971 57.122864
L 135.713208 55.664429
L 136.026705 55.881958
L 136.340203 58.432983
L 136.653701 56.867432
L 136.967199 52.866211
L 137.594194 53.973633
L 137.907692 51.86261
L 138.22119 51.233093
L 138.534688 51.295715
L 138.848186 52.983215
L 139.161684 53.299622
L 139.475181 53.914307
L 139.788679 55.137085
L 140.102177 55.867126
L 140.729173 55.140381
L 141.042671 54.949219
L 141.356168 54.472961
L 141.983164 53.129883
L 142.61016 52.792053
L 142.923657 52.77063
L 143.237155 53.215576
L 143.864151 54.364197
L 144.804644 54.894836
L 145.118142 54.756409
L 146.058636 53.932434
L 146.999129 53.358948
L 147.626125 53.625916
L 148.880116 54.423523
L 149.507112 54.380676
L 150.134107 54.082397
L 151.074601 53.685242
L 151.701597 53.693481
L 152.955588 54.173035
L 153.582583 54.229065
L 154.836575 53.965393
L 155.777068 53.828613
L 157.03106 54.041199
L 158.285051 54.097229
L 160.479536 53.92749
L 162.987518 54.028015
L 165.182003 53.980225
L 167.689985 54.00824
L 169.88447 53.998352
L 175.213933 54.011536
L 181.170392 53.995056
L 185.245864 54.003296
L 190.575327 54.001648
L 197.472279 54
L 207.817707 53.998352
L 237.913498 54.023071
L 238.226996 61.882141
L 238.853991 45.091187
L 239.480987 61.549255
L 240.107983 45.758606
L 240.42148 53.970337
L 240.734978 57.533203
L 241.048476 55.522705
L 241.361974 49.570312
L 241.98897 57.225037
L 242.302467 56.633423
L 242.615965 52.261414
L 242.929463 57.287659
L 243.242961 56.526306
L 243.869956 51.007324
L 244.496952 54.369141
L 245.123948 51.239685
L 245.437446 53.957153
L 245.750943 53.851685
L 246.064441 52.704712
L 246.377939 53.845093
L 246.691437 55.506226
L 247.004935 55.410645
L 247.318433 54.91626
L 247.63193 54.725098
L 247.945428 54.847046
L 248.258926 54.397156
L 248.572424 53.667114
L 248.885922 53.439697
L 249.199419 53.457825
L 249.826415 53.057373
L 250.139913 53.316101
L 250.453411 53.904419
L 250.766909 54.186218
L 251.080406 54.225769
L 251.707402 54.733337
L 253.588389 53.616028
L 253.901887 53.563293
L 254.84238 53.772583
L 255.469376 54.159851
L 256.723367 54.263672
L 257.977358 53.876404
L 258.604354 53.787415
L 259.858345 54.021423
L 260.798839 54.130188
L 261.739332 54.047791
L 262.993324 53.907715
L 264.247315 53.988464
L 265.814804 54.05603
L 270.203774 54.028015
L 278.354717 54.003296
L 281.489695 53.996704
L 291.521626 53.993408
L 294.656604 54.001648
L 305.002032 53.996704
L 309.077504 54.001648
L 319.109434 54.004944
L 345.756749 54.034607
L 346.070247 52.932129
L 346.383745 58.077026
L 346.697242 48.9375
L 347.01074 52.500366
L 347.324238 54.245544
L 347.637736 62.933533
L 348.264731 46.608948
L 348.891727 61.40918
L 349.205225 51.135864
L 349.518723 53.044189
L 350.145718 55.529297
L 350.459216 49.77301
L 351.086212 53.698425
L 351.39971 54.721802
L 351.713208 51.959839
L 352.026705 54.25708
L 352.340203 55.723755
L 352.653701 55.415588
L 352.967199 53.673706
L 353.280697 54.138428
L 353.594194 55.006897
L 353.907692 54.153259
L 354.22119 52.587708
L 354.848186 53.902771
L 355.161684 53.222168
L 355.475181 52.983215
L 356.102177 54.491089
L 356.415675 54.113708
L 356.729173 54.074158
L 357.042671 54.687195
L 357.356168 54.830566
L 357.669666 54.326294
L 357.983164 54.034607
L 358.61016 54.075806
L 358.923657 53.620972
L 359.237155 53.503967
L 359.864151 53.80719
L 360.177649 53.714905
L 360.491147 53.843445
L 360.804644 54.108765
L 361.43164 54.212585
L 362.372134 54.19281
L 362.999129 53.986816
L 364.25312 53.810486
L 366.447605 54.118652
L 367.701597 53.981873
L 368.955588 53.904419
L 371.46357 54.049438
L 374.285051 53.986816
L 376.793033 54.004944
L 379.301016 54.00824
L 385.570972 54.006592
L 390.900435 53.993408
L 395.289405 53.998352
L 402.186357 54.006592
L 406.575327 53.996704
L 410.023803 54.003296
L 415.666763 53.995056
L 437.611611 54.001648
L 444.508563 53.995056
L 448.897533 54
L 453.6 54.001648
L 453.6 54.001648
" style="fill:none;stroke:#cc0d1a;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p0c8c92a352">
&lt;rect height="108" width="475.2" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
&lt;/blockquote>
&lt;p>which gives the peaks, and we can do whatever we want with them. But the isolation isn&amp;rsquo;t perfect. The decaying tone causes the compressor to dampen the signal more when the tone is loud, and some of the tone ends up in the peaks signal. What we care about is that they sum exactly to the original signal, and we can now process them independently.&lt;/p>
&lt;h2 id="saturators">Saturators&lt;/h2>
&lt;p>Applying a saturator to the difference signal of a saturator &lt;em>reduces&lt;/em> the amount of distortion. The simplest saturator is the hard-clip
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 28.8 18" width="28.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 28.8 18
L 28.8 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p7eb5527340)" d="M 1.309091 11.97
L 1.724675 11.97
L 2.14026 11.97
L 2.555844 11.97
L 2.971429 11.97
L 3.387013 11.97
L 3.802597 11.97
L 4.218182 11.97
L 4.633766 11.97
L 5.049351 11.97
L 5.464935 11.97
L 5.880519 11.97
L 6.296104 11.97
L 6.711688 11.97
L 7.127273 11.97
L 7.542857 11.97
L 7.958442 11.97
L 8.374026 11.97
L 8.78961 11.97
L 9.205195 11.97
L 9.620779 11.97
L 10.036364 11.97
L 10.451948 11.714286
L 10.867532 11.428571
L 11.283117 11.142857
L 11.698701 10.857143
L 12.114286 10.571429
L 12.52987 10.285714
L 12.945455 10
L 13.361039 9.714286
L 13.776623 9.428571
L 14.192208 9.142857
L 14.607792 8.857143
L 15.023377 8.571429
L 15.438961 8.285714
L 15.854545 8
L 16.27013 7.714286
L 16.685714 7.428571
L 17.101299 7.142857
L 17.516883 6.857143
L 17.932468 6.571429
L 18.348052 6.285714
L 18.763636 6.03
L 19.179221 6.03
L 19.594805 6.03
L 20.01039 6.03
L 20.425974 6.03
L 20.841558 6.03
L 21.257143 6.03
L 21.672727 6.03
L 22.088312 6.03
L 22.503896 6.03
L 22.919481 6.03
L 23.335065 6.03
L 23.750649 6.03
L 24.166234 6.03
L 24.581818 6.03
L 24.997403 6.03
L 25.412987 6.03
L 25.828571 6.03
L 26.244156 6.03
L 26.65974 6.03
L 27.075325 6.03
L 27.490909 6.03
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p7eb5527340">
&lt;rect height="18" width="28.8" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
, and the difference signal of this is also a saturator,
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 28.8 18" width="28.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 28.8 18
L 28.8 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p411deb80e6)" d="M 1.309091 2.97
L 1.724675 3.255714
L 2.14026 3.541429
L 2.555844 3.827143
L 2.971429 4.112857
L 3.387013 4.398571
L 3.802597 4.684286
L 4.218182 4.97
L 4.633766 5.255714
L 5.049351 5.541429
L 5.464935 5.827143
L 5.880519 6.112857
L 6.296104 6.398571
L 6.711688 6.684286
L 7.127273 6.97
L 7.542857 7.255714
L 7.958442 7.541429
L 8.374026 7.827143
L 8.78961 8.112857
L 9.205195 8.398571
L 9.620779 8.684286
L 10.036364 8.97
L 10.451948 9
L 10.867532 9
L 11.283117 9
L 11.698701 9
L 12.114286 9
L 12.52987 9
L 12.945455 9
L 13.361039 9
L 13.776623 9
L 14.192208 9
L 14.607792 9
L 15.023377 9
L 15.438961 9
L 15.854545 9
L 16.27013 9
L 16.685714 9
L 17.101299 9
L 17.516883 9
L 17.932468 9
L 18.348052 9
L 18.763636 9.03
L 19.179221 9.315714
L 19.594805 9.601429
L 20.01039 9.887143
L 20.425974 10.172857
L 20.841558 10.458571
L 21.257143 10.744286
L 21.672727 11.03
L 22.088312 11.315714
L 22.503896 11.601429
L 22.919481 11.887143
L 23.335065 12.172857
L 23.750649 12.458571
L 24.166234 12.744286
L 24.581818 13.03
L 24.997403 13.315714
L 25.412987 13.601429
L 25.828571 13.887143
L 26.244156 14.172857
L 26.65974 14.458571
L 27.075325 14.744286
L 27.490909 15.03
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p411deb80e6">
&lt;rect height="18" width="28.8" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
. If we hard-clip this curve, we get
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 28.8 18" width="28.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 28.8 18
L 28.8 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#pabcc87849c)" d="M 1.309091 6.03
L 1.724675 6.03
L 2.14026 6.03
L 2.555844 6.03
L 2.971429 6.03
L 3.387013 6.03
L 3.802597 6.03
L 4.218182 6.03
L 4.633766 6.03
L 5.049351 6.03
L 5.464935 6.03
L 5.880519 6.112857
L 6.296104 6.398571
L 6.711688 6.684286
L 7.127273 6.97
L 7.542857 7.255714
L 7.958442 7.541429
L 8.374026 7.827143
L 8.78961 8.112857
L 9.205195 8.398571
L 9.620779 8.684286
L 10.036364 8.97
L 10.451948 9
L 10.867532 9
L 11.283117 9
L 11.698701 9
L 12.114286 9
L 12.52987 9
L 12.945455 9
L 13.361039 9
L 13.776623 9
L 14.192208 9
L 14.607792 9
L 15.023377 9
L 15.438961 9
L 15.854545 9
L 16.27013 9
L 16.685714 9
L 17.101299 9
L 17.516883 9
L 17.932468 9
L 18.348052 9
L 18.763636 9.03
L 19.179221 9.315714
L 19.594805 9.601429
L 20.01039 9.887143
L 20.425974 10.172857
L 20.841558 10.458571
L 21.257143 10.744286
L 21.672727 11.03
L 22.088312 11.315714
L 22.503896 11.601429
L 22.919481 11.887143
L 23.335065 11.97
L 23.750649 11.97
L 24.166234 11.97
L 24.581818 11.97
L 24.997403 11.97
L 25.412987 11.97
L 25.828571 11.97
L 26.244156 11.97
L 26.65974 11.97
L 27.075325 11.97
L 27.490909 11.97
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="pabcc87849c">
&lt;rect height="18" width="28.8" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
, and adding this to the original signal
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 28.8 18" width="28.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 28.8 18
L 28.8 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#pecdcb50537)" d="M 1.309091 18
L 1.724675 17.714286
L 2.14026 17.428571
L 2.555844 17.142857
L 2.971429 16.857143
L 3.387013 16.571429
L 3.802597 16.285714
L 4.218182 16
L 4.633766 15.714286
L 5.049351 15.428571
L 5.464935 15.142857
L 5.880519 14.857143
L 6.296104 14.571429
L 6.711688 14.285714
L 7.127273 14
L 7.542857 13.714286
L 7.958442 13.428571
L 8.374026 13.142857
L 8.78961 12.857143
L 9.205195 12.571429
L 9.620779 12.285714
L 10.036364 12
L 10.451948 11.714286
L 10.867532 11.428571
L 11.283117 11.142857
L 11.698701 10.857143
L 12.114286 10.571429
L 12.52987 10.285714
L 12.945455 10
L 13.361039 9.714286
L 13.776623 9.428571
L 14.192208 9.142857
L 14.607792 8.857143
L 15.023377 8.571429
L 15.438961 8.285714
L 15.854545 8
L 16.27013 7.714286
L 16.685714 7.428571
L 17.101299 7.142857
L 17.516883 6.857143
L 17.932468 6.571429
L 18.348052 6.285714
L 18.763636 6
L 19.179221 5.714286
L 19.594805 5.428571
L 20.01039 5.142857
L 20.425974 4.857143
L 20.841558 4.571429
L 21.257143 4.285714
L 21.672727 4
L 22.088312 3.714286
L 22.503896 3.428571
L 22.919481 3.142857
L 23.335065 2.857143
L 23.750649 2.571429
L 24.166234 2.285714
L 24.581818 2
L 24.997403 1.714286
L 25.412987 1.428571
L 25.828571 1.142857
L 26.244156 0.857143
L 26.65974 0.571429
L 27.075325 0.285714
L 27.490909 0
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="pecdcb50537">
&lt;rect height="18" width="28.8" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
produces the saturator curve
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 28.8 18" width="28.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 28.8 18
L 28.8 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#pac73bfb031)" d="M 1.309091 15.03
L 1.724675 14.744286
L 2.14026 14.458571
L 2.555844 14.172857
L 2.971429 13.887143
L 3.387013 13.601429
L 3.802597 13.315714
L 4.218182 13.03
L 4.633766 12.744286
L 5.049351 12.458571
L 5.464935 12.172857
L 5.880519 11.97
L 6.296104 11.97
L 6.711688 11.97
L 7.127273 11.97
L 7.542857 11.97
L 7.958442 11.97
L 8.374026 11.97
L 8.78961 11.97
L 9.205195 11.97
L 9.620779 11.97
L 10.036364 11.97
L 10.451948 11.714286
L 10.867532 11.428571
L 11.283117 11.142857
L 11.698701 10.857143
L 12.114286 10.571429
L 12.52987 10.285714
L 12.945455 10
L 13.361039 9.714286
L 13.776623 9.428571
L 14.192208 9.142857
L 14.607792 8.857143
L 15.023377 8.571429
L 15.438961 8.285714
L 15.854545 8
L 16.27013 7.714286
L 16.685714 7.428571
L 17.101299 7.142857
L 17.516883 6.857143
L 17.932468 6.571429
L 18.348052 6.285714
L 18.763636 6.03
L 19.179221 6.03
L 19.594805 6.03
L 20.01039 6.03
L 20.425974 6.03
L 20.841558 6.03
L 21.257143 6.03
L 21.672727 6.03
L 22.088312 6.03
L 22.503896 6.03
L 22.919481 6.03
L 23.335065 5.827143
L 23.750649 5.541429
L 24.166234 5.255714
L 24.581818 4.97
L 24.997403 4.684286
L 25.412987 4.398571
L 25.828571 4.112857
L 26.244156 3.827143
L 26.65974 3.541429
L 27.075325 3.255714
L 27.490909 2.97
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="pac73bfb031">
&lt;rect height="18" width="28.8" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
. This saturator doesn&amp;rsquo;t clip very quiet or very loud signal values, only those somewhere inbetween.&lt;/p>
&lt;p>One of the nice things about this is that good saturators have good anti-aliasing, and so will any curves you make this way.&lt;/p>
&lt;p>Less esoterically, think of saturation as adding harmonic content. Taking the difference isolates the added harmonics, and you can apply EQ to them.&lt;/p>
&lt;p>Another option is to EQ before saturating, take the difference between the EQ&amp;rsquo;d and saturated signals, then add that back to the original signal. Now you have a saturator that responds differently to certain frequencies. Mixing linear and non-linear effects this way will mess with any intuition you have for them&amp;ndash;don&amp;rsquo;t think you&amp;rsquo;re being scientific when you do this.&lt;/p>
&lt;h3 id="bit-depth-reduction">Bit-depth Reduction&lt;/h3>
&lt;p>You can think of bit-depth reduction as an extreme saturation curve,
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 28.8 18" width="28.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 28.8 18
L 28.8 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#p97dbe01af5)" d="M 1.309091 18
L 1.724675 18
L 2.14026 18
L 2.555844 18
L 2.971429 18
L 3.387013 18
L 3.802597 18
L 4.218182 18
L 4.633766 15.75
L 5.049351 15.75
L 5.464935 15.75
L 5.880519 15.75
L 6.296104 15.75
L 6.711688 15.75
L 7.127273 15.75
L 7.542857 15.75
L 7.958442 13.5
L 8.374026 13.5
L 8.78961 13.5
L 9.205195 13.5
L 9.620779 13.5
L 10.036364 13.5
L 10.451948 13.5
L 10.867532 13.5
L 11.283117 11.25
L 11.698701 11.25
L 12.114286 11.25
L 12.52987 11.25
L 12.945455 11.25
L 13.361039 11.25
L 13.776623 11.25
L 14.192208 11.25
L 14.607792 9
L 15.023377 9
L 15.438961 9
L 15.854545 9
L 16.27013 9
L 16.685714 9
L 17.101299 9
L 17.516883 9
L 17.932468 6.75
L 18.348052 6.75
L 18.763636 6.75
L 19.179221 6.75
L 19.594805 6.75
L 20.01039 6.75
L 20.425974 6.75
L 20.841558 6.75
L 21.257143 4.5
L 21.672727 4.5
L 22.088312 4.5
L 22.503896 4.5
L 22.919481 4.5
L 23.335065 4.5
L 23.750649 4.5
L 24.166234 4.5
L 24.581818 2.25
L 24.997403 2.25
L 25.412987 2.25
L 25.828571 2.25
L 26.244156 2.25
L 26.65974 2.25
L 27.075325 2.25
L 27.490909 0
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="p97dbe01af5">
&lt;rect height="18" width="28.8" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
. This particular curve reduces a signal to 3 bits of dynamic range: &lt;code>000&lt;/code>, &lt;code>001&lt;/code>, &lt;code>010&lt;/code>, &lt;code>011&lt;/code>, &lt;code>100&lt;/code>, &lt;code>101&lt;/code>, &lt;code>110&lt;/code>, and &lt;code>111&lt;/code>. The entire dynamic range is mapped to these 8 values, so there are 8 steps. Its difference signal is
&lt;span class="svgi">&lt;svg height="18pt" version="1.1" viewBox="0 0 28.8 18" width="28.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
&lt;g id="figure_1">
&lt;g id="patch_1">
&lt;path d="M 0 18
L 28.8 18
L 28.8 0
L 0 0
z
" style="fill:none;"/>
&lt;/g>
&lt;g id="axes_1">
&lt;g id="line2d_1">
&lt;path clip-path="url(#pe010576229)" d="M 1.309091 9
L 1.724675 9.285714
L 2.14026 9.571429
L 2.555844 9.857143
L 2.971429 10.142857
L 3.387013 10.428571
L 3.802597 10.714286
L 4.218182 11
L 4.633766 9.035714
L 5.049351 9.321429
L 5.464935 9.607143
L 5.880519 9.892857
L 6.296104 10.178571
L 6.711688 10.464286
L 7.127273 10.75
L 7.542857 11.035714
L 7.958442 9.071429
L 8.374026 9.357143
L 8.78961 9.642857
L 9.205195 9.928571
L 9.620779 10.214286
L 10.036364 10.5
L 10.451948 10.785714
L 10.867532 11.071429
L 11.283117 9.107143
L 11.698701 9.392857
L 12.114286 9.678571
L 12.52987 9.964286
L 12.945455 10.25
L 13.361039 10.535714
L 13.776623 10.821429
L 14.192208 11.107143
L 14.607792 9.142857
L 15.023377 9.428571
L 15.438961 9.714286
L 15.854545 10
L 16.27013 10.285714
L 16.685714 10.571429
L 17.101299 10.857143
L 17.516883 11.142857
L 17.932468 9.178571
L 18.348052 9.464286
L 18.763636 9.75
L 19.179221 10.035714
L 19.594805 10.321429
L 20.01039 10.607143
L 20.425974 10.892857
L 20.841558 11.178571
L 21.257143 9.214286
L 21.672727 9.5
L 22.088312 9.785714
L 22.503896 10.071429
L 22.919481 10.357143
L 23.335065 10.642857
L 23.750649 10.928571
L 24.166234 11.214286
L 24.581818 9.25
L 24.997403 9.535714
L 25.412987 9.821429
L 25.828571 10.107143
L 26.244156 10.392857
L 26.65974 10.678571
L 27.075325 10.964286
L 27.490909 9
" style="fill:none;stroke:#1a0dcc;stroke-linecap:square;stroke-width:1.5;"/>
&lt;/g>
&lt;/g>
&lt;/g>
&lt;defs>
&lt;clipPath id="pe010576229">
&lt;rect height="18" width="28.8" x="0" y="0"/>
&lt;/clipPath>
&lt;/defs>
&lt;/svg>
&lt;/span>
.&lt;/p>
&lt;p>There are two ways to interpret it. One is that it&amp;rsquo;s wraparound distortion (see the &lt;a href="https://docs.cycling74.com/max7/maxobject/pong~">pong~&lt;/a> object in Max/MSP). Another is that it&amp;rsquo;s giving you back the bits the other curve throws away. Either way, I like experimenting with this. I&amp;rsquo;m one of those unfortunate people who likes the sound of bit crushing, and you can use this to get a lot more variation (and dynamic range) out of it.&lt;/p>
&lt;h3 id="doing-it">Doing It&lt;/h3>
&lt;p>The best way of actually doing this took me a while to figure out. I use Ableton Live, which has audio effect racks. They are almost enough to do everything we want to do, but if you&amp;rsquo;re using a DAW that instead relies on complex routing, it might be too unwieldly to use in practice. I&amp;rsquo;ve settled on using two audio effect racks, one for the &lt;code>a[n] + (b[n] - a[n])&lt;/code> case, and another for the &lt;code>b[n] + (a[n] - b[n])&lt;/code> case, which I call positive and negative.&lt;/p>
&lt;p>The positive case is easiest, because &lt;code>a[n]&lt;/code> is the original dry signal, and we can duplicate it as needed by adding more chains to an audio effect rack.&lt;/p>
&lt;div style="display: flex; justify-content: center;">
&lt;img src="https://graemephi.github.io/posts/dumb-tricks-with-phase-inversion/AER1.png" width="796" height="192" loading="lazy" />
&lt;/div>
&lt;p>The outer A chain is empty. The inner -A chain contains a utility effect with both channels inverted. Then, you place any effect you want the difference signal of in the B chain, and can process the difference signal after the inner audio effect rack. The utility effects gain knob acts as a dry/wet mix; the effect is 100% wet at 0 dB.&lt;/p>
&lt;p>The negative case is more tricky. Implementing &lt;code>b[n] + (a[n] - b[n])&lt;/code> requires routing that effect racks can&amp;rsquo;t do. You could produce &lt;code>b&lt;/code> twice, but having to update the every parameter twice (or being limited to the effect rack&amp;rsquo;s macro controls) is a huge pain. Instead, I use the side chain from some of Ableton&amp;rsquo;s effects, which can capture audio before or after any effect rack. &lt;a href="https://graemephi.github.io/posts/dumb-tricks-with-phase-inversion/AER2.png">See here for an image&lt;/a>, where I&amp;rsquo;ve used the compressor&amp;rsquo;s side chain, set to audition, to tap audio from the B chain before inverting it. To use this, we put an effect in the B chain, and everything after the inner effect rack is the difference signal. Collapse the side chain rack and never think about it. This effect&amp;rsquo;s final utility gain is 100% dry at 0 dB (if the difference signal is left unmodified).&lt;/p>
&lt;p>One thing is missing from these: it&amp;rsquo;s really nice to be able to apply effects to the original signal after we&amp;rsquo;ve used it to produce a difference signal but before we&amp;rsquo;ve summed them back together. To do that, put another empty effect rack in the A (positive) or B (negative) chain, and use the side chain audition to tap that rack pre-fx. Now, anything before the emtpy rack gets differenced, and anything after only gets sent to the output.&lt;/p>
&lt;p>I think you should figure out recreating this for yourself, or even find your own solution, because keeping track of what signal you&amp;rsquo;re producing and what needs to be inverted at what point in the signal chain is informative. My early attempts weren&amp;rsquo;t pretty. But if you&amp;rsquo;d rather just use mine, here&amp;rsquo;s &lt;a href="https://graemephi.github.io/posts/dumb-tricks-with-phase-inversion/Difference%20(Positive).adg">Difference (Positive)&lt;/a> and &lt;a href="https://graemephi.github.io/posts/dumb-tricks-with-phase-inversion/Difference%20(Negative).adg">Difference (Negative)&lt;/a> (Live 10. I don&amp;rsquo;t know if these things are backwards compatible, but I don&amp;rsquo;t use any features that are 10 only).&lt;/p>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>The process for finding the frequency content of a signal, the fourier transform, &lt;a href="https://math.stackexchange.com/questions/140788/how-is-the-fourier-transform-linear">is linear&lt;/a>.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>One thing I&amp;rsquo;m taking for granted here is that plugins correctly report the delay they introduce to the signal chain. It&amp;rsquo;s hard to write a compressor people want to use without knowing exactly how much delay you&amp;rsquo;re inserting into the signal chain, but equalisers and filters can introduce frequency-specific delays. I couldn&amp;rsquo;t say how common or uncommon it is for plugins to handle this properly. All this still works, but the difference signal contains information you don&amp;rsquo;t want it to contain. Then again, if a filter is messing with the phase of your audio, it&amp;rsquo;s already screwing up your audio! You don&amp;rsquo;t care about theoretical correctness if you&amp;rsquo;re still using that filter.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item></channel></rss>