Skip to content

Instantly share code, notes, and snippets.

Last active August 9, 2022 09:00
Show Gist options
  • Save maurisvh/df919538bcef391bc89f to your computer and use it in GitHub Desktop.
Save maurisvh/df919538bcef391bc89f to your computer and use it in GitHub Desktop.
ANSI art spectrogram viewer that reads audio from a microphone
import numpy
import pyaudio
import re
import sys
WIDTH = 79
BOOST = 1.0
# Create a nice output gradient using ANSI escape sequences.
cols = [30, 34, 35, 91, 93, 97]
chars = [(' ', False), (':', False), ('%', False), ('#', False),
('#', True), ('%', True), (':', True)]
gradient = []
for bg, fg in zip(cols, cols[1:]):
for char, invert in chars:
if invert:
bg, fg = fg, bg
gradient.append('\x1b[{};{}m{}'.format(fg, bg + 10, char))
class Spectrogram(object):
def __init__(self): = pyaudio.PyAudio()
def __enter__(self):
"""Open the microphone stream."""
device_index = self.find_input_device()
device_info =
rate = int(device_info['defaultSampleRate'])
self.buffer_size = int(rate * 0.02) =,
channels=1, rate=rate, input=True,
return self
def __exit__(self, *ignored):
"""Close the microphone stream."""
def find_input_device(self):
Find a microphone input device. Return None if no preferred
deveice was found, and the default should be used.
for i in range(
name =['name']
if re.match('mic|input', name, re.I):
return i
return None
def color(self, x):
Given 0 <= x <= 1 (input is clamped), return a string of ANSI
escape sequences representing a gradient color.
x = max(0.0, min(1.0, x))
return gradient[int(x * (len(gradient) - 1))]
def listen(self):
"""Listen for one buffer of audio and print a gradient."""
block_string =
block = numpy.fromstring(block_string, dtype='h') / 32768.0
nbands = 30 * WIDTH
fft = abs(numpy.fft.fft(block, n=nbands))
pos, neg = numpy.split(fft, 2)
bands = (pos + neg[::-1]) / float(nbands) * BOOST
line = (self.color(x) for x in bands[:WIDTH])
print ''.join(line) + '\x1b[0m'
if __name__ == '__main__':
with Spectrogram() as s:
while True:
Copy link

wolever commented Sep 28, 2015

Very cool!

I've fixed an issue I was having on the mac where the input device wasn't found by find_input_device here:

Copy link

mgeier commented Oct 3, 2015

There's a bug in the color inversion logic: bg and fg get switched each time invert is True, even if they are already switched.

As a result of this, the second % ends up un-inverted.

Copy link

mgeier commented Oct 4, 2015

Here's an alternative implementation:

colors = 30, 34, 35, 91, 93, 97
chars = ' :%#\t#%:'
gradient = []
for bg, fg in zip(colors, colors[1:]):
    for char in chars:
        if char == '\t':
            bg, fg = fg, bg
            gradient.append('\x1b[{};{}m{}'.format(fg, bg + 10, char))

Copy link

mgeier commented Oct 8, 2015

I took the liberty of turning this into an example application for the sounddevice module:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment