Skip to content

python

Updating SongMatrix

One of my favorite CircuitPython projects I've done is SongMatrix. Using a microphone on a Raspberry Pi, it records the song playing in the background, uploads a sample to shazamio, uploads the song and artist to AdafruitIO via MQTT, and an S3 MatrixPortal listens for the MQTT update and displays it on an LED matrix.

I don't run it all the time, but I do like to use it when I'm listening to a new album to learn the song names. I know a lot of music and can sometimes tell you the name of the artist or the album within seconds of a song starting, but not so much the song title.

I'm using a 2.5mm pitch LED Matrix and I have not found a lot of 3D printed cases for either one or two LED matrices. That is, until a few months ago when I came across the Transit Tracker project from EastsideUrbanism.

It uses two LED matrices in a beautiful 3D printed cases that hold both of them and the S3 MatrixPortal and screws together. I printed it out months ago but couldn't get SongMatrix to work with 2 matrices.

In theory, I should be able to update the MatrixPortal library by changing: matrix = Matrix(width=64, height=32, bit_depth=3) to matrix = Matrix(width=128, height=32, bit_depth=3) But no joy. I started over with some simple test programs, and I could get a 128x32 matrix to work without a problem, but as soon as I tried in my original program it did not work.

Next I tried to replace the MatrixPortal library by using the pins directly. Success! But now it doesn't scroll across the matrix, it's just a static display. It turns out that using ScrollingLabel only scrolls when the character count is larger than the max_character when setting the text:

title_scroll = ScrollingLabel(
    terminalio.FONT,
    text=song_title_scroll,
    max_characters=20,
    color=0xff0000,
    animate_time=0.3
)

To get it scrolling, I just did a len on the string returned from AdafruitIO and added a string of spaces to get over the 20 character limit to scroll it.

But the weirdest part? The original program scrolled the text with less than 10 characters. I tried to recreate it with a basic ScrollingLabel example, and of course I couldn't get it to work. Don't believe me? You can see the original scrolling the band Metric which shouldn't be scrolling on the project's GitHub page.

Introducing SongMatrix

I listen to music. A lot of music. If I’m in my home office I’m usually listening to a record, and if not, the radio. But I’ve always been an album person, not into playlist (or mixtapes to date myself). I’ve found by listening to albums front to back, I don’t always learn the song names.

Just a few weeks ago, I came across the shazamio Python library. I have a Raspberry Pi already sitting on my desk. I also have a couple extra 64x32 RGB Matrices and I recently picked up one of the new Adafruit S3 MatrixPortals, so I have the hardware and software to start a new project.

Enter SongMatrix (GitHub).

SongMatrix records a short audio sample on the Raspberry Pi and then sends it to shazamio to be identified. It then sends a MQTT message to Adafruit IO’s MQTT broker with the song title and artist. The MQTT message is received by the S3 MatrixPortal, which then scrolls the song and artist on separate lines, like so:

A 32x64 Matrix displaying the song Breathing Underwater on the top row and the artist, Metric, on the bottom row

I whipped up a proof of concept for the Python part of recording audio and sending it to shazamioin one Friday evening. The CircuitPython part took me a couple weeks and I’ll share some of the challenges in upcoming blog posts (no promises).

A special thank you to todbot for bootstrapping some asyncio code to get me started. And to anecdata for spending a good chunk of yesterday helping me get around the last issue and getting to done. (Well, it’s never done).

MatrixPortal Album Art Display

A year and a half ago I made some progress on displaying album art on a MatrixPortal and 2 32x64 RGB matrices using CircuitPython. I was never really happy with the results and using two 32x64 matrices instead of one 64x64 matrix was difficult. I moved on and re-created the project using a PyPortal Titano. It worked well: when I chose an album I wanted to listen to SilverSaucer.com, my FastAPI web app would convert the image and send a MQTT message. The PyPortal would listen for the message, and when a new message arrived, download and display the album art along with a Winamp skin that also showed the artist and album name. I should have blogged it and taken a picture!

When Adafruit announced the new S3 MatrixPortal with so much memory - 8MB flash and 2 MB of SRAM, I decided to try again. My hope was that with that much memory, I could download and load the image into memory without having to save it.

I haven't figured out how to do it without saving the image to the MatrixPortal yet, but I was able to repurpose my original code and had it up and running in just a few minutes.

Now came the hard part: adding gamma correction to the image so it looks closer to normal on the MatrixPortal and not washed out. Adafruit has a great Learn Guide for Image Correction for RGB LED Matrices. The guide includes a CPython program that uses Python's PIL / Pillow library to manipulate the image and some logic to apply gamma correction.

I needed to re-create that program, which used command line arguments where you would pass the image name to the program, as one function within my FastAPI app. It took a few days of banging on it (and a few hours lost to a wrong indent(!)), but I got it. The get_discogs_image function downloads the given image and converts it from a 600px image to 320 x 320px (for the PyPortal) and a 64x64 image for the MatrixPortal. The process_image function then takes the 64x64 image and applies gamma correction and saves a new copy (which the MatrixPortal will download and display).

Some albums look a lot better than others. Considering it's a 64x64 image, it's practically pixel art at this point. It's too bad that it's difficult to photograph RGB matrices, but here is a picture showing Divine Fits' album, it being chosen on SilverSaucer.com, and the converted image being displayed on the MatrixPortal.

Divine Fits

SilverSaucer completed!

Just like that, I crossed the finish line and SilverSaucer.com is finished. I wasn’t planning on finishing it so quick after my blog post, but on Friday it just clicked and the “On this day” feature was completed in just a few hours.

I always thought the “On this Day” feature was a pipe dream. I had no idea how I was going to tie a Discogs release to MusicBrainz and then import that date. But after discovering the discodos app a few weeks ago and working with the database having clicked last week, I was able to finish it up after discovering a key feature: the LIKE operator in SQL and SQLAlchemy.

``` today = pendulum.today(tz="America/Chicago") print("Today: ", today, today.month, today.day)

if today.month < 10: search = "0" + str(today.month) + "-" + str(today.day) else: search = str(today.month) + "-" + str(today.day)

async with db_session.create_async_session() as session: query = ( select(Album) .filter(Album.mb_release_date.like(“%” + search)) .order_by(Album.mb_release_date) ) print(query)

results = await session.execute(query)
query_results = results.scalars()

The Pendulum library comes to the rescue again as it is still my favorite way to work with datetime objects.

This returns a list of rows from the database where the results are today’s date, not including the year. (There is one bug related to 6/18 I can’t track down where it displays a different result - but not both - when an order_by is added to the query).

I’m pretty happy with how everything has turned out. I’ll probably do a couple more blog posts about the project, including reviewing the project goals.

It feels weird to be done. Not necessarily elation, not relief, just weird.

June 2022 SilverSaucer Update

As usual, I haven’t blogged my progress on SilverSaucer and I’ve had some major progress since January.

First, I did follow-up on that blog post by switching from a MatrixPortal with a 64x64 LED to a PyPortal Titano.

Liz Phair's self titled album displayed on a PyPortal

In FastAPI, I did two things. I added a service that uses the Pillow library to convert the image from the Discogs image URL to a small bitmap that the PyPortal can use.

The second thing is that sends a message using the MQTT protocol. The PyPortal is watching for that message, and when received displays the Bitmap like above.

async def get_discogs_image(release_image_url):
    image_dl = requests.get(release_image_url, stream=True).raw
    download = Image.open(image_dl)
    download.save('static/img/album-art/image_600.jpg')

    img = Image.open('static/img/album-art/image_600.jpg')
    img.quantize(colors=16, method=2)
    smol_img = img.resize((320, 320))
    convert = smol_img.convert(mode="P", palette=Image.WEB)
    convert.save('static/img/album-art/image_300.bmp')


async def publish_image(image_url):
    client = mqtt.Client()
    client.username_pw_set(config.mqtt_user, config.mqtt_pw)
    client.connect("mqtt.silversaucer.com", 1883)
    client.publish("albumart", "Ping!")
    client.disconnect()

That was a pretty big breakthrough to get hardware working with the website back in April or May.

I then spent early last week playing with a command-line tool called discotools. I help with a little of the documentation in the python3-discogs-client library and one of the co-maintainers wrote discodos for DJs to catalog their collection. It also has the ability to match Discogs Release ID to MusicBrainz ID and using it I was able to get about half (or 400) of my MusicBrainz IDs assigned into the database. (Which is a different story about doing unnatural things with tables).

Then in the last week or so, everything has started to come together. Last week I was struggling with understanding the different kind of objects database queries could return. I was having a hard time understanding and differentiating between scalar, scalars, one_or_none, and lists. That finally clicked and in a couple of days I had re-written the random results page to mostly use the database and the page load time went from about 10 seconds down to about 2 seconds, a huge improvement. After the initial database import, which takes forever as Discogs rate limits you to one request per second, the play-album page uses the database for all information except the genres and tracklist, which are still called using the Discogs API. It turns out that the Discogs API is very slow when querying an object in your personal collection. If you do a general query on a public release object, it’s way faster. The combination of those two things led to the speed improvement.

From there I was able to quickly re-factor the play-single method to match the play-album, so I can randomly return an EP or single to play. Understanding the database query, I also added a list result to the admin page showing a list of all releases that don’t have an associated MusicBrainz ID. Last night in just hours I created the template with form and the get and post methods to manually enter the ID and store it in the database.

I also wrote a method that for the existing database entries that have the MusicBrainz ID, to query the MusicBrainz API and get the release date for that album. It worked, but the dates the API has stored include the year, year and month, and year, month and date, which is what I really want. But just like that, over half the work to build the “On this Day” feature has been done and I just need to manipulate the dates above to get the one I want.

The finish line is in site.

March 2022 Update

I’ve been hip deep in podcast related stuff for the last two months, but with the first two episodes out I can come up for air.

I don’t know what good looks like yet, but I’m happy with the reception the show has received. The YouTube comments have been nice (what?!) and I’ve received some compliments here and there.

I did build a complete website for FastAPI in that time. It was useful to go through the Talk Python training again and it helped me gain a (slightly) better grasp of asyncio. All the code to manage the episodes (add, edit, and view) is my code. I still need to get a better handle on SQLAlchemy and how and what type the results are returned as, but it’s working. I’m sure I missed some type hints, too, and I’m really enjoying working with those, though I need some better discipline.

All season one episodes are now recorded and edited and will be released over the next four weeks. Season 2 is in the planning stage and I’ve invited all the guests, and we’ll start recording in just a couple of weeks. I’m enjoying meeting folks from the community, it definitely gets me out of comfort zone and I’m learning a ton.

And don't forget to subscribe to the show - add this RSS feed to your app in your favorite podcast app.

The CircuitPython Show Progress

I’m about ten days away from the first episode of The CircuitPython Show dropping. Saying I’m a nervous wreck right now might be an understatement.

Five of the six episodes have been recorded and sent off to the sound engineering to make them sound good. (I’m using the same sound engineer as Talk Python to Me, how about that for a referral?!). I’ve received one of the episodes back and it sounds good. I shared it with my best friend, who agreed, and he’s not even in to CircuitPython and microcontrollers. Paying for sound production is worth it to level the audio and remove all those “Umms” and pauses is worth every penny.

I’ll be posting each episode on YouTube and I’m doing the editing for that. In other words, it will be a much more of a rough cut on YouTube and I recommend listening to the show as a podcast.

I’ve already learned a lot in just a few episodes. This TedX talk sums it well: “Shut up and get out of the way”. I just need to let the guests talk and I need to remove the extra words. The other big thing I need to work on is the ending, it’s… a little abrupt, let’s just say.

I would say every episode has gone really well. The guests have been engaging and the conversation has just flowed.

The website is pretty much done. I can edit the episode details, show notes, and transcripts and store them all in a database. I can edit them, but still having some issues with how it updates the edits. I’ve built the site using FastAPI and the Talk Python training course. I went to Talk Python’s office hours last week, which helped me a ton, and I may need to visit one more time to get this last thing done.

There’s no hurry to getting some of the features up as it’s all behind the scenes stuff, but it would be nice to cross that off the list.

The only other big chunks of work are going through each episode to create the show notes and check the transcripts and also to create and edit the videos for each episode to put up on YouTube. And no, I’m still not used to the sound of my own voice.

What, you haven't subscribed to the show yet in your favorite podcast app? Just add this RSS feed to your app and you'll be ready when the first episode drops in just over a week..

The Podcast Has Been Submitted

CircuitPython Show on PocketCasts

I mentioned in my blog post introducing the podcast that I had a lot to do to get the podcast ready. I may have underestimated the amount of work, but progress is being made! The podcast was submitted to all of the major podcast networks today, including PocketCasts (my personal player of choice), Google, Apple Podcasts, and everywhere you'd expect to listen to a show. If you want to add the show manually to your podcast player, here is the link.

This is the thirty second teaser trailer that shares some of the guests who will joining me in Season 1 of the show.

The biggest way to help the show right now is to subscribe to the link or search for the show in your favorite podast player (when it appears), and if you like what you hear after the show debuts, please consider writing a review.

More to come soon!

Silver Saucer Progress (January 2022)

When I’m not doing something podcast related, I’m still trying to find time to code.

With the football games in the background, I was able to build off my last post, which would convert an image to Bitmap if I had it.

I made sure I had it today. Using the requests library, I implemented the ability to go get the photo and pass it to Pillow to conversion. The process looks like this:

  1. Request image URL location from Discogs via the authenticated API
  2. Get the request object and write it to disk
  3. Use the object to pass it to Pillow, which converts and saves it.

It was easier than I expected. I have two things to do:

  1. Only download the image if I’m the one logged in
  2. I was only to save it in the default directory, I need to store the the images in the static directory.

I’m not 100% sure the project is going to work. Technically, it will work. But I should really be about 12 feet away from the LED matrix to see the display in the right light. Currently I’m about three feet away and it doesn’t look the best. Looking around my home office, there isn’t a great place put it. I also need to learn how to take better pictures of LEDs.

Those two things plus wiring up MQTT equals done. Getting close now. The pictures look much better in person.

Sugar's Copper Blue album artwork displayed on a 64 by 64 matrix

Sugar's Copper Blue album standing up  next to itself displayed on a 64 by 64 matrix

Introducing The CircuitPython Show

CircuitPython Show

I’ve decided to start a podcast! The very original show name is The CircuitPython Show, about - you guessed it - CircuitPython! More specifically, about the people and the cool things they're doing with CircuitPython.

The show will be a question and answer style interview podcast. I’m shooting for each episode to be about 30 minutes. I’ll interview a person in (or around) the CircuitPython community and it will give listeners an opportunity to learn more about that person.

I’m still a ways out from releasing episodes. I’m planning on six episodes for season one and I’ve been emailing invites out to potential guests. So far I have three confirmed guests so I still have a little work to do. If you have any guest recommendations or want to be on the show, please let me know! (Really, please!)

I’m brand new to all this and I’m sure it will take me a few episodes to find my “voice”. I’m recording a teaser trailer and already I’m learning how hard this is! But if you’ll stick with me, I’m guessing the episodes will get better as I go along. I've really enjoyed my time in the CircuitPython community, especially the Adafruit Discord channel, and thought there are enough interesting people to make a podcast about those people and their projects.

A special shout-out to Michael Kennedy, host of the Talk Python To Me podcast. When I was considering doing this he took time out of his schedule to answer some questions for me and make some introductions. (And we’re both using Boostrap Dark for our websites, the similarity is unintentional!)

You can find out more at the newly launched website (running FastAPI of course) or follow the show on Twitter, which I recommend as that’s my platform of choice. I’ll probably drop some spoilers and other info on Twitter first.

I’ll blog some more when the teaser trailer is out or I have other news. Until then, I have a lot of work to do…