Idea: vdFSK modulation
854 words | Posted on July 22nd, 2010
Scott was 24.82 years old when he wrote this!
Filed under: General, Python, Radio
| My goal is to create a QRPP (extremely low power) transmitter and modulation method to send QRSS (extremely slow, frequency shifting data) efficiently, able to be decoded visually or with automated image analysis software. This evolving post will document the thought process and development behind AJ4VD’s Frequency Shift Keying method, vdFSK. |
Briefly, this is what my idea is. Rather than standard 2-frequencies (low for space, high for tone) QRSS3 (3 seconds per dot), I eliminate the need for pauses between dots by using 3 frequencies (low for a space between letters, medium for dot, high for dash). The following images compare my call sign (AJ4VD) being sent with the old method, and the vdFSK method.
Again, both of these images say the same thing: AJ4VD, (.- .— ….- …- -..). However, note that the above image has greater than a 3 second dot, so it’s unfairly long if you look at the time scale. Until I get a more fairly representative image, just appreciate it graphically. It’s obviously faster to send 3 frequencies rather than two. In my case, it’s over 200% faster.
This is the code to generate audio files converting a string of text into vdFSK audio, saving the output as a WAV file. Spectrographs can be created from these WAV files.
### generate_audio.py ###
# converts a string into vdFSK audio saved as a WAV file
import numpy
import wave
from morse import *
def makeTone(freq,duration=1,samplerate=5000,shape=True):
signal = numpy.arange(duration*samplerate)/float(samplerate)*float(freq)*3.14*2
signal = numpy.sin(signal)*16384
if shape==True: #soften edges
for i in range(100):
signal[i]=signal[i]*(i/100.0)
signal[-i]=signal[-i]*(i/100.0)
ssignal=''
for i in range(len(signal)): #make it binary
ssignal += wave.struct.pack('h',signal[i])
return ssignal
def text2tone(msg,base=800,sep=5):
audio=''
mult=3 #secs per beep
msg=" "+msg+" "
for char in msg.lower():
morse=lookup[char]
print char, morse
audio+=makeTone(base,mult)
for step in lookup[char]:
if step[0]==".":
audio+=makeTone(base+sep,int(step[1])*mult)
if step[0]=="-":
audio+=makeTone(base+sep*2,int(step[1])*mult)
if step[0]=="|":
audio+=makeTone(base,3*mult)
return audio
msg="aj4vd"
file=wave.open('test.wav', 'wb')
file.setparams((1, 2, 5000, 5000*4, 'NONE', 'noncompressed'))
file.writeframes(text2tone(msg))
file.close()
print 'file written'
And the other file needed…
### morse.py ###
# library for converting between text and Morse code
raw_lookup="""
a.- b-... c-.-. d-.. e. f..-. g--. h.... i.. j.--- k-- l.-.. m--
n-. o--- p.--. q--.- r.-. s... t- u.- v...- w.-- x-..- y-.-- z--..
0----- 1.---- 2..--- 3...-- 4....- 5..... 6-.... 7--... 8---.. 9----.
..-.-.- =-...- :---... ,--..-- /-..-. --....-
""".replace("\n","").split(" ")
lookup={}
lookup[" "]=["|1"]
for char in raw_lookup:
"""This is a silly way to do it, but it works."""
char,code=char[0],char[1:]
code=code.replace("-----","x15 ")
code=code.replace("----","x14 ")
code=code.replace("---","x13 ")
code=code.replace("--","x12 ")
code=code.replace("-","x11 ")
code=code.replace(".....","x05 ")
code=code.replace("....","x04 ")
code=code.replace("...","x03 ")
code=code.replace("..","x02 ")
code=code.replace(".","x01 ")
code=code.replace("x0",'.')
code=code.replace("x1",'-')
code=code.split(" ")[:-1]
#print char,code
lookup[char]=code
Automated decoding is trivial. The image above was analyzed, turned into the image below, and the string (AJ4VD) was extracted:
The code to do this:
### decode.py ###
# given an image, it finds peaks and pulls data out
from PIL import Image
from PIL import ImageDraw
import pylab
import numpy
pixelSeek=10
pixelShift=15
def findPeak(data):
maxVal=0
maxX=0
for x in range(len(data)):
if data[x]>maxVal:
maxVal,maxX=data[x],x
return maxX
def peaks2morse(peaks):
baseFreq=peaks[0]
lastSignal=peaks[0]
lastChange=0
directions=[]
for i in range(len(peaks)):
if abs(peaks[i]-baseFreq)<pixelSeek:
baseFreq=peaks[i]
if abs(peaks[i]-lastSignal)<pixelSeek and i<len(peaks)-1:
lastChange+=1
else:
if abs(baseFreq-lastSignal)<pixelSeek:c=" "
if abs(baseFreq-lastSignal)<pixelSeek:c=" "
if abs(baseFreq-lastSignal)<pixelSeek:c=" "
directions.append([lastSignal,lastChange,baseFreq,baseFreq-lastSignal])
lastChange=0
lastSignal=peaks[i]
return directions
def morse2image(directions):
im=Image.new("L",(300,100),0)
draw = ImageDraw.Draw(im)
lastx=0
for d in directions:
print d
draw.line((lastx,d[0],lastx+d[1],d[0]), width=5,fill=255)
lastx=lastx+d[1]
im.show()
im=Image.open('raw.png')
pix=im.load()
data=numpy.zeros(im.size)
for x in range(im.size[0]):
for y in range(im.size[1]):
data[x][y]=pix[x,y]
peaks=[]
for i in range(im.size[0]):
peaks.append(findPeak(data[i]))
morse=peaks2morse(peaks)
morse2image(morse)
print morse
This entry was posted on Thursday, July 22nd, 2010 at 12:39 pmand is filed under General, Python, Radio. You can follow any responses to this entry through the RSS 2.0 feed. You can skip to the end and leave a response. Pinging is currently not allowed.
3 Responses to “Idea: vdFSK modulation”
| bill w4hbk wrote the following at 10:13:51 PM on July 22nd, 2010 |
|
Scott: I recalled seeing something similar recently when I was looking into DFCW….called DFCWi, where the i means a third frequency or “idle” frequency to indicate space. Here is the link: 73 bill w4hbk |
| Jason NT7S wrote the following at 04:22:55 PM on July 24th, 2010 |
|
I think this is a great idea. The only change I would suggest is to perhaps have a short period in between elements where the AFSK tone returns to the “baseline” in order to make it more human-readable. It’s a bit hard to tell how many elements are in characters such as the “4″ when you’ve got four “dits” in a row. |
| Chris N2MCS wrote the following at 12:19:04 PM on August 15th, 2010 |
|
It would be more human readable as well (in my opinion) if dits were + the center frequency and dahs were – the center, like a paddle is configured. |

