Text mode AF spectrum analyser.

Well guys, this MUST be a first.

This is DEMO code only and has NO error detection or correction, nor out of bounds checking.

I have succumbed to Python and scipy to do the FFT heavy lifting as I have absolutely no idea where to start do such a thing using AWK. This is a taster for me to include into AudioScope.sh. I am thinking of doing each vertical plot in differing colours and juggling the numbers to suit.

This IS a bash script but creates a 250Hz 1 second WAV file and the python script on the fly.

At the moment the plotting takes a few seconds, but I am not bothered about that at this point. The fact that it works has even amazed me!

The two images show the pre-generated 250Hz spectrum and a 1 second burst of me talking into the MBP's mic...

COMMENTS?!

I await the flak...

#!/bin/bash
# AF_Spec_An.sh

# Generate a 250Hz square wave, WAV file, for testing. Mono, 8KHz sample, unsigned integer, 8 bit depth.
CHAR=0
: > /tmp/250Hz.wav
: > /tmp/bash_array
: > /tmp/FFT_WAV.py
printf "\122\111\106\106\144\037\000\000\127\101\126\105\146\155\164\040\020\000\000\000\001\000\001\000\100\037\000\000\100\037\000\000\001\000\010\000\144\141\164\141\100\037\000\000" >> /tmp/250Hz.wav
while [ $CHAR -le 249 ]
do
	printf "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" >> /tmp/250Hz.wav
	CHAR=$(( $CHAR + 1 ))
done

# A test display only, active area x = 64, y = 21.
display()
{
	printf "%b" "\033[2J\033[H"
	graticule="           +----------------------------[DISPLAY]----------------------------+\n"
	graticule=$graticule"       100 ++-------+-------+-------+-------+-------+-------+-------+-------++\n"
	graticule=$graticule"           |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"        90 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    R      |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    E   80 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    L      |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    A   70 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    T      |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    I   60 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    V      |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    E   50 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"           |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    L   40 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    E      |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    V   30 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    E      |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"    L   20 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule" Log10(x)  |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"        10 +        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"           |        |       |       |       |       |       |       |        |\n"
	graticule=$graticule"          0++-------+-------+-------+-------+-------+-------+-------+-------++\n"
	graticule=$graticule"   FREQ Hz +0------500----1000----1500----2000----2500----3000----3500----4000\n"
	printf "$graticule"
}

# Pythoin code to do the heavy FFT lifting.
cat << PYTHON_CODE > /tmp/FFT_WAV.py
# Python 2.7.10 (default, Feb  6 2017, 23:53:20) 
# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
# Type "help", "copyright", "credits" or "license" for more information.

import sys
import scipy
from scipy.io import wavfile

global ARRAY_STRING
ARRAY_STRING = ""

# The inbuilt tester.
RATE, WAVEDATA = wavfile.read('/tmp/250Hz.wav')
# A 1 second speech burst.
# RATE, WAVEDATA = wavfile.read('/Users/amiga/Desktop/Sound/test.wav')
DATA = WAVEDATA.T
ELEMENTS = [(ELEMENT/2**8.0)*2-1 for ELEMENT in DATA]
COMPLEX = scipy.fft(ELEMENTS)

LIST = scipy.ndarray.tolist(abs(COMPLEX))

for SUBSCRIPT in range(7999, 3999, -1):
	FFT = int(LIST[SUBSCRIPT])
	if FFT < 1.0: FFT = 1
	FFT = int(5*scipy.log10(FFT))
	ARRAY_STRING = ARRAY_STRING + str(FFT) + " "
filename = open('/tmp/bash_array', 'w+')
filename.write(ARRAY_STRING)
filename.close()
sys.exit()
PYTHON_CODE

# Now run the Python code.
python /tmp/FFT_WAV.py

# Place the space delimited string into a bash array.
bash_array=( $( cat /tmp/bash_array ) )

# Setup the display.
display

# Finally plot the audio spectrum.
COUNT=0
HORIZ=13
VERT=${bash_array[$COUNT]}
# Display window...
# HORIZ, 13 minimum, 77 maximum.
# VERT, 2 minimum, 22 maximum.
# VERT MUST be inverted.
while [ $COUNT -le 3999 ]
do
	VERT=$(( 22 - $VERT ))
	if [ $HORIZ -gt 77 ]
	then
		break
	fi
	for DRAW in $( seq $VERT 1 22 )
	do
		# Bending the rules a little for printf, but this is only a proof of principle demo.
		printf "%b" "\033["$DRAW";"$HORIZ"f\033[1;31m*\033[0m\n\n"
	done
	if [ $(( $COUNT % 63 )) -eq 0 ]
	then
		HORIZ=$(( $HORIZ + 1 ))
	fi
	COUNT=$(( $COUNT + 1 ))
	VERT=${bash_array[$COUNT]}
done
1 Like

Quite an achievement!

Your graticle can be made a lot simpler:

printf "%s" "\033[2J\033[H" # %s is good enough for this

printf "%s\n" \
    "           +----------------------------[DISPLAY]----------------------------+" \
    "       100 ++-------+-------+-------+-------+-------+-------+-------+-------++" \
    "           |        |       |       |       |       |       |       |        |" \
    "        90 +        |       |       |       |       |       |       |        |" \
    "    R      |        |       |       |       |       |       |       |        |" \
    "    E   80 +        |       |       |       |       |       |       |        |" \
    "    L      |        |       |       |       |       |       |       |        |" \
    "    A   70 +        |       |       |       |       |       |       |        |" \
    "    T      |        |       |       |       |       |       |       |        |" \
    "    I   60 +        |       |       |       |       |       |       |        |" \
    "    V      |        |       |       |       |       |       |       |        |" \
    "    E   50 +        |       |       |       |       |       |       |        |" \
    "           |        |       |       |       |       |       |       |        |" \
    "    L   40 +        |       |       |       |       |       |       |        |" \
    "    E      |        |       |       |       |       |       |       |        |" \
    "    V   30 +        |       |       |       |       |       |       |        |" \
    "    E      |        |       |       |       |       |       |       |        |" \
    "    L   20 +        |       |       |       |       |       |       |        |" \
    " Log10(x)  |        |       |       |       |       |       |       |        |" \
    "        10 +        |       |       |       |       |       |       |        |" \
    "           |        |       |       |       |       |       |       |        |" \
    "          0++-------+-------+-------+-------+-------+-------+-------+-------++" \
    "   FREQ Hz +0------500----1000----1500----2000----2500----3000----3500----4000"

Also that while loop can write to 250Hz.wav once instead of 249 times:

while [ $CHAR -le 249 ]
do
	printf "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
	CHAR=$(( $CHAR + 1 ))
done >> /tmp/250Hz.wav
1 Like

I do think Python, Perl, etc is about as good as you can expect for FFT unless we can convince a genuine, credentialed Computer Scientist to build one for us. :frowning: It's possible to do it - I've seen it done in Basic - but the mathematics are very subtle and involve complex numbers or ways to cheat around non-complex numbers.

1 Like

Boy don't I know it now.
It took a couple of days to get my head around how to set about jumping from the python result into a bash array but in the end I decided on saving to disk then using cat to get that result into the bash array. So I took the easy way out.

As for you previous post I don't need the for loop at all as it will get the WAV file from the FREQ command in AudioScope to look at its spectrum. There it little point in looking at the main capture as a few users might just use the internal mic only.
However I will use your method of generating the window although I have already modified it for my requirements.

The image is the new look and colours of the spectrum of a single sung note into the mic from FREQ command in AudioScope...

Well isn't that lovely to see :slight_smile: Good job.

Hi Corona688...

Thanks.

Whooda thunk that the humble bash shell, terminal and the plethora of utilities could do something like this eh!
I would love to know if it has been done before.

;o)

---------- Post updated at 08:52 PM ---------- Previous update was at 06:45 PM ----------

Apologies if this reply attaches itself to the previous one.

I have done away with the display window function completely and the code for the window is now generated per file call.

Sometime in the 90's, I made a microphone oscilloscope which operated in DOS 80x25 text mode at 128x128 resolution. It updated the display by changing the font every single frame. This made it fast and flicker-free. I could do 128x256, which was less oddly shaped, but suffered graphical artifacts because of characters the video card blithely assumed were "line drawing" characters which it should extend to meet the neighboring character.

Oscilloscope was the best I could do. I struggled with it for a long time but never managed to port a working FFT algorithm from anywhere. There weren't widely available libraries back then.

Everything was done in Borland Turbo C for DOS, an IDE which students across the globe still use to this day for learning purposes.

Hi Corona688...

Crikey, I still have those HD 3.5 inch floppy disks, 7 of them, and the manual. <shock horror>
If you want to see the code before attaching it to AudioScope I will PM it to you. Assuming I am able to attach a file.
I don't want it public just yet...