****************************** !!!IMPORTANT!!! ******************************
This code uses some escape facilities of the majority of *NIX Terminals!
(Apologies for any typos, etc...)
-
This works on standard terminals in the *NIX family of home computers.
-
On startup the script will attempt to expand the terminal to 40 lines
by 144 columns. -
If it does NOT do this CORRECTLY, or at all, on the first run then make
the terminal window full screen and re[-]run. -
This old HP laptop would only do 39 lines and the display was YUCK!
The image is this laptop running in FULL screen mode. -
The clear screen is all done via terminal escape codes.
-
The display also uses escape codes for the colours, etc...
-
The recursive FFT is a shortened version of my upload to AMINET, here:
Aminet - dev/src/FFT_AMIGA.py.txt -
I timed it and it finshed after around 3-4 seconds on the VERY FIRST run
on this very old HP laptop.
This included, python startup time, auto-expansion, clearing and drawing
the display, doing all the calculations for 65536 data points, and
finally plotting the results <- Plotting was the slowest! -
Although UNCALIBRATED in the vertical axis, it does not mean that with
careful thought it cannot be. I just use it as a DEMO. -
AND FINALLY, this is designed to work on any Python Version from 2.0.1
to the current, as of 17-10-2024, 3.13.0...ENJOY.
# SpecAn_100Hz.py
#
# (C)2023-2024, Barry Walker, G0LCU.
# Works on Python 2.5.x to the current, as of 17-10-2024, 3.13.0 without
# modificationon Linux Mint 20.3, 21.3, and Apple OSX 10.15.7.
# Pure text mode UNCALIBRATED log scaled AF Spectrum Analyser DEMO.
# Start a terminal and place as close to the upper left hand corner of the
# screen as possible.
# Called as: python3 [[/full/]path/to/]SpecAn_100Hz.py
# Auto resize the terminal to 40 lines x 144 columns. ONLY for NON-AMIGA
# platforms.
print("\033[8;40;144t")
# Reset the Terminal and clear the screen.
print("\033c\033[2J\033[H")
import sys
import cmath
# This is the recursive FFT that works on Python 2.0.1 minimum for the AMIGA
# without modification...
def fft(DATA):
N=len(DATA)
if N<=1: return(DATA)
EVEN=fft([DATA[K] for K in range(0,N,2)])
ODD=fft([DATA[K] for K in range(1,N,2)])
return([EVEN[K]+cmath.exp(-2j*cmath.pi*K/N)*ODD[K] for K in range(int(N/2))]+[EVEN[K]-cmath.exp(-2j*cmath.pi*K/N)*ODD[K] for K in range(int(N/2))])
print(" [Audio Spectrum Analyser DEMO, (C)2023 B.Walker, G0LCU. Issued as Public Domain.]")
print(" .+---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+.")
print(" 100-+\033[1;31m+---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+\033[0m+-100")
print(" U || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" N || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" C 90-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-90")
print(" A || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" L || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" I 80-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-80")
print(" B || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" R || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" A 70-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-70")
print(" T || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" E || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" D 60-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-60")
print(" || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" A || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" M 50-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-50")
print(" P || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" L || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" I 40-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-40")
print(" T || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" U || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" D 30-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-30")
print(" E || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" ^ 20-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-20")
print(" L || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" O || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" G 10-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---++-10")
print(" (Y) || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" || | | | | | | | || | | | | | | | || | | | | | | | || | | ||")
print(" 0-+\033[1;31m+---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+\033[0m+-0")
print("LOG10(X) > '+---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+--+--+-+-+-++---------+-----+---+'")
print(" | | | | | | | | | | | | | | |")
print(" 10 20 40 80 100 200 400 800 1K 2K 4K 8K 10K 20K 40K")
print(" Frequency in Hz.")
# Create an almost 100Hz square wave at 65536 samples per second.
array_list=[]
for n in range(0,100):
x=1.0
for m in range(0,327):
array_list.append(x)
x=-1.0
for m in range(0,327):
array_list.append(x)
# Add 136 bytes of 0.0 padding, making 65536 samples total.
x=0.0
for m in range(0,136):
array_list.append(x)
# Create a[n] FFT list without numpy, the AMIGA doesn't have it.
FFT=fft(array_list)
# Create the correct spacing on the X axis.
multiplierX=33.19
# Create a multiplier for the Y axis.
multiplierY=2.83
# Create an integer range for relative amplitude in text mode.
for n in range(0,65536):
FFT[n]=int(abs(multiplierY*cmath.log(1+FFT[n])))
# Plot amplitude relative values only, totally UNcalibrated.
for n in range(10,32768):
X_AXIS=int(abs(multiplierX*cmath.log10(n))-20)
# Allow for a gotcha.
if n==14: X_AXIS=17
if n==15: X_AXIS=18
if FFT[n]>0:
# Invert the value to display correctly in the terminal......
PEAK=(34-FFT[n])
# ......and ensure boundaries are not exceeded.
if PEAK<4: PEAK=4
if PEAK>34: PEAK=34
# And finally plot it.
for Y_AXIS in range(PEAK,35):
print("\033[1;34m\033["+str(Y_AXIS)+";"+str(X_AXIS)+"f*\033[0m\033[39;0f")
sys.exit(0)