A Read Eval Print Loop, in short, REPL is not that much different from a chat with a computer. So I wrote a telegram bot that has the ability to /eval code:

Untitled

Given the input /eval (apply str (repeatedly 1000 #(rand-nth ["🤖" "💬" "😸"]))) the bot responded with the emoji's above.

What's cool is that this automatically works in any Telegram chat. I can open a chat with friends and use my bot. Though as a security measure I made sure the bot only replies to a whitelist of users (currently just me). Another benefit is that multiple bots, each a different process (or different machine!) can be in the same chat room and I can message /eval@denpi4bot or /eval@someotherbot to run commands on multiple environments. They could even read/respond to each other's messages! I seriously cannot think of a simpler way with equivalent reach (desktop, mobile, multi-user, multi-media and portable—what's not to like?) to interact with multiple services.

The Problem

But back to the problem. Oh, there's a problem? Yes: I live in an old T-Shirt factory turned residential loft apartments building in NYC. The ceilings are high but thin. The walls are brick but crumbly. And my upstairs neighbor likes to party it up on school nights radiating sounds of stomping, rumbling, moving chairs, bass and unintelligible yelling into the apartments above and below.

It's really not that bad for sleeping (with earplugs) but it will spoil a movie experience. That said, I have alternatives. I can let my neighbor party it up some time and sleep at my partner's or I can spend more time with my friends and I'd much rather not come home to the rumble and then go back to my alternatives. What would be better is to have a computer at home that can be my remote ears to keep me informed of the noise or quiet at home.

Proof of Concept

I few months back I made novelty purchase and got RaspberryPi 4 with 8 GB of RAM. There's a 64bit OS in beta that enables it to install Java/Clojure and the Pi's specs easily allow it to run a few JVM processes simultaneously. Pi's can be quirky but being the Type B person I am I figured I'll be able to turn it into a recording device and Telegram bot client so I started writing up a PoC on my Mac.

I already had some experiments writing Telegram bots from work so that was the easy part. But what about audio? For the PoC I wanted two things:

  1. 5 seconds of recorded audio
  2. a histogram of the 5 second recording

It turns out each of those are a one liners in FFmpeg. Here's the code for recording audio:

(defn sample
    [& {:keys [secs filename]
        :or   {secs 5}}]
    (let [id       (gensym)
          filename (or filename (str basepath "/" id "_sample_audio.ogg"))
          p        (p/$ ffmpeg -f avfoundation -i ":1" -t ~secs -c:a libopus ~filename)]
      (-> p
          (assoc :id id :audiofile filename))))

I required babashka.process :as p to invoke FFmpeg. Conveniently it returns a Clojure map-like data-structure so I just assoc'd the audio filename onto it. The reason I needed the filename is for the histogram:

(defn histogram [& {:as p :keys [audiofile id filename]}]
  (let [filename (or filename (str basepath "/" id "_sample_histogram.png"))]
    (merge p
           (-> (p/$ ffmpeg -i ~audiofile -filter_complex showwavespic -frames:v 1 ~filename)
               (assoc :histogram-file filename)))))

The histogram takes either a process-map or a regular hash-map and invokes FFmpeg again, this time using the audiofile path to create a histogram from it. It invokes the process and returns it, all without blocking. Nice.

Next, this needs to be wired up with Telegram. Telegram allows bots to install slash commands that autocomplete like /command

Untitled

Telegram calls commands and other inline tags entities. I added a multi-method that dispatches on the command. The one for /audio-sample looks like this: