Displaying Taylor Swift lyrics on my desk

Published on under the Coding category.

A few weeks ago, I created a bot that finds the most related Taylor Swift lyric to a given prompt. For example, given the query “dating”, the bot returns “That’s what people say, mmm-mmm I go on too many dates”, a lyric from the song Shake it Off. Now, the results are printed on an e-ink display. Here is the result:

An e-ink display with Taylor Swift lyrics

In this post, I’m going to summarize how the bot works and how I print the results on an e-ink display that sits on my desk.

Retrieving lyrics

This system to retrieve lyrics related to a text prompt (i.e. “dating” or “December”) works using text embeddings. Text embeddings encode the semantics of text. These embeddings are calculated using machine learning models. You can compare two embeddings to see how similar they are. For this project, I compare a prompt (i.e. “dating”) to every Taylor Swift lyric from her first nine albums. Then, I return the lyric whose embedding is most related to my query.

This has resulted in some fun results:

Query: security
Bot: From when they pulled me apart You knew the password so I let you in the door

Query: December
Bot I go back to December all the time All the time

After creating the bot, a friend shared a system he built that syncs a clickable grid of pixels on his website to an e-ink display. When someone clicks on the grid, the e-ink display changes. A spark was alight in my mind: what if I applied a similar principle to my Taylor Swift project? When someone prompts the bot, I could show the lyric out on the e-ink display. Therein lay a morning of building.

Read my guide on how I built the bot

The hardware

To build this project, I needed an e-ink display. I searched Pimoroni, a company that makes a wide range of hardware, kits, and accessories for use with the Raspberry Pi computer, among other hobbyist devices like the Arduino and BBC micro:bit. I found the “Badger 2040”, an e-ink display small enough to be a badge you could wear. The Badger 2040 uses the Raspberry Pi Pico, a smaller version of the Raspberry Pi that runs MicroPython. MicroPython is a version of Python designed for use with microcontrollers.

The badge can be powered by USB or a battery pack. e-ink doesn’t consume much power compared to LED screens, so a battery pack should last a while. Battery pack support means you can place the device somewhere without having a power supply nearby.

The Badger 2040 comes in two versions: one with WiFi support, and one without WiFi support. For this project, I needed to be able to connect to the internet. Thus, I purchased the Badger 2040 W, which supports connecting to the internet. I bought an accompanying battery pack that I could use with the Badger, too.

With the hardware purchased, I was ready to start building!

Displaying lyrics on the badge

To write custom scripts for the Badger 2040, I needed to connect the device to my computer. I installed Thonny, a tool that you can use to run MicroPython on the Raspberry Pi Pico that the Badger 2040 uses. I recommend reviewing Pimoroni’s full tutorial on how to get set up, which covers everything you need to know about device setup and writing custom scripts.

After you have set up access to your internet network using Thonny, you need to press the stop button in the Thonny interface. This is because the Badger is always running the default program until stopped. When you press the stop button, the default program stops and you can write programs to run on the device.

With Thonny set up, I had a decision to make: how would I connect my script to the IRC bot? I decided to keep things simple. When the bot gets a prompt, I would save a text file that I can access from my website. I can then parse this text file programmatically. Here is an example of what the file looks like:

!ts coffee
I was reminiscing just the other day While having coffee all alone, and Lord, it took me away

The first line contains the !ts control sequence followed by the prompt sent to the bot. The second line contains the response.

To access this response, I had to use urequest, a HTTP networking library available in MicroPython. At first, I aimed to print the result to the console. I wrote code that looks like this:

import badger2040
import urequests

badger = badger2040.Badger2040()

badger.connect()

print(badger2040.is_wireless())

response = urequests.get("https://jamesg.blog/swift.txt")
badger.text(response.text, 20, 20)

This code:

  1. Imports the required dependencies.
  2. Connects the Badger to the internet.
  3. Prints a message to confirm the device is connected to the internet.
  4. Makes a request to retrieve the text file where the last response from the IRC bot is located.
  5. Displays the result on my device.

The code above allowed me to retrieve a lyric and display it on the e-ink display. Success!

But, the badger.text code does not support line wrapping. Thus, there was one problem: the lyrics are usually multiple words long, which means they will not fit on a single line.

I thus came up with an alternate algorithm. I would retrieve the response, break it up into a list of lists where each list contained four words, and display four words in each line. I also decided to remove the !ts command sequence and add some text that says what text is the user’s prompt and what text is the lyric.

As I was implementing this alternate algorithm, I also thought about how the display should refresh. Because the system is connected to the internet, I can check to see if there is a new lyric available any time I want. I decided on a 30 second refresh rate. Every 30 seconds, the program would check to see if a new prompt was available. If a new prompt was available, the results would display on the screen.

Here is the code I wrote to build this:

import badger2040
import urequests
import json
import time

badger = badger2040.Badger2040()

badger.connect()

print(badger2040.is_wireless())

last_response = None

while True:
    response = urequests.get("https://jamesg.blog/swift.txt")

    response_text = response.text.split("\n")

    prompt = " ".join(response_text[0].split(" ")[1:])
    response = response_text[^1]
    
    # divide response every four words
    words = response.split()
    line_delimited_response = [words[i:i+4] for i in range(0, len(words), 4)]
    
    if last_response and last_response == line_delimited_response:
        time.sleep(30)
        continue
    
    last_response = line_delimited_response

    badger.set_pen(0)
    badger.clear()
    badger.update()

    badger.set_pen(15)
    badger.text("Prompt: " + prompt, 20, 20)
    badger.text("Taylor Swift: ", 20, 40)
    y_pos = 60
    for line in line_delimited_response:
        badger.text(" ".join(line), 20, y_pos)
        y_pos += 20  # Adjust the y position for the next line
    badger.update()

    time.sleep(30)

Above, I start an infinite loop that runs every 30 seconds. This loop checks for a lyric from my blog and divides the lyric into lists. Each list contains no more than four words. If the lyrics haven’t changed in the last 30 seconds, the system waits 30 seconds and tries again. Otherwise, the display is cleared and the results are printed to the e-ink display.

I print each list of words on a new line to ensure the words don’t overflow on the screen. With that said, this logic can be better. I could write logic that changes the text size if the prompt is too long. This would allow me to fit more words on a line and mitigate a present flaw: long lyrics overflow at the bottom of the display.

Running the script with the battery

Thus far, my script would work only when run through Thonny. But, I wanted this system to run on battery power, without having to be connected to my computer.

The Badger 2040 has a launch screen with different pre-built apps. These include a News app, which reads content from an RSS reader, and a TODO list app. Some apps require an internet connection. This launch screen is defined in a file called launcher.py.

To add a script to the launcher, you need to make sure your script is in the examples directory. Your script will need an icon with a file name that matches the pattern icon-{yourfilename}.jpg. For example, an icon for a script called swift.py would be icon-swift.jpg. (I instead opted to overwrite this logic in launcher.py because I didn’t have an icon to hand and ensured that my script used one of the pre-defined icons in the examples folder.)

When you have a script and an icon in the examples directory, it is automatically added to the launcher. Thus, I could disconnect my Badger 2040 from my computer and start the script using the Badger 2040 device itself.

Whenever a member of the community in which my bot runs uses my bot to request a lyric, I will see the results on my Badger 2040.

Go Back to the Top