tfft

    In short, tfft (text fft) is a command line based fft. It is a tool for visualizing the frequency of a realtime stream of audio supplied by either JACK or pipewire. Instructions for using and building tfft can be found in its repo. Currently the only way to get tfft is by building it. The only requirement is an installation of Rust, and git to download the repo.


Concept

    The idea for tfft came about in two steps. I use wttr.in for my weather forecast- specifically its 3 day forecast using its v2 output. To show temperature over time, it uses braille characters to draw dots that signify what the temperature is at a given point of time. As temperature rises and falls over the day something resembling a sine wave is drawn, which showed me that it was possible to make an oscilloscope in the terminal.

Showing temperature over time with braille characters

    I love doing things in a terminal emulator, as programs written for the command line tend to be faster to run and faster to interact with. With that in mind, I thought that having some audio analysis tools that can run on the command line would be a nice way to demonstrate the versatility of text based UI's. After a small visual demo, I realized that making a decent oscilloscope would require more work than an fft, and so switched to making tfft instead.

    While developing, I wanted focused on several things. The primary focus was performance and visual fidelity- tfft should be able to render at at least 60fps with no visual artifacts like screen tearing. It should be able to do this without using a large amount of system resources. In addition, the visualization should be easy to interpret (read). To help accomplish the first two, I decided to develop in Rust, because it gives me control over my memory and OS calls, its ease of use, and it's ability to easily integrate other libraries.

Drawing

     Speaking of libraries, all of the controlling the state of the terminal is handled by termion. Terminals can be configured to do certain things or display text in a certain way using sequences of characters called ANSI escape codes. Termion provides a strongly typed interface to generate these escape codes, which can then be integrated into whatever system you use to generate text output. As for that output, I originally used the same braille characters that wttr.in uses, but this had some problems. The first one I noticed was every terminal emulator I used rendered the braille characters with a significant border to the right and bottom of the given dots. This meant that there were noticeable gaps where the shape of the graph was continuous, which is to say, everywhere. In addition, it was both hard to see small changes in bin magnitude over time, and hard to see the visualization from a wide variety of viewing angles and distances. The solution was to use unicode block characters.

     Unicode block characters are a set of rectangles of various widths and heights. The only interesting ones for tfft are the ones whose bottom is at the bottom of the character area and whose width are the same as the character area. This allows for 8 different heights, which gives a better indication of amplitude at the top of each column. All other characters that are filled in are the '█' character. Filling in the area with a color different to the background is makes the block characters easy to read.

    Unfortunately, block characters are rendered inconsistently from emulator to emulator. Most annoying is that several characters might not align- for example a row or column of █ characters might not join each other in either the horizontal or vertical direction. Or they might, but some of the shorter block characters might not be drawn all the way to the bottom of the row.

On the terminal included with macOS, there is a border around all block characters.

     A quick aside on terminal emulator selection. While I've only tried several that are available on macOS, from my testing it seems that any terminal that does rendering on the CPU can't keep up with tfft at 60fps. I've minimized the amount of work I do when rendering by only redrawing the parts of the graph that have changed. That leads me to believe these terminals are not selectively redrawing a portion of the screen, but rather redrawing all characters whenever they receive new input. As a result, any command line app that is updating more than a modest portion of the screen each frame needs a GPU accelerated terminal to ensure that the fps stays consistent, especially at higher fps.

Audio

    Ironically, audio processing was the least exciting thing about this project. There is a Rust crate called realfft that handles the frequency detection. I wrote a library called blockfree-chan that allows for the synchronization of frequency amplitudes across threads. As tfft should work with other tools, tfft can be used in JACK or pipewire graphs, both of which have Rust wrappers. I also wrote a simple initializer for a pipwire filter around pipewire-sys, as pipewire's Rust library does not yet implement easy and flexible initialization of pipewire filters. In the interest of not including audio settings configuration, there is no ability to receive input from a soundcard via an operating system.

Looking Back

    Tfft was the first interactive cli program I completed. During the development process I learned a few tricks to make sure the app and the screen updating run smoothly (blog post for that coming soon!). Even though there is less information out there on how to make responsive cli programs, it's more straightforward than acheiving similar performance in a 2D GUI application. The fact that it can run on every major operating system, and without a graphical desktop in the operating systems that allow that, make me wonder if it's possible to build a reasonably competent set of music production tools that all run on the command line. Based on the work I've done here and on other programs I'm writing, I'm optimistic that it can be done.
-signal_Processor~