#! .venv/bin/python3
-from flask import Flask, Response, request, render_template, redirect
+from flask import Flask, Response, request, render_template, redirect, send_from_directory
import markdown
from os import listdir
from markdown.extensions.toc import TocExtension
page = Page(title = "404", abstract = "Page not found!")
return page.make()
+@app.route("/<string:title>/<string:filename>")
+def get_media(title, filename):
+ return send_from_directory(f"./media/{title}", filename)
+
if __name__ == "__main__":
- app.run(host='127.0.0.1', port=5000)
+ app.run(host='127.0.0.1', port=5000, debug=True)
--- /dev/null
+title: An Audio Bayeux Tapestry
+abstract: With the power of technology, the Bayeux tapestry is now avaiable for the blind.
+keywords: programming,audio
+date: 16/12/2024
+
+# An Audio Bayeux Tapestry
+
+Hello!
+
+With the power of technology, the Bayeux tapestry is now avaiable for the blind.
+
+<!-- <audio src="blog.ozva.co.uk/static/bayeux.wav" controls /> -->
+
+In this post I'll show you how this was achived.
+
+## The image
+
+
+
+The scan available online[^1] is not centered, unless we want an unclear or ragged edge on our audio, we must first straighten and strectch the tapestry flat.
+
+I did this with a simple python script.
+
+```
+for (i, pixel) in enumerate(line):
+ value = np.mean(pixel) # Get the brightness
+ if value < THRESHHOLD:
+ front_index = i # Set the index of our in-point
+ break
+```
+
+As you can see, by scrolling our way across each line and noting where the pixel value is greater than our threshold (set at 225), we can figure out the bounds of that particular line of the tapestry.
+
+```
+trim_line = np.array([line[front_index:back_index]], dtype=np.uint8)
+new_image.append(cv.resize(trim_line, (image_width, 1))[0])
+```
+
+We then stretch the line to the total width of the image.
+
+This produces a far more regular image.
+
+
+
+This is very stretched, but this is no problem. Since it will end up as a spectrogram whose dimentions we don't care much for we will skip the step of fixing this.
+
+## The spectrogram
+
+The image can then be turned into a spectrogram with the power of the FFT.
+
+```
+data = np.fft.irfft(image, h*2, axis=1).reshape((w*h*2)) # Reshape and get the FFT output
+data -= np.average(data) # Remove DC offset
+data *= (2**15-1.)/np.amax(data) # Normalize
+```
+
+As a side effect of the jump from one side of the image to the other, there is a sample impulse every 1,500 samples, we record this high point and subtract it from all following samples.
+
+```
+high_point = data[1500]
+for i in range(0, len(data), 1500):
+ data[i] -= high_point
+```
+
+If we split the image in half and process seperately, we can then combine them over the right and left audio channels to get a more resolute image.
+
+This solves another issue:
+
+Say you have an image which is not so rectangular and is intended to have a "silent" background. As this image rises to the top of the frame, hovering around 20,000kHz it can become quite unplesant. Splitting the sound across right and left not only produces some sence of space, but also gives you a wholy wider and more plesent range of frequencies.
+
+
+
+[^1]: Link: <a href="https://en.wikipedia.org/wiki/File:Tapisserie_de_Bayeux_31109.jpg">https://en.wikipedia.org/wiki/File:Tapisserie_de_Bayeux_31109.jpg</a>
font-size: 2.8em;
}
-nav {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 80%;
- padding: 10px;
- overflow: scroll;
- border-right: 2px solid var(--primary);
+@media (max-width: 700px) {
+ #extra {
+ display: none;
+ }
+ nav {
+ padding: 10px;
+ border-bottom: 2px solid var(--primary);
+ }
+ main {
+ padding: 10px;
+ }
}
-main {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 20%;
- right: 0;
- padding: 10px;
- overflow: scroll;
+@media (min-width: 700px) {
+ nav {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 75%;
+ padding: 10px;
+ overflow: scroll;
+ border-right: 2px solid var(--primary);
+ }
+ main {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 25%;
+ right: 0;
+ padding: 10px;
+ overflow: scroll;
+ }
+}
+
+main > *:not(hr) {
+ width: min(100%, 700px);
}
a:link {
border: 1px solid var(--primary);
}
+img {
+ width: min(80%, 500px);
+}
+
@media (prefers-color-scheme: dark) {
:root {
--primary: white;