<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[SPACEKITCAT]]></title><description><![CDATA[code, code and more code]]></description><link>http://www.spacekitcat.com/</link><image><url>http://www.spacekitcat.com/favicon.png</url><title>SPACEKITCAT</title><link>http://www.spacekitcat.com/</link></image><generator>Ghost 3.2</generator><lastBuildDate>Sat, 20 Dec 2025 10:58:42 GMT</lastBuildDate><atom:link href="http://www.spacekitcat.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Fast Fourier Transform Notes]]></title><description><![CDATA[<p>A signal can contain multiple periodic functions with different amplitudes and frequencies. The Fourier Transform is able to decompose a signal down into the individual sine functions that constitute the overall signal.</p><p>Let's say your signal is a 262hz (or middle C) sine wave, encoded as a Pulse Code Modulation</p>]]></description><link>http://www.spacekitcat.com/fourier-transform/</link><guid isPermaLink="false">5f2db31e01a60a11b61904ba</guid><category><![CDATA[fft]]></category><category><![CDATA[fourier]]></category><category><![CDATA[dsp]]></category><dc:creator><![CDATA[Lisa Burton]]></dc:creator><pubDate>Sat, 02 Jan 2021 21:03:00 GMT</pubDate><media:content url="http://www.spacekitcat.com/content/images/2021/01/Figure_1-1.png" medium="image"/><content:encoded><![CDATA[<img src="http://www.spacekitcat.com/content/images/2021/01/Figure_1-1.png" alt="Fast Fourier Transform Notes"><p>A signal can contain multiple periodic functions with different amplitudes and frequencies. The Fourier Transform is able to decompose a signal down into the individual sine functions that constitute the overall signal.</p><p>Let's say your signal is a 262hz (or middle C) sine wave, encoded as a Pulse Code Modulation (PCM) stream with a sample rate of 44100 samples per second. According to the <a href="https://en.wikipedia.org/wiki/Pulse-code_modulation#:~:text=The%20Nyquist%E2%80%93Shannon%20sampling%20theorem,contained%20in%20the%20input%20signal">Nyquist–Shannon sampling theorem</a>, a signal can represent a frequency range of half of the sampling rate. A PCM sample rate of 44100 samples per second translates to a frequency range between 0-22050hz.</p><!--kg-card-begin: markdown--><p>A DFT configured with a window size of 256 means it will operate on a block of 256 chronologically ordered signal samples. The maximum resolution of the DFT is dictated by the window size and the frequency range of the input signal. This DFT operating on the PCM signal from the previous paragraph would have a resolution of \(\frac{44100}{256} = 172.266hz\) and would detect a dominant frequency range between 172.266hz and 344.531hz.</p>
<!--kg-card-end: markdown--><p>The Discrete Fourier Transform (DFT) is defined by the following equation.</p><!--kg-card-begin: markdown--><p>\begin{equation}<br>
X_{k} = \sum_{n=0}^N x_{n} e^{\frac{-2 i \pi n k}{N}}<br>
\end{equation}</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>\(X_{k}\) represents the fourier transform for frequency bin \(k\), where \(0 \le k \le N\) and each transform for k represents a slice of the frequency spectrum from \(k \cdot \frac{y}{N}\) to \(k \cdot \frac{y}{N} + \frac{y}{N}\), where \(y\) represents the original sample rate of the signal and \(N\) represents the window size of the DFT.</p>
<p><em>n.b. Raising e to an imaginary number represents <a href="https://en.wikipedia.org/wiki/Euler%27s_formula">Euler's formula</a>, i.e. \(e^{ix} = cos(x) - isin(x)\). If using a language without built-in complex number support, you would calculate the real and imaginary components separately by passing \(\frac{-2 \pi n k}{N}\) into the cosine and sine functions, respectively. Complex numbers are denoted by the letter <code>j</code> in Python, so <code>np.exp(1j * np.pi)</code> is the same as <code>cos(π) - isin(π)</code>.</em></p>
<!--kg-card-end: markdown--><h3 id="surfing-on-sine-waves">Surfing On Sine Waves</h3><p>The code below generates a 262hz sine wave represented as a 1 second long, 16-bit floating point PCM stream with a sample rate of 44100 samples per second. The generated graph only shows the time domain of the signal from 0 up to <code>windows_size</code> because this is the signal subset the subsequent Discrete Fourier Transforms will operate on. All of the subsequent code samples depend on this one.</p><!--kg-card-begin: markdown--><pre><code class="language-python">from matplotlib import pyplot as plt
import pandas as pd
import numpy as np

window_size = 256 # Experiment with this value, it should always be a power of two.
sample_rate = 44100
frequency = 262
seconds = 1
sample_count = sample_rate * seconds
pcm_stream_16bit = list(map(lambda x : np.float16(0.8 * np.sin(2.0 * np.pi * 
frequency * x / sample_rate)), np.arange(sample_count)))
s = pd.Series(pcm_stream_16bit[:window_size], index=np.arange(sample_count)[:window_size])

s.plot.line(figsize=(20,10))
plt.xlabel('Time Domain')
plt.ylabel('Amplitude')
plt.title(f'{frequency}hz sine wave')
plt.show()

step = sample_rate / window_size
print(f'Minimum unit size: {step}hz\n')

pcm = pcm_stream_16bit[:window_size]
pivot = len(pcm) // 2
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.spacekitcat.com/content/images/2021/01/Figure_1.png" class="kg-image" alt="Fast Fourier Transform Notes"><figcaption>The first 256 samples (out of 44100 per second) of a PCM signal containing a 262hz Sine wave. It has two peaks, one at around the 42nd sample and one at around the 214th sample. It dips at the centre point between the two peaks and moves smoothly between the three points.</figcaption></figure><h3 id="brute-force">Brute Force</h3><p>The next code snippet demonstrates how a brute force DFT on samples 0 to <code>window_size</code> from the PCM stream works. It outputs the sum of each frequency bin and determines which one is dominant. The outer loop has one iteration for each frequency bin / k-bin, the inner loop has one iteration for each PCM sample. </p><p>As you can see from the code, we only care about the first half of the DFT for figuring out the dominant frequency because when the DFT input is a sequence of real numbers (instead of complex), the output will have conjugate symmetry. In practical terms, the real valued DFT output mirrors the first half of the output sequence in the second half.</p><!--kg-card-begin: markdown--><pre><code class="language-python">import numpy as np

column_width = 45
pcm_column_width = 20

iterations = 0
largest = 0j
largest_index = 0

for k in range(0, len(pcm)):
    frequency_bin = [0j] * len(pcm)
    for i in range(0, len(pcm)):
        exponent = np.exp(-2.0j * np.pi * i * k / len(pcm))
        result = pcm[i] * exponent
        iterations = iterations + 1
        frequency_bin[i] = frequency_bin[i] + result

    total = sum(frequency_bin)
    print(f'TOTAL (FREQUENCY BIN / K-BIN {k}): {total}')
    
    # We only care about the first half of the DFT because
    # of Conjugate symmetry, as explained earlier.
    if total &gt; largest and k &lt; len(pcm) // 2:
        largest = total
        largest_index = k

print(f'\nMinimum unit size: {step}hz\n')        
print(f'Iterations: {iterations}')
print(f'Largest: {largest} {largest_index}')
print(f'hz: {largest_index * step} - {(largest_index * step) + step}')
</code></pre>
<!--kg-card-end: markdown--><h3 id="perfect-specimen">Perfect Specimen</h3><p>Numpy provides a production level Fast Fourier Transform (FFT), it's a good point of reference.   </p><!--kg-card-begin: markdown--><pre><code class="language-python">print(list(np.fft.fft(pcm, len(pcm))))
</code></pre>
<!--kg-card-end: markdown--><p>The outputted values are virtually the same, although rounding can make them appear less alike than they really are. The specifics of rounding complex floating point numbers are a rabbit hole for another day! If you doubt what I'm saying, stick the outputs on a graph.</p><h3 id="cooley-tukey-for-programmers">Cooley-Tukey For Programmers</h3><p>I'm going to walk through how the <a href="https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm">Cooley-Tukey FFT equation</a> is derived from the <a href="https://en.wikipedia.org/wiki/Discrete_Fourier_transform">DFT equation </a>(shown earlier) using code examples to provide a sort of "inductive walkthrough for programmers". Each snippet depends on the first code snippet and I'd recommend running them all locally and comparing them to get the most from this guide.</p><!--kg-card-begin: markdown--><p>The DFT can be calculated from the sum of two smaller DFTs. If you perform a DFT on the even and odd parts of the input, for example, you end up doing two \(\frac{N}{2}\) sized FFTs.</p>
<p>\begin{equation}<br>
X_{k} = \sum_{n=0}^N x_{n} e^{\frac{-2 i \pi n k}{N}} = \sum_{n=0}^{N/2} x_{2 n} e^{\frac{-2 i \pi (2 n) k}{N}} + \sum_{n=0}^{N/2} x_{2 n + 1} e^{\frac{-2 i \pi (2 n + 1) k}{N}}<br>
\end{equation}</p>
<!--kg-card-end: markdown--><p>The code below demonstrates that the equality relationship in the above formula is true.</p><!--kg-card-begin: markdown--><pre><code class="language-python">iterations = 0
transform_vector = [0j] * len(pcm)
for k in range(0, len(pcm)):
    for m in range(0, pivot):
        iterations = iterations + 1
        transform_vector[k] = transform_vector[k] + (pcm[2 * m] * np.exp(-2j * np.pi * (2 * m) * k / len(pcm)))

for k in range(0, len(pcm)):
    for m in range(0, pivot):
        iterations = iterations + 1
        transform_vector[k] = transform_vector[k] + (pcm[2 * m + 1] * np.exp(-2j * np.pi * (2 * m + 1) * k / len(pcm)))

print(f'Iterations: {iterations}\n')
print(list(transform_vector))
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>The even and odd side of the equation are almost identical, the only difference is that the odd DFT exponent has \(2n + 1\) instead of \(2n\). If we could make both exponents identical, we would be able to calcualte \(e^{\frac{-2 \pi i (2 n) k}{N}}\) once for both sides of the equation.</p>
<p>Fortunately, all we have to do is factor a common multiplier out of the odd side of the equation.</p>
<p>\begin{equation}<br>
X_{k} = \sum_{n=0}^{N/2} x_{2 n} e^{\frac{-2 i \pi n k}{N/2}} + e^{\frac{-2 i \pi k}{N}} \sum_{n=0}^{N/2} x_{2 n + 1} e^{\frac{-2 i \pi n k}{N/2}}<br>
\end{equation}</p>
<p>The odd DFT can now reuse the exponent from the even DFT, we just have to multiply it by the common multiplier. The rearranged formula reduces the total number of calculations required for the full DFT and the number of iterations is also easier to reduce from \(N^2\) to \(\frac{N^2}{2}\). The order of complexity is still \(O(N^2)\).</p>
<!--kg-card-end: markdown--><p>The code below implements the rearranged equation.</p><!--kg-card-begin: markdown--><pre><code class="language-python">iterations = 0
even_transform_vector = [0j] * len(pcm)
odd_transform_vector = [0j] * len(pcm)
for k in range(0, len(pcm)):
    for m in range(0, pivot):
        iterations = iterations + 1
        e = np.exp(-2j * np.pi * m * k / pivot)
        even_transform_vector[k] = even_transform_vector[k] + (pcm[2 * m] * e)
        odd_transform_vector[k] = odd_transform_vector[k] + (pcm[2 * m + 1] * e)
    odd_transform_vector[k] = odd_transform_vector[k] * np.exp(-2j * np.pi * k / len(pcm)) 

print(f'Iterations: {iterations}\n')
print(list(np.add(even_transform_vector, odd_transform_vector)))
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>We can calculate \(X_{k+\frac{N}{2}}\) using the equation for \(X_{k}\) if we add \(\frac{N}{2}\) to \(k\).</p>
<p>\begin{equation}<br>
X_{k+\frac{N}{2}} = \sum_{n=0}^{N/2} x_{2 n} e^{\frac{-2 i \pi n (k+\frac{N}{2})}{N/2}} + e^{\frac{-2 i \pi (k+\frac{N}{2})}{N}} \sum_{n=0}^{N/2} x_{2 n + 1} e^{\frac{-2 i \pi n (k+\frac{N}{2})}{N/2}}<br>
\end{equation}</p>
<p>This can halve the number of iterations of the k-loop because we can calculate \(X_{k+\frac{N}{2}}\) while we're calculating \(X_{k}\)</p>
<p>The implementation below demonstrates that the iteration count is a quarter of what is was in the previous step.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-python">iterations = 0
even_transform_vector = [0j] * len(pcm)
odd_transform_vector = [0j] * len(pcm)
for k in range(0, pivot):
    for m in range(0, pivot):
        iterations = iterations + 1
        
        # X(k)         
        e = np.exp(-2j * np.pi * m * k / pivot)
        even_transform_vector[k] = even_transform_vector[k] + (pcm[2 * m] * e)
        odd_transform_vector[k] = odd_transform_vector[k] + (pcm[2 * m + 1] * e)

        # X(k + N/2)
        e = np.exp(-2j * np.pi * m * (k + pivot) / pivot)
        even_transform_vector[k + pivot] = even_transform_vector[k + pivot] + (pcm[2 * m] * e)
        odd_transform_vector[k + pivot] = odd_transform_vector[k + pivot] + (pcm[2 * m + 1] * e)
        
    # X(k)
    odd_transform_vector[k] = odd_transform_vector[k] * np.exp(-2j * np.pi * k / len(pcm)) 
    
    # X(k + N/2)
    odd_transform_vector[k + pivot] = odd_transform_vector[k + pivot] * np.exp(-2j * np.pi * (k + pivot) / len(pcm)) 
    
print(f'Iterations: {iterations}\n')
print(list(np.add(even_transform_vector, odd_transform_vector)))
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>The only real difference between \(X_{k}\) and \(X_{k+\frac{N}{2}}\) is that the latter adds \(\frac{N}{2}\) to k. If we could make them more alike, we could calculate one exponent and use it twice.</p>
<p>Fortunately, we can factor \(\frac{N}{2}\) out of the \(k+\frac{N}{2}\) side by applying the common multiplier, \(e^{-2 \pi i m}\).</p>
<p>\begin{equation}<br>
X_{k+\frac{N}{2}} = \sum_{n=0}^{N/2} x_{2 n} e^{\frac{-2i \pi n k}{N/2}} e^{-2i \pi m} +<br>
e^{\frac{-2 i \pi k}{N}} e^{-\pi i} \sum_{n=0}^{N/2} x_{2 n + 1} e^{\frac{-2 i \pi n k}{N/2}} e^{-2i \pi m}<br>
\end{equation}</p>
<p>The code snippet below demonstrates an implementation of the rearranged equation.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-python">iterations = 0
even_transform_vector = [0j] * len(pcm)
odd_transform_vector = [0j] * len(pcm)
for k in range(0, pivot):
    for m in range(0, pivot):
        iterations = iterations + 1
        
        # X(k)         
        e1 = np.exp(-2j * np.pi * m * k / pivot)
        even_transform_vector[k] = even_transform_vector[k] + (pcm[2 * m] * e1)
        odd_transform_vector[k] = odd_transform_vector[k] + (pcm[2 * m + 1] * e1)

        # X(k + N/2)
        e2 = e1 * np.exp(-2j * np.pi * m)
        even_transform_vector[k + pivot] = even_transform_vector[k + pivot] + (pcm[2 * m] * e2)
        odd_transform_vector[k + pivot] = odd_transform_vector[k + pivot] + (pcm[2 * m + 1] * e2)
        
    # X(k)
    e3 = np.exp(-2j * np.pi * k / len(pcm))
    odd_transform_vector[k] = odd_transform_vector[k] * e3
    
    # X(k + N/2)
    e4 = e3 * np.exp(np.pi * 1j)
    odd_transform_vector[k + pivot] = odd_transform_vector[k + pivot] * e4
    
print(f'Iterations: {iterations}\n')
print(list(np.add(even_transform_vector, odd_transform_vector)))
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>The snippet below demonstrates why \(e^{\frac{-2i \pi n k}{N/2}} e^{-2i \pi m}\) could be replaced by \(e^{\frac{-2i \pi n k}{N/2}}\).</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-python">sum_one = 0j
sum_two = 0j
for k in range(0, len(pcm)):
    for n in range(0, len(pcm)):
        sum_one = sum_one + np.exp(-2j * np.pi * n * k / pivot)
        sum_two = sum_two + np.exp(-2j * np.pi * n * k / pivot) * np.exp(-2j * np.pi * n)
        
print(round(sum_one, 10))
print(round(sum_two, 10))
print(round(sum_two - sum_one, 10))
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>So, based on this, we can simply the equation further like so.</p>
<p>\begin{equation}<br>
X_{k+\frac{N}{2}} = \sum_{n=0}^{N/2} x_{2 n} e^{\frac{-2i \pi n k}{N/2}} +<br>
e^{\frac{-2 i \pi k}{N}} e^{-\pi i} \sum_{n=0}^{N/2} x_{2 n + 1} e^{\frac{-2 i \pi n k}{N/2}}<br>
\end{equation}</p>
<p>The snippet below demonstrates why \(e^{\frac{-2i \pi k}{N}} e^{-\pi i}\) could be replaced by \(-e^{\frac{-2i \pi n k}{N}}\)</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-python">sum_one = 0j
sum_two = 0j
for k in range(0, pivot):
    sum_one = sum_one + np.exp(-2j * np.pi * k / len(pcm))
    sum_two = sum_two - np.exp(-2j * np.pi * k / len(pcm)) * np.exp(-1j * np.pi)
        
print(sum_one)
print(sum_two)
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>The final equation is as follows and it essentially describes the butterfly operation for recombining the sub-transforms.</p>
<p>\begin{equation}<br>
X_{k+\frac{N}{2}} = \sum_{n=0}^{N/2} x_{2 n} e^{\frac{-2i \pi n k}{N/2}} -<br>
e^{\frac{-2 i \pi k}{N}} \sum_{n=0}^{N/2} x_{2 n + 1} e^{\frac{-2 i \pi n k}{N/2}}<br>
\end{equation}</p>
<p>The code below demonstrates the basic (poor performance) idea of the above equation. The code snippet after it is a recursive implementation with an order of complexity of \(O(n log(n)\)</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-python">iterations = 0
even_transform_vector = [0j] * len(pcm)
odd_transform_vector = [0j] * len(pcm)
for k in range(0, pivot):
    for m in range(0, pivot):
        iterations = iterations + 1
        
        # X(k)         
        e1 = np.exp(-2j * np.pi * m * k / pivot)
        even_transform_vector[k] = even_transform_vector[k] + (pcm[2 * m] * e1)
        odd_transform_vector[k] = odd_transform_vector[k] + (pcm[2 * m + 1] * e1)


transform = [0j] * len(pcm)
for k in range(0, pivot):
    iterations = iterations + 1
    e = np.exp(-2j * np.pi * k / len(pcm))
    transform[k] = even_transform_vector[k] + e * odd_transform_vector[k]
    transform[k + pivot] = even_transform_vector[k] - e * odd_transform_vector[k]

    
print(f'Iterations: {iterations}\n')
print(list(transform))
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-python">def fft(sample_set):
  if len(sample_set) == 1:
    return sample_set

  size = len(sample_set)
  even_fft = fft(sample_set[0::2])
  odd_fft = fft(sample_set[1::2])

  freq_bins = [0.0] * size
  for k in range(0, int(size / 2)):
    freq_bins[k] = even_fft[k]
    freq_bins[k + int(size / 2)] = odd_fft[k]

  for k in range(0, int(size / 2)):
    e = np.exp(-2j * np.pi * k / size)
    t = freq_bins[k]
    freq_bins[k] = t + e * freq_bins[k + int(size / 2)]
    freq_bins[k + int(size / 2)] = t - e * freq_bins[k + int(size / 2)]

  return freq_bins

print(fft(pcm))
</code></pre>
<!--kg-card-end: markdown--><h3 id="extra-resources">Extra Resources</h3><p>Here's a list of resources that might help you get unstuck if some my post is unclear. Some of the resources are much more in depth and I'm still learning a lot from them.</p><!--kg-card-begin: markdown--><p><a href="https://betterexplained.com/articles/an-interactive-guide-to-the-fourier-transform/">Interactive Guide To The Fourier Transform</a><br>
<a href="https://www.khanacademy.org/science/electrical-engineering/ee-signals/ee-fourier-series/v/ee-fourier-series-intro">Fourier Series Intro</a><br>
<a href="https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm">Cooley-Tukey Fast Fourier Transform</a><br>
<a href="https://en.wikipedia.org/wiki/Butterfly_diagram">Butterfly diagram</a><br>
<a href="https://www.amazon.co.uk/Fourier-Dover-Mathematics-Georgi-Tolstov/dp/0486633179/ref=pd_lpo_14_t_1/258-0722499-1884913?_encoding=UTF8&amp;pd_rd_i=0486633179&amp;pd_rd_r=0a7e1897-4cfe-4dec-b796-afead8a04634&amp;pd_rd_w=tYZw4&amp;pd_rd_wg=zQRk3&amp;pf_rd_p=7b8e3b03-1439-4489-abd4-4a138cf4eca6&amp;pf_rd_r=JRC8NGHJH4CK5FVSKENZ&amp;psc=1&amp;refRID=JRC8NGHJH4CK5FVSKENZ">Fourier Analysis by Georgi P. Tolstov</a><br>
<a href="https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem">Nyquist-Shannon Sampling Theorem</a><br>
<a href="https://en.wikipedia.org/wiki/Pulse-code_modulation">Pulse Code Modulation</a><br>
<a href="https://en.wikipedia.org/wiki/Euler%27s_formula">Euler's Formula</a><br>
<a href="https://www.khanacademy.org/math/algebra2/x2ec2f6f830c9fb89:complex/x2ec2f6f830c9fb89:complex-num/v/complex-number-intro">Complex Number Intro</a><br>
<a href="https://www.khanacademy.org/math/algebra2/x2ec2f6f830c9fb89:complex/x2ec2f6f830c9fb89:imaginary/v/introduction-to-i-and-imaginary-numbers">Introduction to Imaginary Numbers</a><br>
<a href="https://en.wikipedia.org/wiki/De_Moivre%27s_formula">De Moivre Formula</a></p>
<!--kg-card-end: markdown--><h3 id="further-reading">Further Reading</h3><p>I've implemented a recursive Cooley-Tukey Radix-2 DIT in Python, the script also performs a brute force DFT and a Numpy FFT for comparison.</p><!--kg-card-begin: markdown--><p><a href="https://github.com/spacekitcat/cooley-tukey-fast-fourier-transform">FFT implementation in Python</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Web Audio clock demo]]></title><description><![CDATA[<p>This is a little demo web app that uses the Web Audio clock to correctly control the timing of audio playback. I'm contemplating the idea of implementing a web based drum machine and this is background research.</p><p>The app starts by fetching a JSON manifest file (from S3). The manifest</p>]]></description><link>http://www.spacekitcat.com/web-audio-clock-demo/</link><guid isPermaLink="false">5f2daf4501a60a11b6190451</guid><category><![CDATA[webaudio]]></category><dc:creator><![CDATA[Lisa Burton]]></dc:creator><pubDate>Mon, 10 Feb 2020 19:44:00 GMT</pubDate><content:encoded><![CDATA[<p>This is a little demo web app that uses the Web Audio clock to correctly control the timing of audio playback. I'm contemplating the idea of implementing a web based drum machine and this is background research.</p><p>The app starts by fetching a JSON manifest file (from S3). The manifest tells the app where to download a big list of drum samples from. This initialisation is hidden behind a loading screen. The manifest fetch request explicitly busts client side caching, while the sample fetch requests force client side caching.</p><p>The running app looks like a Windows 98 pop up dialog and when you click the button, it plays a short string of random TB-808 samples, equally spaced with perfect time. </p><p>The importance of the audio clock is that it allows you to schedule the playback of audio for some time in the future <em>exactly </em>while using <code>setInterval</code> or <code>setTimeout</code> simply isn't accurate enough to do audio playback for something like a drum machine. </p><p><a href="http://static.spacekitcat.com/wbdm3/webapp/index.html">http://static.spacekitcat.com/wbdm3/webapp/index.html</a></p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/spacekitcat/wbdm-proto-3"><div class="kg-bookmark-content"><div class="kg-bookmark-title">spacekitcat/wbdm-proto-3</div><div class="kg-bookmark-description">Little experiment with the Web Audio clock. Contribute to spacekitcat/wbdm-proto-3 development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg"><span class="kg-bookmark-author">spacekitcat</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars2.githubusercontent.com/u/8135257?s=400&amp;v=4"></div></a><figcaption>Source code</figcaption></figure><p></p>]]></content:encoded></item><item><title><![CDATA[Conway's game of life]]></title><description><![CDATA[<p><em>Edit (2021): A better solution: <a href="https://github.com/spacekitcat/convolve2d-conways-game-of-life/blob/main/main.py">https://github.com/spacekitcat/convolve2d-conways-game-of-life/blob/main/main.py</a></em></p><p></p><p>This is a fairly simple web based implementation of Conway's game of life. I wrote it as a Kata and for interview preparation and thought the result was kinda neat. </p><!--kg-card-begin: html--><iframe src="https://legacy.spacekitcat.com/cellular-automation/conway-070919/index.html" width="100%" height="420px"></iframe><!--kg-card-end: html--><p></p><p>It starts randomised, sometimes it will</p>]]></description><link>http://www.spacekitcat.com/conways-game-of-life/</link><guid isPermaLink="false">5f2d840301a60a11b61903bc</guid><category><![CDATA[hackathon]]></category><dc:creator><![CDATA[Lisa Burton]]></dc:creator><pubDate>Thu, 08 Aug 2019 16:43:00 GMT</pubDate><content:encoded><![CDATA[<p><em>Edit (2021): A better solution: <a href="https://github.com/spacekitcat/convolve2d-conways-game-of-life/blob/main/main.py">https://github.com/spacekitcat/convolve2d-conways-game-of-life/blob/main/main.py</a></em></p><p></p><p>This is a fairly simple web based implementation of Conway's game of life. I wrote it as a Kata and for interview preparation and thought the result was kinda neat. </p><!--kg-card-begin: html--><iframe src="https://legacy.spacekitcat.com/cellular-automation/conway-070919/index.html" width="100%" height="420px"></iframe><!--kg-card-end: html--><p></p><p>It starts randomised, sometimes it will halt almost immediately, other times it will seem to go on forever. That's the point of Conway's game of life in my view. It should behave different each time you reload.</p><p>The rendering could be much faster with better layout fluidity. There isn't a Git repository for this as such, it's a quick and dirty bit of JS hacked onto my legacy site.</p>]]></content:encoded></item><item><title><![CDATA[NES (6502) Assembly Notes]]></title><description><![CDATA[<p>The following Gist demonstrates how to create a 16-bit counter using 6502 assembly. It increments the Least Significant Byte (LSB) and the Most Significant Byte (MSB) separately in such a way that the MSB is incremented once for every 256 increments of the LSB, totalling 65536 iterations.</p><!--kg-card-begin: html--><script src="https://gist.github.com/spacekitcat/765757ad4341e1c96f5562cd9829136c.js"></script><!--kg-card-end: html--><p>The following Gist</p>]]></description><link>http://www.spacekitcat.com/nes-6502-16-bit-counter/</link><guid isPermaLink="false">5f2d833e01a60a11b61903b3</guid><category><![CDATA[6502]]></category><dc:creator><![CDATA[Lisa Burton]]></dc:creator><pubDate>Thu, 01 Aug 2019 16:39:00 GMT</pubDate><content:encoded><![CDATA[<p>The following Gist demonstrates how to create a 16-bit counter using 6502 assembly. It increments the Least Significant Byte (LSB) and the Most Significant Byte (MSB) separately in such a way that the MSB is incremented once for every 256 increments of the LSB, totalling 65536 iterations.</p><!--kg-card-begin: html--><script src="https://gist.github.com/spacekitcat/765757ad4341e1c96f5562cd9829136c.js"></script><!--kg-card-end: html--><p>The following Gist demonstrates how to get the 8-bit Nintendo (NES) to render a 4 part sprite of /sprites/ with 6502 pure assembly. I generally shadow SPR-RAM in general purpose RAM from address $0200 and when the VBlank NMI occurs, I ask the Direct Memory Access controller at $4014 to transfer RAM to SPR-RAM.</p><p>You cannot write to the Picture Processing Unit’s internal RAM when it’s busy working (unless you want visible rendering glitches), you have to wait for the V-Blank Non-Maskable Interrupt (NMI); V-Blank means that the last scan-line for the current frame has been drawn to the screen and that the PPU will be idle for approximately 2250 (exact number varies depending on the exact NES model) CPU cycles.</p><!--kg-card-begin: html--><script src="https://gist.github.com/spacekitcat/fb28607d7c4cee3dd10725fc0f08a0ec.js"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[LZ77]]></title><description><![CDATA[How the Lempel Ziv 77 compression algorithm works.]]></description><link>http://www.spacekitcat.com/lempel-ziv-77/</link><guid isPermaLink="false">5f2c5a3401a60a11b619020a</guid><category><![CDATA[compression]]></category><category><![CDATA[nodejs]]></category><dc:creator><![CDATA[Lisa Burton]]></dc:creator><pubDate>Mon, 08 Apr 2019 21:32:00 GMT</pubDate><media:content url="http://www.spacekitcat.com/content/images/2021/01/220px-Schraubstock_mini.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.spacekitcat.com/content/images/2021/01/220px-Schraubstock_mini.jpg" alt="LZ77"><p><a href="https://en.wikipedia.org/wiki/LZ77_and_LZ78">LZ77</a> is a lossless, streaming compression algorithm invented in 1977. The central concept is its use of a Sliding Window, which is a fixed size, sequentially ordered buffer; sometimes referred to as the history buffer. The LZ77 compression algorithm starts by reading in a string of bytes (from a stream) the size of the sliding window, it then checks if the history buffer contains an identical sequence of bytes. With a match, the algorithm reads the next byte from the stream and uses it as the <code>token</code> field, together with the position and length of the matching byte block (in the history buffer) as the <code>prefix</code> field; without a match, the algorithm will check each substring that always begins at byte zero in the byte stream to see if any of those match. If none of the substrings match, byte zero (from the byte stream) becomes the <code>token</code> field in the next output compression packet. (the <code>prefix</code> is empty, which might be expressed as <code>null</code>)</p><p>Decompression starts by checking the <code>prefix</code> field of the current packet and if it finds an entry, it reads the indicated number of bytes (starting from the position indicated). Any prefix byte string is outputted, followed by <code>token</code> immediately after it. </p><p>Because it's only the tokens that get pushed onto the history buffer, you guarantee that the buffer content will be identical whether its generating (compression) or consuming (decompression) the packet.</p><p><a href="https://github.com/spacekitcat/lz77-nodejs-streams">I've implemented a small demonstration of the compression process here</a>, I haven't bothered with decompression because it's super easy to implement. (I did implement decompression in this <a href="https://github.com/spacekitcat/prototype-libz77">prototype here</a>, but just know I consider it to be a low quality prototype).</p><p>The first git repository implements the sliding window via a custom library (I created) called <a href="https://github.com/spacekitcat/tiny-toatie-cache">tiny-toatie-cache</a> as its storage engine. The second repository is a vanilla implementation of LZ77.</p><p><a href="https://github.com/spacekitcat/tiny-toatie-cache">tiny-toatie-cache</a> (TTC) is a fixed size storage engine and it allows you to append new bytes to the front and search for specific byte strings. When the internal storage is full, it deletes the oldest items first when it has to make space for new bytes at the front. The find method proxies requests through its internal caching system. The cache is designed to remember the offset and length of matching byte strings it previously found in its internal storage (which behaves exactly like a sliding window). The offsets of cached records are automatically corrected when new bytes push the buffer contents back. It also transparently invalidates cache records when offsets "fall off the end" of the buffer to make space for new bytes at the front.</p><p>The caching idea turned out to be a bit of a bust because you don't get too many duplicate prefixes, unless it's a really low entropy source file or something like that (say, if you used <code>dd</code> to generate a zeroed file). Consider this input <code>the the the the the</code> and the output packets:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Token</th>
<th>Prefix</th>
</tr>
</thead>
<tbody>
<tr>
<td>'t'</td>
<td>null</td>
</tr>
<tr>
<td>'h'</td>
<td>null</td>
</tr>
<tr>
<td>'e'</td>
<td>null</td>
</tr>
<tr>
<td>' '</td>
<td>null</td>
</tr>
<tr>
<td>'t'</td>
<td>'the '</td>
</tr>
<tr>
<td>'h'</td>
<td>'he t'</td>
</tr>
<tr>
<td>'e'</td>
<td>'e th'</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>If you look at the last three packets, it gives an idea of how the prefixes go through a sort of 'phase shift' of sorts. This leads to very frequent cache misses on prefix search caching. In retrospect, it seems so obvious, but doesn't it always?</p><p>The table below is the verbatim output of <a href="https://github.com/spacekitcat/lz77-nodejs-streams">lz77-nodejs-streams</a></p><figure class="kg-card kg-code-card"><pre><code class="language-bash">{ token: &lt;Buffer 74&gt;, prefix: null }
{ token: &lt;Buffer 68&gt;, prefix: null }
{ token: &lt;Buffer 65&gt;, prefix: null }
{ token: &lt;Buffer 20&gt;, prefix: null }
{
  token: &lt;Buffer 74&gt;,
  prefix: { offset: 3, value: &lt;Buffer 74 68 65 20&gt;, length: 4 }
}
{
  token: &lt;Buffer 68&gt;,
  prefix: { offset: 3, length: 4, value: &lt;Buffer 68 65 20 74&gt; }
}
{
  token: &lt;Buffer 65&gt;,
  prefix: { offset: 3, length: 4, value: &lt;Buffer 65 20 74 68&gt; }
}</code></pre><figcaption>Verbatim packet generation from compressing 'the the the the the' through lz77-nodejs-streams</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Hacker Text JS]]></title><description><![CDATA[HackerTextJS is a small JavaScript library that renders Matrix-esque animated HTML widgets with pure text.]]></description><link>http://www.spacekitcat.com/hacker-text-js/</link><guid isPermaLink="false">5f2d7d8b01a60a11b6190380</guid><category><![CDATA[visual]]></category><dc:creator><![CDATA[Lisa Burton]]></dc:creator><pubDate>Tue, 15 May 2018 16:13:00 GMT</pubDate><media:content url="http://www.spacekitcat.com/content/images/2020/08/Screenshot-2020-08-08-at-22.13.08-1.png" medium="image"/><content:encoded><![CDATA[<img src="http://www.spacekitcat.com/content/images/2020/08/Screenshot-2020-08-08-at-22.13.08-1.png" alt="Hacker Text JS"><p><a href="https://github.com/spacekitcat/hackertextjs">HackerTextJS</a> is a small library that renders widgets like the three examples below:</p><!--kg-card-begin: html--><html lang="en">
<head>
    <title></title>
    <meta charset="UTF-8">
    <script type="text/javascript">
      var hacker_text_config = {
        targets: [{
          htmlId: "hacker_text_one",
          text: "Have no fear of perfection, you'll never meet it",
          framerate: 5,
          rows: 10,
        },
          {
          htmlId: "hacker_text_two",
          text: "Have no fear of perfection, you'll never meet it",
          framerate: 5,
              renderer: {
strategy: 'RandomizedFrameRenderStrategy'
},

          rows: 10,
        }, 
                            {
          htmlId: "hacker_text_three",
          text: "Have no fear of perfection, you'll never meet it",
          framerate: 5,
              renderer: {
strategy: 'CoSinePhaseFrameRenderStrategy'
},
          rows: 10,
        },  
                 ]
      };
    </script>
    <script type="text/javascript" src="https://legacy.spacekitcat.com/hackertextjs/demosite/jquery.js"></script>
    <script type="text/javascript" src="https://legacy.spacekitcat.com/hackertextjs/demosite/hackertext.js"></script>
    <link href="https://fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet">
    <link href="https://legacy.spacekitcat.com/hackertextjs/demosite/style.css" rel="stylesheet">

    <style media="screen" type="text/css">
        .wrapper {
            padding: 64px;
            margin: 0;
            font-family: "Lucida Console", Monaco, monospace;
            font-size: 10px;
            color: #3FDF3F;
            /*color: #DF3F3F;*/
            background-color: #292B29;
            line-height: 12px;
        }
    </style>
</head>
<body>
<div class="wrapper" id="wrapper">
    <div class="hacker_text_one" id="hacker_text_one"></div>
    <div class="hacker_text_two" id="hacker_text_two"></div>
    <div class="hacker_text_three" id="hacker_text_three"></div>
</div>
</body>
</html><!--kg-card-end: html--><p></p><p>It can render multiple widgets, each with a different frame rate, size, text, renderer strategy and text filter. In an HTML document, you define an object called <code>hacker_text_config</code> in the <code>head</code> section. The hacker text config object has the property <code>targets</code>, an array of widget descriptor objects. Each widget descriptor specifies at a minimum the <code>htmlId</code> (the ID of the target element), <code>text</code> (the display text) and <code>rows</code> (the number of rows to render). Below is an example of what the configuration object might look like for rendering three elements. </p><!--kg-card-begin: markdown--><pre><code>  &lt;script type=&quot;text/javascript&quot;&gt;
  var hacker_text_config =
  {
    targets: [
      {
        htmlId: &quot;hackertext&quot;,
        text: &quot;A_SPECTRE_HAUNTS_EUROPE_&quot;,
        renderer: {
          strategy: &quot;RandomizedFrameRenderStrategy&quot;
        },
        framerate: 3,
        rows: 15
      },
      {
        htmlId: &quot;hackertexttwo&quot;,
        text: &quot;A_SPECTRE_OF_COMMUNISM_&quot;,
        framerate: 5,
        rows: 15
      },
      {
        htmlId: &quot;hackertextthree&quot;,
        text: &quot;A_SPECTRE_HAUNTS_EUROPE_&quot;,
        framerate: 5,
        text_character_filters: ['LeetSourceFilter'],
        rows: 18
      }
    ]
  };
  &lt;/script&gt;
</code></pre>
<!--kg-card-end: markdown--><p>After the <code>hacker_text_config</code> object has been defined, <code>hackertext.js</code> can be imported. It will only start initialisation if it can find this config descriptor. </p><p>For each widget, Hackertext.js calculates the maximum character length of each line for the current style sheet, so if this is 116, the specified <code>rows</code> field is 15 and the <code>framerate</code> is 10, it will fill the element with 1740 (116 times 15) characters 10 times per second. The renderer sets up a circular iterator over the specified <code>text</code> for each widget. Frames are built sequentially, one character at a time. For each character it can choose to either take the next character from the iterator or output a noise character. The probability is controlled by a dynamically determined noise ratio. When the <code>RandomizedFrameRenderStrategy</code> renderer strategy is used without any parameters, the noise ratio is fixed at 50%; when the <code>SinePhaseFrameRenderStrategy</code>, the noise ratio is controlled by a fixed frequency sine wave. There are other settings, but the documentation covers it adequately I think.</p><p>HTML elements aren't really designed to operate like this and I was fully aware of this at the time, but I wanted to know if you could reliably use JS to completely fill a <code>div</code> with text in such a way that it didn't overflow or underflow and to then make it animate. The first widget uses a sine wave renderer, the middle widget uses a completely randomised renderer and the third widget uses a cosine renderer.</p><p>At runtime it injects a hidden element (no margin or padding) with a test string. It uses the width of the hidden element to calculate the average width of a character, then it looks at the width of the target element for each widget, dividing it by the width of a character to work out the char count for each line.</p><p>It also watches for window resize events, so that it can dynamically adjust the number of characters the renderer spits out for each line.</p>]]></content:encoded></item></channel></rss>