---
title: Posts tagged UGent
canonical: https://0110.be/tags/UGent
markdown_url: https://0110.be/tags/UGent.md
page: 0
posts_per_page: 30
total_posts: 139
filters:
  tag: UGent
previous:
next: https://0110.be/tags/UGent.md?page=1
---

# Posts tagged UGent

## [Sampo-UI 4.0 released at SEMANTiCS 2026](https://0110.be/posts/Sampo-UI_4.0_released_at_SEMANTiCS_2026.md)

- Published: 2026-09-16T00:00:00Z
- Updated: 2026-09-16T13:08:21Z
- Author: Joren
- ID: 585
- Canonical: https://0110.be/posts/Sampo-UI_4.0_released_at_SEMANTiCS_2026

- Tags: [Presentation](https://0110.be/tags/Presentation.md), [UGent](https://0110.be/tags/UGent.md)

Sampo-UI 4 was officially released at the SEMANTiCS conference in Ghent. Together with Jahid Chetti, Heikki, Annastiina Ahola and Frederic Lamsens, we worked on bringing the GhentCDH fork back into the [main Sampo-UI repository](https://github.com/SemanticComputing/sampo-ui).

The release includes an updated software stack, a clearer separation between the shared core and project-specific configuration, and a new plug-in system. These changes make it easier to reuse components across projects and keep portals up to date.

The GhentCDH fork has now been merged upstream. Our work continues as part of the shared Sampo-UI project, in collaboration with the original SeCo team.



<center>
<div style="width:70%">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 500" role="img" aria-labelledby="timeline-title timeline-desc">
<title id="timeline-title">Sampo-UI development and GhentCDH contributions</title>
<desc id="timeline-desc">Sampo-UI development at SeCo continues along the main branch. GhentCDH forks the project, modernises its architecture and software stack, and improves modularity. The fork has merged back into upstream development.</desc>
<defs><marker id="timeline-arrow" markerWidth="10" markerHeight="10" refX="8" refY="4" orient="auto"><path d="M0 0L8 4L0 8Z" fill="#1E64C8"/></marker></defs>
<g font-family="PannoTextNormal, Arial, sans-serif" fill="#202020">
<text x="45" y="65" font-size="34" fill="#1E64C8">SeCo · Helsinki / Aalto</text><text x="45" y="108" font-size="26">Sampo-UI development</text>
<path d="M60 175H1335" fill="none" stroke="#1E64C8" stroke-width="8" marker-end="url(#timeline-arrow)"/>
<path d="M290 175C400 175 350 345 460 345H875C1010 345 975 175 1090 175" fill="none" stroke="#FFD200" stroke-width="10"/>
<g fill="#FFFFFF" stroke="#1E64C8" stroke-width="6"><circle cx="75" cy="175" r="14"/><circle cx="290" cy="175" r="14"/><circle cx="1090" cy="175" r="17"/></g>
<text x="270" y="225" text-anchor="end" font-size="26">Fork</text>
<text x="1060" y="90" font-size="32" fill="#1E64C8">Merged upstream</text><text x="1060" y="130" font-size="25">Shared development</text>
<rect x="435" y="240" width="500" height="70" rx="10" fill="#E9F0FA"/><text x="685" y="284" text-anchor="middle" font-size="32" fill="#1E64C8">GhentCDH modernisation</text>
<text x="455" y="400" font-size="27">Architecture · software stack · modularity</text>
<text x="455" y="447" font-size="25" fill="#646464">Contributions return to the shared framework</text>
</g>
</svg>
</div>
</center>

For more on the changes and their use in our projects, see [Sampo-UI Meets Ontop](https://0110.be/posts/Sampo-UI_Meets_Ontop%3A_Publishing_Relational_Digital_Humanities_Data_-_Semantics_2026).
```


![Official Sampo UI publication](https://0110.be/files/photos/585/PXL_20260916_111149116_small.jpg)

---

## [Explore Intra-Belgian book translation flows with a SAMPO-UI](https://0110.be/posts/Explore_Intra-Belgian_book_translation_flows_with_a_SAMPO-UI.md)

- Published: 2026-09-16T00:00:00Z
- Updated: 2026-09-16T08:10:58Z
- Author: Joren
- ID: 584
- Canonical: https://0110.be/posts/Explore_Intra-Belgian_book_translation_flows_with_a_SAMPO-UI

- Tags: [Presentation](https://0110.be/tags/Presentation.md), [Projecten](https://0110.be/tags/Projecten.md), [Research papers](https://0110.be/tags/Research%20papers.md), [UGent](https://0110.be/tags/UGent.md)


Another contribution presented at the [SEMANTiCS conference](https://2026-eu.semantics.cc/) in Ghent was a Sampo-UI portal for the BELTRANS project. I made a small contribution to the software and the paper, working with colleagues at KBR, the Royal Library of Belgium.

> *[Explore Intra-Belgian book translation flows with a SAMPO-UI](https://0110.be/files/attachments/584/BELTRANS_SAMPO.pdf)*, Sven Lieber¹, Ann Van Camp¹, Joren Six², Julie M. Birkholz¹ ²<br>
> ¹ KBR — Royal Library of Belgium <br>
> ² Ghent University, Department of History & Ghent Centre for Digital Humanities

The BELTRANS project studied book translations between Dutch and French from 1970 to 2020, involving at least one Belgian author, scenarist, illustrator or editor. The resulting dataset brings together publication metadata from different libraries. Making these data available is one thing; making them easy to explore is another.

The [BELTRANS portal](https://w3id.org/kbr/beltrans/ui) uses Sampo-UI to let researchers filter publications, browse authors and publishers, explore maps and download selected data as CSV. It uses the modernized Sampo-UI core described in my [Sampo-UI Meets Ontop post](https://0110.be/posts/Sampo-UI_Meets_Ontop%3A_Publishing_Relational_Digital_Humanities_Data_-_Semantics_2026). Here, the data are served by QLever, a graph database with a SPARQL endpoint. The same interface components find another use, this time for translation history.

> **Abstract**: *In the field of Big Translation History, (historical) book translation flows are studied based on publication metadata. This research data is often kept internally or only provided as domain-specific MARC21 records, a standard library format. Within the recently completed BELTRANS project, we created a large integrated data corpus of worldwide translations between Dutch and French between 1970 and 2020, where at least one Belgian was involved as author, scenarist, illustrator or editor, to study intra-belgian book translation flows, and published the corpus as FAIR data. In this paper we present the SAMPO-UI-based platform that can be used by non-technical users to explore the underlying BELTRANS Linked Data corpus. The off-the-shelf software SAMPO-UI was configured to build a user interface allowing the exploration of our data related to book publications, authors, publishers and linked temporal and geospatial information. The different search perspectives, filters and visualizations enable researchers to explore the corpus or download self-chosen subsets, without having to process the large underlying data dumps. Both the application and corpus data are opened up in a sustainable way, i.e. besides deposits at research repositories, the application is hosted in the long term at the Royal Library of Belgium (KBR) and the integrated data is equipped with provenance information allowing to click-through to the related metadata records at different national libraries. Future work can extend the application itself with novel visualization techniques or different download formats if necessary. Also, this application can serve as a role model to inspire other researchers to distribute their corpora as Linked Data and via such a SAMPO-UI platform.*

The [BELTRANS corpus](https://doi.org/10.5281/zenodo.20668316) and the [portal configuration](https://github.com/kbrbe/beltrans-ui-config) are available for reuse.




---

## [Sampo-UI Meets Ontop: Publishing Relational Digital Humanities Data - Semantics 2026](https://0110.be/posts/Sampo-UI_Meets_Ontop%3A_Publishing_Relational_Digital_Humanities_Data_-_Semantics_2026.md)

- Published: 2026-09-16T00:00:00Z
- Updated: 2026-09-16T07:46:02Z
- Author: Joren
- ID: 582
- Canonical: https://0110.be/posts/Sampo-UI_Meets_Ontop%3A_Publishing_Relational_Digital_Humanities_Data_-_Semantics_2026

- Tags: [Code](https://0110.be/tags/Code.md), [GhentCDH](https://0110.be/tags/GhentCDH.md), [Presentation](https://0110.be/tags/Presentation.md), [UGent](https://0110.be/tags/UGent.md)

The [SEMANTiCS conference](https://2026-eu.semantics.cc/) took place in Ghent from 15 to 17 September 2026. Our contribution was a collaboration between GhentCDH and the Semantic Computing Research Group at Aalto University on publishing humanities data with Sampo-UI and Ontop.

> *[Sampo-UI Meets Ontop: Publishing Relational Digital Humanities Data on the Semantic Web](https://0110.be/files/attachments/582/semantics-poster-sampo-ui.pdf)*, Joren Six¹, Jahid Chetti¹, Annastiina Ahola², Frederic Lamsens¹, Heikki Rantala², Eero Hyvönen²<br>
> ¹ Ghent University, Ghent Centre for Digital Humanities ([GhentCDH](https://www.ghentcdh.ugent.be)) <br>
> ² Aalto University, Department of Computer Science, Semantic Computing Research Group ([SeCo](https://seco.cs.aalto.fi))

At GhentCDH, we often publish research datasets in the digital humanities. Several projects need similar search filters, map visualizations and data exports. Sampo-UI makes it possible to reuse these components when the data is available as Linked Data. In collaboration with the original Sampo-UI team, we worked on modernizing the framework. Our first contribution separates the shared core from project-specific configuration, making updates and reuse more manageable. The second uses Ontop to expose existing *relational databases* through a SPARQL endpoint. This lets us use Sampo-UI while keeping the database infrastructure and tooling we already have. Having one fewer database system to maintain is welcome.

> **Abstract**: *The Sampo model and its Sampo-UI framework have become an established approach for publishing Cultural Heritage and Digital Humanities data on the Semantic Web. Sampo-UI enables exploration of data originating from SPARQL endpoints through faceted search across multiple views with seamlessly integrated visualization and data analytic tools. Here, we present two contributions to the Sampo-UI ecosystem: (i) a separation of Sampo-UI core from application-specific configuration; decoupling core and application simplifies updates and facilitates reuse of efforts, and (ii) the use of Ontop --- a virtual knowledge graph system --- to expose relational databases through a SPARQL endpoint and Sampo-UI. The use of relational databases and reuse of tooling and expertise around relational databases lower the operational burden of publishing small-to-medium-sized datasets typical of digital humanities projects. These contributions originated outside the original Sampo-UI team but were developed in collaboration with it, and are being prepared for upstream integration. We illustrate the approach with two projects at Ghent University: one on Roman coin finds in Belgium (NuMAD) and one on historic Belgian company data (BelHisFirm). Both portals run the same Sampo-UI core with a different configuration, and operate on existing institutional relational database infrastructure without a dedicated graph database.*

The demo below shows the NuMAD portal for exploring Roman coin finds in Belgium through the modernized Sampo-UI. Another [demo presents historical records of Belgian companies](https://0110.be/files/attachments/582/belhisfirm_demo.mp4) through the BelHisFirm portal. Both portals run the same Sampo-UI core with different configurations, demonstrating the flexibility of the system.

Alongside the [Sampo-UI Meets Ontop paper](https://0110.be/files/attachments/582/sampo-ui_ontop-semantics_2026.pdf), a [conference poster on Sampo-UI](https://0110.be/files/attachments/582/semantics-poster-sampo-ui.pdf) that I prepared is also available.

<video style="width: 100%; height: auto;" src="https://0110.be/files/attachments/582/numad_demo.mp4" controls playsinline preload="metadata"></video>
<small>Video: NuMAD — exploring Roman coin finds in Belgium.</small>
<br>


---

## [ Introduction to Sampo-UI at FedOSC Linked Open Data Visualisation Workshop](https://0110.be/posts/_Introduction_to_Sampo-UI_at_FedOSC_Linked_Open_Data_Visualisation_Workshop.md)

- Published: 2026-09-08T00:00:00Z
- Updated: 2026-09-08T12:02:58Z
- Author: Joren
- ID: 583
- Canonical: https://0110.be/posts/_Introduction_to_Sampo-UI_at_FedOSC_Linked_Open_Data_Visualisation_Workshop

- Tags: [GhentCDH](https://0110.be/tags/GhentCDH.md), [Presentation](https://0110.be/tags/Presentation.md), [UGent](https://0110.be/tags/UGent.md)

FedOSC, the Belgian Federal Open Science Cloud, supports open science and research data management at Belgium's federal scientific institutions. On 8 September 2026, it organised a [day on data visualisation and Linked Open Data](https://fedosc.miraheze.org/wiki/Events/DataViz) at KIK-IRPA in Brussels, with talks, demos and a approaches on cultural heritage datasets.

My contribution to the closing talks was an introduction to Sampo-UI, an open-source framework for building search and visualisation interfaces on top of Linked Open Data. I explained why we use it at the Ghent Centre for Digital Humanities: several projects need similar filters, maps and data exports, so it makes sense to reuse a common framework. The presentation covers how Sampo-UI works, our contributions to it, and a practical example with the NuMAD coin database.

The [Sampo-UI presentation](https://0110.be/attachment/cors/2026.09.08-sampo-ui-fedosc-brussels/index.html) is available online and embedded below.

<iframe src="https://0110.be/attachment/cors/2026.09.08-sampo-ui-fedosc-brussels/index.html" title="Introduction to Sampo-UI at FedOSC" style="width: 100%; aspect-ratio: 16 / 9; border: 0;" loading="lazy" allowfullscreen></iframe>

<br>

---

## [The age of AI exploits is here](https://0110.be/posts/The_age_of_AI_exploits_is_here.md)

- Published: 2026-05-18T00:00:00Z
- Updated: 2026-05-19T06:48:39Z
- Author: Joren
- ID: 580
- Canonical: https://0110.be/posts/The_age_of_AI_exploits_is_here

- Tags: [Code](https://0110.be/tags/Code.md), [UGent](https://0110.be/tags/UGent.md)

<div style="float:right;width:25%;margin:10px"><img src="https://0110.be/files/attachments/580/age_of_ai_exploits.webp" style="width:100%;"  ><small>Fig: Gemini imagining <em>the age of AI exploits</em>. </small></div> The following text puts into words a change that I have seen happening the last month. At the Ghent Centre for Digital Humanities we manage a fleet of virtual servers on the Ghent University network and have first-hand experience with security incidents appearing regularly now. It has a different tone than other posts here: it was sent in as a letter for a newspaper but was not published. Let's see in a few years if the tone was too alarmist or if it holds up: 


> *The newest generation of AI coding tools is a great asset for software developers. They help with understanding complex codebases, and assist in writing code. Used properly, AI coding tools increase software quality and developer productivity. Unfortunately, these powerful tools can also be used with bad intentions. AI coding tools are now sufficiently advanced to find and exploit bugs in software. They pose a threat to essentially all computing systems that form the underpinning of modern infrastructure: banking, aviation, industry, and education.*
> 
> *While [earlier reports](https://www.theguardian.com/technology/2026/apr/22/what-is-anthropic-mythos-ai-threat-global-cybersecurity) still used conditional terms to describe these threats, now — only a couple of weeks later — the first effects are already here. With the help of AI coding tools, three bugs were found in Linux, an open-source operating system: [Copy.fail](https://copy.fail/), [Dirty Frag](https://github.com/V4bel/dirtyfrag), and [Fragnesia](https://ubuntu.com/blog/fragnesia-linux-vulnerability-fixes-available). These first AI-discovered exploits in a broadly deployed system form a watershed moment. The bugs led to service outages, but diligent IT teams quickly patched systems, limiting the impact. The effects of Mythos — widely considered the most capable system for finding bugs in software — are now also clear. Mozilla reported [finding and fixing around four hundred bugs](https://hacks.mozilla.org/2026/05/behind-the-scenes-hardening-firefox/) last month, where in a typical month around twenty similar bugs are discovered and patched. Not every bug is exploitable, but every exploit starts with a bug. The age of AI exploits is here.*
> 
> *The transition to this new age will be painful. Many unmaintained systems are connected to the internet and will not receive security updates: IoT devices, orphaned servers, smartphones and proprietary systems locked in time. It will become easier to gain illegitimate access to these machines. The skills needed to break into such systems are diminishing. What previously required highly specialized experts or state actors is now within reach of a much broader pool of bad actors, due to the very tools that are meant to help developers.*
> 
> *What can be done? Companies need more stringent cybersecurity policies today. The broader public needs awareness of this evolution and basic cybersecurity hygiene to keep devices up to date. And we need a regulatory framework that holds producers of computing systems responsible for updates over a longer lifespan. Unfortunately, we can not afford to collectively ignore this emerging reality*.

---

## [Interactive slides on sound, waves, grooves](https://0110.be/posts/Interactive_slides_on_sound%2C_waves%2C_grooves.md)

- Published: 2026-04-19T00:00:00Z
- Updated: 2026-04-20T21:58:03Z
- Author: Joren
- ID: 579
- Canonical: https://0110.be/posts/Interactive_slides_on_sound%2C_waves%2C_grooves

- Tags: [UGent](https://0110.be/tags/UGent.md)


Recently, my son's school teacher asked parents to come in and read in front of the class. I kindly declined, since I am not very comfortable doing voices in front of a room full of ten to twelve-year-olds. As an alternative, I proposed to explain some properties of sound and music, based on material I used [fifteen years ago for the kids' university](https://0110.be/posts/Kinderuniversiteit_-_Muziek_onder_de_microscoop%21): a series of workshops and lectures aimed at children. Unfortunately, after a couple of months, the teacher took me up on the offer.

Fifteen years ago, I painstakingly hand-coded - yes, this is becoming a thing - demos in the Java programming language to show properties of sound: an oscilloscope, a loudness level meter, a spectrogram, a pitch detector. This took ages but worked reasonably well to show basic properties of waves, sounds and music. 

This time, I casually vibe-coded similar demos, not in Java but using self-contained, single-page web applications. Below one of the demos - on sound propagation - is included. With the aid of powerful AI coding tools, programming these interactive demos took only a couple of hours. Fifteen years ago, the hand-coded - arguably worse - versions of the demos took weeks. The main downside is that there is less pride or satisfaction in detailed prompting vs hand-coding.

<center>
<iframe style="width:100%;border:none;height:280px" src="https://0110.be/attachment/cors/2026.04.sounds-waves-grooves/media/iframes/transversal_circle/transversal_circle.html"></iframe>
<small>Fig: visualization on sound propagation.</small>
</center>

With frameworks like reveal.js it is straightforward to string together html pages in a single presentation. Here is the full presentation on [sound, waves and music in Dutch](https://0110.be/attachment/cors/2026.04.sounds-waves-grooves/index.html). Or check the [sound presentation github repository](https://github.com/JorenSix/geluiden_golven_grooves).

Giving the presentation went well and as far as I could tell, the kids were at least involved and may have learned a couple of things. Afterwards, I got some feedback via a parent that their kid was intrigued and got curious, so I will take that as a win.

Some takeaways:

* AI coding tools are great at creating stand-alone, interactive, shader-based visualizations and demos. 
* Use web technologies for slides: great for multimedia, accessibility, distribution, and interactive slides.
* To create interactive slides where shaders or visual information is used the AI coding tool should be able to peer deep into the browser internals. This can be done with the Chrome DevTools MCP or a similar system. This feedback loop allows an LLM to improve on a single-shot attempt automatically.









---

## [Robot-mediated haptic feedback outperforms vision in violin duo coordination](https://0110.be/posts/Robot-mediated_haptic_feedback_outperforms_vision_in_violin_duo_coordination.md)

- Published: 2026-03-19T00:00:00Z
- Updated: 2026-03-24T19:34:20Z
- Author: Joren
- ID: 577
- Canonical: https://0110.be/posts/Robot-mediated_haptic_feedback_outperforms_vision_in_violin_duo_coordination

- Tags: [UGent](https://0110.be/tags/UGent.md)

<img src="https://0110.be/files/attachments/577/scirobotics.2026.11.issue-112.largecover__1_.jpg"  style="float:right;width:20%;margin:10px"> A couple of former colleagues have just published the article [Robot-mediated haptic feedback outperforms vision in violin duo coordination ](https://www.science.org/doi/10.1126/scirobotics.aeb1901) in Science Robotics. The article is about providing guidance to violin players with an exoskeleton. Congratulations Alexandra, Mark and Canan!

It is the result of an European research project in which I was involved in only at the start. I did, however, also contribute to the study in a small but crucial way. During the experiment, the exoskeletons suddenly stopped working. I was asked to have a look and see if there was something to be done. I was a bit hesitant to go near these unique, expensive prototypes with a soldering iron but, after a couple of tense minutes and fixing some - easy to access - connections the arms came back alive. The experiment could continue and everybody started clapping and cheering. This is at least how I remember it. Perhaps that last part is not entirely accurate. Anyhow, I made it into the acknowledgments; thanks Ola.



> **Abstract** *Joint actions among humans rely on the integration of multiple sensory modalities, most notably auditory and visual cues, which support explicit communication between partners. However, haptic feedback provides a direct, implicit channel for sensorimotor communication, and its contribution to fine motor coordination in joint actions remains largely unexplored. Here, we demonstrate that haptic communication, rendered through bidirectionally coupled wearable robots, outperforms traditional auditory-visual feedback in a complex and challenging real-life joint action: ensemble violin performance. 
First, we developed a pair of two–degree-of-freedom upper-limb exoskeletons capable of transparently following violinists’ natural movements and rendering viscoelastic torques proportional to the joint angular deviation between the partners. Then, we designed a within-subject experiment with 20 violin duos performing a musical piece under four sensory feedback conditions: auditory (A), auditory-visual (AV), auditory-haptic (AH), and auditory-visual-haptic (AVH), across two tempi (72 and 100 beats per minute). Despite the musicians being unfamiliar with the robot-mediated haptic feedback and unaware of the bidirectional connection between them, haptic feedback (AH and AVH) substantially enhanced spatiotemporal coordination and dynamic musical alignment compared with the extensively trained auditory-visual feedback (A and AV). The multisensory feedback condition AVH yielded the highest scores across all measures. Our findings demonstrate that haptic feedback can support fine motor coordination in violin duo performance more effectively than visual cues, particularly for professional musicians, because of its implicit and embodied nature, and that it can be effectively delivered via wearable robots, expanding the paradigms of human-human sensorimotor interactions.*

---

## [Guest lecture on \`Music Information Retrieval - Opportunities for digital musicology'](https://0110.be/posts/Guest_lecture_on_%60Music_Information_Retrieval_-_Opportunities_for_digital_musicology%27.md)

- Published: 2025-11-13T00:00:00Z
- Updated: 2025-11-13T11:19:36Z
- Author: Joren
- ID: 572
- Canonical: https://0110.be/posts/Guest_lecture_on_%60Music_Information_Retrieval_-_Opportunities_for_digital_musicology%27

- Tags: [Presentation](https://0110.be/tags/Presentation.md), [UGent](https://0110.be/tags/UGent.md)

This morning I gave a guest lecture introducing the field of music information retrieval to musicology students at Ghent University. Next to the more general MIR intro, two specific topics are fleshed out: duplicate detection and pitch patterns in music around the world. Two topic I have been working on before. 

 The presentation has the form of an interactive website via [reveal.js](https://revealjs.com/). It features a couple of slides which are full-blown applications or have an interactive sound visualization component. Please do try out the slides and check the [Music Information Retrieval - Opportunities for digital musicology](https://0110.be/attachment/cors/2025.11.MIR-Intro/index.html) presentation or try it below.
 
<iframe style="width:100%;min-height:40rem;border:none" src="https://0110.be/attachment/cors/2025.11.MIR-Intro/index.html">
</iframe>

- [MIR\_intro.pdf](https://0110.be/files/attachments/572/MIR_intro.pdf)

---

## [USB MIDI support on ESP32: the ESP32-S3](https://0110.be/posts/USB_MIDI_support_on_ESP32%3A_the_ESP32-S3.md)

- Published: 2025-11-13T00:00:00Z
- Updated: 2025-11-13T20:57:46Z
- Author: Joren
- ID: 563
- Canonical: https://0110.be/posts/USB_MIDI_support_on_ESP32%3A_the_ESP32-S3

- Tags: [UGent](https://0110.be/tags/UGent.md)

<img src="https://0110.be/files/attachments/563/ESP32-S3-Zero.jpg"  style="float:right;width:20%;margin:10px"> If you’re considering adding USB MIDI functionality to a music project with an ESP32, it’s crucial to choose the right variant of the chip. The ESP32-S3 is currently the go-to model for USB-related tasks thanks to its native USB capabilities. Unlike other ESP32 models, the S3 can handle USB MIDI directly without the need for additional components, making it an ideal choice for integrating MIDI devices into your setup. For more details on using USB MIDI with the ESP32-S3, check out the [ESP32USBMIDI project](https://github.com/esp32beans/ESP32USBMIDI).

When combined with the ESP32-S3’s built-in WiFi and support for OSC (Open Sound Control) or ESP Now, the platform becomes very versatile for music controllers or applications. A quick tip: after flashing your device in MIDI mode, the serial is not available any more. Flashing the device also becomes impossible. If you need to reflash your device, the process is simple: just hold down the Boot button and press Reset. 

Another short tip: for troubleshooting and logging, the [mot project](https://github.com/JorenSix/mot) provides useful tools for debugging OSC or MIDI messages. The support is currently [stil in flux](https://github.com/espressif/arduino-esp32/issues/10978) but do not make the mistake I made and do not try to do MIDI with a ESP C3 series. 

---

## [Pompernikkel - the Interactive speaking pumpkin 🎃](https://0110.be/posts/Pompernikkel_-_the_Interactive_speaking_pumpkin_%F0%9F%8E%83.md)

- Published: 2025-11-03T00:00:00Z
- Updated: 2025-11-14T09:34:42Z
- Author: Joren
- ID: 570
- Canonical: https://0110.be/posts/Pompernikkel_-_the_Interactive_speaking_pumpkin_%F0%9F%8E%83

- Tags: [0110.be](https://0110.be/tags/0110.be.md), [Projecten](https://0110.be/tags/Projecten.md), [UGent](https://0110.be/tags/UGent.md)

The last few halloweens I have been building one-off interactive installations for visiting trick-or-treaters. I did not document the build of last year, but the year before I built an [interactive door bell with a jump scare door projection](https://0110.be/posts/Doorbell_triggered_Halloween_window_projection). This year I was trying to take it easy but my son came up with the idea of doing something with a talking pumpkin. I mumbled something about feasibility so he promptly invited all his friends to come over on Halloween to talk to a pumpkin. So I got to work and tried to build something. This blog post documents this build.

A talking pumkin needs a few functions. It needs to understand kids talking in Dutch, it needs to be able to respond with a somewhat logical respons and ideally have a memory about previous interactions. It also needs a way to do turn-taking: indicating who is speaking and listening. It also needs a face and a name. For the name we quickly settled on Pompernikkel. 

For the face I tried a few interactive visualisations: a 3D implementation with three.js and a shader based approach but eventually setteled on an approach of using an SVG and CSS animations to make the face come alive. This approach makes it doable to control animations with javascript since animating a part of the pumkin means adding or removing a css class. See below for the result

<center>
<iframe style="width:50%;min-height:49rem;" src="https://jorensix.github.io/Pompernikkel/"> 
</iframe>
</center>

The other functions I used the following components.


* A decent quality bluetooth speaker for audio output and clear voice projection
* A microphone setup to capture and record children's voices speaking to the pumpkin
* A glass door serving as a projection surface with a projector mounted behind it
* Speech-to-text recognition powered by nvidia/parakeet-tdt-0.6b-v3 (see [this paper](https://arxiv.org/pdf/2509.14128)), implemented via [transcribe-rs](https://github.com/cjpais/transcribe-rs) for transcribing Dutch speech
* Text-to-speech synthesis using the built-in macOS 'say' command to give Pompernikkel a voice
* A controllable interactive projection system displaying the animated SVG website mentioned above
* Response generation handled by the Gemma 3 12B large language model ([paper](https://arxiv.org/pdf/2503.19786)), running locally through [Ollama](https://ollama.com/) with a custom system prompt
* A real pumpkin augmented with an ESP32 microcontroller and capacitive touch sensor embedded inside to detect physical touch - the microphone would only activate while someone was touching the pumpkin
* A custom Ruby websocket driver orchestrating turn-taking behavior and managing the interactive loop of questions and responses

As an extra feature, I implemented a jump scare where a sudden movement would trigger lightning and thunder:

* [EMI-Kit](https://github.com/IPEM/EMI-kit) for responsive movement detection. The mDNS support really makes it easy to use together with [mot](https://github.com/JorenSix/mot).
* A 5V SK6812 LED strip controlled by an ESP32 programmed to react to EMI-Kit events, creating lightning effects synchronized with audio and visual elements on the HTML page


### Lessons learned 

* Asking a code assisting LLM to add animations to a part of an SVG only works after manually adding identifiers to paths of the svg: eyes, mouth, nose, ... Once added, CSS animation are generated with ease. Understanding which svg path corresponds to which semantic object seems out of reach for now for state of the art systems.
* Gemma 3 is not a multilingual LLM. It generates responses in Dutch but these seem very *translated*: it seems that responses are generated in Eglish and translated to Dutch in the final step. This becomes very clear when the LLM attempts jokes. Of course these nonsensical jokes do work on a certain level.
* Gemma 3 has a personality that is difficult to get around if a system promt contains trigger words like dark or scary. In my case it responses became philosophical, nihilistic and very dark. Which was unexpectedly great.
* Parakeet speech to text in Dutch is faster than the several OpenAI Whisper based systems I managed to get running on my macOS. It also gave better results for short excerpts.
* The SK6812 RGBWW is not the best supported by the FastLED library. I managed to get it working with a hack found on GitHub, not ideal.
* There are a few end-to-end systems for voice chat with local LLMs on GitHub but they are not easy to get going with something else than CUDA/linux and almost never support other languages than English. For VAD and transcription the same holds.
* Looking closely to several speech to text or text to speech sysems, the default of CUDA/Linux is difficult to get around. 
* The focus of open source models and tools on the English language is problematic, while Dutch is still relatively well represented many systems are limited to only English.
* While some kids interacted with the pumpkin, the jump scare lighting and thunder effect worked better in the environment.
* Websockets seems a decent way to do inter process communication, even without web technologies it could be considered. I never though of Websockets in this way. See the [Pompernikkel GitHub repository](https://github.com/JorenSix/Pompernikkel) for example Ruby scripts.

Most trick-or-treaters were at least intrigued by it, my son's friends were impressed, and I got to learn a couple of things, see above. Next year, however, I will try to take it easy.




---

## [FFmpeg with Whisper support on macOS via Homebrew](https://0110.be/posts/FFmpeg_with_Whisper_support_on_macOS_via_Homebrew.md)

- Published: 2025-10-22T00:00:00Z
- Updated: 2025-11-03T13:40:09Z
- Author: Joren
- ID: 568
- Canonical: https://0110.be/posts/FFmpeg_with_Whisper_support_on_macOS_via_Homebrew

- Tags: [Code](https://0110.be/tags/Code.md), [UGent](https://0110.be/tags/UGent.md)

Since a couple of months FFmpeg supports audio transcription via OpenAI Whisper and Wisper-cpp. This allows to automatically transcribe interviews and podcasts or generate subtitles for videos. Most packaged versions of  the command line tool `ffmpeg` do not ship with this option enabled. Here we show how to do this on macOS with the Homebrew package manager. On other platforms similar configuration will apply.

On macOS there is a prepared Homebrew keg which allows to enable or disable the many `ffmpeg` options. If you already have `ffmpeg` without options installed you may need to uninstall the current version and install a version with chosen options. See below on how to do this:


```bash
# check if you already have ffmpeg with whisper enabled
ffmpeg --help filter=whisper

# uninstall current ffmpeg, it will be replaced with a version with whisper
brew uninstall ffmpeg

# add a brew tap which provides options to install ffmpeg from source
brew tap homebrew-ffmpeg/ffmpeg

# this commands adds most common functionality and other default functions
brew install homebrew-ffmpeg/ffmpeg/ffmpeg \
--with-fdk-aac \
--with-jpeg-xl \
--with-libgsm \
--with-libplacebo \
--with-librist \
--with-librsvg \
--with-libsoxr \
--with-libssh \
--with-libvidstab \
--with-libxml2 \
--with-openal-soft \
--with-openapv \
--with-openh264 \
--with-openjpeg \
--with-openssl \
--with-rav1e \
--with-rtmpdump \
--with-rubberband \
--with-speex \
--with-srt \
--with-webp \
--with-whisper-cpp
```

Installation will take a while since many dependencies are required for the many options. Once the build is finished the `whisper` filter should be available in FFmpeg. See below on how this should look, once correctly installed:

```
ffmpeg version 8.0 Copyright (c) 2000-2025 the FFmpeg developers
  built with Apple clang version
	...
Filter whisper
  Transcribe audio using whisper.cpp.
    Inputs:
       #0: default (audio)
    Outputs:
       #0: default (audio)
whisper AVOptions:
   model             <string>     ..F.A...... Path to the whisper.cpp model file
   language          <string>     ..F.A...... Language for transcription ('auto' for auto-detect) (default "auto")
   queue             <duration>   ..F.A...... Audio queue size (default 3)
   use_gpu           <boolean>    ..F.A...... Use GPU for processing (default true)
   gpu_device        <int>        ..F.A...... GPU device to use (from 0 to INT_MAX) (default 0)
   destination       <string>     ..F.A...... Output destination (default "")
   format            <string>     ..F.A...... Output format (text|srt|json) (default "text")
   vad_model         <string>     ..F.A...... Path to the VAD model file
   vad_threshold     <float>      ..F.A...... VAD threshold (from 0 to 1) (default 0.5)
   vad_min_speech_duration <duration>   ..F.A...... Minimum speech duration for VAD (default 0.1)
   vad_min_silence_duration <duration>   ..F.A...... Minimum silence duration for VAD (default 0.5)
```

---

## [MuTechLab - Music Technology Workshop in Luxembourg](https://0110.be/posts/MuTechLab_-_Music_Technology_Workshop_in_Luxembourg.md)

- Published: 2025-10-02T00:00:00Z
- Updated: 2025-10-06T13:21:03Z
- Author: Joren
- ID: 567
- Canonical: https://0110.be/posts/MuTechLab_-_Music_Technology_Workshop_in_Luxembourg

- Tags: [Code](https://0110.be/tags/Code.md), [Projecten](https://0110.be/tags/Projecten.md), [UGent](https://0110.be/tags/UGent.md)

<img src="https://0110.be/files/attachments/567/MuTechLab.webp"  style="float:right;width:20%;margin:10px"> Last Friday, I had the pleasure of facilitating a hands-on workshop in Luxembourg as part of [MuTechLab workshop series](https://www.uni.lu/fhse-en/research-projects/mutechlab/), organized by [Luc Nijs](https://www.uni.lu/fhse-en/people/luc-nijs/) at the University of Luxembourg. Together with Bart Moens from [XRHIL](https://xrhil.ugent.be) and [IPEM](https://www.ugent.be/lw/kunstwetenschappen/ipem/en), we presented a system to control musical parameters with body movement.

MuTechLab is a series of workshops for music teachers who wish to dive into the world of music technology. Funded by the Luxembourgish National Research Fund (FNR, PSP-Classic), the initiative brings together educators eager to explore how technology can enhance music education and creative practice.

### What we built and presented

During the workshop, participants got hands-on experience with the [EMI-Kit](https://github.com/IPEM/EMI-kit) (Embodied Music Interface Kit) – an open-source, low-cost system that allows musicians to control Digital Audio Workstation (DAW) parameters through body movement. 

The EMI-Kit consists of:
- **A wearable sensor device** (M5StickC Plus2) that captures body orientation and gestures
- **A receiver unit** (M5Stack STAMP S3A) that converts sensor data to MIDI messages

Unlike expensive commercial alternatives, EMI-Kit is fully open source, customizable, and designed specifically for creative music practice and embodied music interaction practice and research.

### The Experience

Teachers experimented with mapping natural body movements – pitch, yaw, roll, and tap gestures – to various musical parameters in their DAWs. The low-latency wireless system made it possible to move and control sound, opening up new possibilities for expressive musical performance and pedagogy.

### Learn More

Interested in exploring embodied music interaction yourself? Check out:

- [MuTechLab Project Page](https://www.uni.lu/fhse-en/research-projects/mutechlab/)
- [EMI-Kit on GitHub](https://github.com/IPEM/EMI-kit)

The EMI-Kit project as-is is a demonstrator to inspire educators to embrace these tools and imagine new ways of teaching and creating music. The EMI-Kit as a platform can - with some additional programming - be a good basis to control musical parameters using various sensors. Have fun with checking out the EMI-Kit. 

![Workshop hardware set](https://0110.be/files/photos/567/PXL_20251001_113354516.webp)

![Participant package - with sender and receiver pair](https://0110.be/files/photos/567/PXL_20251001_124122616.PORTRAIT.ORIGINAL.webp)

![ESP32-S3 USB MIDI receivers](https://0110.be/files/photos/567/PXL_20251001_130131834.webp)

---

## [MIDI and OSC tools improvements  - MIDI processing and mDNS support](https://0110.be/posts/MIDI_and_OSC_tools_improvements__-_MIDI_processing_and_mDNS_support.md)

- Published: 2025-10-02T00:00:00Z
- Updated: 2025-10-06T13:54:28Z
- Author: Joren
- ID: 566
- Canonical: https://0110.be/posts/MIDI_and_OSC_tools_improvements__-_MIDI_processing_and_mDNS_support

- Tags: [Code](https://0110.be/tags/Code.md), [Music Information Retrieval](https://0110.be/tags/Music%20Information%20Retrieval.md), [UGent](https://0110.be/tags/UGent.md)

I've just pushed some updates to [mot](https://github.com/JorenSix/mot) — a command-line application for working with OSC and MIDI messages. My LLM tells me that these are exciting updates but I am not entirely sure that this is the case. Let me know if this ticks your box and seek professional help. 

### 1. Scriptable MIDI Processor via Lua

I have implemented a **MIDI processor** that lets you transform, filter, and generate MIDI messages using [Lua](https://www.lua.org/about.html) scripts.

**Why is this useful?** MIDI processors act as middlemen between your input devices and output destinations.You can do the following on incoming MIDI messages:

- **Transform** - Transpose notes, generate chords, map velocity curves
- **Filter** - Block unwanted messages - channels - or select specific ranges
- **Route** - Send different notes to different channel
- **Generate** - Create complex patterns from simple input

<svg viewBox="0 0 800 300" xmlns="http://www.w3.org/2000/svg">
                <!-- External MIDI Device -->
                <g id="midi-device">
                    <rect x="20" y="100" width="140" height="100" rx="10" fill="#667eea" stroke="#5568d3" stroke-width="2"/>
                    <text x="90" y="140" text-anchor="middle" fill="white" font-size="16" font-weight="bold">MIDI Device</text>
                    <text x="90" y="160" text-anchor="middle" fill="white" font-size="12">(Keyboard, Pad, etc.)</text>
                    <circle cx="90" cy="185" r="3" fill="white"/>
                    <circle cx="90" cy="195" r="3" fill="white"/>
                </g>

                <!-- Arrow 1 -->
                <g id="arrow1">
                    <line x1="160" y1="150" x2="230" y2="150" stroke="#333" stroke-width="2" marker-end="url(#arrowhead)"/>
                    <text x="195" y="140" text-anchor="middle" font-size="11" fill="#666">MIDI In</text>
                    <text x="195" y="170" text-anchor="middle" font-size="10" fill="#999" font-family="monospace">Note On C4</text>
                </g>

                <!-- mot Processor -->
                <g id="mot-processor">
                    <rect x="230" y="80" width="340" height="140" rx="10" fill="#764ba2" stroke="#6a3f8f" stroke-width="3"/>
                    <text x="400" y="110" text-anchor="middle" fill="white" font-size="18" font-weight="bold">mot midi_processor</text>
                    
                    <!-- Lua Script Box -->
                    <rect x="250" y="125" width="300" height="80" rx="5" fill="rgba(255,255,255,0.15)" stroke="white" stroke-width="1" stroke-dasharray="3,3"/>
                    <text x="400" y="145" text-anchor="middle" fill="white" font-size="14" font-weight="bold">🌙 Lua Script</text>
                    <text x="400" y="165" text-anchor="middle" fill="white" font-size="11" font-family="monospace">process_message()</text>
                    <text x="400" y="183" text-anchor="middle" fill="#ffd700" font-size="10">Transform • Filter • Generate</text>
                    <text x="400" y="198" text-anchor="middle" fill="#90ee90" font-size="9">C4 → C4 + E4 + G4 (chord)</text>
                </g>

                <!-- Arrow 2 -->
                <g id="arrow2">
                    <line x1="570" y1="150" x2="640" y2="150" stroke="#333" stroke-width="2" marker-end="url(#arrowhead)"/>
                    <text x="605" y="140" text-anchor="middle" font-size="11" fill="#666">MIDI Out</text>
                    <text x="605" y="170" text-anchor="middle" font-size="10" fill="#999" font-family="monospace">3 notes</text>
                </g>

                <!-- Virtual MIDI Device -->
                <g id="virtual-device">
                    <rect x="640" y="100" width="140" height="100" rx="10" fill="#28a745" stroke="#218838" stroke-width="2"/>
                    <text x="710" y="135" text-anchor="middle" fill="white" font-size="16" font-weight="bold">Virtual MIDI</text>
                    <text x="710" y="155" text-anchor="middle" fill="white" font-size="12">Device</text>
                    <text x="710" y="175" text-anchor="middle" fill="white" font-size="10">(DAW, Synth, etc.)</text>
                    <circle cx="710" cy="190" r="3" fill="white"/>
                    <circle cx="710" cy="198" r="3" fill="white"/>
                </g>

                <!-- Arrow definition -->
                <defs>
                    <marker id="arrowhead" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
                        <polygon points="0 0, 10 3, 0 6" fill="#333"/>
                    </marker>
                </defs>

                <!-- Data flow animation circles -->
                <circle r="4" fill="#ffd700">
                    <animateMotion dur="3s" repeatCount="indefinite" 
                        path="M160,150 L230,150 L570,150 L640,150"/>
                </circle>
</svg>

The processor reads incoming MIDI from a physical device, processes it through your Lua script, and outputs the modified messages to a virtual MIDI port that your DAW or synth can receive. Some examples:


```ruby
# Generate chords from single notes
mot midi_processor --script scripts/chord_generator.lua 0 6666

# Transpose notes up by one octave
mot midi_processor --script scripts/example_processor.lua 0 6666
```




### 2. Network Discovery via mDNS

OSC receivers now **advertise themselves on the network** using mDNS/Bonjour with the `_osc._udp` service type. 

This makes mot compatible with the [EMI-kit](https://github.com/IPEM/EMI-kit) — the Embodied Music Interface Kit developed at IPEM, Ghent University. OSC-enabled devices can automatically discover mot receivers on your network, eliminating manual configuration if the OSC sources add this functionality.


### Get started

Installation via Rust's cargo:


```ruby
git clone https://github.com/JorenSix/mot.git
cd mot
cargo install --path .
mot midi_processor -h
```


Check out the [mot repository](https://github.com/JorenSix/mot) for full documentation and example Lua scripts!


---

## [Newline.gent - A yearly hacker conference](https://0110.be/posts/Newline.gent_-_A_yearly_hacker_conference.md)

- Published: 2025-05-24T00:00:00Z
- Updated: 2025-06-03T13:53:12Z
- Author: Joren
- ID: 565
- Canonical: https://0110.be/posts/Newline.gent_-_A_yearly_hacker_conference

- Tags: [Hackerspace Ghent](https://0110.be/tags/Hackerspace%20Ghent.md), [UGent](https://0110.be/tags/UGent.md)

This weekend the - more-or-less - yearly conference of [Hackerspace Ghent](https://hackerspace.gent/) took place: [Newline.gent](https://newline.gent/). Hackers, makers, and curious minds gathered to share ideas, tools, experiments and a few beers.

I had a small contribution with a short  [lecture-performance](https://0110.be/attachment/presentations/2025.05.newline-ghent/index.html)  which covered [how to control your computer with a flute]( https://0110.be/posts/Making_a_flute_controlled_mouse). The lecture part covered the technical part of the build, the performance part included playing Flappy Bird with a flute. A third significant part of the talk — arguably the main focus — was devoted to bragging about the global attention the project received.

Other highlights of the Newline conference included talks on Home Assistant, 3D design, BTRFS and workshops that invited everyone to get involved.

Big thanks to the organizers and everyone who joined. I’m already looking forward to the next one!

---

## [Local TLS certificates with Caddy](https://0110.be/posts/Local_TLS_certificates_with_Caddy.md)

- Published: 2025-03-21T00:00:00Z
- Updated: 2025-04-15T12:51:11Z
- Author: Joren
- ID: 562
- Canonical: https://0110.be/posts/Local_TLS_certificates_with_Caddy

- Tags: [UGent](https://0110.be/tags/UGent.md)



This short guide will help you set up a local certificate using Caddy as the webserver to provide local TLS certificates to be able to develop websites immedately using HTTPS. Having a local HTTPS server in development can help with e.g. debugging CORS issues, accessing resources which require a HTTPS connection, or trying out analytics platforms. 

#### 1. Configure your hosts file

If you want to use a domain name, you need to first add a line to `/etc/hosts` which, in this case, sets `localhost` to correspond to `example.com`.


```ruby
echo "127.0.0.1 example.com" | sudo tee -a /etc/hosts
```



#### 2. Configure Caddy 

In a directory of your choosing, create a `Caddyfile` with the following content, it sets Caddy to automatically generate certificates on the fly for example.com or any other domain name. Perhaps you will need to trust the main Caddy certificate on first use:


```ruby
{
    # Enable the internal CA
    local_certs
}

example.com {
    root * .
    file_server
    tls internal
}
```


#### 3. Create a test site

In the same directory, create an `index.html` file with contents similar or use your local web the following, or :


```ruby
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <h1>Hello World!</h1>
</body>
</html>
```


#### 4.  Start the Webserver

Still in the same directory as the `Caddyfile` and the `index.html` file, run the following command to start the Caddy web server: `caddy run`

#### 5. Trust the locally generated certificate

In macOS this means adding the [local caddy root certificate to your keychain](https://blog.lanzani.nl/2024/trust-local-caddy-certificates-on-macos/). It can be found here  `/data/caddy/pki/authorities/local/root.crt` In other environments a similar step is needed.

#### 6. access the Test Site

Open your web browser and navigate to `https://example.com` to access the test site in the command line: `open https://example.com`. If you inspect the certificate it should be issued by the 'Caddy local authority'. 






---

## [Powering low current electronics projects with power banks](https://0110.be/posts/Powering_low_current_electronics_projects_with_power_banks.md)

- Published: 2025-01-26T00:00:00Z
- Updated: 2025-02-01T10:13:55Z
- Author: Joren
- ID: 558
- Canonical: https://0110.be/posts/Powering_low_current_electronics_projects_with_power_banks

- Tags: [Code](https://0110.be/tags/Code.md), [Projecten](https://0110.be/tags/Projecten.md), [UGent](https://0110.be/tags/UGent.md)

<img src="https://0110.be/files/attachments/558/mojogear_mini-evo.jpg" style="float:right;width:20%;margin:10px"> Power banks have become a staple for charging smartphones, tablets, and other devices on the go. They seem ideal to power small microcontroller projects but, they often pose a problem for low-current applications. Most modern power banks include an auto-shutdown feature to conserve energy when they detect a current draw below a specific threshold, often around 50–200mA. The idea being that the power bank can shut off after charging a smartphone. However, if you rely on power banks to power DIY electronics projects or remote applications with low current draw, this auto-off feature can be a significant inconvenience.

To address this issue, consider using power banks designed with an “always-on” or “low-current” mode. These power banks are engineered to sustain power delivery even when the current draw is minimal. Look for models that explicitly mention support for low-power devices in their specifications. If replacing a power bank isn’t an option, you can add a small load resistor or a USB dummy load to artificially increase the current draw. It works, but feels wrong and dirty.

For a [previous electronics project](https://0110.be/posts/When_both_tech_and_nature_call%3A_a_DIY_motion_sensor_to_activate_birdsong) I bought a power bank randomly. After a bit of testing, I determined that the minimal power draw was around 150mA, so I added a resistor to increase current draw. Only afterwards did I check the [manual of the power bank](https://mojogear.eu/cdn/shop/files/Manual-MG-10.pdf?v=683883965702775870) and noticed, luckily,  that there was a low-current mode. I removed the resistor and improved the battery life of the project considerably. If you want to power your DIY Arduino or electronics project, first check the manual of the power bank you want to use!

Edit: after further testing it seemed that the low current mode of this specific power bank still shuts down after a couple of hours. Your mileage may vary, and the main point of this post still holds: check the manual of your power bank. Eventually I went with a  [solution designed for electronics projects](https://www.az-delivery.de/nl/products/battery-expansion-shield-18650-v3-inkl-usb-kabel).




---

## [When both tech and nature call: a DIY motion sensor to activate birdsong](https://0110.be/posts/When_both_tech_and_nature_call%3A_a_DIY_motion_sensor_to_activate_birdsong.md)

- Published: 2025-01-25T00:00:00Z
- Updated: 2025-01-26T20:01:27Z
- Author: Joren
- ID: 556
- Canonical: https://0110.be/posts/When_both_tech_and_nature_call%3A_a_DIY_motion_sensor_to_activate_birdsong

- Tags: [UGent](https://0110.be/tags/UGent.md)

There is this thing that [starts playing birdsong](https://duckduckgo.com/?q=zwitscherbox&ia=web) when it detects movement. It is ideal to connect to nature while nature calls. It is a good idea, executed well but it got me thinking: this can be made less reliable, more time consuming, more expensive, and with a shorter battery life. So I started working on a DIY version. 

<center>
<video controls  src="https://0110.be/files/attachments/556/birhouse-example.mp4" preload="none" poster="https://0110.be/files/attachments/556/birdhouse-static.jpg" style="width:80%">
</video><br>
<small>Vid: Playing birdsong when presence is detected with an ESP32 microcontroller .</small>
</center>

The general idea is to start playing birdsong if someone is present in a necessary room. In addition to a few of electronics components the project needs birdsong recordings. Freesound is a great resource for all kinds of environmental sounds and has a [collection of birdsong](https://freesound.org/people/juskiddink/packs/3675/) which was used for this project. 

For the electronics components the project needs a microcontroller and a way to detect presence.  I had a laser ranging sensor lying around which measures distance but can be repurposed to detect presence in a small room: most of the time, the distance to an opposite wall is reported. If a smaller distance is measured it is probably due to a person being present. The other components:

* An[ ESP32-C6 based Sparkfun Thing Plus](https://docs.sparkfun.com/SparkFun_Thing_Plus_ESP32_C6/introduction/). It has a battery recharging circuit, SD card reader, and a Quic connector. 
* A time-of-flight laser sensor to measure distance. I used a VL53L0X with a grove connector.
* An I2**S** Amplifier to drive a speaker. I used the MAX 98357A, it seems to do what it needs to do.
* A small speaker, salvaged from some other device
* A MicroSD card, to store some audio files
* A rechargeable LiPo battery, or [a power bank with a low-current mode](https://0110.be/posts/Powering_low_current_electronics_projects_with_power_banks)

As is often the case with builds like this, neither the software nor the hardware is challenging conceptually but, making hard and software  cooperate is. Some pitfalls I encountered: the ESP32 C6 needs USB CDC set in the Arduino IDE, the non standard I2C GPIO pins. Getting the many I2S parameters right. Dealing with a nasty pop sound once audio started. A broken LiPo battery. Most of the fixes can be found in the Arduino code

I use a polling strategy to detect presence. A distance measurement is taken and then the ESP32 goes into a deep sleep until the next measurement. A sensor with the ability to wake up the microcontroller would be a better approach. 

Once everything was installed  it worked well enough — motion triggered a random birdsong, creating a soothing, natural vibe. It may be less practical than the off-the-shelf version but I did learn quite a lot more than I would have by simply filling in a form and providing payment details... 



![](https://0110.be/files/photos/556/PXL_20250119_222554429.MP.webp)

![](https://0110.be/files/photos/556/vlcsnap-2025-01-26-17h32m32s696_small.jpg)

- [birhouse-static.webp](https://0110.be/files/attachments/556/birhouse-static.webp)

- [birhouse-example.mp4](https://0110.be/files/attachments/556/birhouse-example.mp4)

- [birdsong\_arduino.ino](https://0110.be/files/attachments/556/birdsong_arduino.ino)

- [birdhouse-static.jpg](https://0110.be/files/attachments/556/birdhouse-static.jpg)

---

## [The time an HTTP request takes to pass through a proxy, a proxy, a proxy, a proxy, ...](https://0110.be/posts/The_time_an_HTTP_request_takes_to_pass_through_a_proxy%2C_a_proxy%2C_a_proxy%2C_a_proxy%2C_....md)

- Published: 2025-01-16T00:00:00Z
- Updated: 2025-01-21T10:57:17Z
- Author: Joren
- ID: 557
- Canonical: https://0110.be/posts/The_time_an_HTTP_request_takes_to_pass_through_a_proxy%2C_a_proxy%2C_a_proxy%2C_a_proxy%2C_...

- Tags: [UGent](https://0110.be/tags/UGent.md)

A discussion at work led to the question how much time it takes for a HTTP request to pass through a HTTP proxy. This blog post deals with this question by measuring a request passing through a stupid amount of HTTP proxies.
<br><br>


```ruby
curl -s -o /dev/null -w  "%{time_starttransfer}\n" http://localhost:2000
```

<small style="display:block;margin-top: -1.5em">Fig: Measuring the time it takes to pass 500 proxies with Curl.</small>


In modern development setups it is not uncommon that your HTTP request passes a few HTTP proxies before reaching a final server that actually handles the request. In our case there is a proxy which ensures an SSL certificate, which is forwarded to a proxy which automatically forwards requests to a docker container. A final HTTP proxy runs in the docker network that forwards the request to  a webserver. A response follows the same way in reverse. <br><br>


```ruby
#Caddyfile, to pass through 500 HTTP proxies

:2000 { 
    reverse_proxy localhost:2001 
}
:2001 { 
    reverse_proxy localhost:2002
}

...

:2500 {
   respond "Hello, proxy!"
}
```

<small style="display:block;margin-top: -1.5em">Fig: Configuration to pass a HTTP request through many proxies. The final response is a simple text.</small>

To measure the time it take to pass through a HTTP proxy, I wrote a small script to start 500 separate instances of the Caddy webserver configured as a HTTP/2 proxy. Then, I measure the time it takes to pass through all 500 of the HTTP proxies or only 490, 480,... which results in the graph below.


<center>
<img src="https://0110.be/files/attachments/557/proxy_time_graph.png" style="width:50%"><br>
<small style="display:block;width:50%">Fig: Time it takes to pass x amount of HTTP proxies. The y-axis represents the time taken (in seconds), and the x-axis indicates the number of HTTP proxies passed.</small>
</center>


So each proxy pass takes about 0.4 milliseconds in one of the best cases, where requests are forwarded from and to localhost. Network overhead adds to that but assuming that interconnects are fast, adding a few HTTP proxies does not affect latency in a meaningful way. Of course it is best to evaluate your situation and measure.




- [proxy\_time\_graph.png](https://0110.be/files/attachments/557/proxy_time_graph.png)

- [measure.rb](https://0110.be/files/attachments/557/measure.rb)

- [script.rb](https://0110.be/files/attachments/557/script.rb)

---

## [Tasmota for custom  ESP32 smart home devices](https://0110.be/posts/Tasmota_for_custom__ESP32_smart_home_devices.md)

- Published: 2025-01-03T00:00:00Z
- Updated: 2025-01-08T20:12:39Z
- Author: Joren
- ID: 555
- Canonical: https://0110.be/posts/Tasmota_for_custom__ESP32_smart_home_devices

- Tags: [Code](https://0110.be/tags/Code.md), [Projecten](https://0110.be/tags/Projecten.md), [UGent](https://0110.be/tags/UGent.md)

<img src="https://0110.be/files/attachments/555/frontlogo.svg" style="float:right;width:20%;margin:10px"> 
In my house, I have a few smart home features: to control ventilation, to open and close solar screens, and to switch a few smart sockets. Up until a couple of days ago, the ventilation and screen controllers operated using custom software running on an ESP32. However, configuring, maintaining, upgrading, and integrating with this custom software gradually became a headache.

Recently, I switched from custom software to Tasmota, an open-source smart home platform targeting ESP32 devices. Tasmota includes a web UI, flexible configuration options, OTA upgrades, and scripting features. The scripting functionality allows devices to be extended with additional commands, which is especially practical for controlling my solar screens. These screens use pulses to toggle between up-stop-down-stop states. By default, Tasmota only supports enabling or disabling a relay, not enabling it for a very brief period (e.g., 150 milliseconds). With a short ‘Berry’ script, such functionality is quickly added.

I appreciate the effort of the Tasmota team to lower the entry barrier for users. They provide ample documentation and a web installer, making setup straightforward. Simply connect your ESP32 via USB, flash it with Tasmota, and configure it—all from your browser. It’s a surprisingly simple process compared to installing a dedicated toolchain. While this might not be what Tim Berners-Lee envisioned 35 years ago, it certainly simplifies the user experience. Lowering the entry barrier even further, some manufacturers even offer smart home devices with Tasmota preinstalled, such as the Nous A1 smart sockets. Eternal september is here.

If you’re managing custom ESP32 smart home devices, consider switching to Tasmota. Its robust features, ease of setup, and active community support make it an excellent choice for both beginners and advanced users.


---

## [GhentCDH at the Faculty Research Day](https://0110.be/posts/GhentCDH_at_the_Faculty_Research_Day.md)

- Published: 2024-11-27T00:00:00Z
- Updated: 2024-11-27T10:07:51Z
- Author: Joren
- ID: 551
- Canonical: https://0110.be/posts/GhentCDH_at_the_Faculty_Research_Day

- Tags: [GhentCDH](https://0110.be/tags/GhentCDH.md), [Presentation](https://0110.be/tags/Presentation.md), [UGent](https://0110.be/tags/UGent.md)

The [research day of the faculty of Arts and Philosophy of Ghent University](https://www.lwresearchday.ugent.be/) took place last November. The theme of the day was ‘*From Source to Understanding*’ and the program gave an overview of the breadth of research at our faculty with topics as logic, history, archeology, chemistry, geography, language studies, ... There were several contributions by our group: the Ghent Center for Digital Humanities. The contribution by me and my close colleagues [was a poster about a reusable text annotation](https://0110.be/files/attachments/551/poster_text_annotation.webp) building block.

<center>
<img style="max-height:550px" src="https://0110.be/files/attachments/551/poster_text_annotation.webp" /><br>
<small>Fig: Poster on a text annotation component.</small>
</center>

At GhentCDH we support several text annotation projects and have extracted a text annotation component for reuse. The abstract reads: 

> "*Text annotation is essential for analyzing ancient texts, identifying entities in texts, or documenting evolving grammar. There is a need for reusable annotation methods which copes with challenges such as overlapping annotations, filtering annotation types, and enabling large-scale collaboration and computational analysis on text annotation work.*
> 
> *We present a reusable text annotation component built with TypeScript and Vue 3. It provides an intuitive interface for creating, visualizing, and editing annotations, it allows component users to enrich annotations with complex metadata, and facilitates flexible annotation filtering. This solution meets many needs of researchers in digital humanities and ancient language studies and will be used in several GhentCDH projects.*"

Get more info, try it out and use it in your project by checking the [text annotator source code repository](https://github.com/GhentCDH/vue_component_annotated_text).




---

## [Connecting two Bluetooth headsets to your Linux system: audio routing in PipeWire](https://0110.be/posts/Connecting_two_Bluetooth_headsets_to_your_Linux_system%3A_audio_routing_in_PipeWire.md)

- Published: 2024-10-23T00:00:00Z
- Updated: 2024-10-25T09:00:02Z
- Author: Joren
- ID: 552
- Canonical: https://0110.be/posts/Connecting_two_Bluetooth_headsets_to_your_Linux_system%3A_audio_routing_in_PipeWire

- Tags: [0110.be](https://0110.be/tags/0110.be.md), [UGent](https://0110.be/tags/UGent.md)

Imagine you want to stream a movie at home but also want to keep things quiet to avoid disturbing others. Evidently, this what headsets were invented for. Connecting one wireless Bluetooth headset is typically straightforward - aside from the occasional Bluetooth pairing issues. But what if you want to watch that movie with someone else, and you both want to use headsets? Connecting two Bluetooth headsets, or even combining wired and wireless headsets to share the same audio, isn’t as simple as it sounds. This blog post shows how to achieve this on modern Linux distributions.

<center>
<img src="https://0110.be/files/attachments/552/routing_audio.png" style="max-width:50%"> <br>
<small>Fig: Connecting an audio source - Spotify - to multiple output devices by using audio routing with PipeWire and `qpwgraph`.</small>
</center>

During the last years, several Linux distributions have started to support the PipeWire audio server. It is even the default audio server in Debian 12 and Ubuntu 22.10. With PipeWire, managing audio devices has become much easier. PipeWire enables flexible audio setups and supports audio routing: sending out audio from a single source to several output devices. This is exactly what we need to stream audio to multiple headsets.

If you use PipeWire on your system,  `qpwgraph` provides an intuitive graphical interface that lets you visualize and control audio routing. To connect multiple headsets:

1. First install `qpwgraph`  e.g via `apt install qpwgraph`
1. Startup `qpwgraph` which should show your current audio routing graph.
1. Pair your Bluetooth headsets to your machine. They will appear in the audio routing graph once paired successfully.
1. Connect the audio source to your headsets by connecting  'wires' from your media player to the headsets.

I was surprised how robust audio has become on Linux and how easy and user friendly it is to set up even more complex audio / MIDI configurations. Give it a try!



![](https://0110.be/files/photos/552/routing_audio.png)

---

## [Validity and reliability of peak tibial accelerations as real-time measure of impact loading during over-ground rearfoot running at different speeds](https://0110.be/posts/Validity_and_reliability_of_peak_tibial_accelerations_as_real-time_measure_of_impact_loading_during_over-ground_rearfoot_running_at_different_speeds.md)

- Published: 2024-10-01T00:00:00Z
- Updated: 2024-10-01T14:30:24Z
- Author: Joren
- ID: 550
- Canonical: https://0110.be/posts/Validity_and_reliability_of_peak_tibial_accelerations_as_real-time_measure_of_impact_loading_during_over-ground_rearfoot_running_at_different_speeds

- Tags: [Research papers](https://0110.be/tags/Research%20papers.md), [UGent](https://0110.be/tags/UGent.md)

<div style="float:right;width:30%;max-width:200px;margin-left:10px;margin-bottom:10px;box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4);">
<center>
<img loading="lazy" style="width:100%" src="https://0110.be/files/attachments/550/foot_strike.png">
</center>
</div>

The publication of this paper seemed an almost sisyphean task, but it is now finally in print after about four years since first submission.
All's well that ends well and it is well indeed: the paper contributes a fundamental insight around the resultant peak tibial acceleration (PTA) in forefoot running: contrary to what is often presumed, the resultant PTA is higher in forefoot running! The paper combines two separate experiments into a single analysis framework which ensures robustness in the finding. The conclusions of the article can be found below:


> ***Conclusions*** *Many coaches and practitioners presume that forefoot striking decreases impact severity and prevents overuse injuries; however, our data show that instructed and habitual forefoot strikes have greater resultant but not axial PTA than habitual rearfoot strikes in level running at a submaximal speed. The forefoot strikes had a sharp decrease in the antero-posterior velocity of the shank following touchdown and, therefore, a greater antero-posterior acceleration, which resulted in the greater resultant peak tibial acceleration compared to the rearfoot strikes. Conclusively, the foot strike pattern differently affected PTAs and should be taken into account when evaluating 3D impact severity in distance runners.*

My contribution to the paper was limited to technical development and support during the measurements at Ghent University. For the full paper, please read the publishers version of [Validity and reliability of peak tibial accelerations as real-time measure of impact loading during over-ground rearfoot running at different speeds](https://www.tandfonline.com/doi/full/10.1080/14763141.2024.2367619). 


---

## [OnTracx product launch - a Ghent University sports-tech spin-off](https://0110.be/posts/OnTracx_product_launch_-_a_Ghent_University_sports-tech_spin-off.md)

- Published: 2024-04-24T00:00:00Z
- Updated: 2024-04-25T11:54:49Z
- Author: Joren
- ID: 544
- Canonical: https://0110.be/posts/OnTracx_product_launch_-_a_Ghent_University_sports-tech_spin-off

- Tags: [UGent](https://0110.be/tags/UGent.md)

<div style="float:right;width:250px;margin-left:15px">
<center>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 485.49 71.32" style="width:100%" id="svg-656346908_4188"><defs><linearGradient id="svg-656346908_4188_a" x1="23129.87" y1="34.23" x2="23159.13" y2="63.49" gradientTransform="matrix(-1 0 0 1 23159 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2f2f2" stop-opacity="0"></stop><stop offset="1" stop-color="#002b7c"></stop></linearGradient><linearGradient id="svg-656346908_4188_b" x1="29.26" y1="42.86" x2="64" y2="42.86" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002b7c"></stop><stop offset="1" stop-color="#f2f2f2" stop-opacity="0"></stop></linearGradient><linearGradient id="svg-656346908_4188_c" x1="71.03" y1="66.94" x2="109.23" y2=".78" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002b7c"></stop><stop offset="1" stop-color="#f2f2f2" stop-opacity="0"></stop></linearGradient></defs><path d="M159.29 71.32c-6.24 0-11.68-1.37-16.35-4.1-4.66-2.73-8.31-6.52-10.96-11.36-2.64-4.84-3.96-10.44-3.96-16.79s1.32-11.95 3.96-16.79c2.64-4.84 6.29-8.62 10.96-11.36 4.66-2.73 10.11-4.1 16.35-4.1s11.61 1.37 16.3 4.1c4.69 2.73 8.34 6.52 10.96 11.36 2.61 4.84 3.92 10.44 3.92 16.79s-1.31 11.95-3.92 16.79-6.27 8.63-10.96 11.36c-4.69 2.73-10.13 4.1-16.3 4.1Zm0-10.24c5.94 0 10.67-1.96 14.21-5.88 3.53-3.92 5.3-9.29 5.3-16.12s-1.77-12.2-5.3-16.12c-3.53-3.92-8.27-5.88-14.21-5.88s-10.69 1.96-14.25 5.88c-3.56 3.92-5.34 9.29-5.34 16.12s1.78 12.2 5.34 16.12c3.56 3.92 8.31 5.88 14.25 5.88Zm40.98 9.17V26.07h10.07l.89 7.48c1.37-2.61 3.34-4.69 5.92-6.24 2.58-1.54 5.63-2.32 9.13-2.32 5.46 0 9.71 1.72 12.74 5.17 3.03 3.44 4.54 8.49 4.54 15.14v24.94h-11.4V46.37c0-3.8-.77-6.71-2.32-8.73-1.54-2.02-3.95-3.03-7.21-3.03s-5.83 1.13-7.88 3.38c-2.05 2.26-3.07 5.4-3.07 9.44v22.8h-11.4Zm68.94 0V17.07h-18.17V7.9h47.83v9.17h-18.26v53.18h-11.4Zm33.49 0V26.07h10.16l1.07 8.28c1.6-2.85 3.79-5.12 6.55-6.81 2.76-1.69 6.01-2.54 9.75-2.54v12.03h-3.21c-2.49 0-4.72.39-6.68 1.16-1.96.77-3.49 2.11-4.59 4.01-1.1 1.9-1.65 4.54-1.65 7.93v20.13h-11.4Zm50.51 1.07c-3.8 0-6.92-.61-9.35-1.83-2.44-1.22-4.25-2.83-5.43-4.85-1.19-2.02-1.78-4.25-1.78-6.68 0-4.1 1.6-7.42 4.81-9.98 3.21-2.55 8.02-3.83 14.43-3.83h11.22v-1.07c0-3.03-.86-5.26-2.58-6.68-1.72-1.42-3.86-2.14-6.41-2.14-2.32 0-4.34.55-6.06 1.65-1.72 1.1-2.79 2.72-3.21 4.85h-11.13c.3-3.21 1.38-6 3.25-8.37 1.87-2.37 4.29-4.2 7.26-5.48 2.97-1.28 6.29-1.92 9.98-1.92 6.29 0 11.25 1.57 14.88 4.72 3.62 3.15 5.43 7.6 5.43 13.36v27.17h-9.71l-1.07-7.13c-1.31 2.38-3.13 4.34-5.48 5.88-2.35 1.54-5.36 2.32-9.04 2.32Zm2.58-8.91c3.27 0 5.8-1.07 7.62-3.21 1.81-2.14 2.95-4.78 3.43-7.93h-9.71c-3.03 0-5.2.55-6.5 1.65-1.31 1.1-1.96 2.45-1.96 4.05 0 1.72.65 3.06 1.96 4.01 1.3.95 3.03 1.43 5.17 1.43Zm55.23 8.91c-4.51 0-8.49-.98-11.94-2.94-3.44-1.96-6.16-4.69-8.15-8.2-1.99-3.5-2.98-7.51-2.98-12.03s.99-8.52 2.98-12.03c1.99-3.5 4.71-6.24 8.15-8.19 3.44-1.96 7.42-2.94 11.94-2.94 5.64 0 10.39 1.47 14.25 4.41 3.86 2.94 6.32 6.99 7.39 12.16h-12.03c-.59-2.14-1.77-3.81-3.52-5.03-1.75-1.22-3.82-1.83-6.19-1.83-3.15 0-5.82 1.19-8.02 3.56-2.2 2.38-3.3 5.67-3.3 9.89s1.1 7.51 3.3 9.89c2.2 2.38 4.87 3.56 8.02 3.56 2.38 0 4.44-.59 6.19-1.78s2.92-2.88 3.52-5.08h12.03c-1.07 4.99-3.53 9-7.39 12.03-3.86 3.03-8.61 4.54-14.25 4.54Zm27.61-1.07 16.03-22.09-16.03-22.09h12.2l11.22 15.68 11.13-15.68h12.29l-16.12 22.09 16.12 22.09h-12.29l-11.13-15.68-11.22 15.68h-12.2Z" fill="#002b7c"></path><path d="M6.99 67.71a6.997 6.997 0 0 1-5.9-10.74l15-23.71a6.996 6.996 0 0 1 9.66-2.17 6.996 6.996 0 0 1 2.17 9.66l-15 23.71A6.98 6.98 0 0 1 7 67.72Z" fill="url(#svg-656346908_4188_a)"></path><path d="M36.26 67.72a7.004 7.004 0 0 1-6.05-10.52l20.74-35.72a7.004 7.004 0 0 1 9.57-2.54 7.004 7.004 0 0 1 2.54 9.57L42.32 64.23a7 7 0 0 1-6.06 3.49Z" fill="url(#svg-656346908_4188_b)"></path><path d="M74.26 67.72c-1.21 0-2.44-.31-3.55-.97a7.009 7.009 0 0 1-2.47-9.59L99.97 3.44a7.009 7.009 0 0 1 9.59-2.47 7.009 7.009 0 0 1 2.47 9.59L80.29 64.28a7.003 7.003 0 0 1-6.03 3.44Z" fill="url(#svg-656346908_4188_c)"></path><circle cx="103" cy="12" r="7" fill="#002b7c"></circle><path d="M36.01 67.71c-2.4 0-4.73-1.23-6.03-3.44l-14-23.71c-1.97-3.33-.86-7.62 2.47-9.59 3.33-1.97 7.62-.86 9.59 2.47l14 23.71c1.97 3.33.86 7.62-2.47 9.59-1.12.66-2.34.97-3.55.97Zm37.94.01a7.01 7.01 0 0 1-6.33-4L50.68 28a6.987 6.987 0 0 1 3.33-9.32 6.987 6.987 0 0 1 9.32 3.33l16.94 35.72c1.66 3.49.17 7.67-3.33 9.32-.97.46-1.99.68-2.99.68Z" fill="#002b7c"></path>
</svg>
<small>Fig: OnTracx logo</small>
</center>
 </div>

A couple of days ago, [OnTracx](https://www.ontracx.com/) launched their first product: a system to measure impact during running with the aim to become *'The Future of injury-free running'*.  Next to the launch event itself, OnTracx was featured in the [national](https://www.vrt.be/vrtnws/nl/2024/04/17/nieuwe-tracker-voor-belasting-te-meten-bij-lopen-of-joggen/) [media](https://www.tijd.be/ondernemen/technologie/slimme-sensor-van-gentse-start-up-ontracx-moet-komaf-maken-met-loopblessures/10541459.html) as well. 

OnTracx is a Ghent University spin-off and their product is based on a couple of scientific studies. I had the chance to collaborate on some of these studies:

* Van den Berghe, P., Six, J., Gerlo, J., Leman, M., & De Clercq, D. (2019). [__Validity and reliability of peak tibial accelerations as real-time measure of impact loading during over-ground rearfoot running at different speeds__](https://pubmed.ncbi.nlm.nih.gov/30824234/). Journal of Biomechanics
* Van den Berghe, P., Lorenzoni, V., Derie, R., Six, J., Gerlo, J., Leman, M., & De Clercq, D. (2021). __Music-based biofeedback to reduce tibial shock in over-ground running: A proof-of-concept study__. Scientific reports

As is already clear from the title of the second paper: originally the idea was to use music-based biofeedback as a way to reduce impact. Unfortunately, this feature is not - yet? - present in the commercial project which focuses on the measurement and clearly reporting a proxy to mechanical load. This does make the message focused and is probably a good commercial move. I bought one of the sensors and already tested it out during a 5k-run. I was pleasantly surprised with the smooth on-boarding and the slick, well thought-out, user-friendly app.


Become part of the *'The Future of injury-free running'* and go and [get OnTracx](https://www.ontracx.com/)!



![The product launch](https://0110.be/files/photos/544/PXL_20240423_173145479.MP.webp)

![The OnTracx CEO during the Flanders Technology and Innovation festival](https://0110.be/files/photos/544/PXL_20240320_191538986.MP.webp)

---

## [Making a flute controlled mouse](https://0110.be/posts/Making_a_flute_controlled_mouse.md)

- Published: 2024-04-22T00:00:00Z
- Updated: 2024-05-15T10:22:18Z
- Author: Joren
- ID: 539
- Canonical: https://0110.be/posts/Making_a_flute_controlled_mouse

- Tags: [Code](https://0110.be/tags/Code.md), [UGent](https://0110.be/tags/UGent.md)



There is something about surprising interfaces: clapping to switch on lights is more fun than a flipping a switch. [Pressing a panic-button to order a pizza](https://0110.be/posts/Order_Pizza_with_USB_Pizza_Button) is more fun than ordering via an app. Recently I came across [this surprising interface](https://www.reddit.com/r/JustGuysBeingDudes/comments/195gqnm/imagine_losing_to_this_dude/): a flute controlled mouse cursor for a first person shooter. I recognize a good idea when I see one, and immediately wanted replicate the idea and make it freely available. So I got to work:

<center>
<video controls  poster="https://0110.be/files/attachments/539/pipe-popo-pitch_controlled_mouse.jpg" preload="none" src="https://0110.be/files/attachments/539/pipe-popo-pitch_controlled_mouse.webm" style="width:80%">
</video><br>
<small>Vid: a microcontroller controlling mouse movements based on pitch detection.</small>
</center>

What do we need for **flute-based mouse**? First we need a way to determine if a note is being played and if a note is produced, we need to be able to determine which note is being played by the musician. Next, we need to hijack and control a cursor via the detected note and trigger a click event when a specific note is played. Finally we need to play a flute, preferably a recorder, to move the mouse cursor in an obviously superior and relaxed fashion. It is not strictly required to use a recorder but a recorder is [very much advised](https://www.youtube.com/watch?v=QuFTCirwmoM&list=PLhJfRNMFx-hX7NUhYaintwbFGMs6lmvmQ).



The note determination can be done by a fundamental frequency detector. A detector returns a frequency in Hertz and a confidence score which tells you how reliable the detection is. With some filtering, this is exactly what we need. If the frequency is close enough to a configured value, a note is detected. The confidence score tells us to either accept or ignore the detection. With this info it is possible to connect a note-detection to an action - like moving a cursor left or right, up or down.

Finally we need to move the mouse cursor. There are a few ways to do this.

## 🪈 An micro-controller-based solution -  <small>Pitch perfect pointer positioning - PiPePoPo</small>

<div style="float:right;width:35%;max-width:300px;margin-left:10px;margin-bottom:10px;box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4);">
<center>
<img loading="lazy" style="width:100%" src="https://0110.be/files/attachments/539/pipepopo-in-action.jpg"><br>
<small>Fig: Flute-based web-browsing as envisioned by its developer.</small>
</center>
</div>

A portable way to move a mouse cursor is to let a micro-controller impersonate as a standard mouse, a __'USB Human Interface Device'__. Once the micro-controller is attached via USB it registers as a mouse and allows to move the cursor and register click events.  To build a flute-based mouse, the micro-controller then needs a microphone and a pitch estimator to finally send cursor events. 

I based my project on an RP2040 - a [*micro-controller chip designed by Raspberry Pi*](https://www.raspberrypi.com/products/rp2040/specifications/) - since it offers a simple way to present itself to an operating system as a mouse. Just include `PluggableUSBHID.h` and `USBMouse.h` and use the Mouse API to control the mouse. For me it only behaved as a standard mouse if `Serial` is not used at the same time: in other words the dual USB profile does not seem to work reliably.
Sending mouse events from your code looks, for example, like ` Mouse.move(-4, 7)` to move the mouse minus four units in the horizontal and seven units in the vertical direction. Click events have a similarly straightforward API. The RP2040 also has a built-in microphone, which makes it ideal for audio applications, or so it seems.

Unfortunately, the RP2040 chip performs poorly for computationally heavy audio processing workloads. Such applications need to perform many floating point operations per second, but the RP2040 lacks a hardware floating point unit (FPU) which makes it [relatively slow](https://github.com/tana/pico_float_bench). When attempting to run a pitch-detection algorithm, the RP2040 was too slow to run the algorithm in real-time. After profiling the pitch estimation algorithm there was a clear place where most float operations occurred. Replacing those with much quicker fixed point operations makes the algorithm faster than real-time and usable on the RP2040.lt

To give a sens of the difference in speed between fixed point and floating point operations on the RP2040: with the default arduino build process, a million floating point operations take over 883 000 microseconds, a million fixed point operations take 8 microseconds. Fixed point operations are around 5 orders of magnitude faster!  

I have named released the code under the name [Pitch perfect pointer positioning or PiPePoPo](https://github.com/JorenSix/PiPePoPo) for short. For the details, please do check the source code repository. Perhaps the most intresting, reusable component is the [ANSI C implementation of the YIN pitch estimator](https://github.com/JorenSix/PiPePoPo/blob/main/RP2040/PiPePoPo/yin_fixed.c), both in floating point and sped up with fixed point operations.

## 🪈 A browser extension -  <small>Pitch perfect pointer positioning - PiPePoPo</small>

The hardware based solution works reliably but, evidently, it needs a piece of hardware. To make sure everybody can enjoy a solution in software is provided in this section in the form of a chrome browser extension.

Moving a cursor is not possible in a browser: if a pointer location could be modified it would open a whole range of possibilities for abuse. A surprisingly easy workaround, however, is to hide the actual cursor and show a replacement cursor-like icon. This fake cursor can be moved programmatically. With the position of this fake cursor known, a click event can be triggered and result in, for example, following a link.

To take this idea to its logical next step, I implemented a chrome browser plug-in for flute-based web-browsing. I also relased this on GitHub under the Pitch perfect Pointer Positioning or PiPePoPo brand.  Check the installation instructions in the [PiPePoPo  repository](https://github.com/JorenSix/PiPePoPo).  Perhaps most of interest is how audio processing is handled by a Web Audio API Audio Worklet.

<center>
<video controls  poster="https://0110.be/files/attachments/539/PiPe_PoPo-chrome-extension.jpg" preload="none" src="https://0110.be/files/attachments/539/PiPe_PoPo-chrome-extension.mp4" style="width:80%">
</video><br>
<small>Vid:  Controlling a cursor via a browser extension.</small>
</center>


Join the flute-based web-browsing revolution today and experience web browsing like never before and install PiPePoPo.


<small>
I am not sure how but PiPePoPo was also featured on [HackADay](https://hackaday.com/2024/04/24/flute-now-included-on-list-of-human-interface-devices) and the official [Arduino Blog](https://blog.arduino.cc/2024/04/28/this-arduino-nano-rp2040-connect-powered-flute-blows-your-pcs-mouse-away/).
</small>


---

## [Measuring rain water tank level with an Arduino](https://0110.be/posts/Measuring_rain_water_tank_level_with_an_Arduino.md)

- Published: 2024-03-28T00:00:00Z
- Updated: 2024-03-29T10:47:07Z
- Author: Joren
- ID: 543
- Canonical: https://0110.be/posts/Measuring_rain_water_tank_level_with_an_Arduino

- Tags: [UGent](https://0110.be/tags/UGent.md)

<div style="float:right;width:35%;max-width:300px;margin-left:10px;margin-bottom:10px;">
<center>
<img loading="lazy" style="width:100%"  src="https://0110.be/files/attachments/543/Diagram_zonder_titel.drawio.svg" ><br>
</center>
<small>Fig: Submersible water level meter.</small>
</div>

Due to the climate crisis, long droughts are becoming more common in Western Europe during the summers. Conversely, during the winters, it seems that there is more and more extreme rainfall. With [record breaking](https://www.meteo.be/nl/klimaat/klimaat-van-belgie/klimatologisch-overzicht/2024/februari) droughts and rainfall, it is essential that enough rainwater buffering capacity is available. As a private citizen this means installing a large rain water tank - perhaps larger than outdated models suggest - and using the captured water effectively, if at all possible.

<div style="float:left;width:15%;max-width:300px;margin-right:10px;margin-bottom:10px;">
<center>
<img loading="lazy" style="width:100%"  src="https://0110.be/files/attachments/543/submersible-water-level-sensor.jpg" ><br>
</center>
<small>Fig: Submersible water level meter.</small>
</div>

To effectively use rain-water, it helps to have an easy way to view the water level in the tank. This makes clear when to conserve water or when a pump might end up running dry and overheat. So I wanted to install **a water level measurement device in my rain water tank**.

My first attempt used sonar. This sends out a sound-wave which reflects on the water surface. The sonar measures the time it takes for the echo to return. This setup is finicky in an echoy tank but I managed to get it working. However, after couple of months the sonar stopped working in the damp environment.

Next I tried a optical, infra-red-based setup: the idea was to time the infra-red reflection on the water. After installation it appeared that water is transparent for infra-red. I basically measured the position of the floor of my tank. I took comfort in the fact that this measurement remained stable but had to look for a third option.

I finally became aware of sturdy, submersible pressure sensors which are designed for water level measurements. One of those is the QDY30A sensor, which is available in many versions but there is one which takes five volts as input and returns a 0 - 3.3V output: ideal to use with about any modern micro-controller. The sensor can be connected via a long cable. This helps to keep the Arduino in a dry place indoors. After recording a few measurements and the corresponding sensor depth, the sensor seems to show a very linear response: with two references, converting a sensor value to a depth in millimeters is doable. The readings ended up on a website and are [visualized via a SVG sparkline](https://alexplescan.com/posts/2023/07/08/easy-svg-sparklines/).

<center>
<svg height="70px" style="width:80%" viewBox="0 183.46000000000004 432 475.0" preserveAspectRatio="none">
  <path d="M 
        0 185.2199999999998 L
        1 186.0999999999999 L
        2 186.0999999999999 L
        3 183.46000000000004 L
        4 185.2199999999998 L
        5 185.2199999999998 L
        6 186.98999999999978 L
        7 186.98999999999978 L
        8 186.98999999999978 L
        9 190.51000000000022 L
        10 190.51000000000022 L
        11 200.19999999999982 L
        12 200.19999999999982 L
        13 220.46000000000004 L
        14 226.6199999999999 L
        15 225.73999999999978 L
        16 227.5 L
        17 228.3800000000001 L
        18 228.3800000000001 L
        19 232.78999999999996 L
        20 231.90999999999985 L
        21 236.30999999999995 L
        22 236.30999999999995 L
        23 237.19000000000005 L
        24 239.82999999999993 L
        25 240.71000000000004 L
        26 245.1199999999999 L
        27 246.0 L
        28 250.4000000000001 L
        29 254.80999999999995 L
        30 268.02 L
        31 301.4899999999998 L
        32 307.6500000000001 L
        33 310.3000000000002 L
        34 308.5300000000002 L
        35 308.5300000000002 L
        36 305.0100000000002 L
        37 305.0100000000002 L
        38 305.8899999999999 L
        39 304.1300000000001 L
        40 309.4200000000001 L
        41 308.5300000000002 L
        42 312.94000000000005 L
        43 312.94000000000005 L
        44 312.05999999999995 L
        45 312.05999999999995 L
        46 311.17999999999984 L
        47 312.94000000000005 L
        48 324.3899999999999 L
        49 312.94000000000005 L
        50 312.94000000000005 L
        51 314.6999999999998 L
        52 380.76 L
        53 389.56999999999994 L
        54 389.56999999999994 L
        55 389.56999999999994 L
        56 394.8499999999999 L
        57 397.49 L
        58 397.49 L
        59 397.49 L
        60 413.3499999999999 L
        61 423.03999999999996 L
        62 421.28 L
        63 422.1600000000001 L
        64 423.9200000000001 L
        65 424.79999999999995 L
        66 423.9200000000001 L
        67 421.28 L
        68 423.9200000000001 L
        69 425.68000000000006 L
        70 428.31999999999994 L
        71 425.68000000000006 L
        72 430.0799999999999 L
        73 430.0799999999999 L
        74 430.0799999999999 L
        75 425.68000000000006 L
        76 430.0799999999999 L
        77 434.49 L
        78 437.1300000000001 L
        79 438.01 L
        80 438.8900000000001 L
        81 437.1300000000001 L
        82 443.29999999999995 L
        83 439.77 L
        84 439.77 L
        85 439.77 L
        86 439.77 L
        87 437.1300000000001 L
        88 438.01 L
        89 435.3699999999999 L
        90 440.6500000000001 L
        91 439.77 L
        92 439.77 L
        93 442.4100000000001 L
        94 445.05999999999995 L
        95 442.4100000000001 L
        96 442.4100000000001 L
        97 442.4100000000001 L
        98 445.94000000000005 L
        99 444.18000000000006 L
        100 441.53 L
        101 441.53 L
        102 442.4100000000001 L
        103 444.18000000000006 L
        104 445.05999999999995 L
        105 445.94000000000005 L
        106 447.70000000000005 L
        107 449.46000000000004 L
        108 449.46000000000004 L
        109 451.22 L
        110 451.22 L
        111 449.46000000000004 L
        112 451.22 L
        113 447.70000000000005 L
        114 447.70000000000005 L
        115 447.70000000000005 L
        116 445.05999999999995 L
        117 444.18000000000006 L
        118 442.4100000000001 L
        119 442.4100000000001 L
        120 441.53 L
        121 440.6500000000001 L
        122 438.8900000000001 L
        123 439.77 L
        124 438.8900000000001 L
        125 437.1300000000001 L
        126 437.1300000000001 L
        127 439.77 L
        128 439.77 L
        129 445.05999999999995 L
        130 445.05999999999995 L
        131 444.18000000000006 L
        132 441.53 L
        133 442.4100000000001 L
        134 443.29999999999995 L
        135 444.18000000000006 L
        136 445.94000000000005 L
        137 441.53 L
        138 444.18000000000006 L
        139 447.70000000000005 L
        140 449.46000000000004 L
        141 459.1500000000001 L
        142 452.0999999999999 L
        143 452.0999999999999 L
        144 452.0999999999999 L
        145 468.8399999999999 L
        146 452.0999999999999 L
        147 449.46000000000004 L
        148 452.0999999999999 L
        149 452.98 L
        150 453.8599999999999 L
        151 452.98 L
        152 453.8599999999999 L
        153 452.0999999999999 L
        154 452.98 L
        155 452.98 L
        156 452.98 L
        157 456.51 L
        158 453.8599999999999 L
        159 454.75 L
        160 453.8599999999999 L
        161 455.6300000000001 L
        162 453.8599999999999 L
        163 453.8599999999999 L
        164 456.51 L
        165 456.51 L
        166 454.75 L
        167 453.8599999999999 L
        168 453.8599999999999 L
        169 453.8599999999999 L
        170 453.8599999999999 L
        171 451.22 L
        172 449.46000000000004 L
        173 451.22 L
        174 452.0999999999999 L
        175 452.98 L
        176 452.98 L
        177 451.22 L
        178 447.70000000000005 L
        179 467.96000000000004 L
        180 448.5799999999999 L
        181 447.70000000000005 L
        182 449.46000000000004 L
        183 447.70000000000005 L
        184 444.18000000000006 L
        185 448.5799999999999 L
        186 444.18000000000006 L
        187 444.18000000000006 L
        188 445.94000000000005 L
        189 452.0999999999999 L
        190 452.98 L
        191 452.0999999999999 L
        192 452.98 L
        193 452.98 L
        194 452.0999999999999 L
        195 452.0999999999999 L
        196 451.22 L
        197 453.8599999999999 L
        198 453.8599999999999 L
        199 453.8599999999999 L
        200 452.98 L
        201 449.46000000000004 L
        202 451.22 L
        203 445.05999999999995 L
        204 439.77 L
        205 430.0799999999999 L
        206 425.68000000000006 L
        207 423.9200000000001 L
        208 421.28 L
        209 411.5899999999999 L
        210 412.47 L
        211 411.5899999999999 L
        212 409.8299999999999 L
        213 414.23 L
        214 408.94000000000005 L
        215 407.18000000000006 L
        216 402.78 L
        217 403.6600000000001 L
        218 405.4200000000001 L
        219 402.78 L
        220 402.78 L
        221 400.1400000000001 L
        222 399.26 L
        223 405.4200000000001 L
        224 403.6600000000001 L
        225 402.78 L
        226 403.6600000000001 L
        227 408.94000000000005 L
        228 406.29999999999995 L
        229 409.8299999999999 L
        230 407.18000000000006 L
        231 416.8699999999999 L
        232 420.3900000000001 L
        233 419.51 L
        234 416.8699999999999 L
        235 417.75 L
        236 415.99 L
        237 434.49 L
        238 416.8699999999999 L
        239 425.68000000000006 L
        240 425.68000000000006 L
        241 440.6500000000001 L
        242 425.68000000000006 L
        243 438.01 L
        244 427.44000000000005 L
        245 429.20000000000005 L
        246 428.31999999999994 L
        247 434.49 L
        248 433.6099999999999 L
        249 439.77 L
        250 439.77 L
        251 439.77 L
        252 444.18000000000006 L
        253 439.77 L
        254 438.01 L
        255 439.77 L
        256 439.77 L
        257 438.8900000000001 L
        258 439.77 L
        259 431.8499999999999 L
        260 435.3699999999999 L
        261 433.6099999999999 L
        262 427.44000000000005 L
        263 430.0799999999999 L
        264 433.6099999999999 L
        265 433.6099999999999 L
        266 433.6099999999999 L
        267 435.3699999999999 L
        268 433.6099999999999 L
        269 433.6099999999999 L
        270 431.8499999999999 L
        271 437.1300000000001 L
        272 436.25 L
        273 435.3699999999999 L
        274 428.31999999999994 L
        275 430.0799999999999 L
        276 428.31999999999994 L
        277 438.01 L
        278 438.01 L
        279 437.1300000000001 L
        280 438.8900000000001 L
        281 438.8900000000001 L
        282 439.77 L
        283 438.8900000000001 L
        284 439.77 L
        285 442.4100000000001 L
        286 445.05999999999995 L
        287 448.5799999999999 L
        288 444.18000000000006 L
        289 443.29999999999995 L
        290 447.70000000000005 L
        291 450.3399999999999 L
        292 451.22 L
        293 452.0999999999999 L
        294 453.8599999999999 L
        295 452.98 L
        296 452.0999999999999 L
        297 451.22 L
        298 450.3399999999999 L
        299 453.8599999999999 L
        300 453.8599999999999 L
        301 456.51 L
        302 456.51 L
        303 456.51 L
        304 456.51 L
        305 458.27 L
        306 457.3900000000001 L
        307 460.9100000000001 L
        308 455.6300000000001 L
        309 460.03 L
        310 455.6300000000001 L
        311 456.51 L
        312 453.8599999999999 L
        313 453.8599999999999 L
        314 447.70000000000005 L
        315 449.46000000000004 L
        316 449.46000000000004 L
        317 448.5799999999999 L
        318 440.6500000000001 L
        319 445.05999999999995 L
        320 439.77 L
        321 434.49 L
        322 429.20000000000005 L
        323 424.79999999999995 L
        324 422.1600000000001 L
        325 416.8699999999999 L
        326 414.23 L
        327 410.71000000000004 L
        328 408.05999999999995 L
        329 400.1400000000001 L
        330 399.26 L
        331 401.9000000000001 L
        332 399.26 L
        333 424.79999999999995 L
        334 395.73 L
        335 391.3299999999999 L
        336 391.3299999999999 L
        337 388.69000000000005 L
        338 386.9200000000001 L
        339 385.1600000000001 L
        340 384.28 L
        341 405.4200000000001 L
        342 384.28 L
        343 382.52 L
        344 383.4000000000001 L
        345 382.52 L
        346 384.28 L
        347 383.4000000000001 L
        348 383.4000000000001 L
        349 381.6400000000001 L
        350 382.52 L
        351 383.4000000000001 L
        352 382.52 L
        353 383.4000000000001 L
        354 383.4000000000001 L
        355 382.52 L
        356 380.76 L
        357 383.4000000000001 L
        358 383.4000000000001 L
        359 380.76 L
        360 381.6400000000001 L
        361 381.6400000000001 L
        362 379.8800000000001 L
        363 384.28 L
        364 383.4000000000001 L
        365 383.4000000000001 L
        366 396.6099999999999 L
        367 439.77 L
        368 439.77 L
        369 462.6700000000001 L
        370 458.27 L
        371 466.20000000000005 L
        372 467.0799999999999 L
        373 472.3599999999999 L
        374 475.0 L
        375 473.24 L
        376 469.72 L
        377 465.31999999999994 L
        378 463.54999999999995 L
        379 466.20000000000005 L
        380 463.54999999999995 L
        381 461.78999999999996 L
        382 465.31999999999994 L
        383 463.54999999999995 L
        384 461.78999999999996 L
        385 458.27 L
        386 461.78999999999996 L
        387 461.78999999999996 L
        388 458.27 L
        389 461.78999999999996 L
        390 455.6300000000001 L
        391 456.51 L
        392 454.75 L
        393 453.8599999999999 L
        394 453.8599999999999 L
        395 453.8599999999999 L
        396 455.6300000000001 L
        397 453.8599999999999 L
        398 453.8599999999999 L
        399 453.8599999999999 L
        400 452.98 L
        401 452.0999999999999 L
        402 449.46000000000004 L
        403 448.5799999999999 L
        404 445.94000000000005 L
        405 449.46000000000004 L
        406 445.94000000000005 L
        407 444.18000000000006 L
        408 445.94000000000005 L
        409 445.05999999999995 L
        410 444.18000000000006 L
        411 442.4100000000001 L
        412 444.18000000000006 L
        413 440.6500000000001 L
        414 441.53 L
        415 449.46000000000004 L
        416 445.94000000000005 L
        417 445.05999999999995 L
        418 441.53 L
        419 443.29999999999995 L
        420 443.29999999999995 L
        421 442.4100000000001 L
        422 449.46000000000004 L
        423 445.94000000000005 L
        424 442.4100000000001 L
        425 442.4100000000001 L
        426 442.4100000000001 L
        427 456.51 L
        428 451.22 L
        429 463.54999999999995 L
        430 460.03 L
        431 450.3399999999999 L
        432 447.70000000000005 L
    " stroke-width="2" stroke="blue" fill="transparent" vector-effect="non-scaling-stroke"></path>
</svg><br/>
<small>Fig: Rain water level visualization for a few days, measured every 15 minutes. Going from 221cm to 197cm.</small>
</center>

With the Arduino code attached you should be able to get going. Next to reading and converting a sensor value, it also includes reading the median of several values to add some smoothing. The sensor is read on request: only when a serial message arrives a measurement is done.


---

## [Offloading authentication and user management to Keycloak - A minimal example with Nuxt and Litestar](https://0110.be/posts/Offloading_authentication_and_user_management_to_Keycloak_-_A_minimal_example_with_Nuxt_and_Litestar.md)

- Published: 2024-03-13T00:00:00Z
- Updated: 2024-03-19T09:13:26Z
- Author: Joren
- ID: 542
- Canonical: https://0110.be/posts/Offloading_authentication_and_user_management_to_Keycloak_-_A_minimal_example_with_Nuxt_and_Litestar

- Tags: [GhentCDH](https://0110.be/tags/GhentCDH.md), [UGent](https://0110.be/tags/UGent.md)

<div style="width:30%;float:right;margin-bottom:10px;margin-left:10px">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 193.036 39.861"><path fill="#4d4d4d" d="M5.795 9.869 11.511.003 34.53 0l5.685 9.964.007 19.932-5.691 9.958-23.012.008-5.782-9.965z"/><path fill="#ededed" d="M5.738 29.894h10.374l-5.698-10.041 4.634-9.982-9.252-.002L0 19.934"/><path fill="#e0e0e0" d="M19.258 29.894h7.583l6.705-9.773-6.588-10.156h-8.92l-5.373 9.814z"/><path fill="#acacac" d="m0 19.933 5.738 9.964h10.375l-5.636-9.93z"/><path fill="#9e9e9e" d="m12.774 19.935 6.485 9.962h7.582l6.6-9.958z"/><path fill="#00b8e3" d="m15.342 19.94-1.957.57-1.878-.571 7.667-13.29 1.918 3.318"/><path fill="#33c6e9" d="m21.084 29.894-1.908 3.332-5.093-5.487-2.58-7.797v-.004h3.838"/><path fill="#008aaa" d="M11.508 19.939h-.004v.003l-1.917 3.322-1.925-3.307 1.952-3.386 5.728-9.92h3.834"/><path fill="#d4d4d4" d="M32.604 29.894h11.612l-.007-19.93H32.604Z"/><path fill="#919191" d="M32.604 19.966v9.93h11.591v-9.93z"/><path fill="#00b8e3" d="M19.178 33.23h-3.837l-5.754-9.967 1.917-3.32z"/><path fill="#008aaa" d="M34.519 19.939 26.85 33.227c-.705-1.036-1.913-3.33-1.913-3.33l5.753-9.96z"/><path fill="#00b8e3" d="m30.68 33.227-3.83-.001 7.67-13.288 1.916-3.318 1.922 3.34m-3.839-.021h-3.828l-5.755-9.973 1.905-3.314 4.658 5.922z"/><path fill="#33c6e9" d="M36.436 16.618v.003l-1.917 3.318-7.677-13.286 3.841.002z"/><path fill="#626262" d="M175.972 9.98a.193.193 0 0 0-.195.196v19.503c0 .106.086.195.195.195h4.049c.106 0 .195-.09.195-.195V24.31c0-.046.016-.093.05-.13l.181-.204 1.6-1.782a.196.196 0 0 1 .302.016l1.528 2.06 2.735 3.68 1.002 1.347.367.497c.04.05.096.08.159.08h4.7a.2.2 0 0 0 .162-.312l-.973-1.356-1.389-1.954c-.413-.576-.856-1.198-1.333-1.866l-.215-.304-1.76-2.48a1565.7 1565.7 0 0 0-1.934-2.706.19.19 0 0 1 .016-.241l1.551-1.724 3.725-4.14 2.239-2.487a.218.218 0 0 0 .05-.123.195.195 0 0 0-.196-.201h-4.97a.195.195 0 0 0-.143.062l-1.558 1.694-3.324 3.614-2.235 2.428a.194.194 0 0 1-.338-.132v-7.471a.195.195 0 0 0-.195-.195Z"/><path fill="#6f6f6f" d="M161.562 9.98a.198.198 0 0 0-.178.12l-5.213 12.065-.003.003-.007.013-.003.01-.674 1.565-.04.096-.087.195h.001l-.073.166v.003l-2.027 4.693-.298.69a.196.196 0 0 0 .178.275h4.3a.196.196 0 0 0 .175-.112c0-.004.004-.004.004-.007l1.498-3.485.248-.576a.199.199 0 0 1 .182-.12h8.08a.19.19 0 0 1 .178.12l1.664 3.866 1.865-6.618-1.499-3.47-1.339-3.098-2.712-6.273a.191.191 0 0 0-.179-.12h-2.526zm7.905 19.58v.003l.086.192c.03.073.099.12.178.12h4.3a.197.197 0 0 0 .179-.276l-.04-.09-2.838-6.567zm-5.87-13.362c.074 0 .147.04.181.117l.943 2.2.952 2.225.285.664a.194.194 0 0 1-.179.272h-4.389a.194.194 0 0 1-.178-.272l.516-1.19 1.69-3.9a.195.195 0 0 1 .18-.116z"/><path fill="#7d7d7d" d="M143.452 9.524c-.826 0-1.617.076-2.368.235v.004-.004h-.003a9.78 9.78 0 0 0-3.556 1.488 10.59 10.59 0 0 0-1.497 1.224c-1.486 1.453-2.428 3.197-2.815 5.176l-.002.003-.014.076-.005.023-.042.225v-.001l-.003.012v.006l-.01.085v.009a11.29 11.29 0 0 0-.126 1.715c0 2.63.816 4.872 2.45 6.727.175.206.367.404.565.599.506.496 1.043.93 1.608 1.3 1.68 1.098 3.619 1.647 5.818 1.647.245 0 .486-.006.724-.023 2.13-.12 4.001-.784 5.615-1.991a10.44 10.44 0 0 0 1.089-.933l.01-.01a9.983 9.983 0 0 0 1.922-2.583c.148-.288.28-.582.396-.887.46-1.174.688-2.457.688-3.845 0-.374-.016-.742-.05-1.1v-.006a9.841 9.841 0 0 0-.783-3.1c-.5-1.15-1.227-2.191-2.183-3.124-.397-.39-.814-.74-1.25-1.052-1.76-1.263-3.817-1.895-6.178-1.895zm.017 3.873c1.66 0 3.059.625 4.197 1.875.062.067.122.136.175.205 1.022 1.214 1.531 2.66 1.531 4.336 0 1.17-.248 2.223-.744 3.162a6.456 6.456 0 0 1-1.138 1.545 5.658 5.658 0 0 1-1.382 1.048c-.79.423-1.67.635-2.64.635a5.57 5.57 0 0 1-2.56-.592 5.46 5.46 0 0 1-1.12-.77 5.668 5.668 0 0 1-.516-.503c-1.14-1.243-1.71-2.751-1.71-4.524 0-.053 0-.103.003-.156.03-1.707.599-3.168 1.707-4.386.83-.912 1.802-1.495 2.913-1.74a6.03 6.03 0 0 1 1.284-.135z"/><path fill="#888" d="M120.252 9.98a.193.193 0 0 0-.195.196v19.503c0 .106.086.195.195.195h12.528c.11 0 .196-.09.196-.195v-3.57a.195.195 0 0 0-.196-.194h-8.09a.192.192 0 0 1-.194-.191V10.176a.19.19 0 0 0-.113-.176.174.174 0 0 0-.083-.02h-2.242z"/><path fill="#949494" d="M109.792 9.525c-2.923 0-5.394.989-7.415 2.96-.367.36-.7.738-1.002 1.13v.004c-1.352 1.756-2.027 3.84-2.027 6.25 0 1.377.215 2.64.649 3.794l.006-.002-.005.003c.254.695.592 1.336.999 1.948v.003c.003 0 .003.003.003.003a9.828 9.828 0 0 0 1.323 1.58 10.01 10.01 0 0 0 1.984 1.512h.003a9.831 9.831 0 0 0 .953.484h.007l.003.003h.003c1.37.602 2.907.903 4.617.903.503 0 .996-.037 1.475-.106h.01c.308-.043.612-.102.913-.175 1.683-.404 3.225-1.257 4.63-2.557v-.002l.001.002c.281-.258.559-.54.83-.837a.199.199 0 0 0-.007-.27l-2.566-2.643a.19.19 0 0 0-.172-.057.212.212 0 0 0-.116.07c-.685.81-1.465 1.419-2.338 1.822-.867.4-1.826.6-2.874.6-.718 0-1.39-.113-2.011-.341a5.641 5.641 0 0 1-2.057-1.34 5.433 5.433 0 0 1-1.3-2.066h-.007l.007-.001c-.255-.715-.38-1.508-.38-2.385 0-1.723.512-3.118 1.544-4.183.076-.076.152-.153.235-.23 1.184-1.09 2.612-1.636 4.283-1.636.611 0 1.197.076 1.75.229v-.001c1.27.344 2.384 1.085 3.343 2.216.076.09.215.096.29.007l2.498-2.832a.201.201 0 0 0-.003-.264c-1.955-2.147-4.323-3.334-7.1-3.556-.322-.025-.646-.04-.977-.04z"/><path fill="#aaa" d="M82.588 9.98a.195.195 0 0 0-.17.29l1.892 3.309 3.423 5.983 1.38 2.41a.2.2 0 0 1 .026.097v7.61c0 .106.086.195.195.195h4.049c.109 0 .194-.09.194-.195v-7.61c0-.033.01-.066.027-.096l.976-1.706 3.704-6.476.757-1.32.503-.88h-.007l.007-.003.754-1.316a.194.194 0 0 0-.168-.29h-4.363a.196.196 0 0 0-.168.095l-3.953 6.803-.003.003v.003l-.212.156-.159.122a.203.203 0 0 1-.082-.076l-.067-.112-.099-.17-3.905-6.73a.196.196 0 0 0-.169-.095zm15.537 2.04v.002l-.004.002z"/><path fill="#bcbcbc" d="M66.598 9.98a.193.193 0 0 0-.196.196v19.503c0 .106.086.195.195.195h14.264c.11 0 .196-.09.196-.195v-3.54a.196.196 0 0 0-.196-.194h-9.825a.195.195 0 0 1-.196-.195v-3.737c0-.106.09-.196.196-.196h8.519a.195.195 0 0 0 .195-.194v-3.394c0-.11-.09-.195-.195-.195h-8.52a.196.196 0 0 1-.195-.195V14.13c0-.11.09-.195.196-.195h9.515a.195.195 0 0 0 .195-.195v-3.565a.195.195 0 0 0-.195-.195h-7.829z"/><path fill="#b8b8b8" d="M47.256 9.98a.193.193 0 0 0-.195.196v19.503c0 .106.086.195.195.195h4.048c.11 0 .196-.09.196-.195V24.31c0-.046.016-.093.05-.13l.842-.942.94-1.044a.196.196 0 0 1 .3.016l2.808 3.777.002.002 2.016 2.713.81 1.09a.19.19 0 0 0 .156.08h4.703a.197.197 0 0 0 .158-.31l-.171-.241a693.927 693.927 0 0 1-1.846-2.587c-.566-.79-1.194-1.673-1.888-2.652l-.07-.1v.002-.002a1569.226 1569.226 0 0 0-3.628-5.086.195.195 0 0 1 .017-.241l1.772-1.968h-.006l.006-.001v-.003l1.459-1.62 1.402-1.558 2.88-3.202a.194.194 0 0 0-.145-.324H59.1a.196.196 0 0 0-.146.063l-3.079 3.345-4.038 4.39a.194.194 0 0 1-.338-.132V10.17a.193.193 0 0 0-.195-.192h-2.11zm.091 9.823v.01h-.001zm12.27 8.34v.002h-.001z"/><path fill="#a0a0a0" d="M89.222 29.806c-.024-.025-.045-1.79-.045-3.92V22.01l-3.38-5.904c-1.996-3.487-3.364-5.945-3.342-6.003.032-.084.357-.098 2.278-.098 2.052 0 2.25.01 2.342.115.055.064.986 1.649 2.069 3.523 1.082 1.873 2.003 3.443 2.045 3.488.194.21.46-.181 2.48-3.662l2.01-3.465h2.275c1.894 0 2.28.016 2.31.094.02.05-1.477 2.737-3.326 5.969l-3.362 5.876-.066 7.872-2.121.018c-1.167.01-2.142-.003-2.167-.028z"/><path fill="#acacac" d="M66.456 29.767c-.017-.045-.023-4.503-.014-9.905l.017-9.823h14.22v3.837l-4.855.017c-3.453.012-4.88.038-4.944.09-.13.109-.13 3.91 0 4.018.063.052 1.362.079 4.448.09l4.36.018v3.638l-4.36.017c-3.086.012-4.385.039-4.448.09-.07.06-.089.465-.089 1.98 0 1.047.018 1.95.04 2.009.036.094.538.104 4.999.104 3.29 0 5 .023 5.086.069.122.065.128.156.128 1.872 0 1.314-.022 1.825-.08 1.883-.118.118-14.463.115-14.508-.003z"/></svg>
</div>

At the [Ghent Center for Digital Humanities](https://www.ghentcdh.ugent.be/) we provide software services for researchers. Think about, for example, annotation platforms for ancient texts or collaborative databases with geographically referenced historical data. Each of those services need some kind of authentication and user management: some parts of the service might be public, some only accessible for researchers at Ghent University and other parts need to be accessible e.g. to external researchers or collaborators. Providing authentication, login-flows and user management for each and every service quickly becomes tedious and, frankly, boring.

We went looking for a solution and stumbled on [Keycloak](https://www.keycloak.org/). Keycloak is an open source __Identity and Access Management__ system and is able to either authenticate users itself or pass through authenticate to other authentication providers like LDAP, GitHub, OAuth accounts, or others. It avoids the need to setup application-specific user management system. Flows like 'forget password', 'verify email', 'two factor authentication' are not part of your application itself but are provided by Keycloak. Leaving the developer to focus on application specific tasks.

<center>
<video src="https://0110.be/files/attachments/542/keycloak_screen-recording.mp4" style="width:70%" controls autoplay></video><br>
<small>Video: An API call to a protected back-end first fails. After authentication and receiving a JWT token, the back-end call succeeds. The authenticated session is then shown in Keycloak.</small>
</center>


Integration with Keycloak is a bit of work and not straightforward. As part of trying out Keycloak I have created a [minimal working example of a front-end / back-end system which uses Keycloak for authentication](https://github.com/GhentCDH/nuxt-keycloak-jwt-auth/). To get full access to the back-end API the user first needs a valid JWT-token provided by Keycloak. The flow can be seen in the video above. The dockerized environment can perhaps serve as inspiration for similar setups. Please do try out the [dockerized minimal working example](https://github.com/GhentCDH/nuxt-keycloak-jwt-auth/) and see if Keycloak can fit your use-case.





---

## [3D modeling with ChatGPT - Solidified ephemerality](https://0110.be/posts/3D_modeling_with_ChatGPT_-_Solidified_ephemerality.md)

- Published: 2024-02-21T00:00:00Z
- Updated: 2025-12-10T13:04:12Z
- Author: Joren
- ID: 541
- Canonical: https://0110.be/posts/3D_modeling_with_ChatGPT_-_Solidified_ephemerality

- Tags: [Code](https://0110.be/tags/Code.md), [UGent](https://0110.be/tags/UGent.md)


I have asked ChatGPT to generate 3D models. ChatGPT can not generate 3D models directly but 3D models can generated via intermediary [OpenSCAD](https://openscad.org/news.html) scripts: OpenSCAD provides a scripting language to describe objects which can be combined to form 3D models. ChatGPT understands the syntax of this scripting language and generates perfectly cromulent scripts. I have asked two versions of ChatGPT to generate a 3D model of a house, a cat, a stick figure, a chair and a tree.  The results are interesting...

<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5em">

<iframe src="https://0110.be/attachment/cors/2024.01.chatgpt-3d/stl-viewer/chatgpt35-chair/index.html" style="width:100%;border: 1px dotted gray;height:
10rem"></iframe>
<iframe src="https://0110.be/attachment/cors/2024.01.chatgpt-3d/stl-viewer/chatgpt40-chair/index.html" style="width:100%;border: 1px solid gray;height:10rem"></iframe>
<small style="text-align:center">ChatGPT 3.5 chair</small>
<small style="text-align:center">ChatGPT 4.0 chair</small>


<iframe src="https://0110.be/attachment/cors/2024.01.chatgpt-3d/stl-viewer/chatgpt35-cat/index.html" style="width:100%;border: 1px solid gray;height:10rem"></iframe>
<iframe src="https://0110.be/attachment/cors/2024.01.chatgpt-3d/stl-viewer/chatgpt40-cat/index.html" style="width:100%;border: 1px solid gray;height:10rem"></iframe>
<small style="text-align:center">ChatGPT 3.5 cat</small>
<small style="text-align:center">ChatGPT 4.0 cat</small>

</div>

The models immediately make the difference between ChatGPT 3.5 Turbo and ChatGPT 4.0 clear: 4.0 generates much better models with, at least, recognizable elements: a chair has four legs, a cat has a head and a tail. It is impressive that reasonable 3D models are generated but there is still room for improvement: proportions are not respected and elements are not always connected. Anyway, if __the 3D-models can be seen as a way to visualize code quality__, then 4.0 is a clear improvement and it makes me curious about future ChatGPT versions. It also made me reflect on a couple of aspects of LLMs in general.

<div style="float:right;width:30%;margin-left:10px">
<img style="width:100%"  src="https://0110.be/files/attachments/541/black_box_3D_modeling.png" >
<small>Fig: a black box generating 3D models.</small>
</div>


* **ephemerality**  The response of a LLM to a prompt is ephemeral: the same prompt causes a different response depending on context, randomness and the position of heavenly bodies - or so it seems. Traditional software systems follow a strict set of clear rules and provide deterministic, predictable and reliable results. The inverse is true for LLMs which takes some getting used to. As a user, an LLM is effectively a [vantablack](https://en.wikipedia.org/wiki/Vantablack) box - there is no way to know why a certain response was given instead of another. 
* **Updates** LLMs services - and SaaS in general - have an additional feature which makes them even more unpredictable: updates to systems can happen without notice. After a recent unannounced update, for example, [ChatGPT 4 started to produce gibberish](https://status.openai.com/incidents/ssg8fh7sfyz3). This adds another layer to the already uncontrollable and ephemeral nature of responses to LLM prompts.

To counter the ephemeral quality of prompt responses, I have 3D printed the generated [3D models](https://0110.be/files/attachments/541/ChatGPT3D.zip). Some pictures can be found below. I find that these physical, tangible, immutable objects provide a comforting counterbalance to the digital, ephemeral nature of LLM responses. Additionally, it highlights the absurdity of the generated models. 

There are other ways to solidify ephemerality: [crochet patterns](https://www.theguardian.com/technology/2023/feb/26/chatgpt-generated-crochet-pattern-results),  juggling patterns, guitar tablature, music notation all have some kind of structured text representation which LLMs can generate and which can have a physical representation. I would encourage people to bring prompt responses to the physical world: it really makes the - current - limitations of LLMs very clear. 


![ChatGPT 4.0 generating a 3D model of a cat](https://0110.be/files/photos/541/chatgpt40-cat-3d-model.png)

![Houses](https://0110.be/files/photos/541/PXL_20240131_192907899.MP.jpg)

![Cats](https://0110.be/files/photos/541/PXL_20240131_193622473.MP.jpg)

![Chairs](https://0110.be/files/photos/541/PXL_20240131_193825340.MP.jpg)

![Stick figures](https://0110.be/files/photos/541/PXL_20240131_194010741.MP.jpg)

![two trees](https://0110.be/files/photos/541/PXL_20240131_194108835.MP.jpg)

---

## [Containerized development - Dockerized Python database development](https://0110.be/posts/Containerized_development_-_Dockerized_Python_database_development.md)

- Published: 2024-01-22T00:00:00Z
- Updated: 2024-01-22T10:31:50Z
- Author: Joren
- ID: 540
- Canonical: https://0110.be/posts/Containerized_development_-_Dockerized_Python_database_development

- Tags: [GhentCDH](https://0110.be/tags/GhentCDH.md), [UGent](https://0110.be/tags/UGent.md)

<div style="float:right;width:35%;max-width:300px;margin-left:10px;margin-bottom:10px;box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4);">
<center>
<img loading="lazy" style="width:100%" src="https://0110.be/files/attachments/540/containerized_development.webp"><br>
<small>Fig: Containerized development.</small>
</center>
</div>

At the [Ghent Center for Digital Humanities](https://www.ghentcdh.ugent.be/) (GhentCDH) we offer IT-services mainly for researchers in the Humanities at Ghent University. The services range from internal collaborative research tools to publicly facing science communication platforms. Technically, it is a mix of off the shelve software with or without modifications and custom solutions using several technical stacks. It is a challenge to keep these services running, secure and up-to-date for years with a limited budget.

In an attempt to make maintenance of these services more manageable we are in the process of [containerizing](https://en.wikipedia.org/wiki/Containerization_(computing)) our software. __Running software in containers has advantages__. One of the advantages is a guaranteed consistency across environments. Also, isolated software containers can be beneficial for security and stability. It also allows one to run different versions of a stack on the same server without running into compatibility problems. 

Next to running software in containers, __development in containers also has advantages__. It allows you to switch projects easily without needing to install dependencies - e.g a specific database system version - directly on a development machine. The main advantage I see is that containerization promotes developer hygiene. Stereotypically, developers do not have the best hygiene and can use any available help. Containerization forces developers to think about separation of code and configuration, code and data and it forces to be explicit about dependencies and environmental assumptions.

The main disadvantage is that some configuration is needed to get the containers running and that there is a small performance penalty. The following might help with that first part.


## Dockerized Python database development

To put the theory to the test my colleagues and I put together a [GitHub repository with a dockerized Python development setup](https://github.com/GhentCDH/Python-db-dev). It shows interaction between Python and a PostgreSQL database. The database system runs in a container and the development environment is also kept in a container. Both containers are started with `docker compose` and configured via a `.env` file.

The stack uses a recent Python version, [PDM](https://pdm-project.org/latest/) to resolve Python dependencies and SQLAlchemy to interact with the PostgreSQL database. The VS code editor allows developers to __run and debug software in a container__. The video below shows the startup procedure and setting a breakpoint in some Python code.

<center>
<video style="width:50%" poster="https://0110.be/files/attachments/540/screengrab.webp" controls preload="none">
<source src="https://0110.be/files/attachments/540/dev_in_container.mp4" type="video/mp4">
</video><br>
<small>Vid: Starting a database server and development container. Running and debugging Python code in a container.</small>
</center>
<br>

Note that this is just an example setup, your setup might look quite different. You might need a different stack, use a different container environment (e.g. podman) or IDE but the principle of container based development could stay the same. 

I have put off using containers for quite a while and I am quite a late convert, but now that I am doing more technical work in a small team I do see the advantages of an easy-to-set up, controlled, containerized development with explicitly defined dependencies. If you have no experience with containers yet, I would encourage you to at least __try container based development__ out and see where it could help you!


---

## [OnTracx launch - a Ghent University sports-tech spin-off](https://0110.be/posts/OnTracx_launch_-_a_Ghent_University_sports-tech_spin-off.md)

- Published: 2024-01-10T00:00:00Z
- Updated: 2024-01-10T10:57:56Z
- Author: Joren
- ID: 515
- Canonical: https://0110.be/posts/OnTracx_launch_-_a_Ghent_University_sports-tech_spin-off

- Tags: [UGent](https://0110.be/tags/UGent.md)

<div style="float:right;width:250px;margin-left:15px">
<center>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 485.49 71.32" style="width:100%" id="svg-656346908_4188"><defs><linearGradient id="svg-656346908_4188_a" x1="23129.87" y1="34.23" x2="23159.13" y2="63.49" gradientTransform="matrix(-1 0 0 1 23159 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2f2f2" stop-opacity="0"></stop><stop offset="1" stop-color="#002b7c"></stop></linearGradient><linearGradient id="svg-656346908_4188_b" x1="29.26" y1="42.86" x2="64" y2="42.86" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002b7c"></stop><stop offset="1" stop-color="#f2f2f2" stop-opacity="0"></stop></linearGradient><linearGradient id="svg-656346908_4188_c" x1="71.03" y1="66.94" x2="109.23" y2=".78" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002b7c"></stop><stop offset="1" stop-color="#f2f2f2" stop-opacity="0"></stop></linearGradient></defs><path d="M159.29 71.32c-6.24 0-11.68-1.37-16.35-4.1-4.66-2.73-8.31-6.52-10.96-11.36-2.64-4.84-3.96-10.44-3.96-16.79s1.32-11.95 3.96-16.79c2.64-4.84 6.29-8.62 10.96-11.36 4.66-2.73 10.11-4.1 16.35-4.1s11.61 1.37 16.3 4.1c4.69 2.73 8.34 6.52 10.96 11.36 2.61 4.84 3.92 10.44 3.92 16.79s-1.31 11.95-3.92 16.79-6.27 8.63-10.96 11.36c-4.69 2.73-10.13 4.1-16.3 4.1Zm0-10.24c5.94 0 10.67-1.96 14.21-5.88 3.53-3.92 5.3-9.29 5.3-16.12s-1.77-12.2-5.3-16.12c-3.53-3.92-8.27-5.88-14.21-5.88s-10.69 1.96-14.25 5.88c-3.56 3.92-5.34 9.29-5.34 16.12s1.78 12.2 5.34 16.12c3.56 3.92 8.31 5.88 14.25 5.88Zm40.98 9.17V26.07h10.07l.89 7.48c1.37-2.61 3.34-4.69 5.92-6.24 2.58-1.54 5.63-2.32 9.13-2.32 5.46 0 9.71 1.72 12.74 5.17 3.03 3.44 4.54 8.49 4.54 15.14v24.94h-11.4V46.37c0-3.8-.77-6.71-2.32-8.73-1.54-2.02-3.95-3.03-7.21-3.03s-5.83 1.13-7.88 3.38c-2.05 2.26-3.07 5.4-3.07 9.44v22.8h-11.4Zm68.94 0V17.07h-18.17V7.9h47.83v9.17h-18.26v53.18h-11.4Zm33.49 0V26.07h10.16l1.07 8.28c1.6-2.85 3.79-5.12 6.55-6.81 2.76-1.69 6.01-2.54 9.75-2.54v12.03h-3.21c-2.49 0-4.72.39-6.68 1.16-1.96.77-3.49 2.11-4.59 4.01-1.1 1.9-1.65 4.54-1.65 7.93v20.13h-11.4Zm50.51 1.07c-3.8 0-6.92-.61-9.35-1.83-2.44-1.22-4.25-2.83-5.43-4.85-1.19-2.02-1.78-4.25-1.78-6.68 0-4.1 1.6-7.42 4.81-9.98 3.21-2.55 8.02-3.83 14.43-3.83h11.22v-1.07c0-3.03-.86-5.26-2.58-6.68-1.72-1.42-3.86-2.14-6.41-2.14-2.32 0-4.34.55-6.06 1.65-1.72 1.1-2.79 2.72-3.21 4.85h-11.13c.3-3.21 1.38-6 3.25-8.37 1.87-2.37 4.29-4.2 7.26-5.48 2.97-1.28 6.29-1.92 9.98-1.92 6.29 0 11.25 1.57 14.88 4.72 3.62 3.15 5.43 7.6 5.43 13.36v27.17h-9.71l-1.07-7.13c-1.31 2.38-3.13 4.34-5.48 5.88-2.35 1.54-5.36 2.32-9.04 2.32Zm2.58-8.91c3.27 0 5.8-1.07 7.62-3.21 1.81-2.14 2.95-4.78 3.43-7.93h-9.71c-3.03 0-5.2.55-6.5 1.65-1.31 1.1-1.96 2.45-1.96 4.05 0 1.72.65 3.06 1.96 4.01 1.3.95 3.03 1.43 5.17 1.43Zm55.23 8.91c-4.51 0-8.49-.98-11.94-2.94-3.44-1.96-6.16-4.69-8.15-8.2-1.99-3.5-2.98-7.51-2.98-12.03s.99-8.52 2.98-12.03c1.99-3.5 4.71-6.24 8.15-8.19 3.44-1.96 7.42-2.94 11.94-2.94 5.64 0 10.39 1.47 14.25 4.41 3.86 2.94 6.32 6.99 7.39 12.16h-12.03c-.59-2.14-1.77-3.81-3.52-5.03-1.75-1.22-3.82-1.83-6.19-1.83-3.15 0-5.82 1.19-8.02 3.56-2.2 2.38-3.3 5.67-3.3 9.89s1.1 7.51 3.3 9.89c2.2 2.38 4.87 3.56 8.02 3.56 2.38 0 4.44-.59 6.19-1.78s2.92-2.88 3.52-5.08h12.03c-1.07 4.99-3.53 9-7.39 12.03-3.86 3.03-8.61 4.54-14.25 4.54Zm27.61-1.07 16.03-22.09-16.03-22.09h12.2l11.22 15.68 11.13-15.68h12.29l-16.12 22.09 16.12 22.09h-12.29l-11.13-15.68-11.22 15.68h-12.2Z" fill="#002b7c"></path><path d="M6.99 67.71a6.997 6.997 0 0 1-5.9-10.74l15-23.71a6.996 6.996 0 0 1 9.66-2.17 6.996 6.996 0 0 1 2.17 9.66l-15 23.71A6.98 6.98 0 0 1 7 67.72Z" fill="url(#svg-656346908_4188_a)"></path><path d="M36.26 67.72a7.004 7.004 0 0 1-6.05-10.52l20.74-35.72a7.004 7.004 0 0 1 9.57-2.54 7.004 7.004 0 0 1 2.54 9.57L42.32 64.23a7 7 0 0 1-6.06 3.49Z" fill="url(#svg-656346908_4188_b)"></path><path d="M74.26 67.72c-1.21 0-2.44-.31-3.55-.97a7.009 7.009 0 0 1-2.47-9.59L99.97 3.44a7.009 7.009 0 0 1 9.59-2.47 7.009 7.009 0 0 1 2.47 9.59L80.29 64.28a7.003 7.003 0 0 1-6.03 3.44Z" fill="url(#svg-656346908_4188_c)"></path><circle cx="103" cy="12" r="7" fill="#002b7c"></circle><path d="M36.01 67.71c-2.4 0-4.73-1.23-6.03-3.44l-14-23.71c-1.97-3.33-.86-7.62 2.47-9.59 3.33-1.97 7.62-.86 9.59 2.47l14 23.71c1.97 3.33.86 7.62-2.47 9.59-1.12.66-2.34.97-3.55.97Zm37.94.01a7.01 7.01 0 0 1-6.33-4L50.68 28a6.987 6.987 0 0 1 3.33-9.32 6.987 6.987 0 0 1 9.32 3.33l16.94 35.72c1.66 3.49.17 7.67-3.33 9.32-.97.46-1.99.68-2.99.68Z" fill="#002b7c"></path>
</svg>
<small>Fig: OnTracx logo</small>
</center>
 </div>
 
 A couple of months ago, [OnTracx, a Ghent University sports-tech spin-off](https://www.ontracx.com/) launched with the *'dream of a world where every runner can stay injury-free'*. That dream is based on a firmly grounded [interdisciplinary research project](https://0110.be/posts/Low_impact_runner%3A_a_music_based_bio-feedback_system), which I was fortunate to contributed to. The research project - headed by the UGent sports science department -  developed a music-based bio-feedback system to reduce footfall shock while running with the aim to lower common running-related injury risk. I fondly remember soldering and programing the first cluncky prototypes, now already eight years ago!
 
In my role, I contributed to several key papers that form the foundation of OnTracx. Notably, the 'validity and reliability' paper, which has become the most cited work in my academic portfolio, which at least indicates academic interest. The main author of the paper is now doing a post-doc in Harvard, so he must have been doing something right! Additionally, I am also recognized as co-inventor on a patent related to the system.

* Van den Berghe, P., Six, J., Gerlo, J., Leman, M., & De Clercq, D. (2019). [__Validity and reliability of peak tibial accelerations as real-time measure of impact loading during over-ground rearfoot running at different speeds__](https://pubmed.ncbi.nlm.nih.gov/30824234/). Journal of Biomechanics
* Van den Berghe, P., Lorenzoni, V., Derie, R., Six, J., Gerlo, J., Leman, M., & De Clercq, D. (2021). __Music-based biofeedback to reduce tibial shock in over-ground running: A proof-of-concept study__. Scientific reports

 <center>
<video  width="430" autoplay muted loop>
<source  src="https://0110.be/files/attachments/515/lir-demo.mp4">
</video>
<small style="display:block;width:480px">Fig: schema of the low impact runner research system. Foot-fall impact is measured with wearable sensors and music-based feedback is given to the runner with the aim to avoid high impact.</small>
</center>

The journey from research to commercial realization is always thrilling. As OnTracx steps into the market, I am filled with hope and anticipation for its success, mirroring and potentially exceeding the fruitful research track.

For more, read the [spin-off story of OnTracx, the solution for lower impact running](https://www.victoris.be/the-spin-off-story-of-ontracx-the-solution-for-lower-impact-running/) or join the [beta program on the OnTracx website](http://ontracx.com) and help with *'dream of a world where every runner can stay injury-free'*.


---

## [Printing a part of the world - a 3D-printed cityscape](https://0110.be/posts/Printing_a_part_of_the_world_-_a_3D-printed_cityscape.md)

- Published: 2024-01-09T00:00:00Z
- Updated: 2025-11-13T22:55:02Z
- Author: Joren
- ID: 537
- Canonical: https://0110.be/posts/Printing_a_part_of_the_world_-_a_3D-printed_cityscape

- Tags: [0110.be](https://0110.be/tags/0110.be.md), [GhentCDH](https://0110.be/tags/GhentCDH.md), [UGent](https://0110.be/tags/UGent.md)

<div style="float:right;width:20%;max-width:190px;margin-left:10px;margin-bottom:10px">
<center>
<img  style="width:100%" src="https://0110.be/files/attachments/537/3dprinting-globe.jpg"><br>
<small>Fig: 3D printing your part of the world.</small>
</center>
</div>
My ex-girlfriend and current wife likes maps. While looking for a gift for the new-years I got the idea to give her a 3D map of the nearby [historic city center of Ghent with its three iconic towers](https://earth.google.com/web/@51.05385689,3.72537111,16.45165093a,593.4748548d,35y,19.92318437h,59.99406466t,359.99999999r/data=OgMKATA). I have a 3D printer at home but still need to find a printable 3D model of Ghent.

Luckily, a couple of days ago a piece of software appeared to [capture Google Earth tiles -cubes- into a single 3D file](https://github.com/OmarShehata/google-earth-as-gltf). There you can select an area of interest via google maps and download a GLTF file which captures the landscape in 3D. The software needs an API key which can be requested via the Google Developer tools. 

After downloading a GLTF file, the 3D model needs to be made 3D-printable. There are online [GLTF to STL converters](https://products.aspose.app/3d/conversion/gltf-to-stl) but a bit of care needs to be taken to end up with an actually **printable** STL. My selected area of interest only has slight height differences in the landscape which are handled by placing the STL file on a base which compensates for these differences. Your 3D slicer can also generate structure to support inclinations in the landscape.

The 3D model generated by Google Earth is quite noisy and can contain floating parts and holes. It may be needed to edit the STL mesh directly. Selecting a slightly shifted area of interest may also solve problems with the edges of the print: take care to chop less buildings in two. 

Have fun printing your own piece of the world!

<center>
<iframe  src="https://0110.be/attachment/cors/2023.12.stlviewer/index.html" loading="lazy" style="border:none;box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4);width: 100%;height:15rem" ></iframe>
<small>
Fig: a 3D model for the Ghent city center visualized with an <a href="https://tonybox.net/posts/simple-stl-viewer/">Three.js STL viewer</a>. 
</small>
</center>

&nbsp;

![Unfinished 3D printed map](https://0110.be/files/photos/537/3d-printing.jpg)

![Finished 3D printed map](https://0110.be/files/photos/537/Ghent-city-center-3d-print.jpg)

![Ghent framed ](https://0110.be/files/photos/537/PXL_20231226_182335084.MP.jpg)

![Framed Ghent](https://0110.be/files/photos/537/PXL_20231226_182355545.MP.jpg)

---

[Older posts](https://0110.be/tags/UGent.md?page=1)
