kopia lustrzana https://github.com/dgiardini/rtl-ais
Clean tree
Removes all unrelated files. Signed-off-by: Nuno Goncalves <nunojpg@gmail.com>pull/2/head
rodzic
43c8e9c30f
commit
8151c6757e
BIN
heatmap/Vera.ttf
BIN
heatmap/Vera.ttf
Plik binarny nie jest wyświetlany.
|
@ -1,52 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
# todo
|
||||
# interval based summary
|
||||
# tall vs wide vs super wide output
|
||||
|
||||
def help():
|
||||
print("flatten.py input.csv")
|
||||
print("turns any rtl_power csv into a more compact summary")
|
||||
sys.exit()
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
help()
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
help()
|
||||
|
||||
path = sys.argv[1]
|
||||
|
||||
sums = defaultdict(float)
|
||||
counts = defaultdict(int)
|
||||
|
||||
def frange(start, stop, step):
|
||||
i = 0
|
||||
f = start
|
||||
while f <= stop:
|
||||
f = start + step*i
|
||||
yield f
|
||||
i += 1
|
||||
|
||||
for line in open(path):
|
||||
line = line.strip().split(', ')
|
||||
low = int(line[2])
|
||||
high = int(line[3])
|
||||
step = float(line[4])
|
||||
weight = int(line[5])
|
||||
dbm = [float(d) for d in line[6:]]
|
||||
for f,d in zip(frange(low, high, step), dbm):
|
||||
sums[f] += d*weight
|
||||
counts[f] += weight
|
||||
|
||||
ave = defaultdict(float)
|
||||
for f in sums:
|
||||
ave[f] = sums[f] / counts[f]
|
||||
|
||||
for f in sorted(ave):
|
||||
print(','.join([str(f), str(ave[f])]))
|
||||
|
||||
|
|
@ -1,549 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import os, sys, gzip, math, argparse, colorsys, datetime
|
||||
from collections import defaultdict
|
||||
from itertools import *
|
||||
|
||||
urlretrieve = lambda a, b: None
|
||||
try:
|
||||
import urllib.request
|
||||
urlretrieve = urllib.request.urlretrieve
|
||||
except:
|
||||
import urllib
|
||||
urlretrieve = urllib.urlretrieve
|
||||
|
||||
# todo:
|
||||
# matplotlib powered --interactive
|
||||
# arbitrary freq marker spacing
|
||||
# ppm
|
||||
# blue-less marker grid
|
||||
# fast summary thing
|
||||
# gain normalization
|
||||
|
||||
vera_url = "https://github.com/keenerd/rtl-sdr-misc/raw/master/heatmap/Vera.ttf"
|
||||
vera_path = os.path.join(sys.path[0], "Vera.ttf")
|
||||
|
||||
parser = argparse.ArgumentParser(description='Convert rtl_power CSV files into graphics.')
|
||||
parser.add_argument('input_path', metavar='INPUT', type=str,
|
||||
help='Input CSV file. (may be a .csv.gz)')
|
||||
parser.add_argument('output_path', metavar='OUTPUT', type=str,
|
||||
help='Output image. (various extensions supported)')
|
||||
parser.add_argument('--offset', dest='offset_freq', default=None,
|
||||
help='Shift the entire frequency range, for up/down converters.')
|
||||
parser.add_argument('--ytick', dest='time_tick', default=None,
|
||||
help='Place ticks along the Y axis every N seconds/minutes/hours/days.')
|
||||
parser.add_argument('--db', dest='db_limit', nargs=2, default=None,
|
||||
help='Minimum and maximum db values.')
|
||||
parser.add_argument('--compress', dest='compress', default=0,
|
||||
help='Apply a gradual asymptotic time compression. Values > 1 are the new target height, values < 1 are a scaling factor.')
|
||||
slicegroup = parser.add_argument_group('Slicing',
|
||||
'Efficiently render a portion of the data. (optional) Frequencies can take G/M/k suffixes. Timestamps look like "YYYY-MM-DD HH:MM:SS" Durations take d/h/m/s suffixes.')
|
||||
slicegroup.add_argument('--low', dest='low_freq', default=None,
|
||||
help='Minimum frequency for a subrange.')
|
||||
slicegroup.add_argument('--high', dest='high_freq', default=None,
|
||||
help='Maximum frequency for a subrange.')
|
||||
slicegroup.add_argument('--begin', dest='begin_time', default=None,
|
||||
help='Timestamp to start at.')
|
||||
slicegroup.add_argument('--end', dest='end_time', default=None,
|
||||
help='Timestamp to stop at.')
|
||||
slicegroup.add_argument('--head', dest='head_time', default=None,
|
||||
help='Duration to use, starting at the beginning.')
|
||||
slicegroup.add_argument('--tail', dest='tail_time', default=None,
|
||||
help='Duration to use, stopping at the end.')
|
||||
|
||||
# hack, http://stackoverflow.com/questions/9025204/
|
||||
for i, arg in enumerate(sys.argv):
|
||||
if (arg[0] == '-') and arg[1].isdigit():
|
||||
sys.argv[i] = ' ' + arg
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isfile(vera_path):
|
||||
urlretrieve(vera_url, vera_path)
|
||||
|
||||
try:
|
||||
font = ImageFont.truetype(vera_path, 10)
|
||||
except:
|
||||
print('Please download the Vera.ttf font and place it in the current directory.')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def frange(start, stop, step):
|
||||
i = 0
|
||||
while (i*step + start <= stop):
|
||||
yield i*step + start
|
||||
i += 1
|
||||
|
||||
def min_filter(row):
|
||||
size = 3
|
||||
result = []
|
||||
for i in range(size):
|
||||
here = row[i]
|
||||
near = row[0:i] + row[i+1:size]
|
||||
if here > min(near):
|
||||
result.append(here)
|
||||
continue
|
||||
result.append(min(near))
|
||||
for i in range(size-1, len(row)):
|
||||
here = row[i]
|
||||
near = row[i-(size-1):i]
|
||||
if here > min(near):
|
||||
result.append(here)
|
||||
continue
|
||||
result.append(min(near))
|
||||
return result
|
||||
|
||||
def floatify(zs):
|
||||
# nix errors with -inf, windows errors with -1.#J
|
||||
zs2 = []
|
||||
previous = 0 # awkward for single-column rows
|
||||
for z in zs:
|
||||
try:
|
||||
z = float(z)
|
||||
except ValueError:
|
||||
z = previous
|
||||
if math.isinf(z):
|
||||
z = previous
|
||||
if math.isnan(z):
|
||||
z = previous
|
||||
zs2.append(z)
|
||||
previous = z
|
||||
return zs2
|
||||
|
||||
def freq_parse(s):
|
||||
suffix = 1
|
||||
if s.lower().endswith('k'):
|
||||
suffix = 1e3
|
||||
if s.lower().endswith('m'):
|
||||
suffix = 1e6
|
||||
if s.lower().endswith('g'):
|
||||
suffix = 1e9
|
||||
if suffix != 1:
|
||||
s = s[:-1]
|
||||
return float(s) * suffix
|
||||
|
||||
def duration_parse(s):
|
||||
suffix = 1
|
||||
if s.lower().endswith('s'):
|
||||
suffix = 1
|
||||
if s.lower().endswith('m'):
|
||||
suffix = 60
|
||||
if s.lower().endswith('h'):
|
||||
suffix = 60 * 60
|
||||
if s.lower().endswith('d'):
|
||||
suffix = 24 * 60 * 60
|
||||
if suffix != 1 or s.lower().endswith('s'):
|
||||
s = s[:-1]
|
||||
return float(s) * suffix
|
||||
|
||||
def date_parse(s):
|
||||
if '-' not in s:
|
||||
return datetime.datetime.fromtimestamp(int(s))
|
||||
return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
def gzip_wrap(path):
|
||||
"hides silly CRC errors"
|
||||
iterator = gzip.open(path, 'rb')
|
||||
running = True
|
||||
while running:
|
||||
try:
|
||||
line = next(iterator)
|
||||
if type(line) == bytes:
|
||||
line = line.decode('utf-8')
|
||||
yield line
|
||||
except IOError:
|
||||
running = False
|
||||
|
||||
def reparse(label, fn):
|
||||
if args.__getattribute__(label) is None:
|
||||
return
|
||||
args.__setattr__(label, fn(args.__getattribute__(label)))
|
||||
|
||||
path = args.input_path
|
||||
output = args.output_path
|
||||
|
||||
raw_data = lambda: open(path)
|
||||
if path.endswith('.gz'):
|
||||
raw_data = lambda: gzip_wrap(path)
|
||||
|
||||
reparse('low_freq', freq_parse)
|
||||
reparse('high_freq', freq_parse)
|
||||
reparse('offset_freq', freq_parse)
|
||||
if args.offset_freq is None:
|
||||
args.offset_freq = 0
|
||||
reparse('time_tick', duration_parse)
|
||||
reparse('begin_time', date_parse)
|
||||
reparse('end_time', date_parse)
|
||||
reparse('head_time', duration_parse)
|
||||
reparse('tail_time', duration_parse)
|
||||
reparse('head_time', lambda s: datetime.timedelta(seconds=s))
|
||||
reparse('tail_time', lambda s: datetime.timedelta(seconds=s))
|
||||
args.compress = float(args.compress)
|
||||
|
||||
if args.begin_time and args.tail_time:
|
||||
print("Can't combine --begin and --tail")
|
||||
sys.exit(2)
|
||||
if args.end_time and args.head_time:
|
||||
print("Can't combine --end and --head")
|
||||
sys.exit(2)
|
||||
if args.head_time and args.tail_time:
|
||||
print("Can't combine --head and --tail")
|
||||
sys.exit(2)
|
||||
|
||||
print("loading")
|
||||
|
||||
def slice_columns(columns, low_freq, high_freq):
|
||||
start_col = 0
|
||||
stop_col = len(columns)
|
||||
if args.low_freq is not None and low <= args.low_freq <= high:
|
||||
start_col = sum(f<args.low_freq for f in columns)
|
||||
if args.high_freq is not None and low <= args.high_freq <= high:
|
||||
stop_col = sum(f<=args.high_freq for f in columns)
|
||||
return start_col, stop_col-1
|
||||
|
||||
freqs = set()
|
||||
f_cache = set()
|
||||
times = set()
|
||||
labels = set()
|
||||
min_z = 0
|
||||
max_z = -100
|
||||
start, stop = None, None
|
||||
|
||||
if args.db_limit:
|
||||
min_z = min(map(float, args.db_limit))
|
||||
max_z = max(map(float, args.db_limit))
|
||||
|
||||
for line in raw_data():
|
||||
line = [s.strip() for s in line.strip().split(',')]
|
||||
#line = [line[0], line[1]] + [float(s) for s in line[2:] if s]
|
||||
line = [s for s in line if s]
|
||||
|
||||
low = int(line[2]) + args.offset_freq
|
||||
high = int(line[3]) + args.offset_freq
|
||||
step = float(line[4])
|
||||
t = line[0] + ' ' + line[1]
|
||||
if '-' not in line[0]:
|
||||
t = line[0]
|
||||
|
||||
if args.low_freq is not None and high < args.low_freq:
|
||||
continue
|
||||
if args.high_freq is not None and args.high_freq < low:
|
||||
continue
|
||||
if args.begin_time is not None and date_parse(t) < args.begin_time:
|
||||
continue
|
||||
if args.end_time is not None and date_parse(t) > args.end_time:
|
||||
break
|
||||
times.add(t)
|
||||
columns = list(frange(low, high, step))
|
||||
start_col, stop_col = slice_columns(columns, args.low_freq, args.high_freq)
|
||||
f_key = (columns[start_col], columns[stop_col], step)
|
||||
zs = line[6+start_col:6+stop_col+1]
|
||||
if not zs:
|
||||
continue
|
||||
if f_key not in f_cache:
|
||||
freq2 = list(frange(*f_key))[:len(zs)]
|
||||
freqs.update(freq2)
|
||||
#freqs.add(f_key[1]) # high
|
||||
#labels.add(f_key[0]) # low
|
||||
f_cache.add(f_key)
|
||||
|
||||
if not args.db_limit:
|
||||
zs = floatify(zs)
|
||||
min_z = min(min_z, min(zs))
|
||||
max_z = max(max_z, max(zs))
|
||||
|
||||
if start is None:
|
||||
start = date_parse(t)
|
||||
stop = date_parse(t)
|
||||
if args.head_time is not None and args.end_time is None:
|
||||
args.end_time = start + args.head_time
|
||||
|
||||
if args.tail_time is not None:
|
||||
times = [t for t in times if date_parse(t) >= (stop - args.tail_time)]
|
||||
start = date_parse(min(times))
|
||||
|
||||
freqs = list(sorted(list(freqs)))
|
||||
times = list(sorted(list(times)))
|
||||
labels = list(sorted(list(labels)))
|
||||
|
||||
if len(labels) == 1:
|
||||
delta = (max(freqs) - min(freqs)) / (len(freqs) / 500.0)
|
||||
delta = round(delta / 10**int(math.log10(delta))) * 10**int(math.log10(delta))
|
||||
delta = int(delta)
|
||||
lower = int(math.ceil(min(freqs) / delta) * delta)
|
||||
labels = list(range(lower, int(max(freqs)), delta))
|
||||
|
||||
def compression(y, decay):
|
||||
return int(round((1/decay)*math.exp(y*decay) - 1/decay))
|
||||
|
||||
height = len(times)
|
||||
height2 = height
|
||||
if args.compress:
|
||||
if args.compress > height:
|
||||
args.compress = 0
|
||||
print("Image too short, disabling compression")
|
||||
if 0 < args.compress < 1:
|
||||
args.compress *= height
|
||||
if args.compress:
|
||||
args.compress = -1 / args.compress
|
||||
height2 = compression(height, args.compress)
|
||||
|
||||
print("x: %i, y: %i, z: (%f, %f)" % (len(freqs), height2, min_z, max_z))
|
||||
|
||||
def rgb2(z):
|
||||
g = (z - min_z) / (max_z - min_z)
|
||||
return (int(g*255), int(g*255), 50)
|
||||
|
||||
def rgb3(z):
|
||||
g = (z - min_z) / (max_z - min_z)
|
||||
c = colorsys.hsv_to_rgb(0.65-(g-0.08), 1, 0.2+g)
|
||||
return (int(c[0]*256),int(c[1]*256),int(c[2]*256))
|
||||
|
||||
def collate_row(x_size):
|
||||
# this is more fragile than the old code
|
||||
# sensitive to timestamps that are out of order
|
||||
old_t = None
|
||||
row = [0.0] * x_size
|
||||
for line in raw_data():
|
||||
line = [s.strip() for s in line.strip().split(',')]
|
||||
#line = [line[0], line[1]] + [float(s) for s in line[2:] if s]
|
||||
line = [s for s in line if s]
|
||||
t = line[0] + ' ' + line[1]
|
||||
if '-' not in line[0]:
|
||||
t = line[0]
|
||||
if t not in times:
|
||||
continue # happens with live files and time cropping
|
||||
if old_t is None:
|
||||
old_t = t
|
||||
low = int(line[2]) + args.offset_freq
|
||||
high = int(line[3]) + args.offset_freq
|
||||
step = float(line[4])
|
||||
columns = list(frange(low, high, step))
|
||||
start_col, stop_col = slice_columns(columns, args.low_freq, args.high_freq)
|
||||
if args.low_freq and columns[stop_col] < args.low_freq:
|
||||
continue
|
||||
if args.high_freq and columns[start_col] > args.high_freq:
|
||||
continue
|
||||
start_freq = columns[start_col]
|
||||
if args.low_freq:
|
||||
start_freq = max(args.low_freq, start_freq)
|
||||
# sometimes fails? skip or abort?
|
||||
x_start = freqs.index(start_freq)
|
||||
zs = floatify(line[6+start_col:6+stop_col+1])
|
||||
if t != old_t:
|
||||
yield old_t, row
|
||||
row = [0.0] * x_size
|
||||
old_t = t
|
||||
for i in range(len(zs)):
|
||||
x = x_start + i
|
||||
if x >= x_size:
|
||||
continue
|
||||
row[x] = zs[i]
|
||||
yield old_t, row
|
||||
|
||||
print("drawing")
|
||||
tape_height = 25
|
||||
img = Image.new("RGB", (len(freqs), tape_height + height2))
|
||||
pix = img.load()
|
||||
x_size = img.size[0]
|
||||
average = [0.0] * len(freqs)
|
||||
tally = 0
|
||||
old_y = None
|
||||
for t, zs in collate_row(x_size):
|
||||
y = times.index(t)
|
||||
if not args.compress:
|
||||
for x in range(len(zs)):
|
||||
pix[x,y+tape_height] = rgb2(zs[x])
|
||||
continue
|
||||
# ugh
|
||||
y = height2 - compression(height - y, args.compress)
|
||||
if old_y is None:
|
||||
old_y = y
|
||||
if old_y != y:
|
||||
for x in range(len(average)):
|
||||
pix[x,old_y+tape_height] = rgb2(average[x]/tally)
|
||||
tally = 0
|
||||
average = [0.0] * len(freqs)
|
||||
old_y = y
|
||||
for x in range(len(zs)):
|
||||
average[x] += zs[x]
|
||||
tally += 1
|
||||
|
||||
|
||||
def closest_index(n, m_list, interpolate=False):
|
||||
"assumes sorted m_list, returns two points for interpolate"
|
||||
i = len(m_list) // 2
|
||||
jump = len(m_list) // 2
|
||||
while jump > 1:
|
||||
i_down = i - jump
|
||||
i_here = i
|
||||
i_up = i + jump
|
||||
if i_down < 0:
|
||||
i_down = i
|
||||
if i_up >= len(m_list):
|
||||
i_up = i
|
||||
e_down = abs(m_list[i_down] - n)
|
||||
e_here = abs(m_list[i_here] - n)
|
||||
e_up = abs(m_list[i_up] - n)
|
||||
e_best = min([e_down, e_here, e_up])
|
||||
if e_down == e_best:
|
||||
i = i_down
|
||||
if e_up == e_best:
|
||||
i = i_up
|
||||
if e_here == e_best:
|
||||
i = i_here
|
||||
jump = jump // 2
|
||||
if not interpolate:
|
||||
return i
|
||||
if n < m_list[i] and i > 0:
|
||||
return i-1, i
|
||||
if n > m_list[i] and i < len(m_list)-1:
|
||||
return i, i+1
|
||||
return i, i
|
||||
|
||||
def word_aa(label, pt, fg_color, bg_color):
|
||||
f = ImageFont.truetype(vera_path, pt*3)
|
||||
s = f.getsize(label)
|
||||
s = (s[0], pt*3 + 3) # getsize lies, manually compute
|
||||
w_img = Image.new("RGB", s, bg_color)
|
||||
w_draw = ImageDraw.Draw(w_img)
|
||||
w_draw.text((0, 0), label, font=f, fill=fg_color)
|
||||
return w_img.resize((s[0]//3, s[1]//3), Image.ANTIALIAS)
|
||||
|
||||
def blend(percent, c1, c2):
|
||||
"c1 and c2 are RGB tuples"
|
||||
# probably isn't gamma correct
|
||||
r = c1[0] * percent + c2[0] * (1 - percent)
|
||||
g = c1[1] * percent + c2[1] * (1 - percent)
|
||||
b = c1[2] * percent + c2[2] * (1 - percent)
|
||||
c3 = map(int, map(round, [r,g,b]))
|
||||
return tuple(c3)
|
||||
|
||||
def tape_lines(interval, y1, y2, used=set()):
|
||||
"returns the number of lines"
|
||||
low_f = (min(freqs) // interval) * interval
|
||||
high_f = (1 + max(freqs) // interval) * interval
|
||||
hits = 0
|
||||
blur = lambda p: blend(p, (255, 255, 0), (0, 0, 0))
|
||||
for i in range(int(low_f), int(high_f), int(interval)):
|
||||
if not (min(freqs) < i < max(freqs)):
|
||||
continue
|
||||
hits += 1
|
||||
if i in used:
|
||||
continue
|
||||
x1,x2 = closest_index(i, freqs, interpolate=True)
|
||||
if x1 == x2:
|
||||
draw.line([x1,y1,x1,y2], fill='black')
|
||||
else:
|
||||
percent = (i - freqs[x1]) / float(freqs[x2] - freqs[x1])
|
||||
draw.line([x1,y1,x1,y2], fill=blur(percent))
|
||||
draw.line([x2,y1,x2,y2], fill=blur(1-percent))
|
||||
used.add(i)
|
||||
return hits
|
||||
|
||||
def tape_text(interval, y, used=set()):
|
||||
low_f = (min(freqs) // interval) * interval
|
||||
high_f = (1 + max(freqs) // interval) * interval
|
||||
for i in range(int(low_f), int(high_f), int(interval)):
|
||||
if i in used:
|
||||
continue
|
||||
if not (min(freqs) < i < max(freqs)):
|
||||
continue
|
||||
x = closest_index(i, freqs)
|
||||
s = str(i)
|
||||
if interval >= 1e6:
|
||||
s = '%iM' % (i/1e6)
|
||||
elif interval > 1000:
|
||||
s = '%ik' % ((i/1e3) % 1000)
|
||||
if s.startswith('0'):
|
||||
s = '%iM' % (i/1e6)
|
||||
else:
|
||||
s = '%i' % (i%1000)
|
||||
if s.startswith('0'):
|
||||
s = '%ik' % ((i/1e3) % 1000)
|
||||
if s.startswith('0'):
|
||||
s = '%iM' % (i/1e6)
|
||||
w = word_aa(s, tape_pt, 'black', 'yellow')
|
||||
img.paste(w, (x - w.size[0]//2, y))
|
||||
used.add(i)
|
||||
|
||||
def shadow_text(x, y, s, font, fg_color='white', bg_color='black'):
|
||||
draw.text((x+1, y+1), s, font=font, fill=bg_color)
|
||||
draw.text((x, y), s, font=font, fill=fg_color)
|
||||
|
||||
print("labeling")
|
||||
tape_pt = 10
|
||||
draw = ImageDraw.Draw(img)
|
||||
font = ImageFont.load_default()
|
||||
pixel_width = step
|
||||
|
||||
draw.rectangle([0,0,img.size[0],tape_height], fill='yellow')
|
||||
min_freq = min(freqs)
|
||||
max_freq = max(freqs)
|
||||
delta = max_freq - min_freq
|
||||
width = len(freqs)
|
||||
label_base = 9
|
||||
|
||||
for i in range(label_base, 0, -1):
|
||||
interval = int(10**i)
|
||||
low_f = (min_freq // interval) * interval
|
||||
high_f = (1 + max_freq // interval) * interval
|
||||
hits = len(range(int(low_f), int(high_f), interval))
|
||||
if hits >= 4:
|
||||
label_base = i
|
||||
break
|
||||
label_base = 10**label_base
|
||||
|
||||
for scale,y in [(1,10), (5,15), (10,19), (50,22), (100,24), (500, 25)]:
|
||||
hits = tape_lines(label_base/scale, y, tape_height)
|
||||
pixels_per_hit = width / hits
|
||||
if pixels_per_hit > 50:
|
||||
tape_text(label_base/scale, y-tape_pt)
|
||||
if pixels_per_hit < 10:
|
||||
break
|
||||
|
||||
if args.time_tick:
|
||||
label_format = "%H:%M:%S"
|
||||
if args.time_tick % (60*60*24) == 0:
|
||||
label_format = "%Y-%m-%d"
|
||||
elif args.time_tick % 60 == 0:
|
||||
label_format = "%H:%M"
|
||||
label_next = datetime.datetime(start.year, start.month, start.day, start.hour)
|
||||
tick_delta = datetime.timedelta(seconds = args.time_tick)
|
||||
while label_next < start:
|
||||
label_next += tick_delta
|
||||
last_y = -100
|
||||
for y,t in enumerate(times):
|
||||
label_time = date_parse(t)
|
||||
if label_time < label_next:
|
||||
continue
|
||||
if args.compress:
|
||||
y = height2 - compression(height - y, args.compress)
|
||||
if y - last_y > 15:
|
||||
shadow_text(2, y+tape_height, label_next.strftime(label_format), font)
|
||||
last_y = y
|
||||
label_next += tick_delta
|
||||
|
||||
|
||||
duration = stop - start
|
||||
duration = duration.days * 24*60*60 + duration.seconds + 30
|
||||
pixel_height = duration / len(times)
|
||||
hours = int(duration / 3600)
|
||||
minutes = int((duration - 3600*hours) / 60)
|
||||
margin = 2
|
||||
if args.time_tick:
|
||||
margin = 60
|
||||
shadow_text(margin, img.size[1] - 45, 'Duration: %i:%02i' % (hours, minutes), font)
|
||||
shadow_text(margin, img.size[1] - 35, 'Range: %.2fMHz - %.2fMHz' % (min(freqs)/1e6, (max(freqs)+pixel_width)/1e6), font)
|
||||
shadow_text(margin, img.size[1] - 25, 'Pixel: %.2fHz x %is' % (pixel_width, int(round(pixel_height))), font)
|
||||
shadow_text(margin, img.size[1] - 15, 'Started: {0}'.format(start), font)
|
||||
# bin size
|
||||
|
||||
print("saving")
|
||||
img.save(output)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
takes raw iq, turns into heatmap
|
||||
extremely crude, lacks features like windowing
|
||||
"""
|
||||
|
||||
import sys, math, struct
|
||||
import numpy
|
||||
from PIL import Image
|
||||
|
||||
def help():
|
||||
print("raw_iq.py bins averages sample-type input.raw")
|
||||
print(" sample_types: u1 (uint8), s1 (int8), s2 (int16)")
|
||||
sys.exit()
|
||||
|
||||
def byte_reader(path, sample):
|
||||
dtype = None
|
||||
offset = 0
|
||||
scale = 2**7
|
||||
if sample == 'u1':
|
||||
dtype = numpy.uint8
|
||||
offset = -127
|
||||
if sample == 's1':
|
||||
dtype = numpy.int8
|
||||
if sample == 's2':
|
||||
dtype = numpy.int16
|
||||
scale = 2**15
|
||||
raw = numpy.fromfile(path, dtype).astype(numpy.float64)
|
||||
raw += offset
|
||||
raw /= scale
|
||||
return raw[0::2] + 1j * raw[1::2]
|
||||
|
||||
def psd(data, bin_count, averages):
|
||||
"really basic, lacks windowing"
|
||||
length = len(data)
|
||||
table = [numpy.zeros(bin_count)]
|
||||
ave = 0
|
||||
for i in range(0, length, bin_count):
|
||||
sub_data = numpy.array(data[i:i+bin_count])
|
||||
dc_bias = sum(sub_data) / len(sub_data)
|
||||
#sub_data -= dc_bias
|
||||
fft = numpy.fft.fft(sub_data)
|
||||
if len(fft) != bin_count:
|
||||
continue
|
||||
table[-1] = table[-1] + numpy.real(numpy.conjugate(fft)*fft)
|
||||
ave += 1
|
||||
if ave >= averages:
|
||||
ave = max(1, ave)
|
||||
row = table[-1]
|
||||
row = numpy.concatenate((row[bin_count//2:], row[:bin_count//2]))
|
||||
# spurious warnings
|
||||
table[-1] = 10 * numpy.log10(row / ave)
|
||||
table.append(numpy.zeros(bin_count))
|
||||
ave = 0
|
||||
if ave != 0:
|
||||
row = table[-1]
|
||||
row = numpy.concatenate((row[bin_count//2:], row[:bin_count//2]))
|
||||
table[-1] = 10 * numpy.log10(row / ave)
|
||||
if ave == 0:
|
||||
table.pop(-1)
|
||||
return table
|
||||
|
||||
def rgb2(z, lowest, highest):
|
||||
g = (z - lowest) / (highest - lowest)
|
||||
return (int(g*255), int(g*255), 50)
|
||||
|
||||
def heatmap(table):
|
||||
lowest = -1
|
||||
highest = -100
|
||||
for row in table:
|
||||
lowest = min(lowest, min(z for z in row if not math.isinf(z)))
|
||||
highest = max(highest, max(row))
|
||||
img = Image.new("RGB", (len(table[0]), len(table)))
|
||||
pix = img.load()
|
||||
for y,row in enumerate(table):
|
||||
for x,val in enumerate(row):
|
||||
if not val >= lowest: # fast nan/-inf test
|
||||
val = lowest
|
||||
pix[x,y] = rgb2(val, lowest, highest)
|
||||
return img
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
_, bin_count, averages, sample, path = sys.argv
|
||||
bin_count = int(bin_count)
|
||||
bin_count = int(2**(math.ceil(math.log(bin_count, 2))))
|
||||
averages = int(averages)
|
||||
except:
|
||||
help()
|
||||
print("loading data")
|
||||
data = byte_reader(path, sample)
|
||||
print("estimated size: %i x %i" % (bin_count,
|
||||
int(len(data) / (bin_count*averages))))
|
||||
print("crunching fft")
|
||||
fft_table = psd(data, bin_count, averages)
|
||||
print("drawing image")
|
||||
img = heatmap(fft_table)
|
||||
print("saving image")
|
||||
img.save(path + '.png')
|
||||
|
||||
|
||||
|
Plik binarny nie jest wyświetlany.
|
@ -1,13 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# todo, a real makefile
|
||||
|
||||
files="waterfall.c"
|
||||
binary="waterfall"
|
||||
flags="-Wall -O2"
|
||||
includes="-I/usr/include/libusb-1.0"
|
||||
libs="-lSDL -lSDL_image -lSDL_ttf -lusb-1.0 -lrtlsdr -lpthread -lm"
|
||||
|
||||
rm -f $binary
|
||||
gcc -o $binary $files $flags $includes $libs
|
||||
|
Plik binarny nie jest wyświetlany.
|
@ -1,395 +0,0 @@
|
|||
/*
|
||||
* rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver
|
||||
* Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>
|
||||
* Copyright (C) 2012 by Hoernchen <la@tfc-server.de>
|
||||
* Copyright (C) 2012 by Kyle Keen <keenerd@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// a quick and horrible hack job of rtl_power.c
|
||||
// 1024 element FFT
|
||||
// no downsampling
|
||||
// dedicated thread
|
||||
// external flags for retune, gain change, data ready, quit
|
||||
// todo, preface with fft_
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#include "rtl-sdr.h"
|
||||
|
||||
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
||||
|
||||
#define FFT_LEVEL 10
|
||||
#define FFT_STACK 4
|
||||
#define FFT_SIZE (1 << FFT_LEVEL)
|
||||
#define DEFAULT_BUF_LENGTH (2 * FFT_SIZE * FFT_STACK)
|
||||
#define BUFFER_DUMP (1<<12)
|
||||
#define DEFAULT_ASYNC_BUF_NUMBER 32
|
||||
#define SAMPLE_RATE 3200000
|
||||
#define PRESCALE 8
|
||||
#define POSTSCALE 2
|
||||
#define FREQ_MIN 27000000
|
||||
#define FREQ_MAX 1700000000
|
||||
|
||||
struct buffer
|
||||
{
|
||||
// each buffer should have one writer and one reader thread
|
||||
// the reader waits for the cond
|
||||
int16_t buf[DEFAULT_BUF_LENGTH];
|
||||
int len;
|
||||
pthread_rwlock_t rw;
|
||||
pthread_cond_t ready;
|
||||
pthread_mutex_t ready_m;
|
||||
int ready_fast;
|
||||
};
|
||||
|
||||
// shared items
|
||||
|
||||
static volatile int do_exit = 0;
|
||||
static rtlsdr_dev_t *dev = NULL;
|
||||
static struct buffer fft_out;
|
||||
static int frequency = 97000000;
|
||||
|
||||
// local items
|
||||
|
||||
struct buffer rtl_out;
|
||||
struct buffer fft_tmp;
|
||||
|
||||
int16_t* Sinewave;
|
||||
double* power_table;
|
||||
int N_WAVE, LOG2_N_WAVE;
|
||||
int next_power;
|
||||
int16_t *fft_buf;
|
||||
int *window_coefs;
|
||||
|
||||
pthread_t dongle_thread;
|
||||
pthread_t fft_thread;
|
||||
|
||||
#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m)
|
||||
#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m)
|
||||
|
||||
// some functions from convenience.c
|
||||
|
||||
void gain_default(void)
|
||||
{
|
||||
int count;
|
||||
int* gains;
|
||||
count = rtlsdr_get_tuner_gains(dev, NULL);
|
||||
if (count <= 0)
|
||||
{return;}
|
||||
gains = malloc(sizeof(int) * count);
|
||||
count = rtlsdr_get_tuner_gains(dev, gains);
|
||||
rtlsdr_set_tuner_gain(dev, gains[count-1]);
|
||||
free(gains);
|
||||
}
|
||||
|
||||
void gain_increase(void)
|
||||
{
|
||||
int i, g, count;
|
||||
int* gains;
|
||||
count = rtlsdr_get_tuner_gains(dev, NULL);
|
||||
if (count <= 0)
|
||||
{return;}
|
||||
gains = malloc(sizeof(int) * count);
|
||||
count = rtlsdr_get_tuner_gains(dev, gains);
|
||||
g = rtlsdr_get_tuner_gain(dev);
|
||||
for (i=0; i<(count-1); i++)
|
||||
{
|
||||
if (gains[i] == g)
|
||||
{
|
||||
rtlsdr_set_tuner_gain(dev, gains[i+1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(gains);
|
||||
}
|
||||
|
||||
void gain_decrease(void)
|
||||
{
|
||||
int i, g, count;
|
||||
int* gains;
|
||||
count = rtlsdr_get_tuner_gains(dev, NULL);
|
||||
if (count <= 0)
|
||||
{return;}
|
||||
gains = malloc(sizeof(int) * count);
|
||||
count = rtlsdr_get_tuner_gains(dev, gains);
|
||||
g = rtlsdr_get_tuner_gain(dev);
|
||||
for (i=1; i<count; i++)
|
||||
{
|
||||
if (gains[i] == g)
|
||||
{
|
||||
rtlsdr_set_tuner_gain(dev, gains[i-1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(gains);
|
||||
}
|
||||
|
||||
void frequency_set(void)
|
||||
{
|
||||
if (frequency < FREQ_MIN)
|
||||
{frequency = FREQ_MIN;}
|
||||
if (frequency > FREQ_MAX)
|
||||
{frequency = FREQ_MAX;}
|
||||
rtlsdr_set_center_freq(dev, frequency);
|
||||
}
|
||||
|
||||
// fft stuff
|
||||
|
||||
void sine_table(int size)
|
||||
{
|
||||
int i;
|
||||
double d;
|
||||
LOG2_N_WAVE = size;
|
||||
N_WAVE = 1 << LOG2_N_WAVE;
|
||||
Sinewave = malloc(sizeof(int16_t) * N_WAVE*3/4);
|
||||
power_table = malloc(sizeof(double) * N_WAVE);
|
||||
for (i=0; i<N_WAVE*3/4; i++)
|
||||
{
|
||||
d = (double)i * 2.0 * M_PI / N_WAVE;
|
||||
Sinewave[i] = (int)round(32767*sin(d));
|
||||
}
|
||||
}
|
||||
|
||||
inline int16_t FIX_MPY(int16_t a, int16_t b)
|
||||
/* fixed point multiply and scale */
|
||||
{
|
||||
int c = ((int)a * (int)b) >> 14;
|
||||
b = c & 0x01;
|
||||
return (c >> 1) + b;
|
||||
}
|
||||
|
||||
int fix_fft(int16_t iq[], int m)
|
||||
/* interleaved iq[], 0 <= n < 2**m, changes in place */
|
||||
{
|
||||
int mr, nn, i, j, l, k, istep, n, shift;
|
||||
int16_t qr, qi, tr, ti, wr, wi;
|
||||
n = 1 << m;
|
||||
if (n > N_WAVE)
|
||||
{return -1;}
|
||||
mr = 0;
|
||||
nn = n - 1;
|
||||
/* decimation in time - re-order data */
|
||||
for (m=1; m<=nn; ++m) {
|
||||
l = n;
|
||||
do
|
||||
{l >>= 1;}
|
||||
while (mr+l > nn);
|
||||
mr = (mr & (l-1)) + l;
|
||||
if (mr <= m)
|
||||
{continue;}
|
||||
// real = 2*m, imag = 2*m+1
|
||||
tr = iq[2*m];
|
||||
iq[2*m] = iq[2*mr];
|
||||
iq[2*mr] = tr;
|
||||
ti = iq[2*m+1];
|
||||
iq[2*m+1] = iq[2*mr+1];
|
||||
iq[2*mr+1] = ti;
|
||||
}
|
||||
l = 1;
|
||||
k = LOG2_N_WAVE-1;
|
||||
while (l < n) {
|
||||
shift = 1;
|
||||
istep = l << 1;
|
||||
for (m=0; m<l; ++m) {
|
||||
j = m << k;
|
||||
wr = Sinewave[j+N_WAVE/4];
|
||||
wi = -Sinewave[j];
|
||||
if (shift) {
|
||||
wr >>= 1; wi >>= 1;}
|
||||
for (i=m; i<n; i+=istep) {
|
||||
j = i + l;
|
||||
tr = FIX_MPY(wr,iq[2*j]) - FIX_MPY(wi,iq[2*j+1]);
|
||||
ti = FIX_MPY(wr,iq[2*j+1]) + FIX_MPY(wi,iq[2*j]);
|
||||
qr = iq[2*i];
|
||||
qi = iq[2*i+1];
|
||||
if (shift) {
|
||||
qr >>= 1; qi >>= 1;}
|
||||
iq[2*j] = qr - tr;
|
||||
iq[2*j+1] = qi - ti;
|
||||
iq[2*i] = qr + tr;
|
||||
iq[2*i+1] = qi + ti;
|
||||
}
|
||||
}
|
||||
--k;
|
||||
l = istep;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void remove_dc(int16_t *data, int length)
|
||||
/* works on interleaved data */
|
||||
{
|
||||
int i;
|
||||
int16_t ave;
|
||||
long sum = 0L;
|
||||
for (i=0; i < length; i+=2) {
|
||||
sum += data[i];
|
||||
}
|
||||
ave = (int16_t)(sum / (long)(length));
|
||||
if (ave == 0) {
|
||||
return;}
|
||||
for (i=0; i < length; i+=2) {
|
||||
data[i] -= ave;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t real_conj(int16_t real, int16_t imag)
|
||||
/* real(n * conj(n)) */
|
||||
{
|
||||
return ((int32_t)real*(int32_t)real + (int32_t)imag*(int32_t)imag);
|
||||
}
|
||||
|
||||
// threading stuff
|
||||
|
||||
void rtl_callback_fn(unsigned char *buf, uint32_t len, void *ctx)
|
||||
{
|
||||
int i;
|
||||
if (do_exit)
|
||||
{return;}
|
||||
pthread_rwlock_wrlock(&rtl_out.rw);
|
||||
for (i=0; i<len; i++)
|
||||
{
|
||||
rtl_out.buf[i] = ((int16_t)buf[i]) - 127;
|
||||
}
|
||||
rtl_out.len = len;
|
||||
pthread_rwlock_unlock(&rtl_out.rw);
|
||||
safe_cond_signal(&rtl_out.ready, &rtl_out.ready_m);
|
||||
}
|
||||
|
||||
void* dongle_thread_fn(void *arg)
|
||||
{
|
||||
rtlsdr_read_async(dev, rtl_callback_fn, NULL,
|
||||
DEFAULT_ASYNC_BUF_NUMBER, DEFAULT_BUF_LENGTH);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* fft_thread_fn(void *arg)
|
||||
{
|
||||
int i, i2, p, len, offset;
|
||||
int16_t buf1[DEFAULT_BUF_LENGTH];
|
||||
int16_t buf2[DEFAULT_BUF_LENGTH];
|
||||
while (!do_exit)
|
||||
{
|
||||
safe_cond_wait(&rtl_out.ready, &rtl_out.ready_m);
|
||||
pthread_rwlock_rdlock(&rtl_out.rw);
|
||||
for (i=0; i<rtl_out.len; i++)
|
||||
{
|
||||
buf1[i] = rtl_out.buf[i] * PRESCALE;
|
||||
}
|
||||
len = rtl_out.len;
|
||||
pthread_rwlock_unlock(&rtl_out.rw);
|
||||
// compute
|
||||
//remove_dc(fft_buf, buf_len / ds);
|
||||
//remove_dc(fft_buf+1, (buf_len / ds) - 1);
|
||||
for (offset=0; offset<len; offset+=(2*FFT_SIZE))
|
||||
{
|
||||
fix_fft(buf1+offset, FFT_LEVEL);
|
||||
for (i=0; i<FFT_SIZE; i++)
|
||||
{
|
||||
if (offset == 0)
|
||||
{buf2[i] = 0;}
|
||||
//buf1[i] = rtl_out.buf[i];
|
||||
//p = buf1[i] * buf1[i];
|
||||
i2 = offset + i*2;
|
||||
p = (int16_t)real_conj(buf1[i2], buf1[i2 + 1]);
|
||||
buf2[i] += p;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_wrlock(&fft_out.rw);
|
||||
fft_out.len = FFT_SIZE;
|
||||
// fft is 180 degrees off
|
||||
len = FFT_SIZE / 2;
|
||||
for (i=0; i<len; i++)
|
||||
{
|
||||
fft_out.buf[i] = (int)log10(POSTSCALE * (float)buf2[i+len]);
|
||||
fft_out.buf[i+len] = (int)log10(POSTSCALE * (float)buf2[i]);
|
||||
}
|
||||
pthread_rwlock_unlock(&fft_out.rw);
|
||||
safe_cond_signal(&fft_out.ready, &fft_out.ready_m);
|
||||
fft_out.ready_fast = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int buffer_init(struct buffer* buf)
|
||||
{
|
||||
pthread_rwlock_init(&buf->rw, NULL);
|
||||
pthread_cond_init(&buf->ready, NULL);
|
||||
pthread_mutex_init(&buf->ready_m, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int buffer_cleanup(struct buffer* buf)
|
||||
{
|
||||
pthread_rwlock_destroy(&buf->rw);
|
||||
pthread_cond_destroy(&buf->ready);
|
||||
pthread_mutex_destroy(&buf->ready_m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fft_launch(void)
|
||||
{
|
||||
sine_table(FFT_LEVEL);
|
||||
|
||||
buffer_init(&rtl_out);
|
||||
buffer_init(&fft_tmp);
|
||||
buffer_init(&fft_out);
|
||||
|
||||
rtlsdr_open(&dev, 0); // todo, verbose_device_search()
|
||||
|
||||
// settings
|
||||
rtlsdr_reset_buffer(dev);
|
||||
rtlsdr_set_center_freq(dev, frequency);
|
||||
rtlsdr_set_sample_rate(dev, SAMPLE_RATE);
|
||||
rtlsdr_set_tuner_gain_mode(dev, 1);
|
||||
gain_default();
|
||||
|
||||
pthread_create(&dongle_thread, NULL, &dongle_thread_fn, NULL);
|
||||
pthread_create(&fft_thread, NULL, &fft_thread_fn, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fft_cleanup(void)
|
||||
{
|
||||
do_exit = 1;
|
||||
usleep(10000);
|
||||
rtlsdr_cancel_async(dev);
|
||||
pthread_join(dongle_thread, NULL);
|
||||
safe_cond_signal(&rtl_out.ready, &rtl_out.ready_m);
|
||||
pthread_join(fft_thread, NULL);
|
||||
safe_cond_signal(&fft_out.ready, &fft_out.ready_m);
|
||||
|
||||
rtlsdr_close(dev);
|
||||
|
||||
buffer_cleanup(&rtl_out);
|
||||
buffer_cleanup(&fft_tmp);
|
||||
buffer_cleanup(&fft_out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab
|
BIN
rtl-sdl/sdl1.png
BIN
rtl-sdl/sdl1.png
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 42 KiB |
BIN
rtl-sdl/sdl2.png
BIN
rtl-sdl/sdl2.png
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 46 KiB |
|
@ -1,531 +0,0 @@
|
|||
|
||||
/*
|
||||
|
||||
SDL powered waterfall
|
||||
|
||||
at the moment this everything is hard-coded for a single platform
|
||||
the BeagleboneBlack with an LCD7 touchscreen (framebuffer mode)
|
||||
|
||||
it can run on other platforms, but will not autodetect anything
|
||||
the keybinds are laid out for the touchscreen face buttons
|
||||
|
||||
on the BBB:
|
||||
full screen double buffered blits seem to perform at 140 fps (cpu limited)
|
||||
|
||||
to run automatically:
|
||||
@reboot sleep 1 && cd /the/install/path && ./waterfall
|
||||
|
||||
todo:
|
||||
benchmark against fftw3
|
||||
replace defines with options
|
||||
autodetect things like screen resolution
|
||||
change the displayed bandwidth
|
||||
audio demodulation
|
||||
fix screen blanking
|
||||
a real make file
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SDL/SDL.h"
|
||||
#include "SDL/SDL_image.h"
|
||||
#include "SDL/SDL_ttf.h"
|
||||
|
||||
#include "rtl_power_lite.c"
|
||||
|
||||
#define SCREEN_WIDTH 800
|
||||
#define SCREEN_HEIGHT 480
|
||||
char* font_path = "./din1451alt.ttf";
|
||||
#define FONT_SIZE 24
|
||||
#define FRAME_MS 30
|
||||
#define FRAME_LINES 10
|
||||
#define MAX_STRING 100
|
||||
#define BIG_JUMP 50000000
|
||||
|
||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||||
static const Uint32 r_mask = 0xFF000000;
|
||||
static const Uint32 g_mask = 0x00FF0000;
|
||||
static const Uint32 b_mask = 0x0000FF00;
|
||||
static const Uint32 a_mask = 0x000000FF;
|
||||
#else
|
||||
static const Uint32 r_mask = 0x000000FF;
|
||||
static const Uint32 g_mask = 0x0000FF00;
|
||||
static const Uint32 b_mask = 0x00FF0000;
|
||||
static const Uint32 a_mask = 0xFF000000;
|
||||
#endif
|
||||
|
||||
static SDL_Surface* img_surface;
|
||||
static SDL_Surface* scroll_surface;
|
||||
static SDL_Surface* future_surface;
|
||||
static const SDL_VideoInfo* info = 0;
|
||||
SDL_Surface* screen;
|
||||
TTF_Font *font;
|
||||
int do_flip; // todo, cond
|
||||
int credits_toggle;
|
||||
|
||||
struct text_bin
|
||||
{
|
||||
char string[MAX_STRING];
|
||||
int x, y;
|
||||
int i;
|
||||
int dirty;
|
||||
SDL_Surface* surf_fg;
|
||||
SDL_Surface* surf_bg;
|
||||
};
|
||||
|
||||
struct text_bin credits[6];
|
||||
struct text_bin freq_labels[5];
|
||||
|
||||
int init_video()
|
||||
{
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
fprintf(stderr, "Video initialization failed: %s\n",
|
||||
SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
info = SDL_GetVideoInfo();
|
||||
|
||||
if( !info ) {
|
||||
fprintf( stderr, "Video query failed: %s\n",
|
||||
SDL_GetError( ) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int set_video( Uint16 width, Uint16 height, int bpp, int flags)
|
||||
{
|
||||
if (init_video())
|
||||
{
|
||||
if((screen = SDL_SetVideoMode(width,height,bpp,flags))==0)
|
||||
{
|
||||
fprintf( stderr, "Video mode set failed: %s\n",
|
||||
SDL_GetError( ) );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int init_ttf()
|
||||
{
|
||||
if (TTF_Init() != 0)
|
||||
{
|
||||
fprintf( stderr, "TTF init failed: %s\n",
|
||||
SDL_GetError( ) );
|
||||
return 1;
|
||||
}
|
||||
font = TTF_OpenFont(font_path, FONT_SIZE);
|
||||
if (font == NULL)
|
||||
{
|
||||
fprintf( stderr, "TTF load failed: %s\n",
|
||||
TTF_GetError( ) );
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void quit( int code )
|
||||
{
|
||||
SDL_FreeSurface(scroll_surface);
|
||||
SDL_FreeSurface(future_surface);
|
||||
SDL_FreeSurface(img_surface);
|
||||
|
||||
TTF_Quit( );
|
||||
SDL_Quit( );
|
||||
|
||||
exit( code );
|
||||
}
|
||||
|
||||
void handle_key_down(SDL_keysym* keysym)
|
||||
{
|
||||
switch(keysym->sym)
|
||||
{
|
||||
case SDLK_ESCAPE:
|
||||
quit(0);
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
credits_toggle = !credits_toggle;
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
case SDLK_UP:
|
||||
case SDLK_LEFT:
|
||||
case SDLK_RIGHT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void process_events( void )
|
||||
{
|
||||
SDL_Event event;
|
||||
|
||||
while( SDL_PollEvent( &event ) ) {
|
||||
|
||||
switch( event.type ) {
|
||||
case SDL_KEYDOWN:
|
||||
handle_key_down( &event.key.keysym );
|
||||
break;
|
||||
case SDL_QUIT:
|
||||
quit( 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
SDL_Surface* tmp;
|
||||
int i;
|
||||
SDL_Color colors[256];
|
||||
|
||||
tmp = SDL_CreateRGBSurface(SDL_HWSURFACE, SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT, 8, r_mask, g_mask, b_mask, a_mask);
|
||||
scroll_surface = SDL_DisplayFormat(tmp);
|
||||
SDL_FreeSurface(tmp);
|
||||
|
||||
tmp = SDL_CreateRGBSurface(SDL_HWSURFACE, SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT, 8, r_mask, g_mask, b_mask, a_mask);
|
||||
future_surface = SDL_DisplayFormat(tmp);
|
||||
SDL_FreeSurface(tmp);
|
||||
|
||||
img_surface = IMG_Load("8-bit-arch.pcx");
|
||||
for (i = 0; i < SDL_NUMEVENTS; ++i)
|
||||
{
|
||||
if (i != SDL_KEYDOWN && i != SDL_QUIT)
|
||||
{
|
||||
SDL_EventState(i, SDL_IGNORE);
|
||||
}
|
||||
}
|
||||
|
||||
for(i=0; i<256; i++)
|
||||
{
|
||||
colors[i].r = i;
|
||||
colors[i].g = i;
|
||||
colors[i].b = 50;
|
||||
}
|
||||
colors[0].r = 0; colors[0].g = 0; colors[0].b = 0;
|
||||
colors[255].r = 255; colors[255].g = 255; colors[255].b = 255;
|
||||
|
||||
SDL_SetPalette(future_surface, SDL_LOGPAL|SDL_PHYSPAL, colors, 0, 256);
|
||||
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
|
||||
void putpixel(SDL_Surface *surface, int x, int y, uint32_t pixel)
|
||||
/* taken from some stackoverflow post */
|
||||
{
|
||||
int bpp = surface->format->BytesPerPixel;
|
||||
/* Here p is the address to the pixel we want to set */
|
||||
Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
|
||||
|
||||
switch (bpp) {
|
||||
case 1:
|
||||
*p = pixel;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
*(uint16_t *)p = pixel;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
|
||||
p[0] = (pixel >> 16) & 0xff;
|
||||
p[1] = (pixel >> 8) & 0xff;
|
||||
p[2] = pixel & 0xff;
|
||||
}
|
||||
else {
|
||||
p[0] = pixel & 0xff;
|
||||
p[1] = (pixel >> 8) & 0xff;
|
||||
p[2] = (pixel >> 16) & 0xff;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
*(uint32_t *)p = pixel;
|
||||
break;
|
||||
|
||||
default:
|
||||
break; /* shouldn't happen, but avoids warnings */
|
||||
}
|
||||
}
|
||||
|
||||
int pretty_text(SDL_Surface* surface, struct text_bin* text)
|
||||
{
|
||||
SDL_Color fg_color = {255, 255, 255};
|
||||
SDL_Color bg_color = {0, 0, 0};
|
||||
SDL_Rect fg_rect = {text->x + 0, text->y + 0, SCREEN_WIDTH, SCREEN_HEIGHT};
|
||||
SDL_Rect bg_rect = {text->x + 2, text->y + 2, SCREEN_WIDTH, SCREEN_HEIGHT};
|
||||
|
||||
if (text->dirty)
|
||||
{
|
||||
// this leaks, but freeing segfaults?
|
||||
// in practice, it leaks an MB an hour under very heavy use
|
||||
//if (text->surf_fg != NULL)
|
||||
// {SDL_FreeSurface(text->surf_fg);}
|
||||
//if (text->surf_bg != NULL)
|
||||
// {SDL_FreeSurface(text->surf_bg);}
|
||||
text->surf_fg = TTF_RenderText_Solid(font, text->string, fg_color);
|
||||
text->surf_bg = TTF_RenderText_Solid(font, text->string, bg_color);
|
||||
text->dirty = 0;
|
||||
}
|
||||
|
||||
SDL_BlitSurface(text->surf_bg, NULL, surface, &bg_rect);
|
||||
SDL_BlitSurface(text->surf_fg, NULL, surface, &fg_rect);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void build_credits(void)
|
||||
{
|
||||
int i;
|
||||
int xs[] = {300, 300, 300, 300, 300, 300};
|
||||
int ys[] = {100, 150, 200, 250, 300, 350};
|
||||
strncpy(credits[0].string, "board: BeagleBone Black", MAX_STRING);
|
||||
strncpy(credits[1].string, "display: CircuitCo LCD7", MAX_STRING);
|
||||
strncpy(credits[2].string, "radio: rtl-sdr", MAX_STRING);
|
||||
strncpy(credits[3].string, "graphics: SDL", MAX_STRING);
|
||||
strncpy(credits[4].string, "os: Arch Linux ARM", MAX_STRING);
|
||||
strncpy(credits[5].string, "glue: Kyle Keen", MAX_STRING);
|
||||
for (i=0; i<6; i++)
|
||||
{
|
||||
credits[i].x = xs[i];
|
||||
credits[i].y = ys[i];
|
||||
credits[i].dirty = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void show_credits(SDL_Surface* surface)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<6; i++)
|
||||
{pretty_text(surface, &(credits[i]));}
|
||||
}
|
||||
|
||||
void build_labels(void)
|
||||
{
|
||||
// very similar to the lines code
|
||||
int f, i, x, drift, center;
|
||||
drift = (frequency % 1000000) / (SAMPLE_RATE / FFT_SIZE);
|
||||
center = frequency - (frequency % 1000000);
|
||||
for (i=-2; i<=2; i++)
|
||||
{
|
||||
x = SCREEN_WIDTH / 2 + -drift + i * 1000000 / (SAMPLE_RATE / FFT_SIZE);
|
||||
f = center + i * 1000000;
|
||||
freq_labels[i+2].x = x - FONT_SIZE/2;
|
||||
freq_labels[i+2].y = 10;
|
||||
if (freq_labels[i+2].i == f)
|
||||
{continue;}
|
||||
freq_labels[i+2].dirty = 1;
|
||||
freq_labels[i+2].i = f;
|
||||
snprintf(freq_labels[i+2].string, MAX_STRING, "%i", f/1000000);
|
||||
}
|
||||
}
|
||||
|
||||
void static_events(void)
|
||||
{
|
||||
SDL_Rect blank = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
|
||||
uint8_t* keystate = SDL_GetKeyState(NULL);
|
||||
if (keystate[SDLK_LEFT])
|
||||
{
|
||||
frequency -= BIG_JUMP;
|
||||
frequency_set();
|
||||
build_labels();
|
||||
SDL_FillRect(scroll_surface, &blank, 0);
|
||||
}
|
||||
if (keystate[SDLK_RIGHT])
|
||||
{
|
||||
frequency += BIG_JUMP;
|
||||
frequency_set();
|
||||
build_labels();
|
||||
SDL_FillRect(scroll_surface, &blank, 0);
|
||||
}
|
||||
if (keystate[SDLK_UP])
|
||||
{gain_decrease();}
|
||||
if (keystate[SDLK_DOWN])
|
||||
{gain_increase();}
|
||||
}
|
||||
|
||||
uint32_t frame_callback(uint32_t interval, void* param)
|
||||
{
|
||||
do_flip = 1;
|
||||
return interval;
|
||||
}
|
||||
|
||||
uint32_t rgb(uint32_t i)
|
||||
{
|
||||
return ((b_mask/255)*20 | (r_mask/255)*i | (g_mask/255)*i);
|
||||
}
|
||||
|
||||
int mouse_stuff(void)
|
||||
// returns X scroll offset
|
||||
// kind of crap with variable framerate
|
||||
{
|
||||
static double prev_x = -100;
|
||||
static double velo = 0;
|
||||
double deaccel = 10;
|
||||
int x, y, buttons;
|
||||
buttons = SDL_GetMouseState(&x, &y);
|
||||
if (buttons & SDL_BUTTON_LMASK)
|
||||
{
|
||||
if (prev_x < 0)
|
||||
{
|
||||
prev_x = x;
|
||||
}
|
||||
velo = x - prev_x;
|
||||
prev_x = x;
|
||||
//fprintf(stdout, "%i %f\n", x, velo);
|
||||
} else {
|
||||
prev_x = -100;
|
||||
if (velo > deaccel)
|
||||
{velo -= deaccel;}
|
||||
if (velo < -deaccel)
|
||||
{velo += deaccel;}
|
||||
if (velo >= -deaccel && velo <= deaccel)
|
||||
{velo *= 0.5;}
|
||||
}
|
||||
return (int)round(velo);
|
||||
}
|
||||
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
int i, c, x, y, v, line;
|
||||
int blits = 0;
|
||||
uint32_t pixel = 0;
|
||||
struct text_bin text;
|
||||
SDL_Rect ScrollFrom = {0, 1, SCREEN_WIDTH, SCREEN_HEIGHT};
|
||||
if (!set_video(SCREEN_WIDTH, SCREEN_HEIGHT, 8,
|
||||
SDL_HWSURFACE | SDL_HWACCEL | SDL_HWPALETTE /*| SDL_FULLSCREEN*/))
|
||||
quit(1);
|
||||
init_ttf();
|
||||
//SDL_Init(SDL_INIT_TIMER);
|
||||
|
||||
SDL_WM_SetCaption("Demo", "");
|
||||
|
||||
init();
|
||||
|
||||
build_credits();
|
||||
build_labels();
|
||||
|
||||
strncpy(text.string, "<< >> - + ?", MAX_STRING);
|
||||
text.x = 30;
|
||||
text.y = 450;
|
||||
text.dirty = 1;
|
||||
|
||||
SDL_BlitSurface(img_surface, NULL, scroll_surface, NULL);
|
||||
//SDL_AddTimer(FRAME_MS, frame_callback, NULL);
|
||||
|
||||
fft_launch();
|
||||
y = 0;
|
||||
SDL_LockSurface(future_surface);
|
||||
while(1)
|
||||
{
|
||||
process_events();
|
||||
//safe_cond_wait(&fft_out.ready, &fft_out.ready_m);
|
||||
if (!fft_out.ready_fast)
|
||||
{
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
fft_out.ready_fast = 0;
|
||||
pthread_rwlock_rdlock(&fft_out.rw);
|
||||
for (x=0; x<SCREEN_WIDTH; x++)
|
||||
{
|
||||
//putpixel(future_surface, x, y, pixel);
|
||||
i = x + (FFT_SIZE - SCREEN_WIDTH) / 2;
|
||||
c = 40*fft_out.buf[i] + 1;
|
||||
if (c > 254)
|
||||
{c = 254;}
|
||||
if (c < 1)
|
||||
{c = 1;}
|
||||
//fprintf(stdout, "%i ", fft_out.buf[i]);
|
||||
putpixel(future_surface, x, y, 40*fft_out.buf[i] + 1);
|
||||
pixel++;
|
||||
}
|
||||
// lines every 100KHz
|
||||
line = (frequency % 100000) / (SAMPLE_RATE / FFT_SIZE);
|
||||
for (i=-15; i<15; i++)
|
||||
{
|
||||
if (y%4)
|
||||
{break;}
|
||||
x = SCREEN_WIDTH / 2 + -line + i * 100000 / (SAMPLE_RATE / FFT_SIZE);
|
||||
if (x < 0)
|
||||
{continue;}
|
||||
if (x > SCREEN_WIDTH)
|
||||
{continue;}
|
||||
putpixel(future_surface, x, y, 0xFF);
|
||||
}
|
||||
//fprintf(stdout, "\n");
|
||||
pthread_rwlock_unlock(&fft_out.rw);
|
||||
y++;
|
||||
if (!do_flip && y <= FRAME_LINES)
|
||||
{continue;}
|
||||
static_events();
|
||||
v = mouse_stuff();
|
||||
if (v != 0)
|
||||
{
|
||||
frequency += (-v * SAMPLE_RATE / FFT_SIZE);
|
||||
frequency_set();
|
||||
build_labels();
|
||||
}
|
||||
SDL_UnlockSurface(future_surface);
|
||||
// scroll
|
||||
ScrollFrom.x = -v;
|
||||
ScrollFrom.y = y;
|
||||
ScrollFrom.w = SCREEN_WIDTH;
|
||||
ScrollFrom.h = SCREEN_HEIGHT;
|
||||
SDL_BlitSurface(scroll_surface, &ScrollFrom, scroll_surface, NULL);
|
||||
// nuke edges
|
||||
if (v > 0)
|
||||
{
|
||||
ScrollFrom.x = 0;
|
||||
ScrollFrom.y = 0;
|
||||
}
|
||||
if (v < 0)
|
||||
{
|
||||
ScrollFrom.x = SCREEN_WIDTH+v;
|
||||
ScrollFrom.y = 0;
|
||||
}
|
||||
if (v != 0)
|
||||
{
|
||||
ScrollFrom.w = abs(v);
|
||||
ScrollFrom.h = SCREEN_HEIGHT-y;
|
||||
SDL_FillRect(scroll_surface, &ScrollFrom, 0);
|
||||
}
|
||||
// new stuff
|
||||
ScrollFrom.x = v;
|
||||
ScrollFrom.y = SCREEN_HEIGHT - y;
|
||||
ScrollFrom.w = SCREEN_WIDTH;
|
||||
ScrollFrom.h = SCREEN_HEIGHT;
|
||||
SDL_BlitSurface(future_surface, NULL, scroll_surface, &ScrollFrom);
|
||||
SDL_BlitSurface(scroll_surface, NULL, screen, NULL);
|
||||
// overlay
|
||||
pretty_text(screen, &text);
|
||||
if (credits_toggle)
|
||||
{show_credits(screen);}
|
||||
for (i=0; i<5; i++)
|
||||
{pretty_text(screen, &freq_labels[i]);}
|
||||
pretty_text(screen, &text);
|
||||
SDL_Flip(screen);
|
||||
// only way to keep the BBB from blanking the screen
|
||||
// (the 10 minute timeout can not be changed by any known means)
|
||||
if (blits % 2000 == 0)
|
||||
{system("setterm -blank poke");}
|
||||
blits++;
|
||||
do_flip = 0;
|
||||
y = 0;
|
||||
SDL_LockSurface(future_surface);
|
||||
}
|
||||
quit(0);
|
||||
fft_cleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// vim:set tabstop=4 softtabstop=4 shiftwidth=4 expandtab smarttab:
|
Ładowanie…
Reference in New Issue