Quisk version 4.2.28

This commit is contained in:
James C. Ahlstrom
2023-12-26 15:39:37 -05:00
commit ffa047c2ef
143 changed files with 52434 additions and 0 deletions
+1989
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
# Quisk Software Defined Radio by N2ADR
**December 26, 2023**
This is Quisk, a Software Defined Radio (SDR).
You supply radio hardware such as Hermes Lite2 or SoftRock to convert
the antenna voltage to I/Q samples.
Then send the samples to a computer running Quisk.
The Quisk software will read the I/Q data, tune it, filter it,
demodulate it, and send the audio to headphones or speakers.
Quisk has a microphone input and a key input so it can operate as a
complete transceiver.
The web page for this project is [https://groups.io/g/n2adr-sdr](https://groups.io/g/n2adr-sdr).
The change log is [here](CHANGELOG.txt).
The documentation is [here](docs.html).
The help file is [here](help.html).
This site is under construction. Check back soon.
+2
View File
@@ -0,0 +1,2 @@
#Quisk version 4.2.29
from .quisk import main
+8
View File
@@ -0,0 +1,8 @@
def main():
import quisk
if quisk.__file__.find('__init__') >= 0: # quisk is the package
import quisk.quisk as quisk
quisk.main()
if __name__ == "__main__":
main()
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
#
+408
View File
@@ -0,0 +1,408 @@
# THIS IS THE ENTIRE "RADIO" FOR QUISK RUNNING AS A CONTROL HEAD
# No real radio hardware is attached to the control_head computer.
#
# This software is Copyright (C) 2021-2022 by Ben Cahill and 2006-2022 by James C. Ahlstrom.,
# and is licensed for use under the GNU General Public License (GPL).
# See http://www.opensource.org.
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
#
# This file, control_common.py, along with a radio-specific file, e.g.
# control_softrock.py, control_hermes.py, or similar, allows a radio-less (control_head) Quisk,
# running on this computer, to connect to a remote (remote_radio) instance of Quisk
# that runs on a separate computer.
#
# The remote_radio Quisk controls an attached real radio, and uses the hardware file
# remote_common.py, along with a radio-specific file, e.g. remote_softrock.py,
# remote_hermes.py, or similar, to communicate with this control_head Quisk
# computer via a network connection.
#
# The remote_radio computer should be set up with a static IP address, so that you know
# where to point the control_head. The control head computer may, however, use dynamic
# addressing; the remote radio computer will read the control head address when the
# remote control connection is made.
#
# The main control interface between control_head and remote_radio is via a TCP port;
# this uses very low bandwidth. All functional control, including CW keying, is done via this port.
#
# There are 2 additional ports, both UDP, using low to moderate bandwidth:
# -- Receive graph/waterfall data from the remote_radio
# -- Receive radio sound from the remote_radio and send mic samples
# These use sequential port numbers based on the TCP port number self.remote_ctl_base_port.
# If you need to change the default base port number, you can edit the line in this file
# that looks like (without the #):
#
# self.remote_ctl_base_port = 4585 # Remote Control base TCP port
# self.graph_data_port = self.remote_ctl_base_port + 1 # UDP port for graph data
# self.remote_radio_sound_port = self.remote_ctl_base_port + 2 # UDP port for radio sound and mic
#
# Make sure to edit the corresponding line in remote_common.py to match ports!!
#
# You should be able to use the Quisk control_head along with any/all means of control that
# you normally use to control Quisk, including serial ports, MIDI, and hamlib/rigctl interfaces.
#
# The remote_radio Quisk/computer is assumed to track the local control_head Quisk/computer;
# no attempt is made to verify the remote_radio Quisk's tuning frequency, mode, etc.
# Snap-to Rx tuning for CW works on the control_head Quisk by virtue of graph/waterfall data
# received from the remote_radio Quisk.
#
# To test CW key timing, set DEBUG_CW_SEND_DITS = 1. This issues "perfect" bursts of dits,
# configurable in terms of dit length, dit space, number of dits per burst, and pause between
# bursts (phrases). Search in this file for "DEBUG_CW_SEND_DITS" to find configurable variables.
# To get log output from remote_radio end, in quisk_hardware_remote_radio.py, set DEBUG_CW_JITTER = 1.
from __future__ import print_function
from __future__ import absolute_import
DEBUG_CW_JITTER = 0
DEBUG_CW_SEND_DITS = 0
DEBUG = 0
import socket, time, traceback, hmac, threading, select
import _quisk as QS # Access Quisk C functions via PyMethodDef QuiskMethods[] in quisk.c
import wx
from quisk_hardware_model import Hardware as BaseHardware
class ControlCommon(BaseHardware): # This is the Hardware class for the control head
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.app = app # Access Quisk class App (Python) functions
app.remote_control_head = True
self.remote_ctl_base_port = 4585 # Base of ports for remote connection (maybe edit this)
self.remote_ctl_socket = None
self.remote_ctl_connected = False
self.remote_ctl_timestamp = None
self.graph_data_port = self.remote_ctl_base_port + 1
self.remote_radio_sound_port = self.remote_ctl_base_port + 2
self.thread_lock = threading.Lock()
self.remote_radio_ip = socket.gethostbyname(self.conf.remote_radio_ip) # Allow either host name or IP address
self.first_heartbeat = True
self.cw_keydown = 0
self.cw_phrase_begin_ts = None # timestamp of beginning of cw phrase
self.cw_phrase_end_ts = None
self.cw_phrase_break_duration_secs = 1.0 # cw timestamps will reset to 0
self.cw_poll_started_ts = None
self.cw_poll_started = False
if DEBUG_CW_SEND_DITS:
self.dit_width = 100 # msec (configurable)
self.space_width = 100 # msec (configurable)
self.phrase_gap = 1000 # msec (configurable)
self.num_dits_in_phrase = 5 # number (configurable)
self.num_dits_cur_count = 0
self.key_was_down = False
self.send_cw_dits = False
self.cw_test_next_ts = None
self.cw_test_next_msec = None
self.cw_phrase_start_ts = None
self.smeter_text = ''
self.received = ''
self.closing = False
QS.set_sparams(remote_control_head=1, remote_control_slave=0)
def open(self):
ret = BaseHardware.open(self)
self.remote_ctl_timestamp = time.time()
passw = self.app.local_conf.globals.get("remote_radio_password", "")
passw = passw.strip()
if passw:
del passw
return "Not yet connected to " + self.conf.remote_radio_ip
else:
return "Not yet connected to %s -- Missing Password Here" % self.conf.remote_radio_ip
def close(self):
print('Closing Remote Control connection')
self.closing = True
t = f'QUIT\n' # Tell Remote Radio we are quitting
self.RemoteCtlSend(t)
self.RemoteCtlClose()
return BaseHardware.close(self)
def RemoteCtlClose(self):
if self.remote_ctl_socket:
self.remote_ctl_socket.close()
else:
print(' Remote Control TCP socket already closed')
self.remote_ctl_socket = None
self.remote_ctl_connected = False
QS.stop_control_head_remote_sound()
self.app.main_frame.SetConfigText("Disconnected from remote radio " + self.conf.remote_radio_ip)
self.first_heartbeat = True
def RemoteCtlConnect(self):
if self.remote_ctl_connected:
return True
if not self.remote_ctl_socket:
self.remote_ctl_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.remote_ctl_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 184) # DSCP "Expedite" (46)
if DEBUG: print("Default timeout for remote_ctl_socket = ", self.remote_ctl_socket.gettimeout())
self.remote_ctl_socket.settimeout(0.1) # Allow some time for connection response
if DEBUG: print("Our set timeout for remote_ctl_socket = ", self.remote_ctl_socket.gettimeout())
try:
self.remote_ctl_socket.connect((self.remote_radio_ip, self.remote_ctl_base_port))
except OSError as err:
if str(err).startswith('[WinError 10056]'):
# Connected, in spite of "error" --
# This can occur in Windows; Windows networking infrastructure continues attempting to connect,
# even after the connect() call times out (as set in settimeout(), above).
# If Windows connects after the timeout, then, the next time we call connect(),
# it returns WinError 10056, "A connect request was made on an already connected socket".
# Let's accept this "error" as success!
if DEBUG: print('connect() returned WinError 10056; Windows already connected! Good!')
pass
else:
# Not yet connected; errors may be expected/normal, or unexpected.
if str(err).startswith('time'):
# Timeout "error" is normal when we are waiting for Remote Radio server to become available.
if DEBUG: print('connect() returned timeout; still waiting for remote radio server')
elif str(err).startswith('[WinError 10022]'):
# This can occur in Windows; Windows networking infrastructure continues attempting to connect,
# even after the connect() call times out (as set in settimeout(), above).
# If, by the next time we call connect(), it has not yet connected, but is still trying to do so
# as a continuation of the prior call to connect(), it returns WinError 10022,
# "An invalid argument was supplied", which is a little misleading, but okay for us.
# We have not yet connected, but this is an "expected behavior".
if DEBUG: print('connect() returned WinError 10022; invalid argument; still waiting for remote radio server')
elif str(err).startswith('[Errno 103]'):
# This can occur in Linux; still attempting to connect
if DEBUG: print('connect() returned Errno 103; software connection abort; still waiting for remote radio server')
elif str(err).startswith('[Errno 111]'):
# This can occur in Linux; still attempting to connect
if DEBUG: print('connect() returned Errno 111; connection refused; still waiting for remote radio server')
else:
# Unexpected error. Print error info, regardless of DEBUG status.
print("Remote Control socket.connect() error: {0}".format(err))
return False # Failure to connect
self.remote_ctl_connected = True
self.remote_ctl_socket.settimeout(0.0) # Now that we're connected, don't wait if nothing there
if DEBUG: print("Remote Control connected")
self.app.main_frame.SetConfigText("Connecting to remote radio " + self.conf.remote_radio_ip)
# We have a TCP connection, and the remote will send a challenge token. If we give a valid response
# we will receive "TOKEN_OK" and we can start communication.
return True # Success
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
# TODO: Try to get any modifications of freq or vfo from remote Quisk (?)
t = f'FREQ;{tune};{vfo};{source};{band};{self.app.rxFreq};{self.VarDecimGetIndex()}\n'
#print (t)
self.RemoteCtlSend(t)
#BMC if DEBUG: print('Change', source, tune, vfo, band)
return tune, vfo
def OnSpot(self, level):
pass
def SendCwDits(self):
ts = time.time()
# Use CW key to start/stop stream of dit phrases
key_down = QS.is_cwkey_down()
if not key_down:
self.key_was_down = False
else:
if not self.key_was_down: # Leading edge detector for key down
self.key_was_down = True
if not self.send_cw_dits:
self.send_cw_dits = True # Start sending dits
self.cw_phrase_start_ts = ts # Beginning of phrase
self.cw_test_next_ts = ts
self.cw_test_next_msec = 0 # Start phrase at 0 msec
self.num_dits_cur_count = 0
else:
self.send_cw_dits = False # Stop sending dits
if self.send_cw_dits and ts >= self.cw_test_next_ts:
# Send one on/off pair of CW commands (with msec timestamps since start of CW phrase)
t = f'CW;1;{self.cw_test_next_msec}\n'
self.RemoteCtlSend(t)
self.cw_test_next_msec += self.dit_width
t = f'CW;0;{self.cw_test_next_msec}\n'
self.RemoteCtlSend(t)
self.cw_test_next_msec += self.space_width
self.cw_test_next_ts = self.cw_phrase_start_ts + (float(self.cw_test_next_msec) / 1000)
self.num_dits_cur_count += 1
if self.num_dits_cur_count >= self.num_dits_in_phrase:
# Set up for next phrase
self.cw_test_next_ts = ts + (float(self.phrase_gap) / 1000)
self.cw_phrase_start_ts = self.cw_test_next_ts # Re-start beginning of phrase
self.cw_test_next_msec = 0 # Start phrase at 0 msec
self.num_dits_cur_count = 0
def ThreadPrinter(self, *args, **kw):
# Call this to print from (possibly) the sound thread, which must not be slowed down by a print.
# example: print (x, y, end=' ') ----> self.ThreadPrinter(x, y, end=' ')
with self.thread_lock: # Call thread_lock only once; twice will deadlock.
# If print request is from within sound thread, pass the print request to the GUI thread.
if threading.current_thread().name == "QuiskSound":
wx.CallAfter(print, *args, **kw)
else:
print(*args, **kw)
def PollCwKey(self): # Called by the sound thread
if DEBUG_CW_SEND_DITS:
self.SendCwDits()
return
# Check Quisk key state, send to Remote Radio if change.
# NOTE: Timestamps enable Remote Radio to overcome WiFi/network jitter
ts = time.time()
if not self.cw_phrase_end_ts:
self.cw_phrase_end_ts = ts
self.cw_poll_started_ts = ts
key_down = QS.is_cwkey_down()
if not self.cw_poll_started:
# Detect Quisk startup with CW key down
if ts - self.cw_poll_started_ts > 0.1:
# Check for key down only within first 1/10 second of running
self.cw_poll_started = True
elif key_down == 1:
# Quisk startup with key down
t = f'Quisk is starting with CW key down! Tx is on, and Rx is blocked until you release CW key.'
self.ThreadPrinter(t)
#dlg = wx.MessageDialog(self.app.main_frame, t, "Quisk start, CW key down", style=wx.OK)
#wx.CallAfter(dlg.ShowModal)
self.cw_phrase_begin_ts = ts
self.cw_poll_started = True
if key_down != self.cw_keydown:
if key_down == 1 and (ts - self.cw_phrase_end_ts) > self.cw_phrase_break_duration_secs:
# First CW key-down since a while ago, re-start timestamp sequence for new CW phrase
self.cw_phrase_begin_ts = ts
cw_event_ts_msecs = int((ts - self.cw_phrase_begin_ts) * 1000) # float secs to int msecs
self.cw_keydown = key_down
t = f'CW;{key_down};{cw_event_ts_msecs}\n'
self.RemoteCtlSend(t)
self.cw_phrase_end_ts = ts # End-of-cw-phrase-detection
if DEBUG_CW_JITTER: self.ThreadPrinter(f'{ts:10.4f} {key_down}, {cw_event_ts_msecs}')
def HeartBeat(self): # Called at about 10 Hz by the main
if self.closing: # Don't try to connect if we are closing
return
ts = time.time()
if (ts - self.remote_ctl_timestamp) > 1.0 or self.first_heartbeat:
self.remote_ctl_timestamp = ts
if self.remote_ctl_connected:
# Send keep-alive heartbeat command
t = f'HEARTBEAT\n'
self.RemoteCtlSend(t)
else:
# Else continually try to connect
if DEBUG: print('Heartbeat Connect Attempt')
self.RemoteCtlConnect()
self.first_heartbeat = False
self.RemoteCtlRead()
def RemoteCtlSend(self, text):
# RemoteCtlSend() may be called from sound thread or GUI thread!
# self.thread lock (also used in self.ThreadPrinter) protects against thread collisions.
if not self.remote_ctl_connected:
if DEBUG: self.ThreadPrinter('Cannot send if not TCP connected:', text)
return
if DEBUG: self.ThreadPrinter('Send: ', text, end=' ')
with self.thread_lock: # Do not call ThreadPrinter() from another thread lock!
try:
self.remote_ctl_socket.sendall(text.encode('utf-8', errors='ignore'))
except OSError as err:
errtxt = err
pass
else:
return
self.ThreadPrinter("Closing remote control socket; error in RemoteCtlSend(): {0}".format(errtxt))
self.RemoteCtlClose()
def GetSmeter(self):
return self.smeter_text
def RemoteCtlRead(self):
if not self.remote_ctl_connected:
return
try: # Read any data from the socket
text = self.remote_ctl_socket.recv(1024).decode('utf-8', errors='replace')
except socket.timeout: # This does not work
pass
except socket.error: # Nothing to read
pass
else: # We got some characters
self.received += text
while '\n' in self.received: # A complete response ending with newline is available
reply, self.received = self.received.split('\n', 1) # Split off the reply, save any further characters
reply = reply.strip() # Here is our reply
if DEBUG: print('Rcvd: ', reply)
if reply[0] in 'Qq':
print('Closing Remote Control socket: Q (Quit) from remote radio')
self.RemoteCtlClose()
return
elif reply[0] in 'Mm':
# S-meter text from remote_radio
self.smeter_text = reply[2:]
#print ("Receive smeter", reply[2:])
elif reply[0:6] == "TOKEN;":
passw = self.app.local_conf.globals.get("remote_radio_password", "")
passw = passw.strip()
if passw:
passw = passw.encode('utf-8')
H = hmac.new(passw, reply[6:].encode('utf-8'), 'sha3_256')
del passw
self.RemoteCtlSend("TOKEN;%s;%d\n" % (H.hexdigest(), self.app.data_width))
else:
print ("Error: Missing password on control head")
elif reply[0:8] == "TOKEN_OK":
self.app.main_frame.SetConfigText("Connected to remote radio " + self.conf.remote_radio_ip)
QS.start_control_head_remote_sound(self.remote_radio_ip, self.remote_radio_sound_port, self.graph_data_port)
self.CommonInit() # Send initial parameters common to all radios
self.RadioInit() # Send initial parameters peculiar to a given radio
elif reply[0:9] == "TOKEN_BAD":
self.app.main_frame.SetConfigText("Error: Remote radio %s: Security challenge failed" % self.conf.remote_radio_ip)
elif reply[0:13] == "TOKEN_MISSING":
self.app.main_frame.SetConfigText("Error: Remote radio %s has no password" % self.conf.remote_radio_ip)
elif reply[0:9] == "HL2_TEMP;":
setattr(self, "HL2_TEMP", reply[9:])
elif reply[:3] == 'ERR':
print('Remote Radio returned ' + reply)
else:
print ("Control head received unrecognized command", reply)
def CommonInit(self): # Send initial frequencies, band, sample rate, etc. to remote
app = self.app
# Frequency and decimation
self.ChangeFrequency(app.txFreq + app.VFO, app.VFO, "NewDecim")
# Band
self.RemoteCtlSend("%s;1\n" % app.lastBand)
# Mode
btn = app.modeButns.GetSelectedButton()
if btn:
self.RemoteCtlSend("%s;%d\n" % (btn.idName, btn.GetIndex()))
# Filter and adjustable bandwidth
name = "Filter 6Slider"
value = app.midiControls[name][0].button.slider_value
self.RemoteCtlSend("%s;%d\n" % (name, value))
btn = app.filterButns.GetSelectedButton()
if btn:
self.RemoteCtlSend("%s;%d\n" % (btn.idName, btn.GetIndex()))
# AGC and Squelch levels, split offset
self.RemoteCtlSend("Split;0\n")
btn = app.BtnAGC
self.RemoteCtlSend("AGCSQLCH;%d;%d;%d;%d;%d\n" % (btn.slider_value_off, btn.slider_value_on,
app.levelSquelch, app.levelSquelchSSB, app.split_offset))
idName = "SqlchSlider"
value = app.midiControls[idName][0].button.slider_value
self.RemoteCtlSend("%s;%d\n" % (idName, value))
# Spot slider
idName = "SpotSlider"
value = app.midiControls[idName][0].button.slider_value
self.RemoteCtlSend("%s;%d\n" % (idName, value))
# Various buttons
for idName in ("Mute", "NR2", "AGC", "Sqlch", "NB 1", "Notch", "Test 1", "Spot", "FDX", "PTT", "VOX"):
self.RemoteCtlSend("%s;%d\n" % (idName, app.idName2Button[idName].GetIndex()))
# Menus
for menu in (app.NB_menu, app.split_menu, app.freedv_menu, app.smeter_menu):
if menu:
for nid in menu.id2data:
menu_item = menu.FindItemById(nid)
kind = menu_item.GetKind()
if kind == wx.ITEM_RADIO:
if menu_item.IsChecked():
self.RemoteCtlSend('MENU;%s;%s;1\n' % (menu.menu_name, menu_item.GetItemLabelText()))
elif kind == wx.ITEM_CHECK:
checked = menu_item.IsChecked()
self.RemoteCtlSend('MENU;%s;%s;%d\n' % (menu.menu_name, menu_item.GetItemLabelText(), int(checked)))
+54
View File
@@ -0,0 +1,54 @@
# This provides access to a remote radio. See ac2yd/remote_common.py and .pdf files for documentation.
from ac2yd.control_common import ControlCommon
class Hardware(ControlCommon):
def __init__(self, app, conf):
ControlCommon.__init__(self, app, conf)
self.hermes_code_version = 40
self.HL2_TEMP = ";;;"
self.var_rates = ['48', '96', '192', '384']
self.var_index = 0
#app.bandscope_clock = conf.rx_udp_clock
def ChangeLNA(self, value):
pass
def ChangeAGC(self, value):
pass
def HeartBeat(self):
ControlCommon.HeartBeat(self)
args = self.HL2_TEMP.split(';')
widg = self.app.bottom_widgets
if widg:
widg.text_temperature.SetLabel(args[0])
widg.text_pa_current.SetLabel(args[1])
widg.text_fwd_power.SetLabel(args[2])
widg.text_swr.SetLabel(args[3])
def RadioInit(self): # Send initial parameters not covered by CommonInit()
idName = "RfLna"
value = self.app.midiControls[idName][0].GetValue()
self.RemoteCtlSend("%s;%d\n" % (idName, value))
def VarDecimGetChoices(self): # return text labels for the control
return self.var_rates
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate ksps"
def VarDecimGetIndex(self): # return the current index
return self.var_index
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set rate before the call to open()
rate = self.app.vardecim_set # May be None or from different hardware
else:
rate = int(self.var_rates[index]) * 1000
if rate == 48000:
self.var_index = 0
elif rate == 96000:
self.var_index = 1
elif rate == 192000:
self.var_index = 2
elif rate == 384000:
self.var_index = 3
else:
self.var_index = 0
rate = 48000
return rate
def VarDecimRange(self):
return (48000, 384000)
+40
View File
@@ -0,0 +1,40 @@
# This provides access to a remote radio. See ac3yd/remote_common.py and .pdf files for documentation.
from ac2yd.control_common import ControlCommon
class Hardware(ControlCommon):
def __init__(self, app, conf):
ControlCommon.__init__(self, app, conf)
self.index = 0
self.rx_udp_clock = 122880000
self.decimations = [] # supported decimation rates
for dec in (40, 20, 10, 8, 5, 4, 2):
self.decimations.append(dec * 64)
self.decimations.append(80)
self.decimations.append(64)
def RadioInit(self): # Send initial parameters not covered by CommonInit()
pass
def VarDecimGetChoices(self): # return text labels for the control
clock = self.rx_udp_clock
l = [] # a list of sample rates
for dec in self.decimations:
l.append(str(int(float(clock) / dec / 1e3 + 0.5)))
return l
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate ksps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set decimation before the call to open()
rate = self.application.vardecim_set # May be None or from different hardware
try:
dec = int(float(self.rx_udp_clock // rate + 0.5))
self.index = self.decimations.index(dec)
except:
self.index = 0
else:
self.index = index
dec = self.decimations[self.index]
return int(float(self.rx_udp_clock) / dec + 0.5)
def VarDecimRange(self):
return (48000, 960000)
+9
View File
@@ -0,0 +1,9 @@
# This provides access to a remote radio. See ac2yd/remote_common.py and .pdf files for documentation.
from ac2yd.control_common import ControlCommon
class Hardware(ControlCommon):
def __init__(self, app, conf):
ControlCommon.__init__(self, app, conf)
def RadioInit(self): # Send initial parameters not covered by CommonInit()
pass
+748
View File
@@ -0,0 +1,748 @@
/*
This software is Copyright (C) 2021-2022 by Ben Cahill and 2006-2022 by James C. Ahlstrom,
and is licensed for use under the GNU General Public License (GPL).
See http://www.opensource.org.
Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <complex.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
#ifdef MS_WINDOWS
#include <winsock2.h>
#include <stdint.h>
#else
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#include <sys/param.h>
#if defined(BSD)
#include <netinet/in.h>
#endif
#endif
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <fcntl.h>
#endif
#include "../quisk.h"
#include "../filter.h"
#define GRAPH_DATA_SCALE 163
#define MAX_UDP_INT16_T 600
#define UDP_SEND_INT16 200
#define REMOTE_DEBUG 0 //BMC TODO: Make this a configuration option
static SOCKET remote_radio_sound_socket = INVALID_SOCKET; // send radio sound to control_head, receive mic samples
static SOCKET control_head_sound_socket = INVALID_SOCKET; // receive radio sound from remote_radio, send mic samples
static SOCKET remote_radio_graph_socket = INVALID_SOCKET; // send graph data to control_head
static SOCKET control_head_graph_socket = INVALID_SOCKET; // receive graph data from remote_radio
static int control_head_sound_socket_started = 0; // sound stream started on the control head
static int remote_radio_sound_socket_started = 0; // sound stream started on the remote radio
static int control_head_graph_socket_started = 0; // graph data stream started on the control head
static int remote_radio_graph_socket_started = 0; // graph data stream started on the remote radio
static int control_head_data_width; // app.data_width of control head for graph data
static int packets_sent;
static int packets_recd;
// Receive stereo 16-bit pcm radio speaker sound on the control head via UDP
int read_remote_radio_sound_socket(complex double * cSamples)
{
int i, bytes, nInt16, nSamples;
int16_t buf[UDP_SEND_INT16];
double samp_r, samp_l;
struct timeval tm_wait;
fd_set fds;
static struct quisk_cHB45Filter HalfBand;
static struct quisk_cFilter cFiltInterp3;
static int init_filters=1;
if (control_head_sound_socket == INVALID_SOCKET)
return 0;
if (init_filters) {
init_filters = 0;
memset(&HalfBand, 0, sizeof(struct quisk_cHB45Filter));
quisk_filt_cInit(&cFiltInterp3, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
}
// Signal far end (server) that we're ready (this sends our address/port to far end)
if (!control_head_sound_socket_started) {
QuiskPrintf("read_remote_radio_sound_socket() sending 'rr'\n");
bytes = send(control_head_sound_socket, "rr\n", 3, 0);
if (bytes != 3)
QuiskPrintf("read_remote_radio_sound_socket(), sendto(): %s\n", strerror(errno));
}
// read all available packets, one per loop
nSamples = 0;
while (1) {
tm_wait.tv_sec = 0;
tm_wait.tv_usec = 0;
FD_ZERO (&fds);
FD_SET (control_head_sound_socket, &fds);
if (select(control_head_sound_socket + 1, &fds, NULL, NULL, &tm_wait) != 1) {
//BMC QuiskPrintf("read_remote_radio_sound_socket(): select returned %i\n", retval);
break;
}
bytes = recv(control_head_sound_socket, (char *)buf, UDP_SEND_INT16 * 2, 0);
if (bytes < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
QuiskPrintf("read_remote_radio_sound_socket(), recv(): %s\n", strerror(errno));
break;
}
if (bytes > 0) {
control_head_sound_socket_started = 1;
nInt16 = bytes / 2;
for (i = 0; i < nInt16; i += 2) {
samp_r = buf[i];
samp_l = buf[i + 1];
cSamples[nSamples++] = (samp_r + I * samp_l) / CLIP16 * CLIP32;
}
}
} // while(1)
nSamples = quisk_cInterpolate(cSamples, nSamples, &cFiltInterp3, 3);
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &HalfBand);
return nSamples;
}
// Receive stereo 16-bit pcm microphone samples at the remote radio via UDP
int read_remote_mic_sound_socket(complex double * cSamples)
{
int i, bytes, nInt16, nSamples;
int16_t buf[UDP_SEND_INT16];
double samp_r, samp_l;
struct timeval tm_wait;
fd_set fds;
static struct quisk_cHB45Filter HalfBand;
static struct quisk_cFilter cFiltInterp3;
static int init_filters=1;
if (remote_radio_sound_socket == INVALID_SOCKET)
return 0;
if (init_filters) {
init_filters = 0;
memset(&HalfBand, 0, sizeof(struct quisk_cHB45Filter));
quisk_filt_cInit(&cFiltInterp3, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
}
// read all available packets, one per loop
nSamples = 0;
while (1) {
tm_wait.tv_sec = 0;
tm_wait.tv_usec = 0;
FD_ZERO (&fds);
FD_SET (remote_radio_sound_socket, &fds);
if (select(remote_radio_sound_socket + 1, &fds, NULL, NULL, &tm_wait) != 1) {
//BMC QuiskPrintf("read_remote_mic_sound_socket(): select returned %i\n", retval);
break;
}
bytes = recv(remote_radio_sound_socket, (char *)buf, UDP_SEND_INT16 * 2, 0);
if (bytes < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
QuiskPrintf("read_remote_mic_sound_socket(), recv(): %s\n", strerror(errno));
break;
}
if (bytes > 0) {
nInt16 = bytes / 2;
for (i = 0; i < nInt16; i += 2) {
samp_r = buf[i];
samp_l = buf[i + 1];
cSamples[nSamples++] = (samp_r + I * samp_l) / CLIP16 * CLIP32;
}
}
} // while(1)
nSamples = quisk_cInterpolate(cSamples, nSamples, &cFiltInterp3, 3);
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &HalfBand);
return nSamples;
}
// Send stereo 16-bit pcm sound via UDP
// This code acts as UDP server for radio sound (on remote radio) or mic sound (on control head)
#define MAX_SAMPLES_FOR_REMOTE_SOUND 15000
#define RX_BUFFER_SIZE 64
// Send microphone samples from the control head to the remote radio
void send_remote_mic_sound_socket(complex double * cSamples, int nSamples)
{
int i;
ssize_t sent;
static int buffer_index=0;
static int16_t buffer[UDP_SEND_INT16];
static struct quisk_cHB45Filter HalfBand;
static struct quisk_cFilter cFiltDecim3;
static int init_filters=1, size_cBuf=0;
static complex double * cBuf=NULL;
if (init_filters) {
init_filters = 0;
memset(&HalfBand, 0, sizeof(struct quisk_cHB45Filter));
quisk_filt_cInit(&cFiltDecim3, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
}
if (nSamples > size_cBuf) {
size_cBuf = nSamples;
cBuf = (complex double *)realloc(cBuf, size_cBuf * sizeof(complex double));
}
if (control_head_sound_socket == INVALID_SOCKET)
return;
if (!control_head_sound_socket_started)
return;
memcpy(cBuf, cSamples, nSamples * sizeof(complex double)); // Do not alter cSamples
// Reduce sample rate from 48 to 8 ksps
nSamples = quisk_cDecim2HB45(cBuf, nSamples, &HalfBand);
nSamples = quisk_cDecimate(cBuf, nSamples, &cFiltDecim3, 3);
// Convert format from complex double to stereo pairs of 16-bit PCM samples.
// Buffer samples until UDP_SEND_INT16 are available, and then send the block.
for (i = 0; i < nSamples; i++) {
buffer[buffer_index++] = (int16_t)(creal(cBuf[i]) * (double)CLIP16 / CLIP32);
buffer[buffer_index++] = (int16_t)(cimag(cBuf[i]) * (double)CLIP16 / CLIP32);
if (buffer_index >= UDP_SEND_INT16) {
sent = send(control_head_sound_socket, (const char *)buffer, buffer_index * 2, 0);
if (sent != buffer_index * 2)
QuiskPrintf("send_remote_mic_sound_socket(), send(): %s\n", strerror(errno));
buffer_index = 0;
}
}
}
// Send radio speaker sound from the remote radio to the control head
void send_remote_radio_sound_socket(complex double * cSamples, int nSamples)
{ // Send nSamples samples. Each sample is sent as two shorts (4 bytes) of L/R audio data.
int i, N, sent;
static int16_t sound_lr[UDP_SEND_INT16];
static int udp_size = 0; // Keep track of UDP payload size, in shorts
char buf[RX_BUFFER_SIZE]; // For startup message
int recv_len;
int retval;
SOCKET * sock = &remote_radio_sound_socket;
static struct quisk_cHB45Filter HalfBand;
static struct quisk_cFilter cFiltDecim3;
static int init_filters=1, size_cBuf=0;
static complex double * cBuf=NULL;
#if REMOTE_DEBUG > 0 //BMC debug
// measure/monitor tools:
static float callcount = 0;
static float sampcount = 0;
static uint64_t bunchcount = 0;
static float callcount_total = 0;
static float sampcount_total = 0;
static double prior_ts = 0;
static double delta_total = 0;
#if REMOTE_DEBUG > 1 //BMC debug
static double prior_packet_ts = 0.0;
double now;
#endif
#endif
if (*sock == INVALID_SOCKET)
return;
if (init_filters) {
init_filters = 0;
memset(&HalfBand, 0, sizeof(struct quisk_cHB45Filter));
quisk_filt_cInit(&cFiltDecim3, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
}
if (nSamples > size_cBuf) {
size_cBuf = nSamples;
cBuf = (complex double *)realloc(cBuf, size_cBuf * sizeof(complex double));
}
#if REMOTE_DEBUG > 0 //BMC debug
callcount++;
#endif
// Wait for far end (client) to send its opening greetings, so we can grab its network address/port
if (!remote_radio_sound_socket_started) {
struct sockaddr_in far_addr;
#ifdef MS_WINDOWS
int addr_len = sizeof(struct sockaddr_in);
#else
socklen_t addr_len = sizeof(struct sockaddr_in);
#endif
struct timeval tm_wait;
fd_set fds;
tm_wait.tv_sec = 0;
tm_wait.tv_usec = 0;
FD_ZERO (&fds);
FD_SET (*sock, &fds);
if ((retval = select(*sock + 1, &fds, NULL, NULL, &tm_wait)) != 1) {
//BMC QuiskPrintf("send_remote_sound_socket(): select returned %i\n", retval);
return;
}
// Receive short msg, grab far end address
if ((recv_len = recvfrom(*sock, buf, RX_BUFFER_SIZE, 0, (struct sockaddr *) &far_addr, &addr_len)) == -1) {
QuiskPrintf("send_remote_sound_socket(), recvfrom(): %s\n", strerror(errno));
return;
}
else if(recv_len > 0) {
if (recv_len >= RX_BUFFER_SIZE)
buf[RX_BUFFER_SIZE - 1] = '\n';
else
buf[recv_len] = '\n';
QuiskPrintf("send_remote_sound_socket(): recv_len = %i, %s", recv_len, buf);
if (connect(*sock, (const struct sockaddr *)&far_addr, sizeof(far_addr)) != 0) {
QuiskPrintf("send_remote_sound_socket), connect(): %s\n", strerror(errno));
close(*sock);
*sock = INVALID_SOCKET;
}
else
remote_radio_sound_socket_started = 1;
}
}
memcpy(cBuf, cSamples, nSamples * sizeof(complex double)); // Do not alter cSamples
// Reduce sample rate from 48 to 8 ksps
nSamples = quisk_cDecim2HB45(cBuf, nSamples, &HalfBand);
nSamples = quisk_cDecimate(cBuf, nSamples, &cFiltDecim3, 3);
// Convert format from complex double to stereo pairs of 16-bit PCM samples, send to client
for (i = 0; i < nSamples; i++) {
sound_lr[udp_size++] = (int16_t)(creal(cBuf[i]) * (double)CLIP16 / CLIP32);
sound_lr[udp_size++] = (int16_t)(cimag(cBuf[i]) * (double)CLIP16 / CLIP32);
if (udp_size >= UDP_SEND_INT16) {
N = UDP_SEND_INT16;
udp_size = 0;
sent = send(*sock, (char *)sound_lr, N * 2, 0);
if (sent != N * 2)
QuiskPrintf("send_remote_sound_socket(), send(): %s\n", strerror(errno));
#if REMOTE_DEBUG > 0 //BMC debug
else {
sampcount += sent/4;
packets_sent++;
#if REMOTE_DEBUG > 1 //BMC debug
now = QuiskTimeSec();
QuiskPrintf("%f, send_remote_sound_socket(): now - prior = %f, samples = %u, sampcount = %lu\n",
now, now - prior_packet_ts, sent / 4, sampcount);
prior_packet_ts = now;
#endif
}
#endif
}
#if REMOTE_DEBUG > 1 //BMC debug
else {
now = QuiskTimeSec();
QuiskPrintf("%f, send_remote_sound_socket(): nSamples = %i\n", now, nSamples);
}
#endif
}
#if REMOTE_DEBUG > 0 //BMC debug
if (callcount >= 200) {
double new_ts = QuiskTimeSec();
double delta = new_ts - prior_ts;
prior_ts = new_ts;
#if REMOTE_DEBUG > 1 //BMC every 200
QuiskPrintf("send_remote_sound_socket CURRENT calls: %lu, samples %lu, deltasec %f\n", callcount, sampcount, delta);
QuiskPrintf("%f: send_remote_sound_socket CURRENT RATES (HZ): calls %f, samples %f\n", new_ts, callcount / delta, sampcount / delta);
#endif
if (bunchcount > 0) { // skip the initial bunch; prebuf may distort some numbers(?)
callcount_total += callcount;
sampcount_total += sampcount;
delta_total += delta;
}
if (bunchcount % 10 == 0 && bunchcount > 0) {
QuiskPrintf("%f: send_remote_sound_socket SUMMARY calls: %f, samples %f, deltasec %f\n",
new_ts, callcount_total, sampcount_total, delta_total);
QuiskPrintf("%f: send_remote_sound_socket SUMMARY RATES (HZ): calls %f, samples %f\n",
new_ts, callcount_total / delta_total, sampcount_total / delta_total);
}
bunchcount++;
callcount = 0;
sampcount = 0;
}
#endif
}
// Send graph data via UDP from the remote radio to the control head
void send_graph_data(double * fft_avg, int fft_size, double zoom, double deltaf, int fft_sample_rate, double scale)
{
static double * pixels = NULL;
static int n_pixels = 0;
static uint8_t sequence = 0;
uint8_t flags;
int16_t buffer[MAX_UDP_INT16_T];
int16_t block;
int pixel_index, buffer_index;
double d1, d2;
ssize_t sent;
char buf[RX_BUFFER_SIZE]; // For startup message
int recv_len;
struct sockaddr_in far_addr;
#ifdef MS_WINDOWS
int addr_len = sizeof(struct sockaddr_in);
#else
socklen_t addr_len = sizeof(struct sockaddr_in);
#endif
if (remote_radio_graph_socket == INVALID_SOCKET)
return;
if ( !control_head_data_width)
return;
if ( !remote_radio_graph_socket_started) {
// Receive short msg, grab far end address
// Receive from control head is necessary to establish a path through NAT
if ((recv_len = recvfrom(remote_radio_graph_socket, buf, RX_BUFFER_SIZE, 0, (struct sockaddr *) &far_addr, &addr_len)) < 2) {
return;
}
else {
if (connect(remote_radio_graph_socket, (const struct sockaddr *)&far_addr, sizeof(far_addr)) != 0) {
QuiskPrintf("send_remote_graph_socket), connect(): %s\n", strerror(errno));
close(remote_radio_graph_socket);
remote_radio_graph_socket = INVALID_SOCKET;
return;
}
else {
remote_radio_graph_socket_started = 1;
}
}
}
if (control_head_data_width > n_pixels) {
n_pixels = control_head_data_width;
if (pixels)
free(pixels);
pixels = (double *)malloc(n_pixels * sizeof(double));
}
if ( ! fft_avg) { // send dummy graph data
send(remote_radio_graph_socket, "dum", 3, 0);
return;
}
copy2pixels(pixels, control_head_data_width, fft_avg, fft_size, zoom, deltaf, fft_sample_rate);
// Send multiple 16-bit data blocks: {flags, sequence}, block number, 16-bit graph data
// 8-bit flags:
// bit 0: clip indicator
// 8-bit sequence: 0, 1, 2, ..., 255
block = 0;
pixel_index = 0;
while (pixel_index < control_head_data_width) {
if (quisk_get_overrange())
flags = 0x01;
else
flags = 0x00;
buffer[0] = flags << 8 | sequence;
buffer[1] = block;
buffer_index = 2;
while (buffer_index < MAX_UDP_INT16_T && pixel_index < control_head_data_width) {
d1 = pixels[pixel_index++];
if (fabs(d1) < 1e-40) // avoid log10(0)
d1 = 1E-40;
d2 = 20.0 * log10(d1) - scale;
if (d2 < -200)
d2 = -200;
else if (d2 > 0)
d2 = 0;
buffer[buffer_index++] = (int16_t)lround(d2 * GRAPH_DATA_SCALE);
}
sent = send(remote_radio_graph_socket, (const char *)buffer, buffer_index * 2, 0);
if (sent != buffer_index * 2)
QuiskPrintf("send_graph_data(), send(): %s\n", strerror(errno));
block++;
}
sequence += 1;
}
// Receive graph data via UDP on the control head
int receive_graph_data(double * fft_avg)
{
int i, i1, i2;
ssize_t count;
uint8_t seq, flags;
int16_t buffer[MAX_UDP_INT16_T];
int16_t block;
static int16_t * pixels = NULL;
static int n_pixels = 0;
static int total = 0;
static int16_t sequence = 0;
if (control_head_graph_socket == INVALID_SOCKET)
return 0;
// Signal far end (server) that we're ready (this sends our address/port to far end)
if ( !control_head_graph_socket_started) {
i = send(control_head_graph_socket, "rr\n", 3, 0);
if (i != 3)
QuiskPrintf("receive_graph_data(), send(): %s\n", strerror(errno));
}
if (n_pixels < data_width) {
n_pixels = data_width;
if (pixels)
free(pixels);
pixels = (int16_t *)malloc(n_pixels * sizeof(int16_t));
}
count = recv(control_head_graph_socket, (char *)buffer, MAX_UDP_INT16_T * 2, 0);
count /= 2; // convert to int16_t
if (count > 2) {
control_head_graph_socket_started = 1;
flags = buffer[0] >> 8;
if (flags & 0x01) // Clip
quisk_sound_state.overrange++;
seq = buffer[0] & 0xFF;
if (seq != sequence) { // new graph data
sequence = seq;
total = 0;
}
block = buffer[1];
count -= 2; // number of 16-bit graph data items
i1 = block * (MAX_UDP_INT16_T - 2);
i2 = i1 + count;
if (i1 >= 0 && i2 <= data_width) {
memcpy(pixels + i1, buffer + 2, count * 2);
total += count;
if (total == data_width) {
for (i = 0; i < data_width; i++)
fft_avg[i] = (double)pixels[i] / GRAPH_DATA_SCALE;
return data_width;
}
}
}
return 0;
}
static int start_winsock()
{
#ifdef MS_WINDOWS
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
QuiskPrintf("start_winsock(): %s\n", strerror(errno));
return 0; // failure to start winsock
}
#endif
return 1;
}
static void open_and_bind_socket(SOCKET * sock, char * ip, int port, int sndsize, char * name, int non_block)
{
struct sockaddr_in bind_addr;
const char enable = 1; // for sockopt
#ifndef MS_WINDOWS
int tos = 184; // DSCP "Expedite" (46)
#endif
if (!start_winsock()) {
QuiskPrintf("open_and_bind_socket for %s: Failure to start WinSock\n", name);
return;
}
*sock = socket(PF_INET, SOCK_DGRAM, 0);
if (*sock != INVALID_SOCKET) {
setsockopt(*sock, SOL_SOCKET, SO_SNDBUF, (char *)&sndsize, sizeof(sndsize));
setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
#ifndef MS_WINDOWS
setsockopt(*sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
#endif
// bind to this computer for receiving (and reading far-end address from client)
memset((char *) &bind_addr, 0, sizeof(bind_addr));
bind_addr.sin_family = AF_INET;
bind_addr.sin_port = htons(port);
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(*sock, (const struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0) {
QuiskPrintf("open_and_bind_socket(), bind(): %s\n", strerror(errno));
close(*sock);
*sock = INVALID_SOCKET;
}
else if (non_block) {
#ifdef MS_WINDOWS
unsigned long one = 1;
ioctlsocket(*sock, FIONBIO, &one); // set non-blocking
#else
int flags;
flags = fcntl(*sock, F_GETFL, 0); // set non-blocking
fcntl(*sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
}
if (*sock == INVALID_SOCKET) {
QuiskPrintf("open server %s: Failure to open socket\n", name);
}
else {
QuiskPrintf("open server %s: opened socket %s port %i\n", name, ip, port);
}
}
static void open_and_connect_socket(SOCKET * sock, char * ip, int port, int sndsize, char * name, int non_block)
{
struct sockaddr_in Addr;
const char enable = 1; // for sockopt
if (!start_winsock()) {
QuiskPrintf("open_and_connect_socket for %s: Failure to start WinSock\n", name);
return;
}
*sock = socket(PF_INET, SOCK_DGRAM, 0);
if (*sock != INVALID_SOCKET) {
setsockopt(*sock, SOL_SOCKET, SO_RCVBUF, (char *)&sndsize, sizeof(sndsize));
setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
// set far-end address structure to enable sending initial packet to get things started
Addr.sin_family = AF_INET;
Addr.sin_port = htons(port);
#ifdef MS_WINDOWS
Addr.sin_addr.S_un.S_addr = inet_addr(ip);
#else
inet_aton(ip, &Addr.sin_addr);
#endif
if (connect(*sock, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0) {
close(*sock);
*sock = INVALID_SOCKET;
}
else if (non_block) {
#ifdef MS_WINDOWS
unsigned long one = 1;
ioctlsocket(*sock, FIONBIO, &one); // set non-blocking
#else
int flags;
flags = fcntl(*sock, F_GETFL, 0); // set non-blocking
fcntl(*sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
}
if (*sock == INVALID_SOCKET) {
QuiskPrintf("open client %s: Failure to open socket\n", name);
}
else {
QuiskPrintf("open client %s: opened socket %s port %i\n", name, ip, port);
}
}
static void close_socket(SOCKET * sock, char * name)
{
if (*sock != INVALID_SOCKET) {
close(*sock);
*sock = INVALID_SOCKET;
#ifdef MS_WINDOWS
WSACleanup();
#endif
QuiskPrintf("%s: closed socket\n", name);
}
else {
QuiskPrintf("%s: socket already closed\n", name);
}
}
// start running UDP remote sound on control_head ...
// ... receive radio sound from remote_radio, send mic sound to remote_radio
PyObject * quisk_start_control_head_remote_sound(PyObject * self, PyObject * args)
{
int radio_sound_port;
int graph_data_port;
int sndsize = 48000;
char * remote_radio_ip; // IP address of far end
char * name;
SOCKET * sock;
if (!PyArg_ParseTuple (args, "sii", &remote_radio_ip, &radio_sound_port, &graph_data_port))
return NULL;
name = "radio sound from remote_radio";
sock = &control_head_sound_socket;
open_and_connect_socket(sock, remote_radio_ip, radio_sound_port, sndsize, name, 0);
name = "graph data from remote_radio";
sock = &control_head_graph_socket;
open_and_connect_socket(sock, remote_radio_ip, graph_data_port, 1024 * 8, name, 1);
packets_sent = 0;
packets_recd = 0;
return Py_None;
}
// stop running UDP remote sound on control_head
PyObject * quisk_stop_control_head_remote_sound(PyObject * self, PyObject * args)
{
char * name;
SOCKET * sock;
if (!PyArg_ParseTuple (args, ""))
return NULL;
name = "radio sound from remote_radio";
sock = &control_head_sound_socket;
close_socket(sock, name);
name = "graph data from remote_radio";
sock = &control_head_graph_socket;
close_socket(sock, name);
control_head_sound_socket_started = 0; // reset for next time
remote_radio_sound_socket_started = 0;
control_head_graph_socket_started = 0;
remote_radio_graph_socket_started = 0;
QuiskPrintf("total packets sent = %i, recd = %i\n", packets_sent, packets_recd);
return Py_None;
}
// start running UDP remote sound on remote_radio ...
// ... send radio sound to control_head, receive mic sound from control_head
PyObject * quisk_start_remote_radio_remote_sound(PyObject * self, PyObject * args)
{
int radio_sound_port;
int graph_data_port;
int sndsize = 48000;
char * control_head_ip; // IP address of far end
char * name;
SOCKET * sock;
if (!PyArg_ParseTuple (args, "siii", &control_head_ip, &radio_sound_port,
&graph_data_port, &control_head_data_width))
return NULL;
name = "radio sound to control_head";
sock = &remote_radio_sound_socket;
open_and_bind_socket(sock, control_head_ip, radio_sound_port, sndsize, name, 0);
name = "graph data to control_head";
sock = &remote_radio_graph_socket;
open_and_bind_socket(sock, control_head_ip, graph_data_port, 1024 * 8, name, 1);
packets_sent = 0;
packets_recd = 0;
return Py_None;
}
// stop running UDP remote sound on remote_radio
PyObject * quisk_stop_remote_radio_remote_sound(PyObject * self, PyObject * args)
{
char * name;
SOCKET * sock;
if (!PyArg_ParseTuple (args, ""))
return NULL;
name = "radio sound to control_head";
sock = &remote_radio_sound_socket;
close_socket(sock, name);
name = "graph data to control_head";
sock = &remote_radio_graph_socket;
close_socket(sock, name);
control_head_sound_socket_started = 0; // reset for next time
remote_radio_sound_socket_started = 0;
control_head_graph_socket_started = 0;
remote_radio_graph_socket_started = 0;
control_head_data_width = 0;
QuiskPrintf("total packets sent = %i, recd = %i\n", packets_sent, packets_recd);
return Py_None;
}
+439
View File
@@ -0,0 +1,439 @@
# THIS FILE IS FOR USE ON A REMOTELY CONTROLLED "REMOTE_RADIO" COMPUTER
# RUNNING QUISK TO CONTROL ATTACHED ACTUAL/REAL RADIO HARDWARE.
# IT CONNECTS BY NETWORK TO A SEPARATE "CONTROL_HEAD" COMPUTER ALSO RUNNING QUISK.
#
# This software is Copyright (C) 2021-2022 by Ben Cahill and 2006-2022 by James C. Ahlstrom.,
# and is licensed for use under the GNU General Public License (GPL).
# See http://www.opensource.org.
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
#
# This file, remote_common.py, along with a radio-specific file, e.g.
# remote_softrock.py, remote_hermes.py, or similar, allows a radio-less (control_head) Quisk,
# running on a separate computer, to connect to this (remote_radio) instance of Quisk,
# which has an actual, real radio attached.
#
# The control_head Quisk must use control_common.py, along with a radio-specific file,
# e.g. control_softrock.py, control_hermes.py, or similar, to communicate with this
# remote_radio Quisk via a network connection.
#
# This remote_radio computer should be set up with a static IP address, so that you know
# where to point the control_head. The control head computer may, however, use dynamic
# addressing; this remote_radio computer will read the control head address when the
# remote control connection is made.
#
# The main control interface between control_head and remote_radio is via a TCP port;
# this uses very low bandwidth. All functional control, including CW keying, is done via this port.
#
# There are 2 additional ports, both UDP, using low to moderate bandwidth:
# -- Send graph/waterfall data to the control_head
# -- Send radio sound to the control_head, and receive mic sound
# These use sequential port numbers based on the TCP port number self.remote_ctl_base_port.
# If you need to change the default base port number, you can edit the line in this file
# that looks like (without the #):
#
# self.remote_ctl_base_port = 4585 # Remote Control base TCP port
# self.graph_data_port = self.remote_ctl_base_port + 1 # UDP port for graph data from the remote
# self.remote_radio_sound_port = self.remote_ctl_base_port + 2 # UDP port for radio sound and mic samples
#
#
# Make sure to edit the corresponding line in control_common.py to match ports!!
#
# This remote_radio Quisk/computer is assumed to track the connected control_head Quisk/computer;
# no attempt is made by the control_head to verify the remote_radio Quisk's tuning frequency, mode, etc.
# Snap-to Rx tuning for CW works on the control_head Quisk by virtue of graph/waterfall data
# received from the remote_radio Quisk.
#
# To see detailed log output of CW key timing, set DEBUG_CW_JITTER = 1.
# To additionally see, when CW commands are pending, timestamps of when PollCwKey is called
# (e.g. to check thread scheduling behavior), set DEBUG_CW_JITTER = 2.
# To send "perfect" bursts of CW dits from the control_head, set DEBUG_CW_SEND_DITS = 1
# in the control_head's quisk_hardware_control_head.py.
DEBUG_CW_JITTER = 0
DEBUG = 0
from collections import deque # for CW event queue
import socket, time, traceback, string, hmac, secrets, json
import _quisk as QS
from quisk_widgets import *
class Remot: # Remote comtrol base class
def __init__(self, app, conf):
self.app = app # Access Quisk class App (Python) functions
self.conf = conf
self.token = "abc"
self.token_time = 0
self.control_head_ip = None # IP of control_head compter (read upon connection)
self.remote_ctl_base_port = 4585 # Base of ports for remote connection (maybe edit this)
self.remote_ctl_socket = None
self.remote_ctl_connection = None
self.remote_ctl_heartbeat_ts = None
self.remote_ctl_heartbeat_timeout = 10.0 # Close our connection if we don't hear heartbeat from Control Head
self.graph_data_port = self.remote_ctl_base_port + 1
self.remote_radio_sound_port = self.remote_ctl_base_port + 2
self.cw_delay_secs = 0.020 # time delay to absorb WiFi jitter, in secs
self.cw_phrase_begin_ts = None # timestamp of beginning of cw phrase
self.cw_next_event_ts = None
self.cw_next_keydown = None
self.cw_event_queue = deque()
self.cw_key_down = 0 # Tx-enable management
self.cw_tx_enable = 0
self.received = ''
self.cmd_text = None # cmd received from client (remote head)
self.cmd = None # cmd received from client (remote head)
self.params = None # params = the string following the command
self.extended = None
self.split_mode = 0
print('Remote Overlay Initialized!')
def open(self):
self.token = "abc"
self.remote_ctl_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.remote_ctl_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.remote_ctl_socket.bind(('', self.remote_ctl_base_port)) # '' == INADDR_ANY
self.remote_ctl_socket.settimeout(0.0)
self.remote_ctl_socket.listen(1) # listen for TCP connection from just one client
print('Remote Overlay Opened!')
# Return an informative message for the config screen.
# This method must return a string showing whether the open succeeded or failed.
# Here, we are over-writing the string set by quisk_hardware_model.py
t = f'Quisk Remote Controlled Radio {self.app.width}x{self.app.height} {self.app.graph_width} {self.app.data_width}'
return t
#BMC return ret
def close(self): # Close the listening socket, then the connection socket
if self.remote_ctl_socket:
self.remote_ctl_socket.close()
self.remote_ctl_socket = None
self.token = "abc"
if self.remote_ctl_connection:
print('Closing Remote Control connection: close')
self.RemoteCtlClose(True)
def RemoteCtlOpen(self):
try:
self.remote_ctl_connection, address = self.remote_ctl_socket.accept()
except:
return
else:
self.app.remote_control_slave = True
QS.set_sparams(remote_control_slave=1)
self.token = secrets.token_hex(32)
self.remote_ctl_connection.settimeout(0.0)
self.remote_ctl_heartbeat_ts = time.time()
if DEBUG: print('Remote Control connection: ', self.remote_ctl_connection, ' address: ', address)
self.control_head_ip = address[0]
print ("Remote control connection from", self.control_head_ip)
self.RemoteCtlSend("TOKEN;" + self.token + "\n")
self.token_time = time.time()
def RemoteCtlClose(self, send_quit):
self.StopTransmit()
if self.remote_ctl_connection:
if send_quit:
self.RemoteCtlSend('Q\n')
if self.remote_ctl_connection:
self.remote_ctl_connection.close()
self.remote_ctl_connection = None
QS.stop_remote_radio_remote_sound()
self.app.remote_control_slave = False
QS.set_sparams(remote_control_slave=0)
def RemoteCtlSend(self, text):
# Send text back to the client
if not self.remote_ctl_connection:
return
if isinstance(text, str):
text = text.encode('utf-8', errors='ignore')
try:
self.remote_ctl_connection.sendall(text)
except socket.error:
print('Closing Remote Control connection: sendall() failed. Sent text:\n ' + text.decode('utf-8'))
# NOTE: Cannot send 'Q' to Control Head here; sendall() isn't working!
self.RemoteCtlClose(False)
def ErrParam(self): # Invalid parameter
t = 'ERR_PARAM: ' + self.cmd_text + '\n'
print(t)
self.RemoteCtlSend(t)
def ErrUnsupported(self): # Command recognized but not supported (because of either H/W or configuration)
t = 'ERR_UNSUPPORTED: ' + self.cmd_text + '\n'
print(t)
self.RemoteCtlSend(t)
def ErrUnrecognized(self): # Unrecognized command
t = 'ERR_UNRECOGNIZED_CMD: ' + self.cmd_text + '\n'
print(t)
self.RemoteCtlSend(t)
def ErrBadFormat(self): # Something wrong with format of command
t = 'ERR_BADFORMAT: ' + self.cmd_text + '\n'
print(t)
self.RemoteCtlSend(t)
def HeartBeat(self): # Called at about 10 Hz by the GUI thread
if self.remote_ctl_connection:
# Monitor the remote connection via periodic heartbeat from Control Head
ts = time.time()
if (ts - self.remote_ctl_heartbeat_ts) > self.remote_ctl_heartbeat_timeout:
print('Closing Remote Control connection: Lost HEARTBEAT from Control Head')
self.RemoteCtlClose(True)
else:
# Continually try to connect with Control Head
self.RemoteCtlOpen()
def FastHeartBeat(self): # Called frequently by the GUI thread
"""This is the remote slave processing loop, and is called frequently. It reads and satisfies requests."""
if not self.remote_ctl_connection:
return
try: # Read any data from the socket
text = self.remote_ctl_connection.recv(1024)
except:
#traceback.print_exc()
return
else: # We got some characters
if not isinstance(text, str):
text = text.decode('utf-8')
self.received += text
if not '\n' in self.received: # A complete command ending with newline is not available
return
while '\n' in self.received: # At least one complete command ending with newline *is* available
cmd_text, self.received = self.received.split('\n', 1) # Split off the command, save any further characters
cmd_text = cmd_text.strip() # Here is our command
if not cmd_text:
continue
self.cmd_text = cmd_text
args = cmd_text.split(';') # Split at ';' because some control names have blanks
command = args[0]
params = args[1:]
# TOKEN
if self.token:
if command == "TOKEN":
passw = self.app.local_conf.globals.get("remote_radio_password", "")
passw = passw.strip()
if not passw:
self.RemoteCtlSend("TOKEN_MISSING\n")
print ("Error: Missing password on remote radio")
continue
H = hmac.new(passw.encode('utf-8'), self.token.encode('utf-8'), 'sha3_256')
del passw
if hmac.compare_digest(H.hexdigest(), args[1]):
self.token = None
print ("Security challenge passed", args[2])
self.control_head_data_width = int(args[2])
self.RemoteCtlSend("TOKEN_OK\n")
self.remote_ctl_heartbeat_ts = time.time()
QS.start_remote_radio_remote_sound(self.control_head_ip, self.remote_radio_sound_port,
self.graph_data_port, self.control_head_data_width)
else:
time.sleep(1)
elif time.time() - self.token_time > 5:
self.RemoteCtlSend("TOKEN_BAD\n")
self.RemoteCtlClose(True)
print ("Security failed")
continue
# Check for Quit and Heartbeat before any other commands
if command == 'QUIT':
print('Closing Remote Control connection: QUIT from Control Head')
# NOTE: Do not send 'Q' to Control Head; sendall() will fail because Control Head already disconnected
self.RemoteCtlClose(False)
continue
# HEARTBEAT
if command == 'HEARTBEAT':
self.remote_ctl_heartbeat_ts = time.time()
continue
# Ignore the On/Off button, Help buttons, Small window pop buttons
if command in ("On", "..", "bandBtnGroup", "screenBtnGroup", "modeButns", "Scope", "Config", "RX Filter", "Help"):
continue
if DEBUG: print("Remote receive:", cmd_text)
# Look for radio buttons
if self.ProcessRadioBtn(command, self.cmd_text):
continue
# buttons in idName2Button
btn = self.app.idName2Button.get(args[0], None)
if btn:
#print ("Slave process button", cmd_text, btn.__class__)
value = int(args[1])
btn.SetIndex(value, True)
continue
# controls in midiControls
if command in self.app.midiControls:
ctrl, func = self.app.midiControls[command]
#print ("Slave Process control", cmd_text, ctrl.__class__, func)
value = int(args[1])
if isinstance(ctrl, WrapSlider):
ctrl.ChangeSlider(value)
else:
ctrl.SetValue(value)
func()
continue
# FREQ
if command == 'FREQ':
freq, vfo, source, band, rxFreq, var_decim_index = args[1:]
freq = int(freq)
vfo = int(vfo)
rxFreq = int(rxFreq)
if rxFreq == self.app.rxFreq:
rxFreq = None
var_decim_index = int(var_decim_index)
self.app.ChangeHwFrequency(freq - vfo, vfo, source, band, None, rxFreq)
if source == "NewDecim":
self.app.config_screen.config.btn_decimation.SetSelection(var_decim_index)
sample_rate = self.VarDecimSet(var_decim_index)
self.app.OnBtnDecimation(rate=sample_rate)
# Json function call
elif command == 'JsonAppFunc':
# Call the function self.app. + func with the specified arguments
# func = "%s.SetLabel" % self.idName
# application.Hardware.RemoteCtlSend(f'JsonAppFunc;{json.dumps((func, label, do_cmd, direction))}\n')
jargs = json.loads(params[0])
pyobj = self.app
for nam in jargs[0].split('.'):
pyobj = getattr(pyobj, nam)
pyobj(*jargs[1:])
# AGC and Squelch levels
elif command == 'AGCSQLCH':
ctrl = self.app.midiControls["AGCSlider"][0]
ctrl.SetSlider(value_off=int(args[1]), value_on=int(args[2]))
self.app.levelSquelch = int(args[3])
self.app.levelSquelchSSB = int(args[4])
self.app.split_offset = int(args[5])
# CW Keying
elif command == 'CW':
ts = time.time()
if len(params) < 2:
self.ErrParam()
return
if params[0] in '01':
keydown = int(params[0])
else:
print('Bad keydown value in CW command:', params[0])
self.ErrParam()
return
cw_event_ts = float(params[1]) / 1000.0 # int msecs to float secs
if cw_event_ts == 0.0:
if keydown != 1:
# 'CW 0 0' == "Force Stop of CW"; clear all queued CW commands, and force CW key up
print('Forcing stop of CW')
while len(self.cw_event_queue):
self.cw_event_queue.popleft()
self.cw_next_event_ts = None
self.cw_next_keydown = None
self.cw_key_down = 0
QS.set_remote_cwkey(0)
else:
# Begin new cw phrase; any prior phrase should be done by now.
# Set up first cw event to be ready to execute.
self.cw_begin_phrase_ts = ts + self.cw_delay_secs
cw_new_event_ts = self.cw_begin_phrase_ts + cw_event_ts
if not self.cw_next_event_ts:
self.cw_next_event_ts = cw_new_event_ts
self.cw_next_keydown = keydown
if DEBUG_CW_JITTER: print(f'{ts:10.4f} setting: {keydown} {cw_event_ts:2.3f} {cw_new_event_ts:10.4f}')
else:
self.cw_event_queue.append((cw_new_event_ts, keydown))
if DEBUG_CW_JITTER: print(f'{ts:10.4f} queing: {keydown} {cw_event_ts:2.3f} {cw_new_event_ts:10.4f}')
# Menu
elif command == 'MENU':
menu_name, item_text, checked = args[1:]
if item_text == 'Reverse Rx and Tx':
continue # No need to call handler, as rxFreq and txFreq are already handled
menu = getattr(self.app, menu_name)
nid = menu.item_text2id[item_text]
menu_item = menu.FindItemById(nid)
if menu_item.IsCheckable():
menu_item.Check(int(checked))
menu.Handler(None, nid)
else:
t = 'ERR_UNRECOGNIZED_CMD: %s\n' % cmd_text
print(t)
self.RemoteCtlSend(t)
continue
def PollCwKey(self): # Called periodically at HW Poll usec period (typ. 50-200 Hz) by the sound thread
cw_queue_len = len(self.cw_event_queue)
if self.cw_next_event_ts or cw_queue_len > 0:
# We have at least one CW event. If it's time to do so, set the next CW key down/up, look for next CW event.
ts = time.time()
if DEBUG_CW_JITTER > 1: print(f'{ts:10.4f}')
if not self.cw_next_event_ts:
# Nothing "on deck", but there is something on the cw event queue, so pop it off queue and put it "on deck".
self.cw_next_event_ts, self.cw_next_keydown = self.cw_event_queue.popleft()
if DEBUG_CW_JITTER: print(f'{ts:10.4f} queue len: {cw_queue_len}, popping: {self.cw_next_keydown} {self.cw_next_event_ts:10.4f}')
if ts >= self.cw_next_event_ts:
if DEBUG_CW_JITTER: print(f'{ts:10.4f} set_remote_cwkey: {self.cw_next_keydown}')
QS.set_remote_cwkey(self.cw_next_keydown)
self.cw_key_down = self.cw_next_keydown
cw_queue_len = len(self.cw_event_queue)
if cw_queue_len > 0:
self.cw_next_event_ts, self.cw_next_keydown = self.cw_event_queue.popleft()
if DEBUG_CW_JITTER: print(f'{ts:10.4f} queue len: {cw_queue_len}, popping: {self.cw_next_keydown} {self.cw_next_event_ts:10.4f}')
else:
self.cw_next_event_ts = None
self.cw_next_keydown = None
def StopTransmit(self):
# TODO: Add code for modes other than CW
while len(self.cw_event_queue):
self.cw_event_queue.popleft()
self.cw_next_event_ts = None
self.cw_next_keydown = None
self.cw_key_down = 0
QS.set_remote_cwkey(0)
def ProcessRadioBtn(self, command, cmd_text):
# Large and Small format screens send different button events for radio buttons.
# Band buttons:
if command in self.conf.BandList:
self.app.bandBtnGroup.SetLabel(command, True)
return True # We processed this command
if command in ("Audio", "Time"):
self.app.bandBtnGroup.SetLabel(command, True)
return True # We processed this command
Mode = self.app.modeButns.SetLabel
Screen = self.app.screenBtnGroup.SetLabel
# Mode buttons: process both formats:
if cmd_text in ("CW U/L;0", "CWL;0", "CWL;1"):
Mode("CWL", True)
elif cmd_text in ("CW U/L;1", "CWU;0", "CWU;1"):
Mode("CWU", True)
elif cmd_text in ("SSB U/L;0", "LSB;0", "LSB;1"):
Mode("LSB", True)
elif cmd_text in ("SSB U/L;1", "USB;0", "USB;1"):
Mode("USB", True)
elif cmd_text in ("AM;0", "AM;1"):
Mode("AM", True)
elif cmd_text in ("FM;0", "FM;1"):
Mode("FM", True)
elif cmd_text in ("DGT;0", "DGT-U;0", "DGT-U;1"):
Mode("DGT-U", True)
elif cmd_text in ("DGT;1", "DGT-L;0", "DGT-L;1"):
Mode("DGT-L", True)
elif cmd_text in ("DGT;2", "DGT-FM;0", "DGT-FM;1"):
Mode("DGT-FM", True)
elif cmd_text in ("DGT;3", "DGT-IQ;0", "DGT-IQ;1"):
Mode("DGT-IQ", True)
elif cmd_text in ("FDV;0", "FDV-U;0", "FDV-U;1"):
Mode("FDV-U", True)
elif cmd_text in ("FDV;1", "FDV-L;0", "FDV-L;1"):
Mode("FDV-L", True)
# Screen buttons: process both formats:
# Due to ambiguous received commands, the setting is always "Graph" or "WFall" without "P1 or "P2".
# The P1 and P2 are handled at the control head so this shouldn't matter.
elif cmd_text in ("Graph;0", "Graph;0", "Graph;1"):
Screen("Graph", True)
elif cmd_text in ("Graph;1", "GraphP1;0", "GraphP1;1"):
Screen("Graph", True)
elif cmd_text in ("Graph;2", "GraphP2;0", "GraphP2;1"):
Screen("Graph", True)
elif cmd_text in ("WFall;0", "WFall;0", "WFall;1"):
Screen("WFall", True)
elif cmd_text in ("WFall;1", "WFallP1;0", "WFallP1;1"):
Screen("WFall", True)
elif cmd_text in ("WFall;2", "WFallP2;0", "WFallP2;1"):
Screen("WFall", True)
else:
return False
return True # We processed this command
+25
View File
@@ -0,0 +1,25 @@
# This provides access to a remote radio. See ac2yd/remote_common.py and .pdf files for documentation.
from hermes.quisk_hardware import Hardware as Radio
from ac2yd.remote_common import Remot
class Hardware(Remot, Radio):
def __init__(self, app, conf):
Radio.__init__(self, app, conf)
Remot.__init__(self, app, conf)
def open(self):
Remot.open(self)
return "Server: " + Radio.open(self)
def close(self):
Remot.close(self)
Radio.close(self)
def HeartBeat(self):
Remot.HeartBeat(self)
Radio.HeartBeat(self)
if self.app.remote_control_slave:
widg = self.app.bottom_widgets
t = "HL2_TEMP;%s;%s;%s;%s\n" % (
widg.text_temperature.GetLabel(), widg.text_pa_current.GetLabel(),
widg.text_fwd_power.GetLabel(), widg.text_swr.GetLabel())
self.RemoteCtlSend(t)
+18
View File
@@ -0,0 +1,18 @@
# This provides access to a remote radio. See ac2yd/remote_common.py and .pdf files for documentation.
from hiqsdr.quisk_hardware import Hardware as Radio
from ac2yd.remote_common import Remot
class Hardware(Remot, Radio):
def __init__(self, app, conf):
Radio.__init__(self, app, conf)
Remot.__init__(self, app, conf)
def open(self):
Remot.open(self)
return "Server: " + Radio.open(self)
def close(self):
Remot.close(self)
Radio.close(self)
def HeartBeat(self):
Remot.HeartBeat(self)
Radio.HeartBeat(self)
+19
View File
@@ -0,0 +1,19 @@
# This provides access to a remote radio. See ac2yd/remote_common.py and .pdf files for documentation.
from softrock.hardware_usb import Hardware as Radio
from ac2yd.remote_common import Remot
class Hardware(Remot, Radio):
def __init__(self, app, conf):
Radio.__init__(self, app, conf)
Remot.__init__(self, app, conf)
def open(self):
Remot.open(self)
return "Server: " + Radio.open(self)
def close(self):
Remot.close(self)
Radio.close(self)
def HeartBeat(self):
Remot.HeartBeat(self)
Radio.HeartBeat(self)
+3
View File
@@ -0,0 +1,3 @@
The files in this afedrinet directory came from http://www.afedri-sdr.com. Only slight
modifications were made by N2ADR. Thanks Alex!!!
N2ADR: Changes made 26 January 2019.
+1
View File
@@ -0,0 +1 @@
#
+1
View File
@@ -0,0 +1 @@
gcc -o afedrinet_io.pyd --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -O3 -I"../" -I"C:\Programs\Python27\include" -L"C:\Programs\Python27\libs" -lws2_32 -lpython27
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
#replace include path and library name to right one for your system
# for example for Python 2.7 it will look like -I"/usr/include/python2.7/" -lpython2.7
gcc -o afedrinet_io.so --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -fPIC -O3 -I"../" -I"/usr/include/python2.7/" -lpython2.7
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
#replace include path and library name to right one for your system
# for example for Python 2.7 it will look like -I"/usr/include/python2.7/" -lpython2.7
#/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7
gcc -o afedrinet_io.so --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -I"../" -I"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/" -L"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/" -lpython2.7
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/python
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
##################################################
# AFEDRI Class module
# Title: afedri.py
# Author: k3it
# Adopted to work with Quisk
# by 4Z5LV
# Last Changes: Sat Feb 02 2013
# Version: 2.2
# Adapted to work with Python3
# by N2ADR
# Last Changes: January 2020
##################################################
from socket import *
import wave
import sys
import struct
import time
import datetime
import string
import math
class afedri(object):
"""
class definition for the Afedri SDR-NET
"""
def __init__(self,sdr_address="0.0.0.0", sdr_port=50000):
if sdr_address == "0.0.0.0":
__sdr_address,self.sdr_port = self.__discover_afedri()
if __sdr_address is None:
self.s = None
return
else:
__sdr_address = sdr_address
self.sdr_port = sdr_port
self.s = socket(AF_INET, SOCK_STREAM)
self.s.settimeout(2)
try:
self.s.connect((__sdr_address,self.sdr_port))
#print ("Established control connection with AFEDRI")
except:
print ("Error connecting to SDR")
##sys.exit()
self.s.close()
self.s = None
def close(self):
if self.s:
self.s.close()
self.s = None
def set_center_freq(self,target_freq):
if not self.s: return 1
__next_freq = target_freq
__next_freq = struct.pack("<q",__next_freq)
__set_freq_cmd = b"\x0a\x00\x20\x00\x00" + __next_freq[:5]
self.s.send(__set_freq_cmd)
__data = self.s.recv(10)
__freq = __data[5:] + b"\x00\x00\x00"
__freq = struct.unpack("<q",__freq)[0]
return __freq
def set_samp_rate(self,target_samprate):
if not self.s: return 1
#__samp_rate = target_samprate
__samp_rate = struct.pack("<L",target_samprate)
__set_rate_cmd = b"\x09\x00\xB8\x00\x00" + __samp_rate[:4]
self.s.send(__set_rate_cmd)
__data = self.s.recv(9)
__samp = __data[5:] + b"\x00\x00\x00\x00"
__samp = struct.unpack("<q",__samp)[0]
return __samp
def set_gain(self,target_gain):
if not self.s: return 1
__gain = target_gain
# special afedri calculation for the gain byte
__gain = ((__gain+10)/3 << 3) + 1
__set_gain_cmd = b"\x06\x00\x38\x00\x00" + struct.pack("B",__gain)
self.s.send(__set_gain_cmd)
__data = self.s.recv(6)
__rf_gain = -10 + 3 * (struct.unpack("B",__data[5:6])[0]>>3)
return __rf_gain
def set_gain_indx(self,indx):
if not self.s: return 1
__gain = (indx << 3) + 1
# special afedri calculation for the gain byte
#__gain = ((__gain+10)/3 << 3) + 1
__set_gain_cmd = b"\x06\x00\x38\x00\x00" + struct.pack("B",__gain)
self.s.send(__set_gain_cmd)
__data = self.s.recv(6)
__rf_gain = -10 + 3 * (struct.unpack("B",__data[5:6])[0]>>3)
return __rf_gain
def get_gain(self):
"""
NOT IMPLEMENTED IN AFEDRI?. DON'T USE
"""
if not self.s: return 1
__get_gain_cmd = b"\x05\x20\x38\x00\x00"
self.s.send(__get_gain_cmd)
__data = self.s.recv(6)
__rf_gain = -10 + 3 * (struct.unpack("B",__data[5:])[0]>>3)
return __rf_gain
def get_fe_clock(self):
if not self.s: return 1
__get_lword_cmd = b"\x09\xE0\x02\x55\x00\x00\x00\x00\x00"
__get_hword_cmd = b"\x09\xE0\x02\x55\x01\x00\x00\x00\x00"
self.s.send(__get_lword_cmd)
__data_l = self.s.recv(9)
self.s.send(__get_hword_cmd)
__data_h = self.s.recv(9)
__fe_clock = struct.unpack("<H",__data_l[4:6])[0] + (struct.unpack("<H",__data_h[4:6])[0]<<16)
return __fe_clock
def start_capture(self):
#start 16-bit contiguous capture, complex numbers
if not self.s: return 1
__start_cmd=b"\x08\x00\x18\x00\x80\x02\x00\x00"
self.s.send(__start_cmd)
__data = self.s.recv(8)
return __data
def get_sdr_name(self):
#Request SDR's Name string command = array.array('B',[0x4, 0x20,1,0])
if not self.s: return 1
__start_cmd=b"\x04\x20\x01\x00"
self.s.send(__start_cmd)
__data = self.s.recv(16)
__data = __data.decode('utf-8')
return __data
def stop_capture(self):
if not self.s: return 1
__stop_cmd=b"\x08\x00\x18\x00\x00\x01\x00\x00"
self.s.send(__stop_cmd)
__data = self.s.recv(8)
return __data
def __discover_afedri(self):
# attempt to find AFEDRI SDR on the network
# using AE4JY Simple Network Discovery Protocol
__DISCOVER_SERVER_PORT=48321 # PC client Tx port, SDR Server Rx Port
__DISCOVER_CLIENT_PORT=48322 # PC client Rx port, SDR Server Tx Port
__data=b"\x38\x00\x5a\xa5" # magic discovery packet
__data=__data.ljust(56,b"\x00") # pad with zeroes
self.s = socket(AF_INET, SOCK_DGRAM)
self.s.bind(('', 0))
self.s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
self.sin = socket(AF_INET, SOCK_DGRAM)
self.sin.bind(('', __DISCOVER_CLIENT_PORT))
#exception if no response to broadcast
self.sin.settimeout(1)
self.s.sendto(__data, ('<broadcast>',__DISCOVER_SERVER_PORT))
try:
__msg=self.sin.recv(256,0)
__devname=__msg[5:20]
__devname=__devname.decode('utf-8')
__sn=__msg[21:36]
__sn=__sn.decode('utf-8')
__ip=inet_ntoa(__msg[40:36:-1])
__port=struct.unpack("<H",__msg[53:55])[0]
self.s.close()
self.sin.close()
print ("found ", __devname, __sn, __ip, __port)
return (__ip,__port)
except timeout:
print ("No response from AFEDRI on the LAN")
##sys.exit()
return None, None
def __del__(self):
self.stop_capture()
if self.s: self.s.close()
"""
# verify and correct sampling rate according to the main clock speed
# Alex Trushkin code 4z5lv:
fe_main_clock_freq = a.get_fe_clock()
tmp_div = fe_main_clock_freq / (4 * samp_rate)
floor_div = math.floor(tmp_div)
if (tmp_div - floor_div >= 0.5):
floor_div += 1
if floor_div < 15:
floor_div = 15
#print ("Warning: Max supported sampling rate is", math.floor(fe_main_clock_freq / (4 * floor_div)))
elif floor_div > 625:
floor_div = 625
#print ("Warning: Min supported sampling rate is", math.floor(fe_main_clock_freq / (4 * floor_div)))
dSR = fe_main_clock_freq / (4 * floor_div)
floor_SR = math.floor(dSR)
if (dSR - floor_SR >= 0.5):
floor_SR += 1
if floor_SR != samp_rate:
print ("Warning: invalid sample rate selected for the AFEDRI main clock (", fe_main_clock_freq, "Hz )")
print (" setting to the next valid value", samp_rate, " => ", floor_SR)
samp_rate = floor_SR
"""
+399
View File
@@ -0,0 +1,399 @@
#include <Python.h>
#ifdef MS_WINDOWS
#include <Winsock2.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#endif
#ifdef MS_WINDOWS
#define QUISK_SHUT_RD SD_RECEIVE
#define QUISK_SHUT_BOTH SD_BOTH
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#define SOCKET int
#define INVALID_SOCKET -1
#define QUISK_SHUT_RD SHUT_RD
#define QUISK_SHUT_BOTH SHUT_RDWR
#endif
#include <complex.h>
#define IMPORT_QUISK_API
#include "quisk.h"
//#include "sdriq.h"
static SOCKET rx_udp_socket = INVALID_SOCKET; // Socket for receiving ADC samples from UDP
static int rx_udp_started = 0; // Have we received any data yet?
static int rx_udp_read_blocks = 0; // Number of blocks to read for each read call
static double rx_udp_gain_correct = 1; // For decimation by 5, correct by 4096 / 5**5
static int use_remove_dc=0; // Remove DC from samples
//static int quisk_using_udp = 0;
// This module provides access to the SDR-IQ by RfSpace. It is the source
// for the Python extension module sdriq. It can be used as a model for an
// extension module for other hardware. Read the end of this file for more
// information. This module was written by James Ahlstrom, N2ADR.
// This module uses the Python interface to import symbols from the parent _quisk
// extension module. It must be linked with import_quisk_api.c. See the documentation
// at the start of import_quisk_api.c.
// Start of SDR-IQ specific code:
//
#define DEBUG 0
// Type field for the message block header; upper 3 bits of byte
#define TYPE_HOST_SET 0
#define TYPE_HOST_GET (1 << 5)
#define NAME_SIZE 16
//#define UDP_BROADCAST
#ifdef UDP_BROADCAST
#define FIRST_IQ_DATA_IDX 20
#define RX_UDP_SIZE 1044 // Expected size of UDP samples packet
#else
#define RX_UDP_SIZE 1028 // Expected size of UDP samples packet
#define FIRST_IQ_DATA_IDX 4
#endif
#define BROADCAST_HEADER_SIZE 16
#define UDP_PROTCOL_ID1 0x04
#define UDP_PROTCOL_ID2 0x18
#ifdef DEBUG_IO
#undef DEBUG_IO
#define DEBUG_IO 1
#endif
static PyObject * open_rx_udp(const char * ip, int port)
{
// const char * ip;
// int port;
char buf[128];
struct sockaddr_in Addr;
int recvsize;
char optval;
#if DEBUG_IO
int intbuf;
#ifdef MS_WINDOWS
int bufsize = sizeof(int);
#else
socklen_t bufsize = sizeof(int);
#endif
#endif
#ifdef MS_WINDOWS
WORD wVersionRequested;
WSADATA wsaData;
#endif
// if (!PyArg_ParseTuple (args, "si", &ip, &port))
// return NULL;
// port = 50000;
#ifdef MS_WINDOWS
wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
sprintf(buf, "Failed to initialize Winsock (WSAStartup)");
return PyString_FromString(buf);
}
#endif
// quisk_using_udp = 1;
rx_udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (rx_udp_socket != INVALID_SOCKET)
{
optval=1;
setsockopt( rx_udp_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval) );
recvsize = 256000;
setsockopt(rx_udp_socket, SOL_SOCKET, SO_RCVBUF, (char *)&recvsize, sizeof(recvsize));
memset(&Addr, 0, sizeof(Addr));
Addr.sin_family = AF_INET;
Addr.sin_port = htons(port);
Addr.sin_addr.s_addr = htonl(INADDR_ANY);//inet_addr("192.168.0.8");
// Addr.sin_addr.S_un.S_addr = inet_addr(ip);
// if (connect(rx_udp_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0)
if (bind(rx_udp_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0)
{
shutdown(rx_udp_socket, QUISK_SHUT_BOTH);
close(rx_udp_socket);
rx_udp_socket = INVALID_SOCKET;
sprintf(buf, "Failed to connect to UDP %s port %u", ip, port);
}
else {
sprintf(buf, "Capture from UDP %s port %u", ip, port);
#if DEBUG_IO
if (getsockopt(rx_udp_socket, SOL_SOCKET, SO_RCVBUF, (char *)&intbuf, &bufsize) == 0)
{
printf("UDP socket receive buffer size %d\n", intbuf);
printf("address %s port %u\n", ip, port);
}
else
printf ("Failure SO_RCVBUF\n");
#endif
}
}
else {
sprintf(buf, "Failed to open socket");
}
return PyString_FromString(buf);
}
static PyObject * close_rx_udp(PyObject * self, PyObject * args)
{
short msg = 0x7373; // shutdown
if (!PyArg_ParseTuple (args, ""))
return NULL;
if (rx_udp_socket != INVALID_SOCKET) {
shutdown(rx_udp_socket, QUISK_SHUT_RD);
send(rx_udp_socket, (char *)&msg, 2, 0);
send(rx_udp_socket, (char *)&msg, 2, 0);
QuiskSleepMicrosec(3000000);
close(rx_udp_socket);
rx_udp_socket = INVALID_SOCKET;
}
rx_udp_started = 0;
// if (quisk_using_udp) {
// quisk_using_udp = 0;
#ifdef MS_WINDOWS
WSACleanup();
#endif
// }
Py_INCREF (Py_None);
return Py_None;
}
int afedri_read_rx_udp(complex * samp) // Read samples from UDP
{ // Size of complex sample array is SAMP_BUFFER_SIZE
ssize_t bytes;
//int SR = 0;
static int sample_rate = 0; // Sample rate such as 48000, 96000, 192000
unsigned char buf[1500]; // Maximum Ethernet is 1500 bytes.
static unsigned short seq0; // must be 8 bits
unsigned short seq_curr = 0;
#ifdef MS_WINDOWS
__int32 i, count, nSamples, xr, xi, index;
#else
int32_t i, count, nSamples, xr, xi, index;
#endif
unsigned char * ptxr, * ptxi;
static complex dc_average = 0; // Average DC component in samples
static complex dc_sum = 0;
static int dc_count = 0;
static int dc_key_delay = 0;
// Data from the receiver is little-endian
// if ( !rx_udp_read_blocks)
if(sample_rate != pt_quisk_sound_state->sample_rate)
{
sample_rate = pt_quisk_sound_state->sample_rate;
// "rx_udp_read_blocks" is the number of UDP blocks to read at once
rx_udp_read_blocks = (int)(pt_quisk_sound_state->data_poll_usec * 1e-6 * sample_rate + 0.5);
rx_udp_read_blocks = (rx_udp_read_blocks + (RX_UDP_SIZE / 12)) / (RX_UDP_SIZE / 6); // 6 bytes per sample
if (rx_udp_read_blocks < 1)
rx_udp_read_blocks = 1;
#if DEBUG_IO
printf("read_rx_udp: rx_udp_read_blocks %d\n", rx_udp_read_blocks);
#endif
}
/* if ( ! rx_udp_gain_correct) {
int dec;
dec = (int)(rx_udp_clock / sample_rate + 0.5);
if ((dec / 5) * 5 == dec) // Decimation by a factor of 5
rx_udp_gain_correct = 1.31072;
else // Decimation by factors of two
rx_udp_gain_correct = 1.0;
}
*/
nSamples = 0;
for (count = 0; count < rx_udp_read_blocks; count++)
{ // read several UDP blocks
#if DEBUG_IO
// printf("Data RX Process Begin %u\n",count);
#endif
bytes = recv(rx_udp_socket, (char *)buf, RX_UDP_SIZE, 0); // blocking read
if (bytes != RX_UDP_SIZE) { // Known size of sample block
pt_quisk_sound_state->read_error++;
#if DEBUG_IO
printf("read_rx_udp: Bad block size %i\n", (int)bytes);
#endif
continue;
}
// buf[0] is the sequence number
// buf[1] is the status:
// bit 0: key up/down state
// bit 1: set for ADC overrange (clip)
seq_curr = buf[2] | (buf[3] << 8);
if (seq_curr != seq0) {
#if DEBUG_IO
printf("read_rx_udp: Bad sequence want %3d got %3d at block %d of %d\n",
(unsigned int)seq0, (unsigned int)buf[0], count, rx_udp_read_blocks);
#endif
pt_quisk_sound_state->read_error++;
}
seq0 = seq_curr + 1; // Next expected sequence number
// quisk_set_key_down(buf[1] & 0x01); // bit zero is key state
// if (buf[1] & 0x02) // bit one is ADC overrange
// quisk_sound_state.overrange++;
index = FIRST_IQ_DATA_IDX;
ptxr = (unsigned char *)&xr;
ptxi = (unsigned char *)&xi;
// convert 24-bit samples to 32-bit samples; int must be 32 bits.
while (index < bytes)
{
xr = xi = 0;
memcpy (ptxr + 2, buf + index, 2);
index += 2;
memcpy (ptxi + 2, buf + index, 2);
index += 2;
samp[nSamples++] = (xr + xi * I) * rx_udp_gain_correct;
xr = xi = 0;
memcpy (ptxr + 2, buf + index, 2);
index += 2;
memcpy (ptxi + 2, buf + index, 2);
index += 2;
samp[nSamples++] = (xr + xi * I) * rx_udp_gain_correct;;
//if (nSamples == 2) printf("%12d %12d\n", xr, xi);
}
}
if (quisk_is_key_down()) {
dc_key_delay = 0;
dc_sum = 0;
dc_count = 0;
}
else if (dc_key_delay < pt_quisk_sound_state->sample_rate) {
dc_key_delay += nSamples;
}
else {
dc_count += nSamples;
for (i = 0; i < nSamples; i++) // Correction for DC offset in samples
dc_sum += samp[i];
if (dc_count > pt_quisk_sound_state->sample_rate * 2) {
dc_average = dc_sum / dc_count;
dc_sum = 0;
dc_count = 0;
//printf("dc average %lf %lf %d\n", creal(dc_average), cimag(dc_average), dc_count);
//printf("dc polar %.0lf %d\n", cabs(dc_average),
// (int)(360.0 / 2 / M_PI * atan2(cimag(dc_average), creal(dc_average))));
}
}
if (use_remove_dc)
for (i = 0; i < nSamples; i++) // Correction for DC offset in samples
samp[i] -= dc_average;
// printf("%u\n", pt_quisk_sound_state->sample_rate);
return nSamples;
}
// End of most AFEDRI specific code.
///////////////////////////////////////////////////////////////////////////
// The API requires at least two Python functions for Open and Close, plus
// additional Python functions as needed. And it requires exactly three
// C funcions for Start, Stop and Read samples. Quisk runs in two threads,
// a GUI thread and a sound thread. You must not call the GUI or any Python
// code from the sound thread. You must return promptly from functions called
// by the sound thread.
//
// The calling sequence is Open, Start, then repeated calls to Read, then
// Stop, then Close.
// Start of Application Programming Interface (API) code:
// Called to close the sample source; called from the GUI thread.
static PyObject * close_samples(PyObject * self, PyObject * args)
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
close_rx_udp(self, args);
Py_INCREF (Py_None);
return Py_None;
}
// Called to open the sample source; called from the GUI thread.
static PyObject * open_samples(PyObject * self, PyObject * args)
{
const char * ip;
int port;
// const char * name;
// char buf[128];
if (!PyArg_ParseTuple (args, "si", &ip, &port))
return NULL;
// name = QuiskGetConfigString("sdriq_name", "NoName");
// sdriq_clock = QuiskGetConfigDouble("sdriq_clock", 66666667.0);
// Record our C-language Start/Stop/Read functions for use by sound.c.
quisk_sample_source(NULL, NULL, &afedri_read_rx_udp);
//////////////
return open_rx_udp(ip, port); // AFEDRI specific
// return PyString_FromString(buf); // return a string message
}
// Miscellaneous functions needed by the SDR-IQ; called from the GUI thread as
// a result of button presses.
// Functions callable from Python are listed here:
static PyMethodDef QuiskMethods[] = {
{"open_samples", open_samples, METH_VARARGS, "Open the AFEDRI SDR-Net."},
{"close_samples", close_samples, METH_VARARGS, "Close the AFEDRI SDR-Net."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
#if PY_MAJOR_VERSION < 3
// Python 2.7:
PyMODINIT_FUNC initafedrinet_io (void)
{
if (Py_InitModule ("afedrinet_io", QuiskMethods) == NULL) {
printf("Py_InitModule failed!\n");
return;
}
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
printf("Failure to import pointers from _quisk\n");
return; //Error
}
}
// Python 3:
#else
static struct PyModuleDef afedrinet_iomodule = {
PyModuleDef_HEAD_INIT,
"afedrinet_io",
NULL,
-1,
QuiskMethods
} ;
PyMODINIT_FUNC PyInit_afedrinet_io(void)
{
PyObject * m;
m = PyModule_Create(&afedrinet_iomodule);
if (m == NULL)
return NULL;
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
printf("Failure to import pointers from _quisk\n");
return m; //Error
}
return m;
}
#endif
+7
View File
@@ -0,0 +1,7 @@
afedrinet2:
python2 setup.py build_ext --force --inplace
afedrinet3:
python3 setup.py build_ext --force --inplace
+61
View File
@@ -0,0 +1,61 @@
# This is a sample quisk_conf.py configuration file for Microsoft Windows.
# For Windows, your default config file name is "My Documents/quisk_conf.py",
# but you can use a different config file by using -c or --config. Quisk creates
# an initial default config file if there is none. To control Quisk, edit
# "My Documents/quisk_conf.py" using any text editor; for example WordPad (not Notepad).
# In Windows you can see what sound devices you have, and you can set the Primary
# Device for capture and playback by using Control Panel/Sounds and Audio Devices.
# If you have only one sound device, it should be set as "Primary". If you have
# several, find the names by using Control Panel/Sounds and Audio Devices; for
# example, you may have "SoundMAX HD Audio" in the list for "Sound playback" and
# "Sound recording". To specify this device for capture (recording) or playback,
# enter a unique part of its name using exact upper/lower case. For example:
# name_of_sound_capture = "SoundMAX"
# name_of_sound_play = "SoundMAX"
# There are many possible options for your config file. Copy the ones you want
# from the master file quisk_conf_defaults.py (but don't change the master file).
# The master config file is located in the site-packages/quisk folder for Python 2.7.
# This file is Python code and the comment character is "#". To ignore a line,
# start it with "#". To un-ignore a line, remove the "#". Generally you must start
# lines in column one (the left edge) except for logic blocks.
from afedrinet import quisk_hardware # Use different hardware file
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.0.8" # Sample source IP address
rx_udp_port = 50000 # Sample source UDP port
rx_udp_clock = 79998382 # ADC sample rate in Hertz
#rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
#sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
data_poll_usec = 10000
#sample_rate = 192000 # ADC hardware sample rate in Hertz
sample_rate = 740740 # ADC hardware sample rate in Hertz
playback_rate = 48000 # Radio sound play rate
name_of_sound_capt = ""#AFEDRI-SDR-Net Audio" # Name of soundcard capture hardware device.
name_of_sound_play = "Buil-in Output" # Use the same device for play back.
#name_of_sound_play = "Line 1"#Virtual Audio Cable" # Use the same device for play back.
latency_millisecs = 50 # latency time in milliseconds
display_fraction = 0.92 # The edges of the full bandwidth are not valid
default_rf_gain = 11
# Select the default screen when Quisk starts:
#default_screen = 'Graph'
default_screen = 'WFall'
# If you use hardware with a fixed VFO (crystal controlled SoftRock) un-comment the following:
# import quisk_hardware_fixed as quisk_hardware
# fixed_vfo_freq = 7056000
# If you use an SDR-IQ for capture, first install the SpectraView software
# that came with the SDR-IQ. This will install the USB driver. Then set these parameters:
# import quisk_hardware_sdriq as quisk_hardware # Use different hardware file
# use_sdriq = 1 # Capture device is the SDR-IQ
# sdriq_name = "SDR-IQ" # Name of the SDR-IQ device to open
# sdriq_clock = 66666667.0 # actual sample rate (66666667 nominal)
# sdriq_decimation = 500 # Must be 360, 500, 600, or 1250
# sample_rate = int(float(sdriq_clock) / sdriq_decimation + 0.5) # Don't change this
# name_of_sound_capt = "" # We do not capture from the soundcard
# playback_rate = 48000 # Radio sound play rate, default 48000
# display_fraction = 0.85 # The edges of the full bandwidth are not valid
+54
View File
@@ -0,0 +1,54 @@
# This is a sample quisk_conf.py configuration file for Linux
# For Windows, your default config file name is "/home/user/quisk_conf.py"
# but you can use a different config file by using -c or --config. Quisk creates
# an initial default config file if there is none. To control Quisk, edit
# "/home/user/quisk_conf.py" using any text editor; for example WordPad (not Notepad).
# In Windows you can see what sound devices you have, and you can set the Primary
# Device for capture and playback by using Control Panel/Sounds and Audio Devices.
# If you have only one sound device, it should be set as "Primary". If you have
# several, find the names by using Control Panel/Sounds and Audio Devices; for
# example, you may have "SoundMAX HD Audio" in the list for "Sound playback" and
# "Sound recording". To specify this device for capture (recording) or playback,
# enter a unique part of its name using exact upper/lower case. For example:
# name_of_sound_capture = "SoundMAX"
# name_of_sound_play = "SoundMAX"
# There are many possible options for your config file. Copy the ones you want
# from the master file quisk_conf_defaults.py (but don't change the master file).
# The master config file is located in the site-packages/quisk folder for Python 2.7.
# This file is Python code and the comment character is "#". To ignore a line,
# start it with "#". To un-ignore a line, remove the "#". Generally you must start
# lines in column one (the left edge) except for logic blocks.
from afedrinet import quisk_hardware # Use different hardware file
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.0.8" # Sample source IP address
rx_udp_port = 50000 # Sample source UDP port
rx_udp_clock = 80000000 # ADC sample rate in Hertz
#rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
#sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
data_poll_usec = 10000
#sample_rate = 192000 # ADC hardware sample rate in Hertz
sample_rate = 740740 # ADC hardware sample rate in Hertz
playback_rate = 48000 # Radio sound play rate
name_of_sound_capt = ""#AFEDRI-SDR-Net Audio" # Name of soundcard capture hardware device.
name_of_sound_play = "portaudiodefault"#hw:0,0" # Use the same device for play back.
#name_of_sound_play = "Line 1"#Virtual Audio Cable" # Use the same device for play back.
latency_millisecs = 50 # latency time in milliseconds
display_fraction = 0.92 # The edges of the full bandwidth are not valid
default_rf_gain = 14
# Select the default screen when Quisk starts:
#default_screen = 'Graph'
default_screen = 'WFall'
# If you use hardware with a fixed VFO (crystal controlled SoftRock) un-comment the following:
# import quisk_hardware_fixed as quisk_hardware
# fixed_vfo_freq = 7056000
# If you use an SDR-IQ for capture, first install the SpectraView software
# that came with the SDR-IQ. This will install the USB driver. Then set these parameters:
# import quisk_hardware_sdriq as quisk_hardware # Use different hardware file
# use_sdriq = 1 # Capture device is the SDR-IQ
# sdriq_name = "SDR-IQ" # Name of the SDR-IQ device to open
# sdriq_clock = 66666667.0 # actual sample rate (66666667 nominal)
# sdriq_decimation = 500 # Must be 360, 500, 600, or 1250
# sample_rate = int(float(sdriq_clock) / sdriq_decimation + 0.5) # Don't change this
# name_of_sound_capt = "" # We do not capture from the soundcard
# playback_rate = 48000 # Radio sound play rate, default 48000
# display_fraction = 0.85 # The edges of the full bandwidth are not valid
+54
View File
@@ -0,0 +1,54 @@
# This is a sample quisk_conf.py configuration file for Linux
# For Windows, your default config file name is "/home/user/quisk_conf.py"
# but you can use a different config file by using -c or --config. Quisk creates
# an initial default config file if there is none. To control Quisk, edit
# "/home/user/quisk_conf.py" using any text editor; for example WordPad (not Notepad).
# In Windows you can see what sound devices you have, and you can set the Primary
# Device for capture and playback by using Control Panel/Sounds and Audio Devices.
# If you have only one sound device, it should be set as "Primary". If you have
# several, find the names by using Control Panel/Sounds and Audio Devices; for
# example, you may have "SoundMAX HD Audio" in the list for "Sound playback" and
# "Sound recording". To specify this device for capture (recording) or playback,
# enter a unique part of its name using exact upper/lower case. For example:
# name_of_sound_capture = "SoundMAX"
# name_of_sound_play = "SoundMAX"
# There are many possible options for your config file. Copy the ones you want
# from the master file quisk_conf_defaults.py (but don't change the master file).
# The master config file is located in the site-packages/quisk folder for Python 2.7.
# This file is Python code and the comment character is "#". To ignore a line,
# start it with "#". To un-ignore a line, remove the "#". Generally you must start
# lines in column one (the left edge) except for logic blocks.
from afedrinet import quisk_hardware # Use different hardware file
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.0.8" # Sample source IP address
rx_udp_port = 50000 # Sample source UDP port
rx_udp_clock = 80000000 # ADC sample rate in Hertz
#rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
#sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
data_poll_usec = 10000
#sample_rate = 192000 # ADC hardware sample rate in Hertz
sample_rate = 740740 # ADC hardware sample rate in Hertz
playback_rate = 48000 # Radio sound play rate
name_of_sound_capt = ""#AFEDRI-SDR-Net Audio" # Name of soundcard capture hardware device.
name_of_sound_play = "portaudiodefault"#hw:0,0" # Use the same device for play back.
#name_of_sound_play = "Line 1"#Virtual Audio Cable" # Use the same device for play back.
latency_millisecs = 50 # latency time in milliseconds
display_fraction = 0.92 # The edges of the full bandwidth are not valid
default_rf_gain = 14
# Select the default screen when Quisk starts:
#default_screen = 'Graph'
default_screen = 'WFall'
# If you use hardware with a fixed VFO (crystal controlled SoftRock) un-comment the following:
# import quisk_hardware_fixed as quisk_hardware
# fixed_vfo_freq = 7056000
# If you use an SDR-IQ for capture, first install the SpectraView software
# that came with the SDR-IQ. This will install the USB driver. Then set these parameters:
# import quisk_hardware_sdriq as quisk_hardware # Use different hardware file
# use_sdriq = 1 # Capture device is the SDR-IQ
# sdriq_name = "SDR-IQ" # Name of the SDR-IQ device to open
# sdriq_clock = 66666667.0 # actual sample rate (66666667 nominal)
# sdriq_decimation = 500 # Must be 360, 500, 600, or 1250
# sample_rate = int(float(sdriq_clock) / sdriq_decimation + 0.5) # Don't change this
# name_of_sound_capt = "" # We do not capture from the soundcard
# playback_rate = 48000 # Radio sound play rate, default 48000
# display_fraction = 0.85 # The edges of the full bandwidth are not valid
+94
View File
@@ -0,0 +1,94 @@
# Please do not change this hardware control module.
# It provides support for the SDR-IQ by RfSpace.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
#import _quisk as QS
import os,sys
#sys.path.append('./afedri')
try:
import afedrinet_io as AF
from sdr_control import *
except:
from afedrinet import afedrinet_io as AF
from afedrinet.sdr_control import *
from ctypes import *
os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH']
#from quisk import App as parent
from quisk_hardware_model import Hardware as BaseHardware
#from quisk import *
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.local_tune = 2
self.index = 0
self.clock = 80000000
self.old_LO_freq = 0
self.rf_gain_labels = ('RF -10','RF -7','RF -4','RF -1','+2','+5','+8','+11','+14','+17','+20','+23','+26','+29','+32','+35')
self.conf = conf
self.plugin = Control(self.conf.rx_udp_ip, self.conf.rx_udp_port)
self.app = app
self.decimations = [] # supported decimation rates
for dec in (53333, 96000, 133333, 185185, 192000, 370370, 740740, 1333333):
self.decimations.append(dec)
def open(self):
self.plugin.OpenHW() # Return a config message
RF_Gain_idx = int((10 + self.conf.default_rf_gain) / 3)
if not 0 <= RF_Gain_idx < len(self.rf_gain_labels):
RF_Gain_idx = 0
self.plugin.SetAttenuator(RF_Gain_idx)
self.app.BtnRfGain.SetIndex(RF_Gain_idx)
#print ("RF Gain %i" % self.conf.default_rf_gain)
return AF.open_samples(self.conf.rx_udp_ip, self.conf.rx_udp_port)
def close(self):
self.plugin.CloseHW()
def OnButtonRfGain(self, event):
btn = event.GetEventObject()
n = btn.index
if n > -1 or n < 16 :
self.plugin.SetAttenuator(n)
else:
print ('Unknown RfGain')
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
self.local_tune = tune
if vfo:
self.plugin.SetHWLO(vfo)
return tune, vfo
def ReturnFrequency(self): # Return the current tuning and VFO frequency
return (None, None) # Return LO frequency
def GetFirmwareVersion(self):
return 226
def HeartBeat(self):
# self.PrintStatus('Start', 'AFEDRI')
return
def VarDecimGetChoices(self): # Return a list/tuple of strings for the decimation control.
l = [] # a list of sample rates
for dec in self.decimations:
l.append(str( dec ))
return l
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate sps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set decimation before the call to open()
rate = self.application.vardecim_set # May be None or from different hardware
try:
dec = rate #int(float(self.conf.rx_udp_clock / rate + 0.5))
self.index = self.decimations.index(dec)
except:
try:
self.index = self.decimations.index(self.conf.sample_rate)
except:
self.index = 0
else:
self.index = index
dec = self.decimations[self.index]
self.plugin.SetHWSR(dec) # Return a config message
return dec
+38
View File
@@ -0,0 +1,38 @@
Hello All!
I want to publish here some information about possibility to use Quisk application on MAC computers running MAC OSX.
It is for experienced MAC OS users that know what is Terminal and command shell tools and how to compile executable from source code:
1. You should download Quisk package from author's web page
2. Xcode SDK and command tools
3. Install macports to manage ports that required to be used with Quisk
4. You should install the next packages:
python27, py27-wxpython-2.8, fftw-3.0 , portaudio, pulseaudio and probably some additional
5. Be ready that packages installation will take long time. Select macports python as default python executable:
>> sudo port select --set python python27
6. After you will extract Quisk files from archive to separate directory you must enter Quisk directory and compile Quisk for your system running next command:
>> make macports
7. After installation to use Quisk with AFEDRi SDR, you have to download additional packages: afedriusb , afedrinet extract contains of archive to separated folders afedriusb and afedrinet in main Quisk directory.
8. For network connected AFEDRI you must enter afedrinet folder and compile SDR support library running afe_library script, before you can run this script you must modify path to python library and include folders in this script, for example on my MAC it looks like:
###############################################################
gcc -o afedrinet_io.so --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -I"../" -I"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/" -L"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/" -lpython2.7
###################################################################
9. For USB connection you must download the sdr_commander v1.22
Extract archive to separate folder and compile source code.
After successful compilation you will get executable sdr_commander, you must
copy it to quick/afedriusb folder
10.
10. Edit qusik_conf.py file to define correct sound device (card) name, copy this file as .quisk_conf.py to user's home directory.
For example for portaudio devices it will look like this one:
#################################################################
from afedriusb import quisk_hardware
sample_rate = 185185
name_of_sound_capt = "portaudio#2"
name_of_sound_play = "portaudiodefault"
latency_millisecs = 150
default_rf_gain = 14
default_screen = 'WFall'
playback_rate = 48000
default 48000
##########################################################
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/python # This is client.py file
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os,sys
os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH']
import afedrinet
import afedrinet.afedri
class Control:
def __init__(self, sdr_address="192.168.0.8", sdr_port=50000):
self.hw = afedrinet.afedri.afedri(sdr_address, sdr_port)
if self.hw.s is None: # Failure to find the hardware
self.hw = None
def OpenHW(self):
if not self.hw: return
data = self.hw.get_sdr_name()
print (data[4:])
self.hw.start_capture()
def CloseHW(self):
if not self.hw: return
self.hw.stop_capture()
self.hw.close # Close the socket when done
def SetHWLO(self, vfo):
if not self.hw: return
self.hw.set_center_freq(vfo)
def SetHWSR(self, sample_rate):
if not self.hw: return
self.hw.set_samp_rate(sample_rate)
print ("Sample Rate %i" % sample_rate)
def SetAttenuator(self, indx):
if not self.hw: return
self.hw.set_gain_indx(indx)
+49
View File
@@ -0,0 +1,49 @@
from distutils.core import setup, Extension
import sys
# Afedri hardware support added by Alex, Alex@gmail.com
if sys.platform == "win32":
Modules = [
Extension ('afedrinet.afedrinet_io',
libraries = ['WS2_32'],
sources = ['../import_quisk_api.c', '../is_key_down.c', 'afedrinet_io.c'],
include_dirs = ['.', '..'],
)
]
else:
Modules = [
Extension ('afedrinet.afedrinet_io',
libraries = ['m'],
sources = ['../import_quisk_api.c', '../is_key_down.c', 'afedrinet_io.c'],
include_dirs = ['.', '..'],
)
]
setup (name = 'afedrinet_io',
version = '0.1',
description = 'Afedri',
long_description = "Afedri.",
author = 'Alex',
author_email = 'Alex@gmail.com',
#url = 'http://',
download_url = 'http://james.ahlstrom.name/quisk/',
packages = ['afedrinet.afedrinet_io'],
package_dir = {'afedrinet' : '.'},
ext_modules = Modules,
classifiers = [
'Development Status :: 6 - Mature',
'Environment :: X11 Applications',
'Environment :: Win32 (MS Windows)',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: English',
'Operating System :: POSIX :: Linux',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python',
'Programming Language :: C',
'Topic :: Communications :: Ham Radio',
],
)
+16
View File
@@ -0,0 +1,16 @@
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os,sys
from ctypes import *
os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH']
from sdr_control import *
#import sdr_control
#class self(control)
cnt = Control()
cnt.OpenHW()
cnt.SetHWLO(28050000)
cnt.SetHWSR(740740)
cnt.CloseHW()
+4294
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>QUISK Configuration Defaults: quisk_conf_defaults.py</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="description" content="The file that describes the default configuration for Quisk.">
<meta name="author" content="James C. Ahlstrom">
<meta name="keywords" content="quisk, sdr, software defined radio, ham radio">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
* {
margin: 0px;
padding: 0px;
border: 0px;
}
iframe {
overflow: scroll;
width: 100%;
height: 100%;
}
div.contents {
position: fixed;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="contents">
<iframe src="quisk_conf_defaults.py"> </iframe>
</div>
</body>
</html>
+1766
View File
File diff suppressed because it is too large Load Diff
+141
View File
@@ -0,0 +1,141 @@
from __future__ import absolute_import
# This code was contributed by Christof, DJ4CM. Many Thanks!!
import threading
import time
import telnetlib
import quisk_conf_defaults as conf
class DxEntry:
def __init__(self):
self.info = []
def getFreq(self):
return self.freq
def getDX(self):
return self.dx
def getSpotter(self, index):
return self.info[index][0]
def getTime(self, index):
return self.info[index][1]
def getLocation(self, index):
return self.info[index][2]
def getComment(self, index):
return self.info[index][3]
def getLen(self):
return len(self.info)
def equal(self, element):
if element.getDX() == self.dx:
return True
else:
return False
def join (self, element):
for i in range (0, len(element.info)):
self.info.insert(0, element.info[i])
length = len(self.info)
# limit to max history
if length > 3:
del (self.info[length-1])
self.timestamp = max (self.timestamp, element.timestamp)
def isExpired(self):
return time.time()-self.timestamp > conf.dxClExpireTime * 60
def parseMessage(self, message):
words = message.split()
sTime = ''
locator = ''
comment = ''
if len(words) > 3 and words[0].lower() == 'dx' and words[1].lower() == 'de':
spotter = words[2].strip(':')
self.freq = int(float(words[3])*1000)
self.dx = words[4]
for index in range (5, len(words)):
word = words[index]
try:
if sTime != '':
locator = word.strip('\07')
#search time
if word[0:3].isdigit() and word[4].isalpha():
sTime = word.strip('\07')
sTime = sTime[0:2]+':'+sTime[2:4]+ ' UTC'
if sTime == '':
if comment != '':
comment += ' '
comment += word
except:
pass
self.info.insert(0, (spotter, sTime, locator, comment))
self.timestamp = time.time()
#print(self.dx, self.freq, spotter, sTime, locator, comment)
return True
return False
class DxCluster(threading.Thread):
def __init__(self):
self.do_init = 1
threading.Thread.__init__(self)
self.doQuit = threading.Event()
self.dxSpots = []
self.doQuit.clear()
def run(self):
self.telnetInit()
self.telnetConnect()
while not self.doQuit.isSet():
try:
self.telnetRead()
except:
self.tn.close()
time.sleep(20)
if not self.doQuit.isSet():
self.telnetConnect()
self.tn.close()
def setListener (self, listener):
self.listener = listener
def telnetInit(self):
self.tn = telnetlib.Telnet()
def telnetConnect(self):
for i in range(10):
try:
self.tn.open(conf.dxClHost, conf.dxClPort, 10)
self.tn.read_until(b"login:", 10)
self.tn.write(conf.user_call_sign.encode('utf-8', errors='ignore') + b"\n") # user_call_sign may be Unicode
break
except:
time.sleep(0.5)
if conf.dxClPassword:
self.tn.read_until(b"Password: ")
self.tn.write(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\n")
def telnetRead(self):
message = self.tn.read_until(b'\n', 60).decode(encoding='utf-8', errors='replace')
if self.doQuit.isSet() == False:
dxEntry = DxEntry();
if dxEntry.parseMessage(message):
for i, listElement in enumerate(self.dxSpots):
if (listElement.equal(dxEntry)):
listElement.join (dxEntry)
return
if listElement.isExpired():
del (self.dxSpots[i])
self.dxSpots.append(dxEntry)
if self.listener:
self.listener()
def getHost(self):
return self.tn.host + ':' + str(self.tn.port)
def stop(self):
self.doQuit.set()
+47
View File
@@ -0,0 +1,47 @@
#include <Python.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include "quisk.h"
// If you set add_extern_demod in your config file, you will get another
// button that will call this module. Change it to what you want. Save
// a copy because new releases of Quisk will overwrite this file.
//
// NOTE: NEW RELEASES OF QUISK WILL OVERWRITE THIS FILE!
int quisk_extern_demod(complex double * cSamples, int nSamples, double decim)
{ // Filter and demodulate the I/Q samples into audio play samples.
// cSamples: The input I/Q samples, and the output stereo play samples.
// nSamples: The number of input samples; maximum is SAMP_BUFFER_SIZE.
// decim: The decimation needed (1.0 for no decimation).
// The output play samples are stereo, and are placed into cSamples.
// The return value is the number of output samples = nSamples / decim.
// See quisk.h for useful data in quisk_sound_state. For example, the
// sample rate is quisk_sound_state.sample_rate. If you need decimation,
// look at iDecimate() and fDecimate() in quisk.c.
int i;
double d, di;
complex double cx;
static complex double fm_1 = 10; // Sample delayed by one
static complex double fm_2 = 10; // Sample delayed by two
if (fabs (decim - 1.0) > 0.001) // no provision for decimation
return 0;
for (i = 0; i < nSamples; i++) { // narrow FM
cx = cSamples[i];
di = creal(fm_1) * (cimag(cx) - cimag(fm_2)) -
cimag(fm_1) * (creal(cx) - creal(fm_2));
d = creal(fm_1) * creal(fm_1) + cimag(fm_1) * cimag(fm_1);
if (d == 0) // I don't think this can happen
di = 0;
else
di = di / d * quisk_sound_state.sample_rate;
fm_2 = fm_1; // fm_2 is sample cSamples[i - 2]
fm_1 = cx; // fm_1 is sample cSamples[i - 1]
cSamples[i] = di + I * di; // monophonic sound, two channels
}
return nSamples; // Number of play samples
}
+489
View File
@@ -0,0 +1,489 @@
#include <Python.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h> // Use native C99 complex type for fftw3
#include "quisk.h"
#include "filter.h"
#include "filters.h"
void quisk_filt_cInit(struct quisk_cFilter * filter, double * coefs, int taps)
{ // Prepare a new filter using coefs and taps. Samples are complex.
filter->dCoefs = coefs;
filter->cpxCoefs = NULL;
filter->cSamples = (complex double *)malloc(taps * sizeof(complex double));
memset(filter->cSamples, 0, taps * sizeof(complex double));
filter->ptcSamp = filter->cSamples;
filter->nTaps = taps;
filter->decim_index = 0;
filter->cBuf = NULL;
filter->nBuf = 0;
}
void quisk_filt_dInit(struct quisk_dFilter * filter, double * coefs, int taps)
{ // Prepare a new filter using coefs and taps. Samples are double.
filter->dCoefs = coefs;
filter->cpxCoefs = NULL;
filter->dSamples = (double *)malloc(taps * sizeof(double));
memset(filter->dSamples, 0, taps * sizeof(double));
filter->ptdSamp = filter->dSamples;
filter->nTaps = taps;
filter->decim_index = 0;
filter->dBuf = NULL;
filter->nBuf = 0;
}
void quisk_filt_differInit(struct quisk_dFilter * filter, int taps)
{ // Prepare a new classic differentiating filter. taps must be odd.
int j, k;
filter->dCoefs = (double *)malloc(taps * sizeof(double));
for (k = - (taps - 1) / 2; k <= (taps - 1) / 2; k++) {
j = (taps - 1) / 2 + k;
if (k == 0)
filter->dCoefs[j] = 0;
else
filter->dCoefs[j] = pow(-1, k) / k;
printf("%4d taps %8.4lf\n", j, filter->dCoefs[j]);
}
filter->cpxCoefs = NULL;
filter->dSamples = (double *)malloc(taps * sizeof(double));
memset(filter->dSamples, 0, taps * sizeof(double));
filter->ptdSamp = filter->dSamples;
filter->nTaps = taps;
filter->decim_index = 0;
filter->dBuf = NULL;
filter->nBuf = 0;
}
void quisk_filt_tune(struct quisk_dFilter * filter, double freq, int ssb_upper)
{ // Tune a filter into an analytic I/Q filter with complex coefficients.
// freq is the center frequency / sample rate. Reverse coef if ssb_upper == 0.
// This is used for both quisk_dFilter and quisk_cFilter with a cast.
// Filter can be re-tuned repeatedly.
//
// The tuned low pass filter has a loss of 0.5 when applied to real signals.
// There is no loss applied to complex signals. Coeffs of the tuned filter are not symetric(??).
int i;
complex double coef, tune;
double D;
if ( ! filter->cpxCoefs)
filter->cpxCoefs = (complex double *)malloc(filter->nTaps * sizeof(complex double));
tune = I * 2.0 * M_PI * freq;
D = (filter->nTaps - 1.0) / 2.0;
for (i = 0; i < filter->nTaps; i++) {
coef = cexp(tune * (i - D)) * filter->dCoefs[i];
if (ssb_upper)
filter->cpxCoefs[i] = coef;
else
filter->cpxCoefs[i] = cimag(coef) + I * creal(coef);
}
}
complex double quisk_dC_out(double sample, struct quisk_dFilter * filter)
{
complex double csample;
complex double * ptCoef;
double * ptSample;
int k;
// FIR bandpass filter; separate double sample into I and Q.
// Put samples into buffer left to right. Use samples right to left.
ptSample = filter->ptdSamp;
*ptSample = sample;
ptCoef = filter->cpxCoefs;
csample = 0;
for (k = 0; k < filter->nTaps; k++, ptCoef++) {
csample += *ptSample * *ptCoef;
if (--ptSample < filter->dSamples)
ptSample = filter->dSamples + filter->nTaps - 1;
}
if (++filter->ptdSamp >= filter->dSamples + filter->nTaps)
filter->ptdSamp = filter->dSamples;
return csample;
}
#if 0
complex double quisk_cC_out(complex double sample, struct quisk_cFilter * filter)
{
complex double csample;
complex double * ptCoef;
complex double * ptSample;
int k;
// FIR bandpass filter; filter complex samples by complex coeffs.
// Put samples into buffer left to right. Use samples right to left.
ptSample = filter->ptcSamp;
*ptSample = sample;
ptCoef = filter->cpxCoefs;
csample = 0;
for (k = 0; k < filter->nTaps; k++, ptCoef++) {
csample += *ptSample * *ptCoef;
if (--ptSample < filter->cSamples)
ptSample = filter->cSamples + filter->nTaps - 1;
}
if (++filter->ptcSamp >= filter->cSamples + filter->nTaps)
filter->ptcSamp = filter->cSamples;
return csample;
}
#endif
int quisk_cInterpolate(complex double * cSamples, int count, struct quisk_cFilter * filter, int interp)
{ // This uses the double coefficients of filter (not the complex). Samples are complex.
int i, j, k, nOut;
double * ptCoef;
complex double * ptSample;
complex double csample;
if (count > filter->nBuf) { // increase size of sample buffer
filter->nBuf = count * 2;
if (filter->cBuf)
free(filter->cBuf);
filter->cBuf = (complex double *)malloc(filter->nBuf * sizeof(complex double));
}
memcpy(filter->cBuf, cSamples, count * sizeof(complex double));
nOut = 0;
for (i = 0; i < count; i++) {
// Put samples into buffer left to right. Use samples right to left.
*filter->ptcSamp = filter->cBuf[i];
for (j = 0; j < interp; j++) {
ptSample = filter->ptcSamp;
ptCoef = filter->dCoefs + j;
csample = 0;
for (k = 0; k < filter->nTaps / interp; k++, ptCoef += interp) {
csample += *ptSample * *ptCoef;
if (--ptSample < filter->cSamples)
ptSample = filter->cSamples + filter->nTaps - 1;
}
if (nOut < SAMP_BUFFER_SIZE * 8 / 10)
cSamples[nOut++] = csample * interp;
}
if (++filter->ptcSamp >= filter->cSamples + filter->nTaps)
filter->ptcSamp = filter->cSamples;
}
return nOut;
}
int quisk_dInterpolate(double * dSamples, int count, struct quisk_dFilter * filter, int interp)
{ // This uses the double coefficients of filter (not the complex). Samples are double.
int i, j, k, nOut;
double * ptCoef;
double * ptSample;
double dsample;
if (count > filter->nBuf) { // increase size of sample buffer
filter->nBuf = count * 2;
if (filter->dBuf)
free(filter->dBuf);
filter->dBuf = (double *)malloc(filter->nBuf * sizeof(double));
}
memcpy(filter->dBuf, dSamples, count * sizeof(double));
nOut = 0;
for (i = 0; i < count; i++) {
// Put samples into buffer left to right. Use samples right to left.
*filter->ptdSamp = filter->dBuf[i];
for (j = 0; j < interp; j++) {
ptSample = filter->ptdSamp;
ptCoef = filter->dCoefs + j;
dsample = 0;
for (k = 0; k < filter->nTaps / interp; k++, ptCoef += interp) {
dsample += *ptSample * *ptCoef;
if (--ptSample < filter->dSamples)
ptSample = filter->dSamples + filter->nTaps - 1;
}
if (nOut < SAMP_BUFFER_SIZE * 8 / 10)
dSamples[nOut++] = dsample * interp;
}
if (++filter->ptdSamp >= filter->dSamples + filter->nTaps)
filter->ptdSamp = filter->dSamples;
}
return nOut;
}
int quisk_cDecimate(complex double * cSamples, int count, struct quisk_cFilter * filter, int decim)
{ // This uses the double coefficients of filter (not the complex).
int i, k, nOut;
complex double * ptSample;
double * ptCoef;
complex double csample;
nOut = 0;
for (i = 0; i < count; i++) {
*filter->ptcSamp = cSamples[i];
if (++filter->decim_index >= decim) {
filter->decim_index = 0; // output a sample
csample = 0;
ptSample = filter->ptcSamp;
ptCoef = filter->dCoefs;
for (k = 0; k < filter->nTaps; k++, ptCoef++) {
csample += *ptSample * *ptCoef;
if (--ptSample < filter->cSamples)
ptSample = filter->cSamples + filter->nTaps - 1;
}
cSamples[nOut++] = csample;
}
if (++filter->ptcSamp >= filter->cSamples + filter->nTaps)
filter->ptcSamp = filter->cSamples;
}
return nOut;
}
int quisk_cCDecimate(complex double * cSamples, int count, struct quisk_cFilter * filter, int decim)
{ // This uses the complex coefficients of filter (not the double). Call quisk_filt_tune() first.
int i, k, nOut;
complex double * ptSample;
complex double * ptCoef;
complex double csample;
nOut = 0;
for (i = 0; i < count; i++) {
*filter->ptcSamp = cSamples[i];
if (++filter->decim_index >= decim) {
filter->decim_index = 0; // output a sample
csample = 0;
ptSample = filter->ptcSamp;
ptCoef = filter->cpxCoefs;
for (k = 0; k < filter->nTaps; k++, ptCoef++) {
csample += *ptSample * *ptCoef;
if (--ptSample < filter->cSamples)
ptSample = filter->cSamples + filter->nTaps - 1;
}
cSamples[nOut++] = csample;
}
if (++filter->ptcSamp >= filter->cSamples + filter->nTaps)
filter->ptcSamp = filter->cSamples;
}
return nOut;
}
int quisk_dDecimate(double * dSamples, int count, struct quisk_dFilter * filter, int decim)
{ // This uses the double coefficients of filter (not the complex).
int i, k, nOut;
double * ptSample;
double * ptCoef;
double dsample;
nOut = 0;
for (i = 0; i < count; i++) {
*filter->ptdSamp = dSamples[i];
if (++filter->decim_index >= decim) {
filter->decim_index = 0; // output a sample
dsample = 0;
ptSample = filter->ptdSamp;
ptCoef = filter->dCoefs;
for (k = 0; k < filter->nTaps; k++, ptCoef++) {
dsample += *ptSample * *ptCoef;
if (--ptSample < filter->dSamples)
ptSample = filter->dSamples + filter->nTaps - 1;
}
dSamples[nOut++] = dsample;
}
if (++filter->ptdSamp >= filter->dSamples + filter->nTaps)
filter->ptdSamp = filter->dSamples;
}
return nOut;
}
int quisk_cInterpDecim(complex double * cSamples, int count, struct quisk_cFilter * filter, int interp, int decim)
{ // Interpolate by interp, and then decimate by decim.
// This uses the double coefficients of filter (not the complex). Samples are complex.
int i, k, nOut;
double * ptCoef;
complex double * ptSample;
complex double csample;
if (count > filter->nBuf) { // increase size of sample buffer
filter->nBuf = count * 2;
if (filter->cBuf)
free(filter->cBuf);
filter->cBuf = (complex double *)malloc(filter->nBuf * sizeof(complex double));
}
memcpy(filter->cBuf, cSamples, count * sizeof(complex double));
nOut = 0;
for (i = 0; i < count; i++) {
// Put samples into buffer left to right. Use samples right to left.
*filter->ptcSamp = filter->cBuf[i];
while (filter->decim_index < interp) {
ptSample = filter->ptcSamp;
ptCoef = filter->dCoefs + filter->decim_index;
csample = 0;
for (k = 0; k < filter->nTaps / interp; k++, ptCoef += interp) {
csample += *ptSample * *ptCoef;
if (--ptSample < filter->cSamples)
ptSample = filter->cSamples + filter->nTaps - 1;
}
if (nOut < SAMP_BUFFER_SIZE * 8 / 10)
cSamples[nOut++] = csample * interp;
filter->decim_index += decim;
}
if (++filter->ptcSamp >= filter->cSamples + filter->nTaps)
filter->ptcSamp = filter->cSamples;
filter->decim_index = filter->decim_index - interp;
}
return nOut;
}
double quisk_dD_out(double samp, struct quisk_dFilter * filter)
{ // Filter double samples.
int k;
double * ptSample;
double * ptCoef;
double dsample;
*filter->ptdSamp = samp;
dsample = 0;
ptSample = filter->ptdSamp;
ptCoef = filter->dCoefs;
for (k = 0; k < filter->nTaps; k++, ptCoef++) {
dsample += *ptSample * *ptCoef;
if (--ptSample < filter->dSamples)
ptSample = filter->dSamples + filter->nTaps - 1;
}
if (++filter->ptdSamp >= filter->dSamples + filter->nTaps)
filter->ptdSamp = filter->dSamples;
return dsample;
}
int quisk_dFilter(double * dSamples, int count, struct quisk_dFilter * filter)
{ // Filter double samples.
int i, k, nOut;
double * ptSample;
double * ptCoef;
double dsample;
nOut = 0;
for (i = 0; i < count; i++) {
*filter->ptdSamp = dSamples[i];
dsample = 0;
ptSample = filter->ptdSamp;
ptCoef = filter->dCoefs;
for (k = 0; k < filter->nTaps; k++, ptCoef++) {
dsample += *ptSample * *ptCoef;
if (--ptSample < filter->dSamples)
ptSample = filter->dSamples + filter->nTaps - 1;
}
dSamples[nOut++] = dsample;
if (++filter->ptdSamp >= filter->dSamples + filter->nTaps)
filter->ptdSamp = filter->dSamples;
}
return nOut;
}
int quisk_cFilter(complex double * cSamples, int count, struct quisk_cFilter * filter)
{ // Filter complex samples using the double coefficients of filter (not the complex).
return quisk_cDecimate(cSamples, count, filter, 1);
}
int quisk_cDecim2HB45(complex double * cSamples, int count, struct quisk_cHB45Filter * filter)
{ // This uses the double coefficients of filter (not the complex).
// Half band filter, sample rate 96 Hz, pass 16, center 24, stop 32, good BW 2/3, 45 taps.
int i, nOut;
complex double * samples, * center;
static double coef[12] = { 0.000018566625444266, -0.000118469698701817, 0.000457318798253456,
-0.001347840471412094, 0.003321838571445455, -0.007198422696929033, 0.014211106939802483,
-0.026424776824073383, 0.048414810444971007, -0.096214669073304823, 0.314881034738348550,
0.500000000000000000 }; // Rate 96, cutoff 16-24-32, atten 120 dB. Coef[0] and [44] are zero.
nOut = 0;
samples = filter->samples;
center = filter->center;
for (i = 0; i < count; i++) {
if (filter->toggle == 0){
filter->toggle = 1;
memmove(center + 1, center, sizeof(complex double) * 10);
center[0] = cSamples[i];
}
else {
filter->toggle = 0;
memmove(samples + 1, samples, sizeof(complex double) * 21);
samples[0] = cSamples[i];
// output a sample
cSamples[nOut++] =
(samples[ 0] + samples[21]) * coef[0] +
(samples[ 1] + samples[20]) * coef[1] +
(samples[ 2] + samples[19]) * coef[2] +
(samples[ 3] + samples[18]) * coef[3] +
(samples[ 4] + samples[17]) * coef[4] +
(samples[ 5] + samples[16]) * coef[5] +
(samples[ 6] + samples[15]) * coef[6] +
(samples[ 7] + samples[14]) * coef[7] +
(samples[ 8] + samples[13]) * coef[8] +
(samples[ 9] + samples[12]) * coef[9] +
(samples[10] + samples[11]) * coef[10] +
center[10] * coef[11];
}
}
return nOut;
}
int quisk_dInterp2HB45(double * dsamples, int count, struct quisk_dHB45Filter * filter)
{ // Half-Band interpolation by 2
int i, k, nOut, nCoef, nSamp;
double out;
double * samples;
static double coef[12] = { 0.000018566625444266, -0.000118469698701817, 0.000457318798253456,
-0.001347840471412094, 0.003321838571445455, -0.007198422696929033, 0.014211106939802483,
-0.026424776824073383, 0.048414810444971007, -0.096214669073304823, 0.314881034738348550,
0.500000000000000000 }; // Rate 96, cutoff 16-24-32, atten 120 dB. Coef[0] and [44] are zero.
if (count > filter->nBuf) { // increase size of sample buffer
filter->nBuf = count * 2;
if (filter->dBuf)
free(filter->dBuf);
filter->dBuf = (double *)malloc(filter->nBuf * sizeof(double));
}
nCoef = 12;
nSamp = (nCoef - 1) * 2;
memcpy(filter->dBuf, dsamples, count * sizeof(double));
samples = filter->samples;
nOut = 0;
for (i = 0; i < count; i++) {
memmove(samples + 1, samples, (nSamp - 1) * sizeof(double));
samples[0] = filter->dBuf[i];
if (nOut > SAMP_BUFFER_SIZE * 8 / 10)
continue;
dsamples[nOut++] = samples[nCoef - 1] * coef[nCoef - 1] * 2;
out = 0;
for (k = 0; k < nSamp / 2; k++)
out += (samples[k] + samples[nSamp - 1 - k]) * coef[k];
dsamples[nOut++] = out * 2;
}
return nOut;
}
int quisk_cInterp2HB45(complex double * cSamples, int count, struct quisk_cHB45Filter * filter)
{ // Half-Band interpolation by 2
int i, k, nOut, nCoef, nSamp;
complex double out;
complex double * samples;
static double coef[12] = { 0.000018566625444266, -0.000118469698701817, 0.000457318798253456,
-0.001347840471412094, 0.003321838571445455, -0.007198422696929033, 0.014211106939802483,
-0.026424776824073383, 0.048414810444971007, -0.096214669073304823, 0.314881034738348550,
0.500000000000000000 }; // Rate 96, cutoff 16-24-32, atten 120 dB. Coef[0] and [44] are zero.
if (count > filter->nBuf) { // increase size of sample buffer
filter->nBuf = count * 2;
if (filter->cBuf)
free(filter->cBuf);
filter->cBuf = (complex double *)malloc(filter->nBuf * sizeof(complex double));
}
nCoef = 12;
nSamp = (nCoef - 1) * 2;
memcpy(filter->cBuf, cSamples, count * sizeof(complex double));
samples = filter->samples;
nOut = 0;
for (i = 0; i < count; i++) {
memmove(samples + 1, samples, (nSamp - 1) * sizeof(complex double));
samples[0] = filter->cBuf[i];
if (nOut > SAMP_BUFFER_SIZE * 8 / 10)
continue;
cSamples[nOut++] = samples[nCoef - 1] * coef[nCoef - 1] * 2;
out = 0;
for (k = 0; k < nSamp / 2; k++)
out += (samples[k] + samples[nSamp - 1 - k]) * coef[k];
cSamples[nOut++] = out * 2;
}
return nOut;
}
+86
View File
@@ -0,0 +1,86 @@
struct quisk_cFilter {
double * dCoefs; // filter coefficients
complex double * cpxCoefs; // make the complex coefficients from dCoefs
int nBuf; // dimension of cBuf
int nTaps; // dimension of dSamples, cSamples, dCoefs and cpxCoefs
int decim_index; // used to count samples for decimation
complex double * cSamples; // storage for old samples
complex double * ptcSamp; // next available position in cSamples
complex double * cBuf; // auxillary buffer for interpolation
} ;
struct quisk_dFilter {
double * dCoefs; // filter coefficients
complex double * cpxCoefs; // make the complex coefficients from dCoefs
int nBuf; // dimension of dBuf
int nTaps; // dimension of dSamples, cSamples, dCoefs and cpxCoefs
int decim_index; // used to count samples for decimation
double * dSamples; // storage for old samples
double * ptdSamp; // next available position in dSamples
double * dBuf; // auxillary buffer for interpolation
} ;
struct quisk_cHB45Filter { // Complex half band decimate by 2 filter with 45 coefficients
complex double * cBuf; // auxillary buffer for interpolation
int nBuf; // dimension of cBuf
int toggle;
complex double samples[22];
complex double center[11];
} ;
struct quisk_dHB45Filter { // Real half band decimate by 2 filter with 45 coefficients
double * dBuf; // auxillary buffer for interpolation
int nBuf; // dimension of dBuf
int toggle;
double samples[22];
double center[11];
} ;
void quisk_filt_cInit(struct quisk_cFilter *, double *, int);
void quisk_filt_dInit(struct quisk_dFilter *, double *, int);
void quisk_filt_differInit(struct quisk_dFilter *, int);
void quisk_filt_tune(struct quisk_dFilter *, double, int);
complex double quisk_dC_out(double, struct quisk_dFilter *);
double quisk_dD_out(double, struct quisk_dFilter *);
int quisk_cInterpolate(complex double *, int, struct quisk_cFilter *, int);
int quisk_dInterpolate(double *, int, struct quisk_dFilter *, int);
int quisk_cDecimate(complex double *, int, struct quisk_cFilter *, int);
int quisk_cCDecimate(complex double *, int, struct quisk_cFilter *, int);
int quisk_dDecimate(double *, int, struct quisk_dFilter *, int);
int quisk_cInterpDecim(complex double *, int, struct quisk_cFilter *, int, int);
int quisk_cDecim2HB45(complex double *, int, struct quisk_cHB45Filter *);
int quisk_dInterp2HB45(double *, int, struct quisk_dHB45Filter *);
int quisk_cInterp2HB45(complex double *, int, struct quisk_cHB45Filter *);
int quisk_dFilter(double *, int, struct quisk_dFilter *);
int quisk_cFilter(complex double *, int, struct quisk_cFilter *);
extern double quiskMicFilt48Coefs[325];
extern double quiskMic5Filt48Coefs[424];
extern double quiskMicFilt8Coefs[93];
extern double quiskLpFilt48Coefs[186];
extern double quiskFilt12_19Coefs[64];
extern double quiskFilt185D3Coefs[189];
extern double quiskFilt133D2Coefs[136];
extern double quiskFilt167D3Coefs[174];
extern double quiskFilt111D2Coefs[114];
extern double quiskFilt53D1Coefs[55];
extern double quiskFilt53D2Coefs[93];
extern double quiskFilt144D3Coefs[147];
extern double quiskFilt240D5Coefs[115];
extern double quiskFilt240D5CoefsSharp[245];
extern double quiskFilt48dec24Coefs[98];
extern double quiskAudio24p6Coefs[36];
extern double quiskAudio48p6Coefs[71];
extern double quiskAudio96Coefs[11];
extern double quiskAudio24p4Coefs[50];
extern double quiskAudioFmHpCoefs[309];
extern double quiskAudio24p3Coefs[100];
extern double quiskFiltTx8kAudioB[168];
extern double quiskFilt16dec8Coefs[62];
extern double quiskFilt120s03[480];
extern double quiskFiltI3D25Coefs[825];
extern double quiskDgtFilt48Coefs[520];
extern double quiskFilt300D5Coefs[125];
extern double quiskFilt300D6Coefs[248];
extern double quiskFilt240D4Coefs[100];
extern double quiskDiff48Coefs[38];
+1308
View File
File diff suppressed because it is too large Load Diff
+770
View File
@@ -0,0 +1,770 @@
# Rate 24000 sps, ripple 0.2 dB, atten 100 dB, shape 1.2
# Filters key is the bandwidth at 24000 sps.
# key = bw * 24000 / rate / 2
# bw = key * 2 * rate / 24000
Filters = {
8500: # 55 taps
[ -0.001578241201420500, -0.002488309592652162, 0.001185909979889287, 0.000286041612735020, -0.001914552184024928, 0.002760744242385052,
-0.001796589368523962, -0.001056335106849282, 0.004390770806455585, -0.005824325923037834, 0.003465568616258560, 0.002462568821267293,
-0.008988281812658983, 0.011629434198087422, -0.007027876977623796, -0.004272818661253936, 0.016890540812408691, -0.022619963224181760,
0.014949848668575091, 0.006115840936585570, -0.031813780331403710, 0.046915611796058194, -0.036226209362519565, -0.007500120805011563,
0.078216492123247466, -0.156996838729026640, 0.218668091485032380, 0.758014834761081340, 0.218668091485032380, -0.156996838729026640,
0.078216492123247466, -0.007500120805011563, -0.036226209362519565, 0.046915611796058194, -0.031813780331403710, 0.006115840936585570,
0.014949848668575091, -0.022619963224181760, 0.016890540812408691, -0.004272818661253936, -0.007027876977623796, 0.011629434198087422,
-0.008988281812658983, 0.002462568821267293, 0.003465568616258560, -0.005824325923037834, 0.004390770806455585, -0.001056335106849282,
-0.001796589368523962, 0.002760744242385052, -0.001914552184024928, 0.000286041612735020, 0.001185909979889287, -0.002488309592652162,
-0.001578241201420500],
7500: # 62 taps
[ 0.000094561136776712, 0.001408783281963872, 0.003252521294198745, 0.001088470223697677, -0.002238006126669353, 0.000797427337925380,
0.001818354390057951, -0.002971083246008131, 0.000737271087741229, 0.003304967641132078, -0.004678221609224611, 0.000618143813431047,
0.005732278632558854, -0.007264716193352521, 0.000382151456262394, 0.009375552017313197, -0.011107789354244654, 0.000048810931888440,
0.014797494013431127, -0.016988015917755381, -0.000322746345617388, 0.023289857616213484, -0.026716501910571362, -0.000681860486120778,
0.038508535988246995, -0.045999278172093885, -0.000961050429347091, 0.076884527279961243, -0.108006033576806130, -0.001113929105132611,
0.551548401015432340, 0.551548401015432340, -0.001113929105132611, -0.108006033576806130, 0.076884527279961243, -0.000961050429347091,
-0.045999278172093885, 0.038508535988246995, -0.000681860486120778, -0.026716501910571362, 0.023289857616213484, -0.000322746345617388,
-0.016988015917755381, 0.014797494013431127, 0.000048810931888440, -0.011107789354244654, 0.009375552017313197, 0.000382151456262394,
-0.007264716193352521, 0.005732278632558854, 0.000618143813431047, -0.004678221609224611, 0.003304967641132078, 0.000737271087741229,
-0.002971083246008131, 0.001818354390057951, 0.000797427337925380, -0.002238006126669353, 0.001088470223697677, 0.003252521294198745,
0.001408783281963872, 0.000094561136776712],
6000: # 77 taps
[0.000208183216794497, 0.001160220144497454, 0.002498877019501132, 0.002217684703504632, -0.000364985361618348, -0.001823661861823501,
0.000356579314286080, 0.002005946277037424, -0.000685757071824683, -0.002428321330453817, 0.001256816376812629, 0.002943833745660115,
-0.002130949481744204, -0.003446793644558851, 0.003380457505759830, 0.003807055791440830, -0.005074958590792311, -0.003869772827804518,
0.007242784565398008, 0.003418656761533474, -0.009894698971107840, -0.002211451952907241, 0.012977467451293289, -0.000058006701182926,
-0.016390443243429843, 0.003773319671211690, 0.019989716564592076, -0.009449626980637388, -0.023581188491464997, 0.017927337095200193,
0.026945533578336647, -0.030887338584165511, -0.029856365801280219, 0.052657593695444822, 0.032108551641510746, -0.099253908185103573,
-0.033533139787951992, 0.315983886115891840, 0.534019780032233380, 0.315983886115891840, -0.033533139787951992, -0.099253908185103573,
0.032108551641510746, 0.052657593695444822, -0.029856365801280219, -0.030887338584165511, 0.026945533578336647, 0.017927337095200193,
-0.023581188491464997, -0.009449626980637388, 0.019989716564592076, 0.003773319671211690, -0.016390443243429843, -0.000058006701182926,
0.012977467451293289, -0.002211451952907241, -0.009894698971107840, 0.003418656761533474, 0.007242784565398008, -0.003869772827804518,
-0.005074958590792311, 0.003807055791440830, 0.003380457505759830, -0.003446793644558851, -0.002130949481744204, 0.002943833745660115,
0.001256816376812629, -0.002428321330453817, -0.000685757071824683, 0.002005946277037424, 0.000356579314286080, -0.001823661861823501,
-0.000364985361618348, 0.002217684703504632, 0.002498877019501132, 0.001160220144497454, 0.000208183216794497],
5000: # 92 taps
[ 0.000172282027074915, 0.000805722137099302, 0.001789488027434626, 0.002208898476835740, 0.001133555920432830, -0.000798012231727563,
-0.001468876483570138, 0.000053213574826391, 0.001640044205108085, 0.000693959429133929, -0.001663633271521857, -0.001609719846438075,
0.001334915862232435, 0.002601113060240987, -0.000511327252542249, -0.003459777069001452, -0.000863807590293483, 0.003915575549171324,
0.002725579328876624, -0.003678783180717698, -0.004875619345633069, 0.002500119680365451, 0.006978228022424931, -0.000223624762857588,
-0.008586543420189311, -0.003156720729060025, 0.009180028261871051, 0.007460886285077675, -0.008222908809263846, -0.012298402032805727,
0.005219135899689294, 0.017069401572755501, 0.000240426913514277, -0.020971624610692834, -0.008474182212803629, 0.022996901553415793,
0.019787130769856574, -0.021836278313112276, -0.034759648853466663, 0.015468734639474426, 0.055194479055695952, 0.000590985958335975,
-0.088238504731419823, -0.043831925654670714, 0.182996493758220890, 0.409587845444253250, 0.409587845444253250, 0.182996493758220890,
-0.043831925654670714, -0.088238504731419823, 0.000590985958335975, 0.055194479055695952, 0.015468734639474426, -0.034759648853466663,
-0.021836278313112276, 0.019787130769856574, 0.022996901553415793, -0.008474182212803629, -0.020971624610692834, 0.000240426913514277,
0.017069401572755501, 0.005219135899689294, -0.012298402032805727, -0.008222908809263846, 0.007460886285077675, 0.009180028261871051,
-0.003156720729060025, -0.008586543420189311, -0.000223624762857588, 0.006978228022424931, 0.002500119680365451, -0.004875619345633069,
-0.003678783180717698, 0.002725579328876624, 0.003915575549171324, -0.000863807590293483, -0.003459777069001452, -0.000511327252542249,
0.002601113060240987, 0.001334915862232435, -0.001609719846438075, -0.001663633271521857, 0.000693959429133929, 0.001640044205108085,
0.000053213574826391, -0.001468876483570138, -0.000798012231727563, 0.001133555920432830, 0.002208898476835740, 0.001789488027434626,
0.000805722137099302, 0.000172282027074915],
4500: # 102 taps
[ 0.000109626786353929, 0.000462685347095325, 0.000976091511950423, 0.001146819725082014, 0.000393473830731237, -0.001141511548465342,
-0.002282834370334400, -0.001829207175666342, -0.000101528442900756, 0.000987382913028173, 0.000039751448950354, -0.001822373509053446,
-0.001985247021210946, 0.000232895236822806, 0.002153431919758378, 0.000930723811183338, -0.002248491432658168, -0.003017055190706798,
0.000342078561845510, 0.003721285661215643, 0.002085819103613060, -0.003140571194897081, -0.004835117960215140, 0.000288263563014682,
0.005924235485555640, 0.003715592386573005, -0.004558690834977428, -0.007633320493226938, 0.000139066056400393, 0.009165154333957292,
0.006093793617689697, -0.006780247949818048, -0.011926916343123978, -0.000057228900700871, 0.014232549280021243, 0.009771685994328400,
-0.010499186884061604, -0.019033587881754202, -0.000249830231388923, 0.023228171670193941, 0.016360728644062135, -0.017855007053639658,
-0.033458833271228797, -0.000405885453398987, 0.044679917304132288, 0.033455780397647107, -0.040439521683945157, -0.085684078775375178,
-0.000493182012170114, 0.201251835014007290, 0.374516101600616320, 0.374516101600616320, 0.201251835014007290, -0.000493182012170114,
-0.085684078775375178, -0.040439521683945157, 0.033455780397647107, 0.044679917304132288, -0.000405885453398987, -0.033458833271228797,
-0.017855007053639658, 0.016360728644062135, 0.023228171670193941, -0.000249830231388923, -0.019033587881754202, -0.010499186884061604,
0.009771685994328400, 0.014232549280021243, -0.000057228900700871, -0.011926916343123978, -0.006780247949818048, 0.006093793617689697,
0.009165154333957292, 0.000139066056400393, -0.007633320493226938, -0.004558690834977428, 0.003715592386573005, 0.005924235485555640,
0.000288263563014682, -0.004835117960215140, -0.003140571194897081, 0.002085819103613060, 0.003721285661215643, 0.000342078561845510,
-0.003017055190706798, -0.002248491432658168, 0.000930723811183338, 0.002153431919758378, 0.000232895236822806, -0.001985247021210946,
-0.001822373509053446, 0.000039751448950354, 0.000987382913028173, -0.000101528442900756, -0.001829207175666342, -0.002282834370334400,
-0.001141511548465342, 0.000393473830731237, 0.001146819725082014, 0.000976091511950423, 0.000462685347095325, 0.000109626786353929],
4000: # 115 taps
[ 0.000078524780320715, 0.000329316620713670, 0.000756688762644958, 0.001124260123708105, 0.001012255791778806, 0.000167097630541898,
-0.001099970608704988, -0.001940975178324101, -0.001625045960154994, -0.000333634994172134, 0.000774600995458535, 0.000561151578352762,
-0.000854751315214004, -0.001984988321940357, -0.001407739886101419, 0.000563473972774177, 0.001900424989770745, 0.000925528523083090,
-0.001609328373038515, -0.002935359144493341, -0.001199176692593675, 0.002118199425756780, 0.003340065554812114, 0.000621160360920014,
-0.003467567745617676, -0.004224605385298791, -0.000122849763708023, 0.004757557369118008, 0.004664298923525774, -0.001148493552322016,
-0.006629807337962404, -0.005080846185003255, 0.002831233307698604, 0.008609924128977818, 0.004891354675929344, -0.005425659232916345,
-0.010937452229276256, -0.004116901228197529, 0.008877312794209294, 0.013300805492116527, 0.002232134062032589, -0.013614806007146969,
-0.015720726837983701, 0.001140172406302503, 0.019985979932262889, 0.017946437617680795, -0.007018595519087580, -0.029113241143045584,
-0.019901010720465118, 0.017502959326722210, 0.043879416112787749, 0.021395356319346116, -0.039872421015430898, -0.076438935865810770,
-0.022353637195009195, 0.124776719393937450, 0.286085089477746360, 0.356006518780215160, 0.286085089477746360, 0.124776719393937450,
-0.022353637195009195, -0.076438935865810770, -0.039872421015430898, 0.021395356319346116, 0.043879416112787749, 0.017502959326722210,
-0.019901010720465118, -0.029113241143045584, -0.007018595519087580, 0.017946437617680795, 0.019985979932262889, 0.001140172406302503,
-0.015720726837983701, -0.013614806007146969, 0.002232134062032589, 0.013300805492116527, 0.008877312794209294, -0.004116901228197529,
-0.010937452229276256, -0.005425659232916345, 0.004891354675929344, 0.008609924128977818, 0.002831233307698604, -0.005080846185003255,
-0.006629807337962404, -0.001148493552322016, 0.004664298923525774, 0.004757557369118008, -0.000122849763708023, -0.004224605385298791,
-0.003467567745617676, 0.000621160360920014, 0.003340065554812114, 0.002118199425756780, -0.001199176692593675, -0.002935359144493341,
-0.001609328373038515, 0.000925528523083090, 0.001900424989770745, 0.000563473972774177, -0.001407739886101419, -0.001984988321940357,
-0.000854751315214004, 0.000561151578352762, 0.000774600995458535, -0.000333634994172134, -0.001625045960154994, -0.001940975178324101,
-0.001099970608704988, 0.000167097630541898, 0.001012255791778806, 0.001124260123708105, 0.000756688762644958, 0.000329316620713670,
0.000078524780320715],
3000: # 153 taps
[ 0.000035516842253053, 0.000120241356692793, 0.000262655558849812, 0.000421822903927101, 0.000504577556110132, 0.000393748344899348,
0.000013887896365045, -0.000595749610102644, -0.001250516069080062, -0.001672797229233531, -0.001624666112532619, -0.001059802835950466,
-0.000199611602796516, 0.000539126532493226, 0.000756552265297489, 0.000316087930054194, -0.000530090257617794, -0.001244496438584232,
-0.001312107606392913, -0.000583465448463906, 0.000572285419075017, 0.001440987738923421, 0.001399231913182619, 0.000355359654986622,
-0.001108071194955440, -0.002036249585608732, -0.001707924634389252, -0.000169594756520662, 0.001685676765584473, 0.002618141236597755,
0.001855681542593165, -0.000301881949096732, -0.002542398236828585, -0.003317624887398237, -0.001892391320644168, 0.001031582912020721,
0.003613502326574886, 0.004009329165673526, 0.001661239094822322, -0.002143832449766766, -0.004935146076383144, -0.004634572876828250,
-0.001061541395886805, 0.003698344992855291, 0.006465358367957256, 0.005053382115343493, -0.000073525601278193, -0.005791007488487868,
-0.008166064459464053, -0.005109407447857288, 0.001926336767092539, 0.008511258651897639, 0.009958456933858797, 0.004574761558815073,
-0.004759500034603159, -0.012000283350062089, -0.011748002733961208, -0.003120526928071337, 0.008993494047071084, 0.016529385910476514,
0.013426861274101313, 0.000181178754148492, -0.015466003330041472, -0.022748666494653846, -0.014877802849879123, 0.005502737892245722,
0.026345716718994539, 0.032621748472455206, 0.016002483863926359, -0.017887767208878101, -0.049636964594866217, -0.054713319818683277,
-0.016713474537575129, 0.061906959876918494, 0.157995584096340960, 0.236648015289099320, 0.266956938993864910, 0.236648015289099320,
0.157995584096340960, 0.061906959876918494, -0.016713474537575129, -0.054713319818683277, -0.049636964594866217, -0.017887767208878101,
0.016002483863926359, 0.032621748472455206, 0.026345716718994539, 0.005502737892245722, -0.014877802849879123, -0.022748666494653846,
-0.015466003330041472, 0.000181178754148492, 0.013426861274101313, 0.016529385910476514, 0.008993494047071084, -0.003120526928071337,
-0.011748002733961208, -0.012000283350062089, -0.004759500034603159, 0.004574761558815073, 0.009958456933858797, 0.008511258651897639,
0.001926336767092539, -0.005109407447857288, -0.008166064459464053, -0.005791007488487868, -0.000073525601278193, 0.005053382115343493,
0.006465358367957256, 0.003698344992855291, -0.001061541395886805, -0.004634572876828250, -0.004935146076383144, -0.002143832449766766,
0.001661239094822322, 0.004009329165673526, 0.003613502326574886, 0.001031582912020721, -0.001892391320644168, -0.003317624887398237,
-0.002542398236828585, -0.000301881949096732, 0.001855681542593165, 0.002618141236597755, 0.001685676765584473, -0.000169594756520662,
-0.001707924634389252, -0.002036249585608732, -0.001108071194955440, 0.000355359654986622, 0.001399231913182619, 0.001440987738923421,
0.000572285419075017, -0.000583465448463906, -0.001312107606392913, -0.001244496438584232, -0.000530090257617794, 0.000316087930054194,
0.000756552265297489, 0.000539126532493226, -0.000199611602796516, -0.001059802835950466, -0.001624666112532619, -0.001672797229233531,
-0.001250516069080062, -0.000595749610102644, 0.000013887896365045, 0.000393748344899348, 0.000504577556110132, 0.000421822903927101,
0.000262655558849812, 0.000120241356692793, 0.000035516842253053],
2800: # 164 taps
[ 0.000031131729843669, 0.000101363194753254, 0.000221445426297415, 0.000366876574918002, 0.000474767078312489, 0.000455842035531617,
0.000232260566090893, -0.000211274148558287, -0.000789599231892209, -0.001321558408924163, -0.001590694231360794, -0.001445876584766002,
-0.000895790759735873, -0.000140741015448872, 0.000492974178868593, 0.000701994832615354, 0.000371048524479058, -0.000337331657814867,
-0.001034893398244673, -0.001296923457098872, -0.000901505495786621, 0.000013737161230886, 0.000983722390445291, 0.001446949255083495,
0.001067648443145340, -0.000031354774249279, -0.001279007610852184, -0.001938862367504847, -0.001531888866790246, -0.000161657161535566,
0.001470243306104189, 0.002410991575715065, 0.002003292184824320, 0.000324608046693798, -0.001764616004515830, -0.003045092340783782,
-0.002637146414877572, -0.000580772176139985, 0.002073712223135724, 0.003782183003317882, 0.003389875946583850, 0.000885046737439423,
-0.002455184150014141, -0.004690784048082864, -0.004326357662532086, -0.001279167957787954, 0.002902671922490944, 0.005795008254325585,
0.005478283962467929, 0.001772012477148658, -0.003450331904449436, -0.007166940301855534, -0.006922766471023288, -0.002399780547747377,
0.004134972375816635, 0.008909643621809859, 0.008776960071400969, 0.003217389082858829, -0.005021094247026112, -0.011201040385085154,
-0.011246577131734643, -0.004323921802028225, 0.006225285499714047, 0.014372818914766290, 0.014725605621083649, 0.005913959753070873,
-0.007987043798634774, -0.019127577707865114, -0.020080950001885520, -0.008435384367710340, 0.010879472951485192, 0.027244661531034900,
0.029649597250285761, 0.013188790214990415, -0.016693663421477496, -0.044885495280403948, -0.052572421004629472, -0.026098663081897257,
0.035426680640922303, 0.117691081437968410, 0.195575709828410660, 0.242828423889182740, 0.242828423889182740, 0.195575709828410660,
0.117691081437968410, 0.035426680640922303, -0.026098663081897257, -0.052572421004629472, -0.044885495280403948, -0.016693663421477496,
0.013188790214990415, 0.029649597250285761, 0.027244661531034900, 0.010879472951485192, -0.008435384367710340, -0.020080950001885520,
-0.019127577707865114, -0.007987043798634774, 0.005913959753070873, 0.014725605621083649, 0.014372818914766290, 0.006225285499714047,
-0.004323921802028225, -0.011246577131734643, -0.011201040385085154, -0.005021094247026112, 0.003217389082858829, 0.008776960071400969,
0.008909643621809859, 0.004134972375816635, -0.002399780547747377, -0.006922766471023288, -0.007166940301855534, -0.003450331904449436,
0.001772012477148658, 0.005478283962467929, 0.005795008254325585, 0.002902671922490944, -0.001279167957787954, -0.004326357662532086,
-0.004690784048082864, -0.002455184150014141, 0.000885046737439423, 0.003389875946583850, 0.003782183003317882, 0.002073712223135724,
-0.000580772176139985, -0.002637146414877572, -0.003045092340783782, -0.001764616004515830, 0.000324608046693798, 0.002003292184824320,
0.002410991575715065, 0.001470243306104189, -0.000161657161535566, -0.001531888866790246, -0.001938862367504847, -0.001279007610852184,
-0.000031354774249279, 0.001067648443145340, 0.001446949255083495, 0.000983722390445291, 0.000013737161230886, -0.000901505495786621,
-0.001296923457098872, -0.001034893398244673, -0.000337331657814867, 0.000371048524479058, 0.000701994832615354, 0.000492974178868593,
-0.000140741015448872, -0.000895790759735873, -0.001445876584766002, -0.001590694231360794, -0.001321558408924163, -0.000789599231892209,
-0.000211274148558287, 0.000232260566090893, 0.000455842035531617, 0.000474767078312489, 0.000366876574918002, 0.000221445426297415,
0.000101363194753254, 0.000031131729843669],
2500: # 184 taps
[ 0.000025917992771962, 0.000080053245121681, 0.000175970492512707, 0.000306338738553062, 0.000441565642149420, 0.000529323030925432,
0.000506924615738917, 0.000324946604356638, -0.000025129491557388, -0.000491406291157732, -0.000962621820235116, -0.001296070805120129,
-0.001367486032675225, -0.001125819560345427, -0.000629044038382859, -0.000039974702137716, 0.000424508165741649, 0.000577252836600570,
0.000345217289298406, -0.000184075454087644, -0.000781983763019378, -0.001165504636264451, -0.001120965614204760, -0.000611972955861357,
0.000181852749867249, 0.000923465749187075, 0.001259585458205595, 0.000987840637523650, 0.000172679353454093, -0.000852839379782549,
-0.001614970097646428, -0.001711408440970664, -0.001014931729801465, 0.000231871248756494, 0.001490613857222986, 0.002154309898448981,
0.001835132214640792, 0.000579040055238457, -0.001106680482118841, -0.002448432281636389, -0.002754083239929045, -0.001760402090267228,
0.000190461179667856, 0.002251447413131553, 0.003429705849477585, 0.003056701038439272, 0.001150595364468159, -0.001518817798865299,
-0.003726827835919960, -0.004345414044232734, -0.002906143603126905, 0.000100825920300210, 0.003366376342127399, 0.005318377282596896,
0.004864845763651887, 0.001979300066273931, -0.002175246374000726, -0.005692516278767321, -0.006780045002879058, -0.004649909783330805,
-0.000010396809644141, 0.005123662380431661, 0.008280363892442090, 0.007693898397635463, 0.003246147289573304, -0.003296547115364492,
-0.008942285826077026, -0.010803588267982528, -0.007523983582706444, -0.000118131836810526, 0.008247900700950585, 0.013549640285344118,
0.012768247956349992, 0.005492469173933735, -0.005544176969275239, -0.015378525898323159, -0.018928522076093565, -0.013447400254072768,
-0.000204681888673014, 0.015499492179231059, 0.026221846254622386, 0.025562428680330915, 0.011389219733612054, -0.012327402868838598,
-0.036056520568517325, -0.047698955925824099, -0.037076675237560336, -0.000252599132841553, 0.058062325226935912, 0.125125149137395140,
0.183789926776792010, 0.217965757483672360, 0.217965757483672360, 0.183789926776792010, 0.125125149137395140, 0.058062325226935912,
-0.000252599132841553, -0.037076675237560336, -0.047698955925824099, -0.036056520568517325, -0.012327402868838598, 0.011389219733612054,
0.025562428680330915, 0.026221846254622386, 0.015499492179231059, -0.000204681888673014, -0.013447400254072768, -0.018928522076093565,
-0.015378525898323159, -0.005544176969275239, 0.005492469173933735, 0.012768247956349992, 0.013549640285344118, 0.008247900700950585,
-0.000118131836810526, -0.007523983582706444, -0.010803588267982528, -0.008942285826077026, -0.003296547115364492, 0.003246147289573304,
0.007693898397635463, 0.008280363892442090, 0.005123662380431661, -0.000010396809644141, -0.004649909783330805, -0.006780045002879058,
-0.005692516278767321, -0.002175246374000726, 0.001979300066273931, 0.004864845763651887, 0.005318377282596896, 0.003366376342127399,
0.000100825920300210, -0.002906143603126905, -0.004345414044232734, -0.003726827835919960, -0.001518817798865299, 0.001150595364468159,
0.003056701038439272, 0.003429705849477585, 0.002251447413131553, 0.000190461179667856, -0.001760402090267228, -0.002754083239929045,
-0.002448432281636389, -0.001106680482118841, 0.000579040055238457, 0.001835132214640792, 0.002154309898448981, 0.001490613857222986,
0.000231871248756494, -0.001014931729801465, -0.001711408440970664, -0.001614970097646428, -0.000852839379782549, 0.000172679353454093,
0.000987840637523650, 0.001259585458205595, 0.000923465749187075, 0.000181852749867249, -0.000611972955861357, -0.001120965614204760,
-0.001165504636264451, -0.000781983763019378, -0.000184075454087644, 0.000345217289298406, 0.000577252836600570, 0.000424508165741649,
-0.000039974702137716, -0.000629044038382859, -0.001125819560345427, -0.001367486032675225, -0.001296070805120129, -0.000962621820235116,
-0.000491406291157732, -0.000025129491557388, 0.000324946604356638, 0.000506924615738917, 0.000529323030925432, 0.000441565642149420,
0.000306338738553062, 0.000175970492512707, 0.000080053245121681, 0.000025917992771962],
2250: # 204 taps
[ 0.000021235929725912, 0.000058222413723091, 0.000120890002831670, 0.000203732920433964, 0.000289842861102114, 0.000349648687783014,
0.000345987806871638, 0.000244741113281256, 0.000028683359304148, -0.000289767552139220, -0.000662562723513035, -0.001011139906426526,
-0.001244168321321761, -0.001284537444429324, -0.001097853884402359, -0.000712241533727792, -0.000220016931252946, 0.000242881192519691,
0.000536533139642942, 0.000564406769439812, 0.000312158569581760, -0.000137882856713157, -0.000625674547956830, -0.000963565458358823,
-0.001002878434444682, -0.000693839038801721, -0.000115120712337792, 0.000542271936437241, 0.001036221937634474, 0.001161519435408656,
0.000833656052982527, 0.000135090190320360, -0.000698564960603252, -0.001354001229403848, -0.001554644716587147, -0.001173297796072647,
-0.000298257519547670, 0.000778476775888895, 0.001654851153636767, 0.001966028879000262, 0.001532563461927731, 0.000450688659966846,
-0.000917352451094544, -0.002059631204322221, -0.002502298531213019, -0.002001396043186186, -0.000662678470838726, 0.001065622033389926,
0.002537010973466811, 0.003144134768937308, 0.002563969753555681, 0.000913777569993959, -0.001253108006318142, -0.003125545369287028,
-0.003932636886255475, -0.003254230145379617, -0.001221262259301159, 0.001484407116176422, 0.003849603664148359, 0.004902048797404353,
0.004101215157545026, 0.001593866974392046, -0.001780147556263722, -0.004757480195317273, -0.006114639178429671, -0.005158848135225734,
-0.002053747031888304, 0.002164989106816366, 0.005919348898632932, 0.007665501534134797, 0.006511589945553144, 0.002635680468215082,
-0.002680807848145089, -0.007454309167215744, -0.009718217353005822, -0.008306681703332177, -0.003400058938312392, 0.003406734158392871,
0.009587959427131761, 0.012589212182082522, 0.010837597719690596, 0.004476080310267183, -0.004494123792760167, -0.012783165957938696,
-0.016952789434463451, -0.014752125292065949, -0.006159094027769298, 0.006304669250227038, 0.018192572127496920, 0.024568955795679859,
0.021832946865353555, 0.009314262398551900, -0.009958208720588032, -0.029683337395096002, -0.041898689176258344, -0.039357322455930641,
-0.017957925042069021, 0.021558306071190737, 0.073246545073647112, 0.127141924217462950, 0.171715605172332860, 0.196918159377366730,
0.196918159377366730, 0.171715605172332860, 0.127141924217462950, 0.073246545073647112, 0.021558306071190737, -0.017957925042069021,
-0.039357322455930641, -0.041898689176258344, -0.029683337395096002, -0.009958208720588032, 0.009314262398551900, 0.021832946865353555,
0.024568955795679859, 0.018192572127496920, 0.006304669250227038, -0.006159094027769298, -0.014752125292065949, -0.016952789434463451,
-0.012783165957938696, -0.004494123792760167, 0.004476080310267183, 0.010837597719690596, 0.012589212182082522, 0.009587959427131761,
0.003406734158392871, -0.003400058938312392, -0.008306681703332177, -0.009718217353005822, -0.007454309167215744, -0.002680807848145089,
0.002635680468215082, 0.006511589945553144, 0.007665501534134797, 0.005919348898632932, 0.002164989106816366, -0.002053747031888304,
-0.005158848135225734, -0.006114639178429671, -0.004757480195317273, -0.001780147556263722, 0.001593866974392046, 0.004101215157545026,
0.004902048797404353, 0.003849603664148359, 0.001484407116176422, -0.001221262259301159, -0.003254230145379617, -0.003932636886255475,
-0.003125545369287028, -0.001253108006318142, 0.000913777569993959, 0.002563969753555681, 0.003144134768937308, 0.002537010973466811,
0.001065622033389926, -0.000662678470838726, -0.002001396043186186, -0.002502298531213019, -0.002059631204322221, -0.000917352451094544,
0.000450688659966846, 0.001532563461927731, 0.001966028879000262, 0.001654851153636767, 0.000778476775888895, -0.000298257519547670,
-0.001173297796072647, -0.001554644716587147, -0.001354001229403848, -0.000698564960603252, 0.000135090190320360, 0.000833656052982527,
0.001161519435408656, 0.001036221937634474, 0.000542271936437241, -0.000115120712337792, -0.000693839038801721, -0.001002878434444682,
-0.000963565458358823, -0.000625674547956830, -0.000137882856713157, 0.000312158569581760, 0.000564406769439812, 0.000536533139642942,
0.000242881192519691, -0.000220016931252946, -0.000712241533727792, -0.001097853884402359, -0.001284537444429324, -0.001244168321321761,
-0.001011139906426526, -0.000662562723513035, -0.000289767552139220, 0.000028683359304148, 0.000244741113281256, 0.000345987806871638,
0.000349648687783014, 0.000289842861102114, 0.000203732920433964, 0.000120890002831670, 0.000058222413723091, 0.000021235929725912],
2200: #208 taps
[ 0.000019773973500698, 0.000050300702719923, 0.000099108356371034, 0.000158194404233062, 0.000210525538441445, 0.000229815943545636,
0.000185844232898202, 0.000053925802103604, -0.000173777821813056, -0.000478713240844808, -0.000812707500835325, -0.001104087723445511,
-0.001273942998532759, -0.001259278130931017, -0.001036517683714317, -0.000637135855849719, -0.000147905388336950, 0.000307371053002897,
0.000601888175399474, 0.000646481239850203, 0.000423598448328852, 0.000001477795930511, -0.000479511456398659, -0.000847542574860787,
-0.000956181777710176, -0.000738934578391376, -0.000241404176917473, 0.000383518196967566, 0.000920251953818672, 0.001163208934048226,
0.000993921481591226, 0.000434388361798019, -0.000346030182304409, -0.001078079759008518, -0.001482742622315739, -0.001374843351537245,
-0.000742498373847161, 0.000230275873300524, 0.001212510588437732, 0.001835971920260912, 0.001830429895718975, 0.001135824361158899,
-0.000053708874146025, -0.001337763115758486, -0.002240751224958943, -0.002384735614959788, -0.001642386517013215, -0.000209813690831605,
0.001436458922346993, 0.002693390382420342, 0.003048363283850710, 0.002282591868736866, 0.000582131982803615, -0.001494683726987109,
-0.003194641328318874, -0.003837926603637492, -0.003083544121084786, -0.001090420131933939, 0.001496937564140111, 0.003749027796032780,
0.004778960997800515, 0.004083937494744029, 0.001771796479680179, -0.001424175363221383, -0.004367185919670888, -0.005912778052177615,
-0.005343837748934002, -0.002682898015250491, 0.001248211259214371, 0.005067191417250770, 0.007305293729039890, 0.006958279310681562,
0.003912446091368335, -0.000927394507475861, -0.005884363717650123, -0.009072440354829070, -0.009093145110623519, -0.005615743824657722,
0.000389138616762466, 0.006884468473346532, 0.011428522924352827, 0.012058034647345471, 0.008084684337356606, 0.000498409327693868,
-0.008214286092348189, -0.014832606629260737, -0.016526271123526743, -0.011969567139734955, -0.002035744936384088, 0.010229678038446030,
0.020433030409654745, 0.024256700710114647, 0.019055511377788287, 0.005093243716641166, -0.014044229049919777, -0.032091634104820632,
-0.041707387370849711, -0.036619723239870061, -0.013689386302707808, 0.025758635108220899, 0.075797616878918297, 0.127078744612693170,
0.169056056140688250, 0.192658606871644660, 0.192658606871644660, 0.169056056140688250, 0.127078744612693170, 0.075797616878918297,
0.025758635108220899, -0.013689386302707808, -0.036619723239870061, -0.041707387370849711, -0.032091634104820632, -0.014044229049919777,
0.005093243716641166, 0.019055511377788287, 0.024256700710114647, 0.020433030409654745, 0.010229678038446030, -0.002035744936384088,
-0.011969567139734955, -0.016526271123526743, -0.014832606629260737, -0.008214286092348189, 0.000498409327693868, 0.008084684337356606,
0.012058034647345471, 0.011428522924352827, 0.006884468473346532, 0.000389138616762466, -0.005615743824657722, -0.009093145110623519,
-0.009072440354829070, -0.005884363717650123, -0.000927394507475861, 0.003912446091368335, 0.006958279310681562, 0.007305293729039890,
0.005067191417250770, 0.001248211259214371, -0.002682898015250491, -0.005343837748934002, -0.005912778052177615, -0.004367185919670888,
-0.001424175363221383, 0.001771796479680179, 0.004083937494744029, 0.004778960997800515, 0.003749027796032780, 0.001496937564140111,
-0.001090420131933939, -0.003083544121084786, -0.003837926603637492, -0.003194641328318874, -0.001494683726987109, 0.000582131982803615,
0.002282591868736866, 0.003048363283850710, 0.002693390382420342, 0.001436458922346993, -0.000209813690831605, -0.001642386517013215,
-0.002384735614959788, -0.002240751224958943, -0.001337763115758486, -0.000053708874146025, 0.001135824361158899, 0.001830429895718975,
0.001835971920260912, 0.001212510588437732, 0.000230275873300524, -0.000742498373847161, -0.001374843351537245, -0.001482742622315739,
-0.001078079759008518, -0.000346030182304409, 0.000434388361798019, 0.000993921481591226, 0.001163208934048226, 0.000920251953818672,
0.000383518196967566, -0.000241404176917473, -0.000738934578391376, -0.000956181777710176, -0.000847542574860787, -0.000479511456398659,
0.000001477795930511, 0.000423598448328852, 0.000646481239850203, 0.000601888175399474, 0.000307371053002897, -0.000147905388336950,
-0.000637135855849719, -0.001036517683714317, -0.001259278130931017, -0.001273942998532759, -0.001104087723445511, -0.000812707500835325,
-0.000478713240844808, -0.000173777821813056, 0.000053925802103604, 0.000185844232898202, 0.000229815943545636, 0.000210525538441445,
0.000158194404233062, 0.000099108356371034, 0.000050300702719923, 0.000019773973500698],
2000: # 230 taps
[ 0.000018263332334824, 0.000047149978647285, 0.000097406241243338, 0.000168379817473922, 0.000253967146823514, 0.000340023431940833,
0.000404952972379638, 0.000422974737428721, 0.000369902505736477, 0.000230539284736893, 0.000005684130024729, -0.000283317092531425,
-0.000594762485225633, -0.000872546941267996, -0.001057521343918215, -0.001102228302856328, -0.000985222996371773, -0.000720881682050162,
-0.000360902436481765, 0.000014352678995945, 0.000313854754768513, 0.000459361314264358, 0.000408357201159471, 0.000169601665199639,
-0.000194309748856567, -0.000579769259034295, -0.000868450605965827, -0.000960850467769566, -0.000807759775150557, -0.000429923379846805,
0.000081456338895501, 0.000585585033204507, 0.000930280363007829, 0.000997553630123726, 0.000743304377770887, 0.000218142932019430,
-0.000439021999187466, -0.001035464584506922, -0.001379274582853274, -0.001339118806894442, -0.000890898584125663, -0.000135426700809927,
0.000721291534689607, 0.001422586528925900, 0.001736709146196323, 0.001531678957580429, 0.000825809348346554, -0.000204332584606896,
-0.001264953954035041, -0.002026681147110289, -0.002224767812024736, -0.001747989347578871, -0.000687126008900549, 0.000675855233958752,
0.001939810141526326, 0.002700254906709327, 0.002676337670079708, 0.001809809227907344, 0.000302346385971505, -0.001425894059131038,
-0.002850111164443048, -0.003497344177823653, -0.003099785097153715, -0.001696282588251145, 0.000354381431382131, 0.002459975729847483,
0.003962117890550910, 0.004338291351008917, 0.003377760857642002, 0.001273722107127891, -0.001404260811957126, -0.003858729944018619,
-0.005296868477641261, -0.005180794578143301, -0.003418238131210470, -0.000428690403667462, 0.002946095146143898, 0.005671970307130408,
0.006832059383649537, 0.005925580242615656, 0.003061135525037048, -0.001025055462746871, -0.005146493168885704, -0.008000447007066858,
-0.008567643322000875, -0.006458387751298429, -0.002092389293377035, 0.003358433615988978, 0.008267542643372157, 0.011021009169962534,
0.010525039504447272, 0.006600477461859877, 0.000131874225722409, -0.007098295196117369, -0.012870086951713777, -0.015169746940300757,
-0.012839241513492812, -0.006035689378457242, 0.003657448927275838, 0.013537787301098309, 0.020464471653522786, 0.021753420096582588,
0.016052407125049746, 0.003944898543127921, -0.011915101650258511, -0.027222337492958711, -0.036934437155297005, -0.036483323022197825,
-0.022992339899140058, 0.003814121297142671, 0.041261656200314752, 0.084061156150449762, 0.125284573669860120, 0.157774677964436120,
0.175667194419142110, 0.175667194419142110, 0.157774677964436120, 0.125284573669860120, 0.084061156150449762, 0.041261656200314752,
0.003814121297142671, -0.022992339899140058, -0.036483323022197825, -0.036934437155297005, -0.027222337492958711, -0.011915101650258511,
0.003944898543127921, 0.016052407125049746, 0.021753420096582588, 0.020464471653522786, 0.013537787301098309, 0.003657448927275838,
-0.006035689378457242, -0.012839241513492812, -0.015169746940300757, -0.012870086951713777, -0.007098295196117369, 0.000131874225722409,
0.006600477461859877, 0.010525039504447272, 0.011021009169962534, 0.008267542643372157, 0.003358433615988978, -0.002092389293377035,
-0.006458387751298429, -0.008567643322000875, -0.008000447007066858, -0.005146493168885704, -0.001025055462746871, 0.003061135525037048,
0.005925580242615656, 0.006832059383649537, 0.005671970307130408, 0.002946095146143898, -0.000428690403667462, -0.003418238131210470,
-0.005180794578143301, -0.005296868477641261, -0.003858729944018619, -0.001404260811957126, 0.001273722107127891, 0.003377760857642002,
0.004338291351008917, 0.003962117890550910, 0.002459975729847483, 0.000354381431382131, -0.001696282588251145, -0.003099785097153715,
-0.003497344177823653, -0.002850111164443048, -0.001425894059131038, 0.000302346385971505, 0.001809809227907344, 0.002676337670079708,
0.002700254906709327, 0.001939810141526326, 0.000675855233958752, -0.000687126008900549, -0.001747989347578871, -0.002224767812024736,
-0.002026681147110289, -0.001264953954035041, -0.000204332584606896, 0.000825809348346554, 0.001531678957580429, 0.001736709146196323,
0.001422586528925900, 0.000721291534689607, -0.000135426700809927, -0.000890898584125663, -0.001339118806894442, -0.001379274582853274,
-0.001035464584506922, -0.000439021999187466, 0.000218142932019430, 0.000743304377770887, 0.000997553630123726, 0.000930280363007829,
0.000585585033204507, 0.000081456338895501, -0.000429923379846805, -0.000807759775150557, -0.000960850467769566, -0.000868450605965827,
-0.000579769259034295, -0.000194309748856567, 0.000169601665199639, 0.000408357201159471, 0.000459361314264358, 0.000313854754768513,
0.000014352678995945, -0.000360902436481765, -0.000720881682050162, -0.000985222996371773, -0.001102228302856328, -0.001057521343918215,
-0.000872546941267996, -0.000594762485225633, -0.000283317092531425, 0.000005684130024729, 0.000230539284736893, 0.000369902505736477,
0.000422974737428721, 0.000404952972379638, 0.000340023431940833, 0.000253967146823514, 0.000168379817473922, 0.000097406241243338,
0.000047149978647285, 0.000018263332334824],
1200: # 390 taps
[ -0.000004331430434813, 0.000000895178754483, 0.000004819206934844, 0.000012387135981227, 0.000024702874344864, 0.000042904801446151,
0.000067985064859609, 0.000100627030615644, 0.000141025873402660, 0.000188738905083740, 0.000242545741476270, 0.000300399529662751,
0.000359433330154281, 0.000416062388387887, 0.000466131771574125, 0.000505276256945394, 0.000529201721306850, 0.000534142706869689,
0.000517270879752382, 0.000477091889474874, 0.000413779420856055, 0.000329374266276144, 0.000227844055373822, 0.000114956874548630,
-0.000002037964985788, -0.000114915331771079, -0.000215124321927526, -0.000294538502564660, -0.000346261198238618, -0.000365357958551737,
-0.000349492174517184, -0.000299346594446952, -0.000218781210350266, -0.000114696259371792, 0.000003439467944363, 0.000124342943768293,
0.000235923038322712, 0.000326439472488279, 0.000385716174095538, 0.000406270345323467, 0.000384244892423745, 0.000320026589486952,
0.000218451713387220, 0.000088560889198400, -0.000057113100623293, -0.000203679685243764, -0.000335341513050654, -0.000437048860128537,
-0.000496163931220179, -0.000503976472887228, -0.000456879139659229, -0.000357064175200153, -0.000212619491029857, -0.000036988477009561,
0.000152196470731637, 0.000334813551887072, 0.000490329017689163, 0.000600045160004744, 0.000649282295414473, 0.000629236561431697,
0.000538304340843976, 0.000382670778203303, 0.000176071280391891, -0.000061311579103233, -0.000304731538051063, -0.000527388822625111,
-0.000703310824877695, -0.000810309677712974, -0.000832681816046785, -0.000763333939111765, -0.000605059771056946, -0.000370777976208385,
-0.000082637093413831, 0.000229962373724818, 0.000533282556646377, 0.000792807201162301, 0.000977090407652497, 0.001061465563886132,
0.001031195280476629, 0.000883666665500652, 0.000629337435771274, 0.000291251466919408, -0.000096894708513463, -0.000493996123025552,
-0.000855794020410478, -0.001139717153414092, -0.001309782074955667, -0.001341010390430392, -0.001222824367769252, -0.000960990590585683,
-0.000577807284515617, -0.000110428182918688, 0.000392587860975165, 0.000876197024190483, 0.001284780403728217, 0.001568419571371085,
0.001688844037687690, 0.001624356624161237, 0.001373126810656710, 0.000954389812320343, 0.000407316271842430, -0.000212443049446438,
-0.000838254589610985, -0.001399522686014101, -0.001829507546348522, -0.002073092512801241, -0.002093612886275790, -0.001877924713394541,
-0.001439048979367570, -0.000815971265919441, -0.000070480695977658, 0.000718736847294852, 0.001464215661002276, 0.002079218519234449,
0.002487627013869813, 0.002633104169545422, 0.002486455189532861, 0.002050261072858978, 0.001360126142354939, 0.000482234298234895,
-0.000492687418387142, -0.001458485408297803, -0.002304891424791415, -0.002929849705482365, -0.003251510669015779, -0.003218513620108414,
-0.002817308401190046, -0.002075543578763564, -0.001060944493273319, 0.000124409621994965, 0.001353984612741832, 0.002489950284201132,
0.003398266860636230, 0.003964087731928019, 0.004105749968228098, 0.003785703607610913, 0.003016991173328030, 0.001864327522378759,
0.000439388406465501, -0.001109448711112102, -0.002612032761559656, -0.003894589690524808, -0.004799215003224437, -0.005202582868054837,
-0.005031731058566960, -0.004274996752467488, -0.002986628326221043, -0.001284225000364678, 0.000661081413588171, 0.002640990158453115,
0.004431457793095885, 0.005817070614785085, 0.006615805917154524, 0.006701450960612391, 0.006021062146242219, 0.004605255832862724,
0.002569791244804701, 0.000107781839820803, -0.002527142979701336, -0.005045394324250144, -0.007151459446961864, -0.008576455016121923,
-0.009109805876143171, -0.008626557809676316, -0.007107110348255605, -0.004646780506669336, -0.001453545516455117, 0.002166532839260334,
0.005835432981396591, 0.009138830213137229, 0.011668164700843125, 0.013065169962452085, 0.013064440018490608, 0.011529579182106710,
0.008478886219133458, 0.004097368728152922, -0.001266921998658053, -0.007122790428060381, -0.012871021717463436, -0.017852189458389456,
-0.021403262319433757, -0.022918110896739993, -0.021906440359153793, -0.018045605353792606, -0.011220225882222989, -0.001545481108326266,
0.010628660576177286, 0.024733344647961189, 0.040016116357312823, 0.055593053292742431, 0.070513617769391357, 0.083832869405100041,
0.094684919293335654, 0.102351302009811070, 0.106318317517167070, 0.106318317517167070, 0.102351302009811070, 0.094684919293335654,
0.083832869405100041, 0.070513617769391357, 0.055593053292742431, 0.040016116357312823, 0.024733344647961189, 0.010628660576177286,
-0.001545481108326266, -0.011220225882222989, -0.018045605353792606, -0.021906440359153793, -0.022918110896739993, -0.021403262319433757,
-0.017852189458389456, -0.012871021717463436, -0.007122790428060381, -0.001266921998658053, 0.004097368728152922, 0.008478886219133458,
0.011529579182106710, 0.013064440018490608, 0.013065169962452085, 0.011668164700843125, 0.009138830213137229, 0.005835432981396591,
0.002166532839260334, -0.001453545516455117, -0.004646780506669336, -0.007107110348255605, -0.008626557809676316, -0.009109805876143171,
-0.008576455016121923, -0.007151459446961864, -0.005045394324250144, -0.002527142979701336, 0.000107781839820803, 0.002569791244804701,
0.004605255832862724, 0.006021062146242219, 0.006701450960612391, 0.006615805917154524, 0.005817070614785085, 0.004431457793095885,
0.002640990158453115, 0.000661081413588171, -0.001284225000364678, -0.002986628326221043, -0.004274996752467488, -0.005031731058566960,
-0.005202582868054837, -0.004799215003224437, -0.003894589690524808, -0.002612032761559656, -0.001109448711112102, 0.000439388406465501,
0.001864327522378759, 0.003016991173328030, 0.003785703607610913, 0.004105749968228098, 0.003964087731928019, 0.003398266860636230,
0.002489950284201132, 0.001353984612741832, 0.000124409621994965, -0.001060944493273319, -0.002075543578763564, -0.002817308401190046,
-0.003218513620108414, -0.003251510669015779, -0.002929849705482365, -0.002304891424791415, -0.001458485408297803, -0.000492687418387142,
0.000482234298234895, 0.001360126142354939, 0.002050261072858978, 0.002486455189532861, 0.002633104169545422, 0.002487627013869813,
0.002079218519234449, 0.001464215661002276, 0.000718736847294852, -0.000070480695977658, -0.000815971265919441, -0.001439048979367570,
-0.001877924713394541, -0.002093612886275790, -0.002073092512801241, -0.001829507546348522, -0.001399522686014101, -0.000838254589610985,
-0.000212443049446438, 0.000407316271842430, 0.000954389812320343, 0.001373126810656710, 0.001624356624161237, 0.001688844037687690,
0.001568419571371085, 0.001284780403728217, 0.000876197024190483, 0.000392587860975165, -0.000110428182918688, -0.000577807284515617,
-0.000960990590585683, -0.001222824367769252, -0.001341010390430392, -0.001309782074955667, -0.001139717153414092, -0.000855794020410478,
-0.000493996123025552, -0.000096894708513463, 0.000291251466919408, 0.000629337435771274, 0.000883666665500652, 0.001031195280476629,
0.001061465563886132, 0.000977090407652497, 0.000792807201162301, 0.000533282556646377, 0.000229962373724818, -0.000082637093413831,
-0.000370777976208385, -0.000605059771056946, -0.000763333939111765, -0.000832681816046785, -0.000810309677712974, -0.000703310824877695,
-0.000527388822625111, -0.000304731538051063, -0.000061311579103233, 0.000176071280391891, 0.000382670778203303, 0.000538304340843976,
0.000629236561431697, 0.000649282295414473, 0.000600045160004744, 0.000490329017689163, 0.000334813551887072, 0.000152196470731637,
-0.000036988477009561, -0.000212619491029857, -0.000357064175200153, -0.000456879139659229, -0.000503976472887228, -0.000496163931220179,
-0.000437048860128537, -0.000335341513050654, -0.000203679685243764, -0.000057113100623293, 0.000088560889198400, 0.000218451713387220,
0.000320026589486952, 0.000384244892423745, 0.000406270345323467, 0.000385716174095538, 0.000326439472488279, 0.000235923038322712,
0.000124342943768293, 0.000003439467944363, -0.000114696259371792, -0.000218781210350266, -0.000299346594446952, -0.000349492174517184,
-0.000365357958551737, -0.000346261198238618, -0.000294538502564660, -0.000215124321927526, -0.000114915331771079, -0.000002037964985788,
0.000114956874548630, 0.000227844055373822, 0.000329374266276144, 0.000413779420856055, 0.000477091889474874, 0.000517270879752382,
0.000534142706869689, 0.000529201721306850, 0.000505276256945394, 0.000466131771574125, 0.000416062388387887, 0.000359433330154281,
0.000300399529662751, 0.000242545741476270, 0.000188738905083740, 0.000141025873402660, 0.000100627030615644, 0.000067985064859609,
0.000042904801446151, 0.000024702874344864, 0.000012387135981227, 0.000004819206934844, 0.000000895178754483,
-0.000004331430434813],
1050: # 436 taps
[ 0.000009448479205375, 0.000012696465277652, 0.000020370172970180, 0.000030184991625035, 0.000042106693955817, 0.000055852072599971,
0.000070887005271560, 0.000086420158465092, 0.000101355825900618, 0.000114363268643203, 0.000123887834821102, 0.000128274519298202,
0.000125832586450277, 0.000114986590640600, 0.000094403728281619, 0.000063154754823051, 0.000020845988244699, -0.000032265535133044,
-0.000095180544123547, -0.000166116860144892, -0.000242513979646880, -0.000321114777409093, -0.000398098262223401, -0.000469277292459888,
-0.000530343005106164, -0.000577150816605647, -0.000606030552867816, -0.000614088176959215, -0.000599488135042163, -0.000561676165437471,
-0.000501537574188219, -0.000421453273437500, -0.000325256102322617, -0.000218069429252057, -0.000106038676866598, 0.000004038733321948,
0.000105163201801783, 0.000190629395147399, 0.000254537701320533, 0.000292276183024851, 0.000300940504739341, 0.000279647367467038,
0.000229712182001491, 0.000154664133559244, 0.000060087287885032, -0.000046708916188273, -0.000157178278901246, -0.000262150604318790,
-0.000352532643004546, -0.000420038843447295, -0.000457900791044766, -0.000461490220027360, -0.000428802562294159, -0.000360749682491985,
-0.000261227783830262, -0.000136942941254748, 0.000003006863095541, 0.000147769782466689, 0.000285560076794725, 0.000404597750694492,
0.000494097819748234, 0.000545227040891837, 0.000551946387992179, 0.000511661803626807, 0.000425615907406962, 0.000298975923041507,
0.000140591237099924, -0.000037572806623586, -0.000221297040657601, -0.000395204977572457, -0.000544014582709044, -0.000653844664412656,
-0.000713468032359235, -0.000715401015700932, -0.000656726220245329, -0.000539565358551840, -0.000371142263662183, -0.000163412087825843,
0.000067734912716773, 0.000303646478822936, 0.000524376688359045, 0.000710337247559984, 0.000843997687273033, 0.000911491282666008,
0.000903981565989860, 0.000818661581901103, 0.000659279571163340, 0.000436124965687766, 0.000165448090041410, -0.000131662114271814,
-0.000430871860061819, -0.000706551570190628, -0.000933927661174878, -0.001091260873927019, -0.001161862605544963, -0.001135768011733947,
-0.001010903443574116, -0.000793624567958766, -0.000498548786522930, -0.000147664383619282, 0.000231239373696082, 0.000606716500508849,
0.000946221796915500, 0.001218879442233109, 0.001398230278516649, 0.001464719587961910, 0.001407696084540016, 0.001226727670795367,
0.000932088534687613, 0.000544338945220889, 0.000092993997809168, -0.000385643796196990, -0.000851335887551482, -0.001263225987480693,
-0.001583356294077108, -0.001780085545791869, -0.001831106846925676, -0.001725785932705194, -0.001466586997062531, -0.001069422391909016,
-0.000562849829295198, 0.000013863768127086, 0.000613691289313864, 0.001185595579630164, 0.001678788016865224, 0.002047148219684201,
0.002253427125685210, 0.002272859461156746, 0.002095846355480220, 0.001729435227991120, 0.001197417642361670, 0.000538977181181343,
-0.000194056307018250, -0.000941167802354134, -0.001638040755694804, -0.002221979108638823, -0.002637419881293469, -0.002841061961880444,
-0.002806150254550888, -0.002525507242008391, -0.002012996612957378, -0.001303224500472842, -0.000449429868884502, 0.000480331461610398,
0.001408451066145862, 0.002254087235765462, 0.002940037601874124, 0.003399594478686270, 0.003582789834066899, 0.003461463426614760,
0.003032665933951381, 0.002320030370388926, 0.001372905661370613, 0.000263228943665216, -0.000919692494408471, -0.002076132077760251,
-0.003104105713267222, -0.003908095577429846, -0.004407611415610481, -0.004544845892385366, -0.004290726778249251, -0.003648773776410054,
-0.002656330572825240, -0.001382946510100638, 0.000074084090594797, 0.001596775046947397, 0.003055612326596284, 0.004320145989574181,
0.005270175821128589, 0.005806607129742208, 0.005861039774711523, 0.005403216045538973, 0.004445593651759434, 0.003044516829901583,
0.001297719390662469, -0.000661812043684862, -0.002675281726881974, -0.004570210544203085, -0.006174367519919625, -0.007330437981090785,
-0.007910248018541601, -0.007827342889142773, -0.007046794045290511, -0.005591280073358511, -0.003542743024648185, -0.001039245490340182,
0.001732978782646685, 0.004551902468552846, 0.007175761837551073, 0.009361715988035382, 0.010885774625277341, 0.011562497118639900,
0.011262913292836945, 0.009929181902797219, 0.007584685668611571, 0.004338552786979482, 0.000383973096667143, -0.004009880384869925,
-0.008512042746918275, -0.012749310695913854, -0.016329981037081505, -0.018870242377519306, -0.020021437259417553, -0.019496284710171480,
-0.017092162548070899, -0.012709698776261012, -0.006365201717689276, 0.001804145292512615, 0.011542917413936767, 0.022487965990827675,
0.034186326021837028, 0.046119492420907078, 0.057732581500000407, 0.068466529917194638, 0.077791244144850280, 0.085237520364008251,
0.090425612489785534, 0.093088535620340640, 0.093088535620340640, 0.090425612489785534, 0.085237520364008251, 0.077791244144850280,
0.068466529917194638, 0.057732581500000407, 0.046119492420907078, 0.034186326021837028, 0.022487965990827675, 0.011542917413936767,
0.001804145292512615, -0.006365201717689276, -0.012709698776261012, -0.017092162548070899, -0.019496284710171480, -0.020021437259417553,
-0.018870242377519306, -0.016329981037081505, -0.012749310695913854, -0.008512042746918275, -0.004009880384869925, 0.000383973096667143,
0.004338552786979482, 0.007584685668611571, 0.009929181902797219, 0.011262913292836945, 0.011562497118639900, 0.010885774625277341,
0.009361715988035382, 0.007175761837551073, 0.004551902468552846, 0.001732978782646685, -0.001039245490340182, -0.003542743024648185,
-0.005591280073358511, -0.007046794045290511, -0.007827342889142773, -0.007910248018541601, -0.007330437981090785, -0.006174367519919625,
-0.004570210544203085, -0.002675281726881974, -0.000661812043684862, 0.001297719390662469, 0.003044516829901583, 0.004445593651759434,
0.005403216045538973, 0.005861039774711523, 0.005806607129742208, 0.005270175821128589, 0.004320145989574181, 0.003055612326596284,
0.001596775046947397, 0.000074084090594797, -0.001382946510100638, -0.002656330572825240, -0.003648773776410054, -0.004290726778249251,
-0.004544845892385366, -0.004407611415610481, -0.003908095577429846, -0.003104105713267222, -0.002076132077760251, -0.000919692494408471,
0.000263228943665216, 0.001372905661370613, 0.002320030370388926, 0.003032665933951381, 0.003461463426614760, 0.003582789834066899,
0.003399594478686270, 0.002940037601874124, 0.002254087235765462, 0.001408451066145862, 0.000480331461610398, -0.000449429868884502,
-0.001303224500472842, -0.002012996612957378, -0.002525507242008391, -0.002806150254550888, -0.002841061961880444, -0.002637419881293469,
-0.002221979108638823, -0.001638040755694804, -0.000941167802354134, -0.000194056307018250, 0.000538977181181343, 0.001197417642361670,
0.001729435227991120, 0.002095846355480220, 0.002272859461156746, 0.002253427125685210, 0.002047148219684201, 0.001678788016865224,
0.001185595579630164, 0.000613691289313864, 0.000013863768127086, -0.000562849829295198, -0.001069422391909016, -0.001466586997062531,
-0.001725785932705194, -0.001831106846925676, -0.001780085545791869, -0.001583356294077108, -0.001263225987480693, -0.000851335887551482,
-0.000385643796196990, 0.000092993997809168, 0.000544338945220889, 0.000932088534687613, 0.001226727670795367, 0.001407696084540016,
0.001464719587961910, 0.001398230278516649, 0.001218879442233109, 0.000946221796915500, 0.000606716500508849, 0.000231239373696082,
-0.000147664383619282, -0.000498548786522930, -0.000793624567958766, -0.001010903443574116, -0.001135768011733947, -0.001161862605544963,
-0.001091260873927019, -0.000933927661174878, -0.000706551570190628, -0.000430871860061819, -0.000131662114271814, 0.000165448090041410,
0.000436124965687766, 0.000659279571163340, 0.000818661581901103, 0.000903981565989860, 0.000911491282666008, 0.000843997687273033,
0.000710337247559984, 0.000524376688359045, 0.000303646478822936, 0.000067734912716773, -0.000163412087825843, -0.000371142263662183,
-0.000539565358551840, -0.000656726220245329, -0.000715401015700932, -0.000713468032359235, -0.000653844664412656, -0.000544014582709044,
-0.000395204977572457, -0.000221297040657601, -0.000037572806623586, 0.000140591237099924, 0.000298975923041507, 0.000425615907406962,
0.000511661803626807, 0.000551946387992179, 0.000545227040891837, 0.000494097819748234, 0.000404597750694492, 0.000285560076794725,
0.000147769782466689, 0.000003006863095541, -0.000136942941254748, -0.000261227783830262, -0.000360749682491985, -0.000428802562294159,
-0.000461490220027360, -0.000457900791044766, -0.000420038843447295, -0.000352532643004546, -0.000262150604318790, -0.000157178278901246,
-0.000046708916188273, 0.000060087287885032, 0.000154664133559244, 0.000229712182001491, 0.000279647367467038, 0.000300940504739341,
0.000292276183024851, 0.000254537701320533, 0.000190629395147399, 0.000105163201801783, 0.000004038733321948, -0.000106038676866598,
-0.000218069429252057, -0.000325256102322617, -0.000421453273437500, -0.000501537574188219, -0.000561676165437471, -0.000599488135042163,
-0.000614088176959215, -0.000606030552867816, -0.000577150816605647, -0.000530343005106164, -0.000469277292459888, -0.000398098262223401,
-0.000321114777409093, -0.000242513979646880, -0.000166116860144892, -0.000095180544123547, -0.000032265535133044, 0.000020845988244699,
0.000063154754823051, 0.000094403728281619, 0.000114986590640600, 0.000125832586450277, 0.000128274519298202, 0.000123887834821102,
0.000114363268643203, 0.000101355825900618, 0.000086420158465092, 0.000070887005271560, 0.000055852072599971, 0.000042106693955817,
0.000030184991625035, 0.000020370172970180, 0.000012696465277652, 0.000009448479205375 ],
900: # 507 taps
[ 0.000008584643858565, 0.000009418988876053, 0.000014080571079703, 0.000019768330546629, 0.000026407938202815, 0.000033835554436160,
0.000041775447258916, 0.000049840688763229, 0.000057525397104827, 0.000064222904381994, 0.000069229653331724, 0.000071774979399342,
0.000071044828173283, 0.000066239730112684, 0.000056611414853593, 0.000041517869163367, 0.000020462723568921, -0.000006825518135279,
-0.000040362137084292, -0.000079866391614059, -0.000124740944604127, -0.000174014477804677, -0.000226421731946560, -0.000280359514663211,
-0.000333959777524947, -0.000385165398367582, -0.000431779497772114, -0.000471602831913601, -0.000502508714800933, -0.000522587601943708,
-0.000530244985591953, -0.000524326246532992, -0.000504199216087390, -0.000469843487184555, -0.000421891912076977, -0.000361653837423190,
-0.000291090671787090, -0.000212772068148448, -0.000129775708505696, -0.000045562167590561, 0.000036181038541454, 0.000111715248667983,
0.000177457791675865, 0.000230168155042476, 0.000267145694421862, 0.000286408115238973, 0.000286822428035563, 0.000268225243572190,
0.000231462231911356, 0.000178399161339807, 0.000111858949442223, 0.000035506782941005, -0.000046322038661538, -0.000128840476454010,
-0.000207064338646769, -0.000276095967153748, -0.000331410885812673, -0.000369138410006413, -0.000386317254135060, -0.000381111227069818,
-0.000352960919521696, -0.000302673831926362, -0.000232432591548855, -0.000145719497401035, -0.000047160366530124, 0.000057705644340284,
0.000162731306278391, 0.000261502601122459, 0.000347728839468377, 0.000415628506871784, 0.000460307676423158, 0.000478101121808084,
0.000466848645480576, 0.000426095215060785, 0.000357188814988566, 0.000263277817392097, 0.000149189135268233, 0.000021201177134175,
-0.000113285445089856, -0.000246159846025947, -0.000369078758947477, -0.000473974817394057, -0.000553573238187010, -0.000601879769614216,
-0.000614609034335983, -0.000589524295786869, -0.000526664670957786, -0.000428434330673012, -0.000299553787970989, -0.000146857332295172,
0.000021049474537857, 0.000194254912976452, 0.000362113995846136, 0.000513885517380605, 0.000639407293886249, 0.000729757323783321,
0.000777868837348859, 0.000779050717176191, 0.000731379294218185, 0.000635932028877494, 0.000496840283261992, 0.000321150767788200,
0.000118496954373743, -0.000099406083911376, -0.000319420735859412, -0.000527759843251443, -0.000710834560643461, -0.000856122360814998,
-0.000953000983271033, -0.000993496322190293, -0.000972892522850171, -0.000890155314802122, -0.000748144004291994, -0.000553578640569728,
-0.000316765531771492, -0.000051080037307638, 0.000227762682493944, 0.000502615360611366, 0.000755932794801656, 0.000970869777260266,
0.001132378198325256, 0.001228230831826114, 0.001249905234811852, 0.001193265605753869, 0.001058991562763383, 0.000852715731034719,
0.000584852833903113, 0.000270116979072993, -0.000073253806782210, -0.000424524625176341, -0.000761689644626737, -0.001062825395605405,
-0.001307487536115730, -0.001478067477842081, -0.001561017661066179, -0.001547863924535660, -0.001435933022484648, -0.001228735487931008,
-0.000935971757611233, -0.000573140492907433, -0.000160766721791647, 0.000276722077340107, 0.000712407276705890, 0.001118502589392694,
0.001468091403060663, 0.001736873763731978, 0.001904808530758137, 0.001957546402581353, 0.001887551700267456, 0.001694830920708386,
0.001387207697842587, 0.000980110478290227, 0.000495866308669078, -0.000037468525946086, -0.000587687238352686, -0.001120332197278825,
-0.001600807439724522, -0.001996577848597569, -0.002279316251738701, -0.002426862344229953, -0.002424859679572050, -0.002267956637237035,
-0.001960478287612480, -0.001516506229070831, -0.000959342574003313, -0.000320365808444990, 0.000362667830279848, 0.001047791955533175,
0.001691344168593488, 0.002250683068847439, 0.002686938772729870, 0.002967628123966539, 0.003068960407414939, 0.002977678084347548,
0.002692297230438709, 0.002223648489886749, 0.001594655540313720, 0.000839340735107497, 0.000001086828787463, -0.000869758241867305,
-0.001718811255366889, -0.002490949831033119, -0.003133783611782112, -0.003601086428852005, -0.003855971828191669, -0.003873602941247202,
-0.003643247691842578, -0.003169524021369342, -0.002472727031941658, -0.001588180215901802, -0.000564613998354494, 0.000538365561072565,
0.001653594591084052, 0.002710281216575659, 0.003638323695477434, 0.004372765950231630, 0.004858115780200945, 0.005052252604909589,
0.004929664405729861, 0.004483785715272022, 0.003728252839970840, 0.002696955568587015, 0.001442828675796044, 0.000035407607162536,
-0.001442758217956277, -0.002900658243916532, -0.004244107302544382, -0.005381470691504941, -0.006229500241120763, -0.006718928923729580,
-0.006799475746570288, -0.006443928763042613, -0.005651020748503490, -0.004446866818108242, -0.002884813426831026, -0.001043632901391050,
0.000975905251500855, 0.003055961712387741, 0.005067831037893753, 0.006879122081429573, 0.008361541203410547, 0.009398858161408168,
0.009894604643011696, 0.009779054490283380, 0.009015056125786116, 0.007602336538990519, 0.005579966604992522, 0.003026770185465134,
0.000059562008821989, -0.003170779553680069, -0.006485285707743206, -0.009684675209522759, -0.012558594763991068, -0.014895884693277550,
-0.016495367870764655, -0.017176610952606329, -0.016790098400516502, -0.015226268406232360, -0.012422909599680294, -0.008370486626783004,
-0.003115063791803408, 0.003241392133040441, 0.010543411361223271, 0.018587073767192364, 0.027127521275133942, 0.035888613808298719,
0.044574291632219959, 0.052881118780651611, 0.060511410978741485, 0.067186317405564938, 0.072658219055817971, 0.076721836801003668,
0.079223499565701364, 0.080068115845938093, 0.079223499565701364, 0.076721836801003668, 0.072658219055817971, 0.067186317405564938,
0.060511410978741485, 0.052881118780651611, 0.044574291632219959, 0.035888613808298719, 0.027127521275133942, 0.018587073767192364,
0.010543411361223271, 0.003241392133040441, -0.003115063791803408, -0.008370486626783004, -0.012422909599680294, -0.015226268406232360,
-0.016790098400516502, -0.017176610952606329, -0.016495367870764655, -0.014895884693277550, -0.012558594763991068, -0.009684675209522759,
-0.006485285707743206, -0.003170779553680069, 0.000059562008821989, 0.003026770185465134, 0.005579966604992522, 0.007602336538990519,
0.009015056125786116, 0.009779054490283380, 0.009894604643011696, 0.009398858161408168, 0.008361541203410547, 0.006879122081429573,
0.005067831037893753, 0.003055961712387741, 0.000975905251500855, -0.001043632901391050, -0.002884813426831026, -0.004446866818108242,
-0.005651020748503490, -0.006443928763042613, -0.006799475746570288, -0.006718928923729580, -0.006229500241120763, -0.005381470691504941,
-0.004244107302544382, -0.002900658243916532, -0.001442758217956277, 0.000035407607162536, 0.001442828675796044, 0.002696955568587015,
0.003728252839970840, 0.004483785715272022, 0.004929664405729861, 0.005052252604909589, 0.004858115780200945, 0.004372765950231630,
0.003638323695477434, 0.002710281216575659, 0.001653594591084052, 0.000538365561072565, -0.000564613998354494, -0.001588180215901802,
-0.002472727031941658, -0.003169524021369342, -0.003643247691842578, -0.003873602941247202, -0.003855971828191669, -0.003601086428852005,
-0.003133783611782112, -0.002490949831033119, -0.001718811255366889, -0.000869758241867305, 0.000001086828787463, 0.000839340735107497,
0.001594655540313720, 0.002223648489886749, 0.002692297230438709, 0.002977678084347548, 0.003068960407414939, 0.002967628123966539,
0.002686938772729870, 0.002250683068847439, 0.001691344168593488, 0.001047791955533175, 0.000362667830279848, -0.000320365808444990,
-0.000959342574003313, -0.001516506229070831, -0.001960478287612480, -0.002267956637237035, -0.002424859679572050, -0.002426862344229953,
-0.002279316251738701, -0.001996577848597569, -0.001600807439724522, -0.001120332197278825, -0.000587687238352686, -0.000037468525946086,
0.000495866308669078, 0.000980110478290227, 0.001387207697842587, 0.001694830920708386, 0.001887551700267456, 0.001957546402581353,
0.001904808530758137, 0.001736873763731978, 0.001468091403060663, 0.001118502589392694, 0.000712407276705890, 0.000276722077340107,
-0.000160766721791647, -0.000573140492907433, -0.000935971757611233, -0.001228735487931008, -0.001435933022484648, -0.001547863924535660,
-0.001561017661066179, -0.001478067477842081, -0.001307487536115730, -0.001062825395605405, -0.000761689644626737, -0.000424524625176341,
-0.000073253806782210, 0.000270116979072993, 0.000584852833903113, 0.000852715731034719, 0.001058991562763383, 0.001193265605753869,
0.001249905234811852, 0.001228230831826114, 0.001132378198325256, 0.000970869777260266, 0.000755932794801656, 0.000502615360611366,
0.000227762682493944, -0.000051080037307638, -0.000316765531771492, -0.000553578640569728, -0.000748144004291994, -0.000890155314802122,
-0.000972892522850171, -0.000993496322190293, -0.000953000983271033, -0.000856122360814998, -0.000710834560643461, -0.000527759843251443,
-0.000319420735859412, -0.000099406083911376, 0.000118496954373743, 0.000321150767788200, 0.000496840283261992, 0.000635932028877494,
0.000731379294218185, 0.000779050717176191, 0.000777868837348859, 0.000729757323783321, 0.000639407293886249, 0.000513885517380605,
0.000362113995846136, 0.000194254912976452, 0.000021049474537857, -0.000146857332295172, -0.000299553787970989, -0.000428434330673012,
-0.000526664670957786, -0.000589524295786869, -0.000614609034335983, -0.000601879769614216, -0.000553573238187010, -0.000473974817394057,
-0.000369078758947477, -0.000246159846025947, -0.000113285445089856, 0.000021201177134175, 0.000149189135268233, 0.000263277817392097,
0.000357188814988566, 0.000426095215060785, 0.000466848645480576, 0.000478101121808084, 0.000460307676423158, 0.000415628506871784,
0.000347728839468377, 0.000261502601122459, 0.000162731306278391, 0.000057705644340284, -0.000047160366530124, -0.000145719497401035,
-0.000232432591548855, -0.000302673831926362, -0.000352960919521696, -0.000381111227069818, -0.000386317254135060, -0.000369138410006413,
-0.000331410885812673, -0.000276095967153748, -0.000207064338646769, -0.000128840476454010, -0.000046322038661538, 0.000035506782941005,
0.000111858949442223, 0.000178399161339807, 0.000231462231911356, 0.000268225243572190, 0.000286822428035563, 0.000286408115238973,
0.000267145694421862, 0.000230168155042476, 0.000177457791675865, 0.000111715248667983, 0.000036181038541454, -0.000045562167590561,
-0.000129775708505696, -0.000212772068148448, -0.000291090671787090, -0.000361653837423190, -0.000421891912076977, -0.000469843487184555,
-0.000504199216087390, -0.000524326246532992, -0.000530244985591953, -0.000522587601943708, -0.000502508714800933, -0.000471602831913601,
-0.000431779497772114, -0.000385165398367582, -0.000333959777524947, -0.000280359514663211, -0.000226421731946560, -0.000174014477804677,
-0.000124740944604127, -0.000079866391614059, -0.000040362137084292, -0.000006825518135279, 0.000020462723568921, 0.000041517869163367,
0.000056611414853593, 0.000066239730112684, 0.000071044828173283, 0.000071774979399342, 0.000069229653331724, 0.000064222904381994,
0.000057525397104827, 0.000049840688763229, 0.000041775447258916, 0.000033835554436160, 0.000026407938202815, 0.000019768330546629,
0.000014080571079703, 0.000009418988876053, 0.000008584643858565 ],
800: # 576 taps
[ 0.000007812421315026, 0.000008939491599758, 0.000013730623424634, 0.000019922294330328, 0.000027648080357536, 0.000037033583075960,
0.000048143861498476, 0.000060975611534136, 0.000075478577997365, 0.000091486545323856, 0.000108759338791293, 0.000126959389238696,
0.000145652693573148, 0.000164310512299333, 0.000182322037177544, 0.000198999590569024, 0.000213614009898736, 0.000225404773677130,
0.000233610866318186, 0.000237501949870309, 0.000236413452609260, 0.000229779689874953, 0.000217172712546861, 0.000198326327006374,
0.000173169245803356, 0.000141846034715806, 0.000104732554871058, 0.000062442756567725, 0.000015827588463025, -0.000034041343381570,
-0.000085892061029301, -0.000138290085401831, -0.000189681915226441, -0.000238447248218383, -0.000282958234132596, -0.000321645514236948,
-0.000353063703221184, -0.000375960779331289, -0.000389338959490572, -0.000392510765617404, -0.000385145111580360, -0.000367301005108244,
-0.000339445383884735, -0.000302456824898826, -0.000257608154338197, -0.000206533574886596, -0.000151176196094362, -0.000093718946756075,
-0.000036501336346782, 0.000018073847262639, 0.000067643276268719, 0.000109986899426513, 0.000143132709269975, 0.000165454349492432,
0.000175757544777044, 0.000173350787020117, 0.000158097036687462, 0.000130440616590328, 0.000091410233747216, 0.000042593436932362,
-0.000013915345466225, -0.000075592647414163, -0.000139587283955501, -0.000202842130917589, -0.000262229388149273, -0.000314696298480236,
-0.000357412309140960, -0.000387912413334777, -0.000404229097174175, -0.000405005829251745, -0.000389585053939050, -0.000358067160688509,
-0.000311334118688652, -0.000251037234855322, -0.000179546871425107, -0.000099865220473760, -0.000015504805729649, 0.000069662241126261,
0.000151579558482804, 0.000226197017031803, 0.000289669081062027, 0.000338550686637535, 0.000369981142430884, 0.000381846581094768,
0.000372912653196213, 0.000342918280693206, 0.000292625889758754, 0.000223822598023746, 0.000139271063990380, 0.000042610318846710,
-0.000061790478444653, -0.000169020516263524, -0.000273849135305279, -0.000370972701089329, -0.000455274194431906, -0.000522083581325246,
-0.000567425902673365, -0.000588244271105343, -0.000582585214321533, -0.000549736803809912, -0.000490309633189676, -0.000406255267998045,
-0.000300818490773199, -0.000178423294758992, -0.000044495794702522, 0.000094768978920233, 0.000232687846388931, 0.000362404482394644,
0.000477224887882656, 0.000570957019366285, 0.000638237711550962, 0.000674829917199715, 0.000677874702997924, 0.000646082835685789,
0.000579855135491155, 0.000481322426719260, 0.000354300348814663, 0.000204158180969291, 0.000037605243749942, -0.000137597792476766,
-0.000312990053930070, -0.000479809451013147, -0.000629420667419594, -0.000753751787384093, -0.000845717937205157, -0.000899610318070534,
-0.000911429428339170, -0.000879144467901314, -0.000802862554846735, -0.000684896491503848, -0.000529724099269400, -0.000343837417747543,
-0.000135485422990712, 0.000085680046384777, 0.000309041306180360, 0.000523514975880865, 0.000718092538346410, 0.000882395437488790,
0.001007217243930078, 0.001085025011663583, 0.001110393602574907, 0.001080348246990215, 0.000994595533680270, 0.000855627164276610,
0.000668686966519433, 0.000441598292681654, 0.000184456030731509, -0.000090806124488888, -0.000370954941565034, -0.000642080313861205,
-0.000890272488296022, -0.001102321495571061, -0.001266404272056495, -0.001372724600470066, -0.001414071700002426, -0.001386267150663963,
-0.001288473577308051, -0.001123345195626614, -0.000897007516317755, -0.000618861629989936, -0.000301217045862462, 0.000041233610152644,
0.000392077118279518, 0.000733950102097711, 0.001049382185659160, 0.001321671816520764, 0.001535751682495805, 0.001678999613916214,
0.001741952717366617, 0.001718885253761411, 0.001608217075415199, 0.001412726517149060, 0.001139550679396572, 0.000799966281315092,
0.000408955257020119, -0.000015430291445883, -0.000452877901224665, -0.000881764419386712, -0.001280202174740378, -0.001627132056108478,
-0.001903410054728627, -0.002092832630669335, -0.002183047066222210, -0.002166297669634002, -0.002039964832379242, -0.001806863227100527,
-0.001475276496367705, -0.001058718218567234, -0.000575422161543602, -0.000047579005659538, 0.000499650640507829, 0.001039304486976568,
0.001543915361236087, 0.001986875765177565, 0.002343803978935238, 0.002593843111794459, 0.002720826151891805, 0.002714244052422841,
0.002569962094668674, 0.002290640442950059, 0.001885828071941918, 0.001371714610702517, 0.000770541414234860, 0.000109689735095918,
-0.000579518937674694, -0.001263260162418279, -0.001906817708604015, -0.002476294031416823, -0.002940338141748221, -0.003271805587759476,
-0.003449265353836316, -0.003458273739088719, -0.003292343903957135, -0.002953552492773730, -0.002452740833751403, -0.001809286792967657,
-0.001050443545598396, -0.000210263213371804, 0.000671856047428268, 0.001552940847463649, 0.002388454943494185, 0.003134462521134076,
0.003749841173565993, 0.004198437225205720, 0.004451055565195895, 0.004487180085620033, 0.004296330509189801, 0.003878975870600702,
0.003246943568337220, 0.002423285251527749, 0.001441585983790830, 0.000344729182561098, -0.000816843070689627, -0.001987309118898022,
-0.003107997569512837, -0.004120159055022873, -0.004967855525593480, -0.005600832763225632, -0.005977237320460713, -0.006066041703895594,
-0.005849050345318046, -0.005322373876566778, -0.004497280230451145, -0.003400356983323659, -0.002072948915619172, -0.000569867563016818,
0.001042597072372037, 0.002689299061586182, 0.004289398058621653, 0.005759936423629122, 0.007019685172079656, 0.007993094199877881,
0.008614171947670925, 0.008830116409127314, 0.008604524292277297, 0.007920018129346281, 0.006780151368661940, 0.005210478633443022,
0.003258711582163722, 0.000993917994210585, -0.001495237701498300, -0.004103172616953404, -0.006710901704621617, -0.009190272912439044,
-0.011408767543032211, -0.013234683860549021, -0.014542500326924340, -0.015218201853231224, -0.015164348354310610, -0.014304669860257387,
-0.012587987008943695, -0.009991279084291772, -0.006521752749437525, -0.002217802857491734, 0.002851200149507507, 0.008586315129089878,
0.014861597832277761, 0.021527754009268797, 0.028416755733892799, 0.035347257703852740, 0.042130616026199795, 0.048577285227121583,
0.054503351348228368, 0.059736951256086870, 0.064124330411942668, 0.067535303652784234, 0.069867905879939018, 0.071052050289491700,
0.071052050289491700, 0.069867905879939018, 0.067535303652784234, 0.064124330411942668, 0.059736951256086870, 0.054503351348228368,
0.048577285227121583, 0.042130616026199795, 0.035347257703852740, 0.028416755733892799, 0.021527754009268797, 0.014861597832277761,
0.008586315129089878, 0.002851200149507507, -0.002217802857491734, -0.006521752749437525, -0.009991279084291772, -0.012587987008943695,
-0.014304669860257387, -0.015164348354310610, -0.015218201853231224, -0.014542500326924340, -0.013234683860549021, -0.011408767543032211,
-0.009190272912439044, -0.006710901704621617, -0.004103172616953404, -0.001495237701498300, 0.000993917994210585, 0.003258711582163722,
0.005210478633443022, 0.006780151368661940, 0.007920018129346281, 0.008604524292277297, 0.008830116409127314, 0.008614171947670925,
0.007993094199877881, 0.007019685172079656, 0.005759936423629122, 0.004289398058621653, 0.002689299061586182, 0.001042597072372037,
-0.000569867563016818, -0.002072948915619172, -0.003400356983323659, -0.004497280230451145, -0.005322373876566778, -0.005849050345318046,
-0.006066041703895594, -0.005977237320460713, -0.005600832763225632, -0.004967855525593480, -0.004120159055022873, -0.003107997569512837,
-0.001987309118898022, -0.000816843070689627, 0.000344729182561098, 0.001441585983790830, 0.002423285251527749, 0.003246943568337220,
0.003878975870600702, 0.004296330509189801, 0.004487180085620033, 0.004451055565195895, 0.004198437225205720, 0.003749841173565993,
0.003134462521134076, 0.002388454943494185, 0.001552940847463649, 0.000671856047428268, -0.000210263213371804, -0.001050443545598396,
-0.001809286792967657, -0.002452740833751403, -0.002953552492773730, -0.003292343903957135, -0.003458273739088719, -0.003449265353836316,
-0.003271805587759476, -0.002940338141748221, -0.002476294031416823, -0.001906817708604015, -0.001263260162418279, -0.000579518937674694,
0.000109689735095918, 0.000770541414234860, 0.001371714610702517, 0.001885828071941918, 0.002290640442950059, 0.002569962094668674,
0.002714244052422841, 0.002720826151891805, 0.002593843111794459, 0.002343803978935238, 0.001986875765177565, 0.001543915361236087,
0.001039304486976568, 0.000499650640507829, -0.000047579005659538, -0.000575422161543602, -0.001058718218567234, -0.001475276496367705,
-0.001806863227100527, -0.002039964832379242, -0.002166297669634002, -0.002183047066222210, -0.002092832630669335, -0.001903410054728627,
-0.001627132056108478, -0.001280202174740378, -0.000881764419386712, -0.000452877901224665, -0.000015430291445883, 0.000408955257020119,
0.000799966281315092, 0.001139550679396572, 0.001412726517149060, 0.001608217075415199, 0.001718885253761411, 0.001741952717366617,
0.001678999613916214, 0.001535751682495805, 0.001321671816520764, 0.001049382185659160, 0.000733950102097711, 0.000392077118279518,
0.000041233610152644, -0.000301217045862462, -0.000618861629989936, -0.000897007516317755, -0.001123345195626614, -0.001288473577308051,
-0.001386267150663963, -0.001414071700002426, -0.001372724600470066, -0.001266404272056495, -0.001102321495571061, -0.000890272488296022,
-0.000642080313861205, -0.000370954941565034, -0.000090806124488888, 0.000184456030731509, 0.000441598292681654, 0.000668686966519433,
0.000855627164276610, 0.000994595533680270, 0.001080348246990215, 0.001110393602574907, 0.001085025011663583, 0.001007217243930078,
0.000882395437488790, 0.000718092538346410, 0.000523514975880865, 0.000309041306180360, 0.000085680046384777, -0.000135485422990712,
-0.000343837417747543, -0.000529724099269400, -0.000684896491503848, -0.000802862554846735, -0.000879144467901314, -0.000911429428339170,
-0.000899610318070534, -0.000845717937205157, -0.000753751787384093, -0.000629420667419594, -0.000479809451013147, -0.000312990053930070,
-0.000137597792476766, 0.000037605243749942, 0.000204158180969291, 0.000354300348814663, 0.000481322426719260, 0.000579855135491155,
0.000646082835685789, 0.000677874702997924, 0.000674829917199715, 0.000638237711550962, 0.000570957019366285, 0.000477224887882656,
0.000362404482394644, 0.000232687846388931, 0.000094768978920233, -0.000044495794702522, -0.000178423294758992, -0.000300818490773199,
-0.000406255267998045, -0.000490309633189676, -0.000549736803809912, -0.000582585214321533, -0.000588244271105343, -0.000567425902673365,
-0.000522083581325246, -0.000455274194431906, -0.000370972701089329, -0.000273849135305279, -0.000169020516263524, -0.000061790478444653,
0.000042610318846710, 0.000139271063990380, 0.000223822598023746, 0.000292625889758754, 0.000342918280693206, 0.000372912653196213,
0.000381846581094768, 0.000369981142430884, 0.000338550686637535, 0.000289669081062027, 0.000226197017031803, 0.000151579558482804,
0.000069662241126261, -0.000015504805729649, -0.000099865220473760, -0.000179546871425107, -0.000251037234855322, -0.000311334118688652,
-0.000358067160688509, -0.000389585053939050, -0.000405005829251745, -0.000404229097174175, -0.000387912413334777, -0.000357412309140960,
-0.000314696298480236, -0.000262229388149273, -0.000202842130917589, -0.000139587283955501, -0.000075592647414163, -0.000013915345466225,
0.000042593436932362, 0.000091410233747216, 0.000130440616590328, 0.000158097036687462, 0.000173350787020117, 0.000175757544777044,
0.000165454349492432, 0.000143132709269975, 0.000109986899426513, 0.000067643276268719, 0.000018073847262639, -0.000036501336346782,
-0.000093718946756075, -0.000151176196094362, -0.000206533574886596, -0.000257608154338197, -0.000302456824898826, -0.000339445383884735,
-0.000367301005108244, -0.000385145111580360, -0.000392510765617404, -0.000389338959490572, -0.000375960779331289, -0.000353063703221184,
-0.000321645514236948, -0.000282958234132596, -0.000238447248218383, -0.000189681915226441, -0.000138290085401831, -0.000085892061029301,
-0.000034041343381570, 0.000015827588463025, 0.000062442756567725, 0.000104732554871058, 0.000141846034715806, 0.000173169245803356,
0.000198326327006374, 0.000217172712546861, 0.000229779689874953, 0.000236413452609260, 0.000237501949870309, 0.000233610866318186,
0.000225404773677130, 0.000213614009898736, 0.000198999590569024, 0.000182322037177544, 0.000164310512299333, 0.000145652693573148,
0.000126959389238696, 0.000108759338791293, 0.000091486545323856, 0.000075478577997365, 0.000060975611534136, 0.000048143861498476,
0.000037033583075960, 0.000027648080357536, 0.000019922294330328, 0.000013730623424634, 0.000008939491599758, 0.000007812421315026],
400: # Shape 1.3, 770 taps
[ 0.000006365988874769, 0.000003794338487480, 0.000004876278267121, 0.000006131564967683, 0.000007569854655698, 0.000009199819153557,
0.000011019782182642, 0.000013040101237056, 0.000015265995566942, 0.000017696391039940, 0.000020312791765427, 0.000023135950087517,
0.000026132051354026, 0.000029299266022519, 0.000032613010658575, 0.000036050466832082, 0.000039588439426462, 0.000043195256918759,
0.000046833693036583, 0.000050464843382682, 0.000054046630064787, 0.000057526210900478, 0.000060855931076401, 0.000063977534092402,
0.000066834004766232, 0.000069367495918698, 0.000071512066722409, 0.000073206600728293, 0.000074384990732719, 0.000074985154083054,
0.000074941358848070, 0.000074196098488127, 0.000072687036065854, 0.000070364017870395, 0.000067174711830892, 0.000063076338216280,
0.000058031241355323, 0.000052010623717769, 0.000044992453647518, 0.000036966078084084, 0.000027928525061234, 0.000017889642287636,
0.000006870052370296, -0.000005097430373119, -0.000017968467835226, -0.000031683467914261, -0.000046174009079182, -0.000061354520788796,
-0.000077131717460578, -0.000093396057473154, -0.000110030073586572, -0.000126901644954483, -0.000143873358110243, -0.000160793959726774,
-0.000177509258936783, -0.000193855013323854, -0.000209665383215103, -0.000224769517767120, -0.000238997508310048, -0.000252178063434561,
-0.000264145812850823, -0.000274737753978821, -0.000283800555901376, -0.000291188041391623, -0.000296767743750952, -0.000300418295008671,
-0.000302037282504393, -0.000301535929224389, -0.000298849141904324, -0.000293929053180242, -0.000286753991998278, -0.000277322604938755,
-0.000265661916507038, -0.000251820837747345, -0.000235878209412169, -0.000217935329921142, -0.000198122592158803, -0.000176593305324287,
-0.000153527761718214, -0.000129127285942292, -0.000103617728527825, -0.000077242169252941, -0.000050264484742360, -0.000022961141775614,
0.000004376321843836, 0.000031449535456591, 0.000057950921452100, 0.000083573577884305, 0.000108007974814302, 0.000130953051335089,
0.000152112926634605, 0.000171207503556438, 0.000187970143706098, 0.000202157681598790, 0.000213547707697876, 0.000221948537710715,
0.000227196325735486, 0.000229163803316040, 0.000227757838181676, 0.000222926398781727, 0.000214655798042235, 0.000202977177552243,
0.000187962083392261, 0.000169728357656893, 0.000148434669356957, 0.000124285090373991, 0.000097522588191792, 0.000068432637827430,
0.000037334851940437, 0.000004586327504349, -0.000029428168903046, -0.000064294238370152, -0.000099577998322800, -0.000134825630330724,
-0.000169573965195115, -0.000203350785393334, -0.000235686105255552, -0.000266113076153881, -0.000294178911934326, -0.000319446762108148,
-0.000341506152453346, -0.000359974720676106, -0.000374508618833672, -0.000384803219892303, -0.000390603203520662, -0.000391702472886875,
-0.000387952968439801, -0.000379263512998934, -0.000365607622249019, -0.000347020514221526, -0.000323605716996244, -0.000295530392174600,
-0.000263029891857937, -0.000226402015568579, -0.000186009246146028, -0.000142271402525828, -0.000095666125277552, -0.000046719850734115,
0.000003993583197103, 0.000055863130913344, 0.000108243891748510, 0.000160468312893279, 0.000211850586288707, 0.000261699269396572,
0.000309322128759128, 0.000354039461924243, 0.000395189349018748, 0.000432140920160531, 0.000464299583558709, 0.000491120046640221,
0.000512110607671310, 0.000526845446928034, 0.000534967981519684, 0.000536201343488612, 0.000530350597696912, 0.000517311013160068,
0.000497068439035188, 0.000469705445435111, 0.000435399334882175, 0.000394425628337960, 0.000347153886365670, 0.000294048338105669,
0.000235661140309264, 0.000172630559665850, 0.000105671355374271, 0.000035570791815593, -0.000036823410279226, -0.000110609450083053,
-0.000184845354264341, -0.000258557173028134, -0.000330754528114594, -0.000400439970264790, -0.000466625524492906, -0.000528342888003039,
-0.000584659866982658, -0.000634690947746067, -0.000677613119135274, -0.000712675673752122, -0.000739214923907549, -0.000756662495413132,
-0.000764558104223763, -0.000762555872583250, -0.000750434429795932, -0.000728100396603541, -0.000695595696462148, -0.000653097503960073,
-0.000600922257516729, -0.000539522021491309, -0.000469484695604341, -0.000391526816237233, -0.000306489974421006, -0.000215329834691466,
-0.000119109058134576, -0.000018982981184245, 0.000083810915235651, 0.000187970000243908, 0.000292139959571129, 0.000394933986966016,
0.000494948142664746, 0.000590782205985295, 0.000681056090464857, 0.000764431197740454, 0.000839627050452169, 0.000905442225580665,
0.000960769995583447, 0.001004618039369409, 0.001036121906273736, 0.001054562391583931, 0.001059376177251162, 0.001050169688173629,
0.001026726061735900, 0.000989014843850052, 0.000937194466007411, 0.000871617237374737, 0.000792826925935815, 0.000701558421554040,
0.000598730377991698, 0.000485439514972828, 0.000362948081805026, 0.000232673195056634, 0.000096169337651215, -0.000044886850392886,
-0.000188718302856941, -0.000333466707544108, -0.000477217660879214, -0.000618022612530573, -0.000753926550301084, -0.000882991568244702,
-0.001003325671471952, -0.001113106854174517, -0.001210611462788910, -0.001294237402501427, -0.001362530796885569, -0.001414206728098591,
-0.001448172776524704, -0.001463546100508022, -0.001459672417038066, -0.001436138251655395, -0.001392784297634421, -0.001329711615112041,
-0.001247288727402393, -0.001146150989584800, -0.001027200706007495, -0.000891599407168167, -0.000740760728594229, -0.000576335492878354,
-0.000400197621159931, -0.000214422239224392, -0.000021265147290331, 0.000176865113062629, 0.000377433546776307, 0.000577811529416548,
0.000775307530646278, 0.000967203529925253, 0.001150788527481832, 0.001323396897145278, 0.001482443038707630, 0.001625459660097671,
0.001750131670233381, 0.001854332615855901, 0.001936155690016456, 0.001993946486300650, 0.002026329220986863, 0.002032233882826800,
0.002010916100718580, 0.001961977029179007, 0.001885375282542786, 0.001781438385022068, 0.001650865633292049, 0.001494730107495952,
0.001314471906401099, 0.001111890114781271, 0.000889126269781853, 0.000648646430391355, 0.000393215009447102, 0.000125867561738431,
-0.000150124130738416, -0.000431287566747790, -0.000713991806927365, -0.000994489713483095, -0.001268965941124358, -0.001533583756312415,
-0.001784536663195940, -0.002018097583665120, -0.002230671541941067, -0.002418844734804261, -0.002579435657364371, -0.002709541359817244,
-0.002806584464746950, -0.002868353953987823, -0.002893045404115256, -0.002879294007683593, -0.002826205616681453, -0.002733379783299579,
-0.002600929724527880, -0.002429493528198460, -0.002220241603361130, -0.001974874872112559, -0.001695618645272131, -0.001385207220497454,
-0.001046863974406666, -0.000684272287342287, -0.000301542320981976, 0.000096831014522634, 0.000506013584333645, 0.000920885468043068,
0.001336097138497067, 0.001746132300635148, 0.002145372272540894, 0.002528165649885475, 0.002888898066467300, 0.003222065542166644,
0.003522346486582096, 0.003784675282650529, 0.004004312987512708, 0.004176917749158860, 0.004298610545483354, 0.004366038868235595,
0.004376434088990949, 0.004327664995106265, 0.004218283602051056, 0.004047565581181025, 0.003815541611827637, 0.003523022274162837,
0.003171612798693589, 0.002763720451889364, 0.002302551278585364, 0.001792098861036779, 0.001237122342212990, 0.000643116381836866,
0.000016270569630469, -0.000636578845765101, -0.001308006968347628, -0.001990065317450888, -0.002674355367539247, -0.003352107995747014,
-0.004014269925046706, -0.004651594401548673, -0.005254737118938589, -0.005814354519325696, -0.006321205162109337, -0.006766251436254966,
-0.007140762118819473, -0.007436412979114623, -0.007645386018101693, -0.007760464454403432, -0.007775124017275518, -0.007683617984862557,
-0.007481056258606499, -0.007163476264301046, -0.006727906019989577, -0.006172417333684170, -0.005496169589494688, -0.004699442443010971,
-0.003783657807377434, -0.002751390001364686, -0.001606364515685684, -0.000353444465756940, 0.001001394411574224, 0.002451100972703334,
0.003987597007902468, 0.005601837062643716, 0.007283878572786960, 0.009022962344277727, 0.010807601882596756, 0.012625681525580169,
0.014464561665273282, 0.016311190660485246, 0.018152221825464991, 0.019974134681837181, 0.021763358913199277, 0.023506400111173612,
0.025189965611159389, 0.026801089460580949, 0.028327254981021979, 0.029756513765852269, 0.031077599771363505, 0.032280037413131318,
0.033354242302380432, 0.034291613871121174, 0.035084618575605728, 0.035726863022388727, 0.036213156095256299, 0.036539559528566301,
0.036703426247133436, 0.036703426247133436, 0.036539559528566301, 0.036213156095256299, 0.035726863022388727, 0.035084618575605728,
0.034291613871121174, 0.033354242302380432, 0.032280037413131318, 0.031077599771363505, 0.029756513765852269, 0.028327254981021979,
0.026801089460580949, 0.025189965611159389, 0.023506400111173612, 0.021763358913199277, 0.019974134681837181, 0.018152221825464991,
0.016311190660485246, 0.014464561665273282, 0.012625681525580169, 0.010807601882596756, 0.009022962344277727, 0.007283878572786960,
0.005601837062643716, 0.003987597007902468, 0.002451100972703334, 0.001001394411574224, -0.000353444465756940, -0.001606364515685684,
-0.002751390001364686, -0.003783657807377434, -0.004699442443010971, -0.005496169589494688, -0.006172417333684170, -0.006727906019989577,
-0.007163476264301046, -0.007481056258606499, -0.007683617984862557, -0.007775124017275518, -0.007760464454403432, -0.007645386018101693,
-0.007436412979114623, -0.007140762118819473, -0.006766251436254966, -0.006321205162109337, -0.005814354519325696, -0.005254737118938589,
-0.004651594401548673, -0.004014269925046706, -0.003352107995747014, -0.002674355367539247, -0.001990065317450888, -0.001308006968347628,
-0.000636578845765101, 0.000016270569630469, 0.000643116381836866, 0.001237122342212990, 0.001792098861036779, 0.002302551278585364,
0.002763720451889364, 0.003171612798693589, 0.003523022274162837, 0.003815541611827637, 0.004047565581181025, 0.004218283602051056,
0.004327664995106265, 0.004376434088990949, 0.004366038868235595, 0.004298610545483354, 0.004176917749158860, 0.004004312987512708,
0.003784675282650529, 0.003522346486582096, 0.003222065542166644, 0.002888898066467300, 0.002528165649885475, 0.002145372272540894,
0.001746132300635148, 0.001336097138497067, 0.000920885468043068, 0.000506013584333645, 0.000096831014522634, -0.000301542320981976,
-0.000684272287342287, -0.001046863974406666, -0.001385207220497454, -0.001695618645272131, -0.001974874872112559, -0.002220241603361130,
-0.002429493528198460, -0.002600929724527880, -0.002733379783299579, -0.002826205616681453, -0.002879294007683593, -0.002893045404115256,
-0.002868353953987823, -0.002806584464746950, -0.002709541359817244, -0.002579435657364371, -0.002418844734804261, -0.002230671541941067,
-0.002018097583665120, -0.001784536663195940, -0.001533583756312415, -0.001268965941124358, -0.000994489713483095, -0.000713991806927365,
-0.000431287566747790, -0.000150124130738416, 0.000125867561738431, 0.000393215009447102, 0.000648646430391355, 0.000889126269781853,
0.001111890114781271, 0.001314471906401099, 0.001494730107495952, 0.001650865633292049, 0.001781438385022068, 0.001885375282542786,
0.001961977029179007, 0.002010916100718580, 0.002032233882826800, 0.002026329220986863, 0.001993946486300650, 0.001936155690016456,
0.001854332615855901, 0.001750131670233381, 0.001625459660097671, 0.001482443038707630, 0.001323396897145278, 0.001150788527481832,
0.000967203529925253, 0.000775307530646278, 0.000577811529416548, 0.000377433546776307, 0.000176865113062629, -0.000021265147290331,
-0.000214422239224392, -0.000400197621159931, -0.000576335492878354, -0.000740760728594229, -0.000891599407168167, -0.001027200706007495,
-0.001146150989584800, -0.001247288727402393, -0.001329711615112041, -0.001392784297634421, -0.001436138251655395, -0.001459672417038066,
-0.001463546100508022, -0.001448172776524704, -0.001414206728098591, -0.001362530796885569, -0.001294237402501427, -0.001210611462788910,
-0.001113106854174517, -0.001003325671471952, -0.000882991568244702, -0.000753926550301084, -0.000618022612530573, -0.000477217660879214,
-0.000333466707544108, -0.000188718302856941, -0.000044886850392886, 0.000096169337651215, 0.000232673195056634, 0.000362948081805026,
0.000485439514972828, 0.000598730377991698, 0.000701558421554040, 0.000792826925935815, 0.000871617237374737, 0.000937194466007411,
0.000989014843850052, 0.001026726061735900, 0.001050169688173629, 0.001059376177251162, 0.001054562391583931, 0.001036121906273736,
0.001004618039369409, 0.000960769995583447, 0.000905442225580665, 0.000839627050452169, 0.000764431197740454, 0.000681056090464857,
0.000590782205985295, 0.000494948142664746, 0.000394933986966016, 0.000292139959571129, 0.000187970000243908, 0.000083810915235651,
-0.000018982981184245, -0.000119109058134576, -0.000215329834691466, -0.000306489974421006, -0.000391526816237233, -0.000469484695604341,
-0.000539522021491309, -0.000600922257516729, -0.000653097503960073, -0.000695595696462148, -0.000728100396603541, -0.000750434429795932,
-0.000762555872583250, -0.000764558104223763, -0.000756662495413132, -0.000739214923907549, -0.000712675673752122, -0.000677613119135274,
-0.000634690947746067, -0.000584659866982658, -0.000528342888003039, -0.000466625524492906, -0.000400439970264790, -0.000330754528114594,
-0.000258557173028134, -0.000184845354264341, -0.000110609450083053, -0.000036823410279226, 0.000035570791815593, 0.000105671355374271,
0.000172630559665850, 0.000235661140309264, 0.000294048338105669, 0.000347153886365670, 0.000394425628337960, 0.000435399334882175,
0.000469705445435111, 0.000497068439035188, 0.000517311013160068, 0.000530350597696912, 0.000536201343488612, 0.000534967981519684,
0.000526845446928034, 0.000512110607671310, 0.000491120046640221, 0.000464299583558709, 0.000432140920160531, 0.000395189349018748,
0.000354039461924243, 0.000309322128759128, 0.000261699269396572, 0.000211850586288707, 0.000160468312893279, 0.000108243891748510,
0.000055863130913344, 0.000003993583197103, -0.000046719850734115, -0.000095666125277552, -0.000142271402525828, -0.000186009246146028,
-0.000226402015568579, -0.000263029891857937, -0.000295530392174600, -0.000323605716996244, -0.000347020514221526, -0.000365607622249019,
-0.000379263512998934, -0.000387952968439801, -0.000391702472886875, -0.000390603203520662, -0.000384803219892303, -0.000374508618833672,
-0.000359974720676106, -0.000341506152453346, -0.000319446762108148, -0.000294178911934326, -0.000266113076153881, -0.000235686105255552,
-0.000203350785393334, -0.000169573965195115, -0.000134825630330724, -0.000099577998322800, -0.000064294238370152, -0.000029428168903046,
0.000004586327504349, 0.000037334851940437, 0.000068432637827430, 0.000097522588191792, 0.000124285090373991, 0.000148434669356957,
0.000169728357656893, 0.000187962083392261, 0.000202977177552243, 0.000214655798042235, 0.000222926398781727, 0.000227757838181676,
0.000229163803316040, 0.000227196325735486, 0.000221948537710715, 0.000213547707697876, 0.000202157681598790, 0.000187970143706098,
0.000171207503556438, 0.000152112926634605, 0.000130953051335089, 0.000108007974814302, 0.000083573577884305, 0.000057950921452100,
0.000031449535456591, 0.000004376321843836, -0.000022961141775614, -0.000050264484742360, -0.000077242169252941, -0.000103617728527825,
-0.000129127285942292, -0.000153527761718214, -0.000176593305324287, -0.000198122592158803, -0.000217935329921142, -0.000235878209412169,
-0.000251820837747345, -0.000265661916507038, -0.000277322604938755, -0.000286753991998278, -0.000293929053180242, -0.000298849141904324,
-0.000301535929224389, -0.000302037282504393, -0.000300418295008671, -0.000296767743750952, -0.000291188041391623, -0.000283800555901376,
-0.000274737753978821, -0.000264145812850823, -0.000252178063434561, -0.000238997508310048, -0.000224769517767120, -0.000209665383215103,
-0.000193855013323854, -0.000177509258936783, -0.000160793959726774, -0.000143873358110243, -0.000126901644954483, -0.000110030073586572,
-0.000093396057473154, -0.000077131717460578, -0.000061354520788796, -0.000046174009079182, -0.000031683467914261, -0.000017968467835226,
-0.000005097430373119, 0.000006870052370296, 0.000017889642287636, 0.000027928525061234, 0.000036966078084084, 0.000044992453647518,
0.000052010623717769, 0.000058031241355323, 0.000063076338216280, 0.000067174711830892, 0.000070364017870395, 0.000072687036065854,
0.000074196098488127, 0.000074941358848070, 0.000074985154083054, 0.000074384990732719, 0.000073206600728293, 0.000071512066722409,
0.000069367495918698, 0.000066834004766232, 0.000063977534092402, 0.000060855931076401, 0.000057526210900478, 0.000054046630064787,
0.000050464843382682, 0.000046833693036583, 0.000043195256918759, 0.000039588439426462, 0.000036050466832082, 0.000032613010658575,
0.000029299266022519, 0.000026132051354026, 0.000023135950087517, 0.000020312791765427, 0.000017696391039940, 0.000015265995566942,
0.000013040101237056, 0.000011019782182642, 0.000009199819153557, 0.000007569854655698, 0.000006131564967683, 0.000004876278267121,
0.000003794338487480, 0.000006365988874769],
}
+707
View File
@@ -0,0 +1,707 @@
#include <Python.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h> // Use native C99 complex type for fftw3
#include <sys/types.h>
#include "quisk.h"
#include "freedv.h"
#include <stdint.h>
int DEBUG;
#define MAX_RECEIVERS 2
typedef struct { // from comp.h
float real;
float imag;
} COMP;
struct freedv; // from freedv_api.h
typedef void (*freedv_callback_rx)(void *, char);
typedef char (*freedv_callback_tx)(void *);
/* Protocol bits are packed MSB-first */
/* Called when a frame containing protocol data is decoded */
typedef void (*freedv_callback_protorx)(void *, char *);
/* Called when a frame containing protocol data is to be sent */
typedef void (*freedv_callback_prototx)(void *, char *);
/* Data packet callbacks */
/* Called when a packet has been received */
typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t size);
/* Called when a new packet can be send */
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size);
/* advanced freedv open options required by some modes */
struct freedv_advanced { // from freedv_api.h
int interleave_frames;
};
#ifdef MS_WINDOWS
#include <windows.h>
HMODULE hLib;
#define GET_HANDLE1 hLib = LoadLibrary("libcodec2.dll")
#define GET_HANDLE2 hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll")
#define GET_HANDLE3 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll")
#define GET_HANDLE4 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll")
#define GET_ADDR(name) (void *)GetProcAddress(hLib, name)
#define CLOSE_LIB FreeLibrary(hLib)
#else
#include <dlfcn.h>
void * hLib;
#define GET_HANDLE1 hLib = dlopen("libcodec2.so", RTLD_LAZY)
#define GET_HANDLE2 hLib = dlopen("./freedvpkg/libcodec2.so", RTLD_LAZY)
#define GET_HANDLE3 hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY)
#define GET_HANDLE4 hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY)
#define GET_ADDR(name) dlsym(hLib, name)
#define CLOSE_LIB dlclose(hLib)
#endif
static int requested_mode = -1; // requested mode
int freedv_current_mode = -1; // the current running mode
static int quisk_freedv_squelch;
static int interleave_frames = 1;
static int freedv_version = -1;
static int quisk_set_tx_bpf = 1;
static int handle_index = -1;
// need access to these here and in quisk.c
int n_speech_sample_rate = 8000;
int n_modem_sample_rate = 8000;
#define SPEECH_BUF_SIZE 12000 // speech buffer size increased from 3000
static struct _rx_channel{
struct freedv * hFreedv;
COMP * demod_in;
int rxdata_index;
short speech_out[SPEECH_BUF_SIZE]; // output buffer
int speech_available; // number of samples in output buffer
int playing; // are we currently returning speech samples?
} rx_channel[MAX_RECEIVERS] = { 0 }; // safe initialisation to 0
// freedv_version is the library version number, or
// -1 no library was found
// -2 a library was found, but freedv_get_version is missing
// FreeDV API functions:
// open, close
struct freedv * (*freedv_open)(int mode);
struct freedv * (*freedv_open_advanced)(int mode, struct freedv_advanced *adv);
void (*freedv_close)(struct freedv *freedv);
// Transmit
void (*freedv_tx)(struct freedv *freedv, short *, short *);
void (*freedv_comptx)(struct freedv *freedv, COMP *, short *);
// Receive
int (*freedv_nin)(struct freedv *freedv);
int (*freedv_rx)(struct freedv *freedv, short *, short demod_in[]);
int (*freedv_floatrx)(struct freedv *freedv, short *, float demod_in[]);
int (*freedv_comprx)(struct freedv *freedv, short *, COMP demod_in[]);
// Set parameters
void (*freedv_set_callback_txt) (struct freedv *freedv, freedv_callback_rx rx, freedv_callback_tx tx, void *callback_state);
void (*freedv_set_test_frames) (struct freedv *freedv, int test_frames);
void (*freedv_set_smooth_symbols) (struct freedv *freedv, int smooth_symbols);
void (*freedv_set_squelch_en) (struct freedv *freedv, int squelch_en);
void (*freedv_set_snr_squelch_thresh) (struct freedv *freedv, float snr_squelch_thresh);
void (*freedv_set_tx_bpf) (struct freedv *freedv, int val);
// Get parameters
int (*freedv_get_version)(void);
void (*freedv_get_modem_stats)(struct freedv *freedv, int *sync, float *snr_est);
int (*freedv_get_test_frames) (struct freedv *freedv);
int (*freedv_get_n_speech_samples) (struct freedv *freedv);
int (*freedv_get_n_max_modem_samples) (struct freedv *freedv);
int (*freedv_get_n_nom_modem_samples) (struct freedv *freedv);
int (*freedv_get_total_bits) (struct freedv *freedv);
int (*freedv_get_total_bit_errors) (struct freedv *freedv);
int (*freedv_get_modem_sample_rate) (struct freedv *freedv);
int (*freedv_get_speech_sample_rate) (struct freedv *freedv);
//
int (*freedv_get_sync) (struct freedv *freedv);
void (*freedv_set_callback_protocol) (struct freedv *freedv, freedv_callback_protorx rx, freedv_callback_prototx tx, void *callback_state);
void (*freedv_set_callback_data) (struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state);
//
// checkAvxSupport
//
// This function was copied from freedv, file fdmdv2_main.cpp and tweaked for use here
//
// Tests the underlying platform for AVX support. 2020 needs AVX support to run
// in real-time, and old processors do not offer AVX support
//
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
#include <cpuid.h>
static int checkAvxSupport(void)
{
int isAvxPresent = 0;
uint32_t eax, ebx, ecx, edx;
eax = ebx = ecx = edx = 0;
__cpuid(1, eax, ebx, ecx, edx);
if (ecx & (1<<27) && ecx & (1<<28)) {
// CPU supports XSAVE and AVX
uint32_t xcr0, xcr0_high;
asm("xgetbv" : "=a" (xcr0), "=d" (xcr0_high) : "c" (0));
isAvxPresent = (xcr0 & 6) == 6; // AVX state saving enabled?
}
return isAvxPresent;
}
#else
static int checkAvxSupport(void)
{
return 0;
}
#endif
/* Called when a new packet can be sent */
void my_datatx(void *callback_state, unsigned char *packet, size_t *size) {
*size = 0;
}
static void GetAddrs(void)
{
handle_index = 1;
GET_HANDLE1;
if ( ! hLib) {
handle_index = 2;
GET_HANDLE2;
if ( ! hLib) {
handle_index = 3;
GET_HANDLE3;
if ( ! hLib) {
handle_index = 4;
GET_HANDLE4;
}
}
}
if (hLib) {
freedv_get_version = GET_ADDR("freedv_get_version");
if (freedv_get_version != NULL) {
freedv_version = freedv_get_version();
if (DEBUG)
printf("FreeDV codec2 library %d found, version %d\n", handle_index, freedv_version);
}
else {
freedv_version = -2;
if (DEBUG)
printf("FreeDV codec2 library %d found, but no freedv_get_version() !!!\n", handle_index);
CLOSE_LIB;
return;
}
}
else {
handle_index = -1;
if (DEBUG)
printf("Could not find the FreeDV codec2 shared library\n");
return;
}
// open, close
freedv_open = GET_ADDR("freedv_open");
freedv_open_advanced = GET_ADDR("freedv_open_advanced");
freedv_close = GET_ADDR("freedv_close");
// Transmit
freedv_tx = GET_ADDR("freedv_tx");
freedv_comptx = GET_ADDR("freedv_comptx");
// Receive
freedv_nin = GET_ADDR("freedv_nin");
freedv_rx = GET_ADDR("freedv_rx");
freedv_floatrx = GET_ADDR("freedv_floatrx");
freedv_comprx = GET_ADDR("freedv_comprx");
// Set parameters
freedv_set_callback_txt = GET_ADDR("freedv_set_callback_txt");
freedv_set_callback_protocol = GET_ADDR("freedv_set_callback_protocol");
freedv_set_callback_data = GET_ADDR("freedv_set_callback_data");
freedv_set_test_frames = GET_ADDR("freedv_set_test_frames");
freedv_set_smooth_symbols = GET_ADDR("freedv_set_smooth_symbols");
freedv_set_squelch_en = GET_ADDR("freedv_set_squelch_en");
freedv_set_snr_squelch_thresh = GET_ADDR("freedv_set_snr_squelch_thresh");
freedv_set_tx_bpf = GET_ADDR("freedv_set_tx_bpf");
// Get parameters
freedv_get_modem_stats = GET_ADDR("freedv_get_modem_stats");
freedv_get_test_frames = GET_ADDR("freedv_get_test_frames");
freedv_get_n_speech_samples = GET_ADDR("freedv_get_n_speech_samples");
freedv_get_n_max_modem_samples = GET_ADDR("freedv_get_n_max_modem_samples");
freedv_get_n_nom_modem_samples = GET_ADDR("freedv_get_n_nom_modem_samples");
freedv_get_total_bits = GET_ADDR("freedv_get_total_bits");
freedv_get_total_bit_errors = GET_ADDR("freedv_get_total_bit_errors");
freedv_get_sync = GET_ADDR("freedv_get_sync"); // requires version 11
if( freedv_version >= 11)
freedv_get_modem_sample_rate = GET_ADDR("freedv_get_modem_sample_rate"); // requires version 11 or later
if (freedv_version >= 12)
freedv_get_speech_sample_rate = GET_ADDR("freedv_get_speech_sample_rate"); // requires version 12
return;
}
static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int count, int bank) // Called from the sound thread.
{ // Input digital modulation is cSamples; decoded voice is dsamples. Each "bank" is a stream of audio.
// caller will have decimated input audio down to n_modem_sample_rate and reduced block size to match reduction
// output data will be returned at n_speech_sample_rate with corresponding block sizes
int i, nout, need, have, sync;
int n_speech_samples;
complex double cx;
double scale = (double)CLIP32 / CLIP16; // convert 32 bits to 16 bits
struct freedv * hF;
struct _rx_channel * pCh;
int nCountOut = 1; // In case we have a higher (or lower) value for o/p count
if (cSamples == NULL) { // shutdown
for (i = 0; i < MAX_RECEIVERS; i++) {
if (rx_channel[i].demod_in) {
free(rx_channel[i].demod_in);
rx_channel[i].demod_in = NULL;
}
}
return 0;
}
if (bank < 0 || bank >= MAX_RECEIVERS)
return 0;
hF = rx_channel[bank].hFreedv;
if ( ! hF)
return 0;
pCh = rx_channel + bank;
n_speech_samples = freedv_get_n_speech_samples(hF);
// if (say) input modem rate is the 2020 mode rate of 8000 sa/sec
// and output rate is the corresponding 16000 sa/sec then for an input
// block size of N, outbut 2N samples block size.
// Apply a multiplier/divisor to 'count' for the output samples count
if(n_speech_sample_rate >= n_modem_sample_rate ) {
int nRatio = n_speech_sample_rate / n_modem_sample_rate;
if( nRatio >= 1 && nRatio <= 6)
nCountOut = nRatio * count;
}
else {
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
if( nRatio >= 1 && nRatio <= 6)
nCountOut = count / nRatio;
}
nout = 0;
need = freedv_nin(hF);
for (i = 0; i < count; i++) {
cx = cRxFilterOut(cSamples[i], bank, 0);
if (rxMode == FDV_L) // lower sideband
cx = conj(cx);
#if 1 // Try it the other way and remove scaling factor
pCh->demod_in[pCh->rxdata_index].real = creal(cx);
pCh->demod_in[pCh->rxdata_index].imag = cimag(cx);
#else
pCh->demod_in[pCh->rxdata_index].real = (creal(cx) - cimag(cx));
pCh->demod_in[pCh->rxdata_index].imag = 0;
#endif
pCh->rxdata_index++;
if (pCh->rxdata_index >= need) {
if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space
have = freedv_comprx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in);
if (freedv_version > 10)
sync = freedv_get_sync(hF);
else
freedv_get_modem_stats(hF, &sync, NULL);
if (freedv_current_mode == 0) { // mode 1600
if (sync) // throw away speech if not in sync
pCh->speech_available += have;
}
else if (pCh->speech_available < SPEECH_BUF_SIZE * 2 / 3) {
pCh->speech_available += have; // keep speech if there is space
}
else {
if (DEBUG) printf("Close to maximum in speech output buffer\n");
}
}
else { // no space in buffer
if (DEBUG) printf("Overflow in speech output buffer\n");
}
pCh->rxdata_index = 0;
need = freedv_nin(hF);
}
}
if ( ! pCh->playing) {
if (pCh->speech_available >= 2 * n_speech_samples) {
pCh->playing = 1;
}
else { // return zero samples
for (i = 0; i < nCountOut; i++)
dsamples[i] = 0;
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
return nCountOut;
}
}
for (nout = 0; nout < pCh->speech_available && nout < nCountOut; nout++)
dsamples[nout] = pCh->speech_out[nout] * scale * 0.7;
if (nout) {
pCh->speech_available -= nout;
memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short));
}
if ( ! pCh->speech_available) {
pCh->playing = 0;
while (nout < nCountOut)
dsamples[nout++] = 0;
}
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
return nout;
}
static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count) // Called from the sound thread.
{ // Input voice samples are dsamples; output digital modulation is cSamples.
int i, nout;
int n_speech_samples;
int n_nom_modem_samples;
static COMP * mod_out = NULL;
static short * speech_in = NULL;
static int speech_index=0, mod_index=0;
// g8kbb - add for 800XA
static short *real_mod_out = NULL;
if (dsamples == NULL) { // shutdown
if (mod_out)
free(mod_out);
mod_out = NULL;
if (real_mod_out)
free(real_mod_out);
real_mod_out = NULL;
if (speech_in)
free(speech_in);
speech_in = NULL;
return 0;
}
if ( ! rx_channel[0].hFreedv)
return 0;
n_speech_samples = freedv_get_n_speech_samples(rx_channel[0].hFreedv);
n_nom_modem_samples = freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv);
// find if need to output at faster rate
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
if( nRatio < 1 || nRatio > 6 )
nRatio = 1;
if (mod_out == NULL) { // initialize
mod_out = (COMP *)malloc(sizeof(COMP) * n_nom_modem_samples);
memset(mod_out, 0, sizeof(COMP) * n_nom_modem_samples);
speech_in = (short*)malloc(sizeof(short) * n_speech_samples);
speech_index=0;
mod_index=0;
real_mod_out = (short *)malloc(sizeof(short) * n_nom_modem_samples);
memset(real_mod_out, 0, sizeof(short) * n_nom_modem_samples);
//if( DEBUG ) printf("Initialise tx buffer size = %d, i/p sample size = %d\n", n_nom_modem_samples, n_speech_samples );
}
nout = 0;
for (i = 0; i < count; i++) {
speech_in[speech_index++] = (short)dsamples[i];
if (speech_index >= n_speech_samples) {
// Calculate a new block, but first write out the rest of the old block
// 800xa uses real not complex
if( freedv_current_mode == FREEDV_MODE_800XA ) {
for ( ; mod_index < n_nom_modem_samples; mod_index++)
cSamples[nout++] = real_mod_out[mod_index];
freedv_tx(rx_channel[0].hFreedv, real_mod_out, speech_in);
}
else {
for ( ; mod_index < n_nom_modem_samples; mod_index++)
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
freedv_comptx(rx_channel[0].hFreedv, mod_out, speech_in);
}
//if( DEBUG ) printf("Block processed, speech index=%d, n_speech_samples=%d, count=%d\n", speech_index, n_speech_samples, count );
mod_index = 0;
speech_index = 0;
}
else { // write out samples
for( int j=0; j<nRatio; j++) {
if (mod_index < n_nom_modem_samples ) {
if( freedv_current_mode == FREEDV_MODE_800XA ) {
cSamples[nout++] = real_mod_out[mod_index];
}
else {
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
}
mod_index++;
}
}
}
}
if (rxMode == FDV_L)
for (i = 0; i < nout; i++)
cSamples[i] = conj(cSamples[i]);
return nout;
}
#define TX_MSG_SIZE 80
static char quisk_tx_msg[TX_MSG_SIZE];
static char get_next_tx_char(void * callback_state)
{
char c;
static int index = 0;
c = quisk_tx_msg[index++];
if (index >= TX_MSG_SIZE)
index = 0;
if ( ! c) {
index = 0;
c = quisk_tx_msg[index++];
}
return c;
}
#define RX_MSG_SIZE 80
static char quisk_rx_msg[RX_MSG_SIZE + 1];
static void put_next_rx_char(void * callback_state, char ch)
{
if (ch == '\n' || ch == '\r')
ch = ' ';
if (ch < 32 || ch > 126) // printable characters
return;
if (strlen(quisk_rx_msg) < RX_MSG_SIZE)
strncat(quisk_rx_msg, &ch, 1);
}
PyObject * quisk_freedv_get_rx_char(PyObject * self, PyObject * args) // Called from the GUI thread.
{
PyObject * txt;
if (!PyArg_ParseTuple (args, ""))
return NULL;
txt = PyString_FromString(quisk_rx_msg);
quisk_rx_msg[0] = 0;
return txt;
}
static void CloseFreedv(void) // Called from the GUI thread or sound thread
{
int i;
for (i = 0; i < MAX_RECEIVERS; i++) {
if (rx_channel[i].hFreedv) {
freedv_close(rx_channel[i].hFreedv);
rx_channel[i].hFreedv = NULL;
}
#if 0 // g8kbb - unnecessary - quisk_freedv_rx() will do it
if (rx_channel[i].demod_in) {
free(rx_channel[i].demod_in);
rx_channel[i].demod_in = NULL;
}
#endif
}
quisk_freedv_rx(NULL, NULL, 0, 0);
quisk_freedv_tx(NULL, NULL, 0);
freedv_current_mode = -1;
}
static int OpenFreedv(void) // Called from the GUI thread or sound thread
{
int i, n_max_modem_samples;
struct freedv * hF;
if ( ! hLib)
GetAddrs(); // Get the entry points for funtions in the codec2 library
if (DEBUG) {
switch (handle_index) {
case 1:
printf("freedv_open: system codec2 library found, version %d\n", freedv_version);
break;
case 2:
printf("freedv_open: codec2 library freedvpkg/libcodec2.dll|so found, version %d\n", freedv_version);
break;
case 3:
case 4:
printf("freedv_open: codec2 library freedvpkg/libcodec2_32|64.dll|so found, version %d\n", freedv_version);
break;
default:
printf("freedv_open: Could not find the FreeDV codec2 shared library\n");
break;
}
}
if (freedv_version < 10) {
CloseFreedv();
requested_mode = -1;
if (DEBUG)
printf("freedv_open: Failure because version is less than 10\n");
return 0; // failure
}
if (requested_mode == FREEDV_MODE_2020) {
if (checkAvxSupport() == 0) {
CloseFreedv();
requested_mode = -1;
if (DEBUG)
printf("freedv_open: Failure because mode 2020 requires Avx support\n");
return 0; // failure
}
if (handle_index >= 3) { // Quisk provided codec2
CloseFreedv();
requested_mode = -1;
if (DEBUG)
printf("freedv_open: Failure because mode 2020 requires a system installation of codec2\n");
return 0; // failure
}
}
if (requested_mode == FREEDV_MODE_700E) {
if (handle_index >= 3) { // Quisk provided codec2
CloseFreedv();
requested_mode = -1;
if (DEBUG)
printf("freedv_open: Failure because mode 700E requires a system installation of codec2\n");
return 0; // failure
}
}
if ((requested_mode == FREEDV_MODE_700D || requested_mode == FREEDV_MODE_700E) && freedv_open_advanced) {
struct freedv_advanced adv;
adv.interleave_frames = interleave_frames;
hF = freedv_open_advanced(requested_mode, &adv);
}
else {
hF = freedv_open(requested_mode);
}
if (hF == NULL) {
CloseFreedv();
requested_mode = -1;
return 0; // failure
}
rx_channel[0].hFreedv = hF;
quisk_dvoice_freedv(&quisk_freedv_rx, &quisk_freedv_tx);
if (quisk_tx_msg[0])
freedv_set_callback_txt(hF, &put_next_rx_char, &get_next_tx_char, NULL);
else
freedv_set_callback_txt(hF, &put_next_rx_char, NULL, NULL);
if (freedv_set_callback_protocol)
freedv_set_callback_protocol(hF, NULL, NULL, NULL);
if (freedv_set_callback_data)
freedv_set_callback_data(hF, NULL, my_datatx, NULL);
freedv_set_squelch_en(hF, quisk_freedv_squelch);
if (freedv_set_tx_bpf)
freedv_set_tx_bpf(hF, quisk_set_tx_bpf);
n_max_modem_samples = freedv_get_n_max_modem_samples(hF);
n_speech_sample_rate = 8000;
if (freedv_version >= 12 && freedv_get_speech_sample_rate)
n_speech_sample_rate = freedv_get_speech_sample_rate(hF);
n_modem_sample_rate = 8000;
if (freedv_version >= 11 && freedv_get_modem_sample_rate)
n_modem_sample_rate = freedv_get_modem_sample_rate(hF);
for (i = 0; i < MAX_RECEIVERS; i++) {
rx_channel[i].rxdata_index = 0;
rx_channel[i].speech_available = 0;
rx_channel[i].playing = 0;
if (rx_channel[i].demod_in)
free(rx_channel[i].demod_in);
rx_channel[i].demod_in = (COMP *)malloc(sizeof(COMP) * n_max_modem_samples);
if( rx_channel[i].demod_in == NULL) {
CloseFreedv();
requested_mode = -1;
return 0; // failure
}
if (i > 0) {
rx_channel[i].hFreedv = freedv_open(requested_mode);
if (rx_channel[i].hFreedv)
freedv_set_squelch_en(rx_channel[i].hFreedv, quisk_freedv_squelch);
}
}
if (DEBUG) printf("n_nom_modem_samples %d\n", freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv));
if (DEBUG) printf("n_speech_samples %d\n", freedv_get_n_speech_samples(rx_channel[0].hFreedv));
if (DEBUG) printf("n_max_modem_samples %d\n", n_max_modem_samples);
if (DEBUG) {
if (freedv_version >= 11) printf("modem_sample_rate %d\n", n_modem_sample_rate);
if (freedv_version >= 12) printf("speech_sample_rate %d\n", n_speech_sample_rate);
}
freedv_current_mode = requested_mode;
return 1; // success
}
void quisk_check_freedv_mode(void)
{ // see if we need to change the mode
if (requested_mode == freedv_current_mode)
return;
if (DEBUG) printf("Change in mode to %d\n", requested_mode);
CloseFreedv();
if (requested_mode >= 0)
OpenFreedv();
else
requested_mode = -1;
}
PyObject * quisk_freedv_open(PyObject * self, PyObject * args) // Called from the GUI thread before freedv is open
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
return PyInt_FromLong(OpenFreedv());
}
PyObject * quisk_freedv_close(PyObject * self, PyObject * args) // Called from the GUI thread.
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
requested_mode = -1; // request close
Py_INCREF (Py_None);
return Py_None;
}
PyObject * quisk_freedv_set_options(PyObject * self, PyObject * args, PyObject * keywds) // Called from the GUI thread.
{ // Call with keyword arguments ONLY to change parameters. Call before quisk_freedv_open() to set an initial mode.
int mode=-1; // Call again to change the mode.
int bpf=-1;
char * ptMsg=NULL;
static char * kwlist[] = {"mode", "tx_msg", "DEBUG", "squelch", "interleave_frames", "set_tx_bpf", NULL} ;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch, &interleave_frames, &bpf))
return NULL;
if (ptMsg)
strMcpy(quisk_tx_msg, ptMsg, TX_MSG_SIZE);
if (bpf != -1) {
quisk_set_tx_bpf = bpf;
if (freedv_set_tx_bpf && rx_channel[0].hFreedv)
freedv_set_tx_bpf(rx_channel[0].hFreedv, quisk_set_tx_bpf);
}
if (mode == -1) { // mode was not entered
;
}
else if (mode == FREEDV_MODE_700E && freedv_version >= 14) {
requested_mode = mode;
}
else if (mode == FREEDV_MODE_2020) {
if (checkAvxSupport() && handle_index <= 2 && freedv_version >= 13)
requested_mode = mode;
}
else if (freedv_version == 10) {
if (mode == 0)
requested_mode = mode;
}
else if (freedv_version == 11) {
if (mode <= 2)
requested_mode = mode;
}
else {
requested_mode = mode;
}
return PyInt_FromLong(requested_mode); // Return the mode
}
PyObject * quisk_freedv_get_snr(PyObject * self, PyObject * args) // Called from the GUI thread.
{
float snr_est = 0.0;
if (!PyArg_ParseTuple (args, ""))
return NULL;
if (rx_channel[0].hFreedv)
freedv_get_modem_stats(rx_channel[0].hFreedv, NULL, &snr_est);
return PyFloat_FromDouble(snr_est);
}
PyObject * quisk_freedv_get_version(PyObject * self, PyObject * args) // Called from the GUI thread.
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
if ( ! hLib)
GetAddrs(); // Get the entry points for funtions in the codec2 library
return PyInt_FromLong(freedv_version);
}
PyObject * quisk_freedv_set_squelch_en(PyObject * self, PyObject * args) // Called from the GUI thread.
{
int i,state;
if (!PyArg_ParseTuple (args, "i", &state))
return NULL;
quisk_freedv_squelch = state;
for (i = 0; i < MAX_RECEIVERS; i++) {
if( rx_channel[i].hFreedv )
freedv_set_squelch_en(rx_channel[i].hFreedv, state);
}
return PyInt_FromLong(state);
}
+12
View File
@@ -0,0 +1,12 @@
extern int freedv_current_mode; // the current FreeDV running mode
#define FREEDV_MODE_1600 0
#define FREEDV_MODE_700 1
#define FREEDV_MODE_700B 2
#define FREEDV_MODE_2400A 3
#define FREEDV_MODE_2400B 4
#define FREEDV_MODE_800XA 5
#define FREEDV_MODE_700C 6
#define FREEDV_MODE_700D 7
#define FREEDV_MODE_2020 8
#define FREEDV_MODE_700E 13
+129
View File
@@ -0,0 +1,129 @@
Note: The directory freedvpkg no longer contains source files. It only contains copies of
the codec2 libraries for use by Quisk. If any other files are present, delete them.
FreeDV and Directory freedvpkg
==============================
FreeDV is the combination of the codec2 codec and the fdmdv modem. It provides digital voice
suitable for HF and VHF transmission. Quisk has native (built-in) support for
FreeDV. Just push the FDV mode and talk. This freedvpkg directory contains copies of the
codec2 libraries for use by Quisk.
You can also use the separate FreeDV program available at freedv.org, and the Quisk DGT-U mode
which attaches to external digital programs. The setup is identical to fldigi and other external
digital programs.
Quisk will add the FDV button if specified on the Config/radio/Controls tab.
If there is a problem with the freedv module, the button will be grayed out.
The freedv module requires the codec2 library. This library is included for Windows and for
Ubuntu 18.04 LTS 32-bit and 64-bit. For other systems (such as ARM) you will need to build another
codec2. Just try the FDV mode and see if it works. It should always work on Windows, and may work
on Linux. If the FDV button is grayed out, you need a different codec2 than the one included. Make
a new codec2 library, and copy it to freedvpkg or to the correct system directory.
2020 mode
=========
You can now use 2020 mode with Quisk. Note however that 2020 mode requires a processor with AVX.
Also note a Raspberry PI (model 4B) does not have enough processing power even if neon is used
and it is overclocked.
There are two parts to this:
1. build LPCNet
2. rebuild codec2
The details are found here: https://github.com/drowe67/codec2#freedv-2020-support-building-with-lpcnet
Both liblpcnetfreedv and libcodec2 are needed. If you are running under windows, the resultant
liblpcnetfreedv.dll file needs to be placed in the quisk folder. Libcodec2 may be placed in the
quisk freedvpkg folder. Or install the DLL's in the correct system folders.
Search Order
============
Quisk will search for a valid codec2 library in this order on Windows:
1. The system codec2 library installed outside of Quisk by another program.
2. freedvpkg/libcodec2.dll. Not included. You can copy your own codec2 here but a system-wide installation is better.
3. freedvpkg/libcodec2_32.dll. The 32-bit codec2 shipped with Quisk.
4. freedvpkg/libcodec2_64.dll. The 64-bit codec2 shipped with Quisk.
Quisk will search for a valid codec2 library in this order on Linux:
1. The system codec2 library installed outside of Quisk by another program.
2. freedvpkg/libcodec2.so. Not included. You can copy your own codec2 here but a system-wide installation is better.
3. freedvpkg/libcodec2_32.so. The 32-bit codec2 shipped with Quisk.
4. freedvpkg/libcodec2_64.so. The 64-bit codec2 shipped with Quisk.
How to Build a New codec2
=========================
The source for codec2 is available on github. Or google for other sources or perhaps a pre-built library.
If you need to compile codec2 from source, first change to a suitable directory (not the Quisk directory)
and download the source with git:
git clone https://github.com/drowe67/codec2.git
Then build codec2 using the directions found at https://github.com/drowe67/codec2/blob/master/INSTALL.
The directions given below are current as of June 2020, but check for changes.
Then copy the codec2 library to the freedvpkg directory under Quisk.
Build a New codec2 on Linux
===========================
Create the codec2 shared library. This assumes a 64-bit linux. Change the directory
name from build_linux64 to build_linux32 for 32-bit linux. Note the "../".
cd codec2
mkdir build_linux64
cd build_linux64
cmake -DCMAKE_BUILD_TYPE=Release ../
make
cd src
cp libcodec2.so my-quisk-directory/freedvpkg
Build a New 32-bit codec2 on 64-bit Linux
=========================================
Make sure package libc6-dev-i386 is installed.
Create the codec2 shared library. Note the "../".
cd codec2
mkdir build_linux32
cd build_linux32
export CFLAGS=-m32
cmake -DCMAKE_BUILD_TYPE=Release ../
make
cd src
cp libcodec2.so my-quisk-directory/freedvpkg
Build a New codec2 on Windows
=============================
For Windows you need to install MinGW-w64, MSYS2, and g++. Use the mingw shell. The Speex libraries
are not needed by codec2, but are required for the Unit Test modules. To build the Unit Test modules,
you need to install Speex and add -DSPEEXDSP_INCLUDE_DIR=../speex/include/speex -DSPEEXDSP_LIBRARY=../speex/bin.
# Use mingw32.exe 32-bit shell:
cd codec2
mkdir build_win32
cd build_win32
cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF ../
make codec2
cd src
cp libcodec2.dll my-quisk-directory/freedvpkg
# Use mingw64.exe 64-bit shell:
cd codec2
mkdir build_win64
cd build_win64
cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF -DCMAKE_SYSTEM_PROCESSOR=x86_64 ../
make codec2
cd src
cp libcodec2.dll my-quisk-directory/freedvpkg
Testing
=======
You can just start Quisk and see if the FDV button is not grayed out, and FDV works. Or you can
test the import of freedv and look for error messages.
cd my-quisk-directory
python # Use the correct Python 2 or 3 in 32 or 64 bit
import _quisk
_quisk.freedv_get_version() # This should return 13 or higher for a recent codec2
+1
View File
@@ -0,0 +1 @@
#
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+527
View File
@@ -0,0 +1,527 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="description" content="How to Operate Quisk">
<meta name="author" content="James C. Ahlstrom">
<meta name="keywords" content="quisk, sdr, software defined radio, ham radio">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<style>
p {font-size:1.2rem;}
pre {font-size:1.2rem;}
ul {font-size:1.2rem;}
table {font-size:1.2rem;}
h1 {font-size:2.0rem;}
h2 {font-size:1.8rem;}
h3 {font-size:1.6rem;}
</style>
<body>
<h3>
QUISK Help
</h3>
<p>
<a href="http://james.ahlstrom.name/quisk/CHANGELOG.txt">The current CHANGELOG.txt is here</a>.
<br>
<br>
<a href="http://james.ahlstrom.name/quisk/docs.html">The documentation is here</a>.
<br>
<br>
</p>
<p>This is the Help file for Quisk, a Software Defined Radio (SDR).
Quisk was written and is maintained by Jim Ahlstrom, N2ADR,
www.james.ahlstrom.name. Mail to jahlstr at gmail.com. Quisk has been
greatly improved and extended by Leigh L. Klotz Jr. WA5ZNU, and by Andrew Nilsson VK6JBL.
Thanks to Terry Fox WB4JFI for code improvements, and for adding support
for the Charleston hardware. Thanks to Sid Boyce, G3VBV, for sending me SoftRock hardware to work
with. Thanks to Christof, DJ4CM, for many improvements to the GUI and for the Dx cluster display.
Thanks to Philip G. Lee for adding native support for PulseAudio, and to Eric Thornton for
adding PulseAudio async low-latency support. There are many other contributors who are
mentioned in the source code.
</p>
<p>
Quisk supports several radios, such as HiQSDR, SDR-IQ, Hermes-Lite,
SoftRock, etc. The Quisk software will read the samples from UDP or
your sound card data, tune it, filter it,
demodulate it, and send the audio to headphones or speakers.
Quisk can also be used as a panadapter, by sending the radio IF output to Quisk.
</p>
<p>
Quisk rhymes with "brisk", and is QSK plus a few letters to make it
easier to pronounce. QSK is a Q signal meaning full break in CW operation,
and Quisk has been designed for low latency. Quisk includes an input
keying signal that can mute the audio and substitute a side tone.
To install and configure Quisk, please see the docs.html file in the
Quisk directory.
</p>
<p>
Quisk is written in Python, an easy
to learn but powerful language; see www.python.org. Source is provided
because your own hardware is probably different from mine, and you will
need to change something. Changing Python is easy.
</p>
<h3 id="g0.0.2">Configuration</h3>
<p>
Quisk is highly configurable. Many aspects of Quisk such as which buttons
are displayed can be changed. You can even add additional buttons of your own. Quisk
was originally designed to be a general purpose component in anyone's homebrew
software defined radio. Besides the usual configuration settings,
Quisk uses a hardware control file and an optional widgets file.
Quisk comes with a variety of hardware control files for different radios, but
you can also write your own custom hardware file. You can create a custom
quisk_widgets.py file to add custom controls to the Quisk screen. I use custom
files for both. See the n2adr directory for a rather complicated example.
See docs.html for details.
</p>
<p>
There are three sources of configuration settings. When Quisk starts, it
first imports default configuration information from the file
quisk_conf_defaults.py. Then it reads your configuration file from either the standard
location, or a custom location given by the command line "-c" option. Your
configuration file should not be a copy of quisk_conf_defaults.py. It should be
a small file with only the items that are different. The use of a configuration file is optional.
</p>
<p>
Quisk then reads the settings you have made on the configuration screens. Most settings are
available here, so a configuration file will not be necessary for most users. To reach the
configuration screens, press the Config button; or for the small screen layout, the screen
selection master button followed by the Config button. The configuration screens are on the right side
and are called "radios" because you can save different settings for different radios. There
is a separate help screen describing the radio screens.
</p>
<h3 id="g0.0.3">QUISK Screens</h3>
<p>
Quisk can run with either the "Large screen" or "Small screen" layout. The large screen layout
is meant for PC screens, and all buttons are shown in four rows. The small screen layout is
meant for small touch screen devices, for those with sight impairment, or for those who run Quisk at
a small screen size. Most buttons are shown, but the band selection buttons, the mode buttons and the
screen selection buttons are hidden behind three master buttons on the left. Pressing one of the
master buttons pops up a row of choices.
</p>
<p>
The screen area can show a frequency graph, a waterfall display, an oscilloscope, the configuration
screens, a graph of the receive filter in use or this Help screen. The screen selection buttons are shown
when you use the large screen layout. Remember to press the master button the see the screen choices if you use the
small screen layout.
</p>
<p>
The Graph screen shows a frequency graph of received signals.
Use the scale "Ys" and zero "Yz" controls to adjust the graph scale.
You should see a noise trace that changes
randomly. Then press "Test 1". This generates a test
signal at 10 kHz, and you should see the spike on the graph. To tune
to that signal, click the mouse on the graph near the spike. Hold the
mouse button down and drag the red tuning line back and forth across
the test signal. You should head a pure audio tone in your speakers.
Use the "Vol" (Volume) slider on the left to change the volume.
</p>
<p>If you press Graph again, you will activate the peak hold functions
labeled "GraphP1" and "GraphP2". These will cause the graph to
follow the peak signal, and decay back down at a slow rate. You can
configure the time constants. Some buttons, like the Graph button, can be pressed
repeatedly to select different settings. These buttons have a circular arrow on the right.
</p>
<p>
The Waterfall shows a time history of the amplitude of received signals.
You will need to adjust "Ys" (Y scale) and "Yz" (Y zero) to get a
colored display. Press "Test 1" to turn the test signal
on and off. Watch it appear and disappear from the waterfall.
The top of the waterfall shows a small graph screen. You can grab the
bar between the screens with your mouse and move it up and down to
adjust the relative sizes.
To adjust the scale and zero of this graph, hold down the Shift key while
using the "Ys" and "Yz" sliders.
</p>
<p>
The oscilloscope and RX Filter screens are mostly used for debugging.
The Config screen shows a number of sub-screens that display status,
and provide for control of Quisk parameters. See below.
</p>
<h3 id="g0.0.3">QUISK Controls</h3>
<p>
Below the screen area there are one or two slider controls to the left that
control the radio sound volume, and optionally the CW sidetone volume.
Side tone only appears if you configure Quisk to operate as a CW transceiver, and you provide a key signal.
The four sliders to the right control RIT,
the graph and waterfall scale and zero points, and the zoom feature.
The Rit slider controls receiver incremental tuning, and is active when the RIT button is down.
It adds a small offset to the receive frequency. Leave RIT off for SSB unless
a station is off frequency a bit; then use RIT to tune him in while leaving
your transmit frequency unchanged. When you select CW, the RIT
must be turned on to provide an audio output, so Quisk automatically turns on RIT and sets it
to plus or minus the configured CW tone frequency. The audio side tone (if a hardware key
line is used) is set to the same. Just click CWL or CWU, tune the
frequency by clicking exactly on the signal, and everything will work.
The Ys and Yz sliders control the scale and zero of
the screen in view. For the Graph screen and the Rx Filter screen
they control the scale and zero of the Y axis. For the Waterfall
screen they control the Waterfall color scale; and if the Shift key is
down, they control the upper graph Y axis. For the Scope screen
the Ys slider controls the Y axis scale, and the Yz slider does
nothing. The sliders have no effect on other screens.
The Zo (zoom) slider expands the frequency scale (X axis) of the Graph
and Waterfall screens so that narrow signals can be examined.
Quisk operates normally when this slider is all the way down. As
it is raised, the frequency is expanded around the tuning
frequency. That is, the tuning frequency is moved to the center,
and the frequency scale is expanded. It is still possible to tune
Quisk as usual while this control is in use.
</p>
<p>
The frequency display always displays the transmit frequency. This is the frequency shown
by the red tuning line on the Graph and Waterfall screens. This equals the receive
frequency unless Split is used (see below).
You can change the transmit frequency by clicking
the top or bottom of the digits, or by rolling the mouse wheel over the digit.
The frequency display window will turn red to indicate sample capture overrun (ADC clipping).
The large screen layout has a frequency entry window. Enter a frequency in Hertz without a decimal point, or megahertz with one.
The up and down pointing arrows move the frequency up and down the band without changing the tuning. You can
right or left click them, or hold them down with the mouse to keep moving.
</p>
<p>
The S-meter displays the signal strength in S units and in dB. Zero dB is
clipping, the same as on the graph screen. The S-meter uses the specified
filter bandwidth to choose the exact number of FFT bins to square and average.
That is, it displays true RMS based on the FFT bins, not on the post-filter audio. Note
that for a noise floor on the graph of -110 dB the S-meter will read -93 dB (depending
on some details). That is
because the bandwidth specified is much greater than the FFT bin width, and more
noise is getting through. Find a flat noise frequency, change the filter bandwidth,
and watch your S-meter measure the noise. The conversion from S-units to dB depends
on your hardware. There are 6 dB per S-unit, and you can adjust to 50 microvolts for S9.
If the correction depends on the band, you can make a band-dependent
correction in your hardware file.
The S-meter window has a button to the
right to select what is shown; the S-meter, the frequency measurement or the RMS audio voltage.
Quisk can measure and display the frequency of a continuous tone RF
signal. To use this feature, press the S-meter button, and
select one of the Frequency items. The numbers are the averaging
times in seconds. Then find a signal of interest and put the
tuning line exactly on it. Quisk will search 500 Hertz up and
down from the tuning line and will display the frequency of the largest
signal. This feature works on AM signals
and continuous signals from oscillators, etc. It does not work
for SSB as there is no continuous signal. To calibrate your
hardware, measure the carrier from WWV
or other frequency standard, and change your clock rate until the
indicated frequency is correct.
Quisk can display the RMS audio voltage. This is used for noise measurements.
</p>
<h3 id="g0.0.3">QUISK Control Buttons</h3>
<p>
The Quisk buttons are the usual buttons you see on any radio. Some buttons have a cycle symbol
or a cycle button marked ↷ that means the button can cycle through a list of values. Some buttons have
a secondary button that can pop up a menu or a slider to make adjustments.
</p>
<p>
Mode: The mode buttons select CW, USB, AM, FM etc. This is a master button in the small screen layout.
The special mode IMD generates a two-tone test signal for transmitter testing. The "DGT" modes are
for external digital mode programs such as fldigi. See below.
<br><br>
Band: The band buttons select the band 40 meters, 20 meters etc. The bands shown can be configured.
This is a master button in the small screen layout.
<br><br>
Screen: The screen button selects the screen that is shown. See above.
This is a master button in the small screen layout.
<br><br>
Filters: The filter buttons select the bandwidth of the receive filters, 2700 Hertz, 6000 Hertz etc.
The last filter button has an adjustable bandwidth.
<br><br>
Band Up/Down: The up and down arrow buttons move up and down the band without changing the tuning frequency.
<br><br>
Mute: Mute (zero) the receive audio volume.
<br><br>
AGC: Automatic gain control is active when this button is either up or down, but the settings are different.
Adjust the AGC for both the up and down positions to control how much gain variation is allowed. When the
slider is at maximum (all the way up), all signals, even band noise,
will have the same maximum amplitude. For lower settings, loud
signals will have the maximum amplitude, but weaker signals will have
less amplitude. A medium setting allows you to hear the relative
amplitude of signals and any QSB while still protecting your
ears. I set the AGC On setting to a high value, and the AGC Off
setting to a lower value that allows band noise to be faintly heard.
<br><br>
Squelch: This turns off the audio when there are no signals in the pass band. For FM, adjust the level
on an empty channel. For SSB the adjustment for band noise is automatic, so start with a setting of 0.200.
<br><br>
NB: The noise blanker has several levels.
<br><br>
Notch: An automatic notch feature that can null out one or two continuous signals, such
as AM carriers that interfere with SSB reception.
<br><br>
RfGain: The Rf gain control if your hardware supports this.
<br><br>
Antenna: Antenna selection if your hardware supports this.
<br><br>
Spot: This transmits a constant CW signal for tuning. The level is adjustable. You might also need to press PTT
or assert your key line depending on your hardware.
<br><br>
Split: This splits the receive and transmit frequencies so they can be different. Two tuning lines
will appear; red for the transmit frequency and green for the receive frequency. This is useful for
working a DX station split. When you tune with the mouse, the closest tuning line is moved.
You can lock the transmit frequency. You can reverse the receive and transmit frequencies.
You can decode audio from both frequencies and play them on the left and right speakers.
<br><br>
FDX: Full duplex allows you to transmit and receive at the same time if your hardware allows this.
<br><br>
PTT: Push to talk; start transmitting.
<br><br>
VOX: Voice operated relay; turn on PTT when you speak into the microphone.
<br><br>
Test 1: Generate a test signal 10 kHz above the screen center frequency.
<br><br>
Memory buttons:
Quisk can remember and return to stations. When you have tuned in
a signal of interest, press the "Save" button Ⓜ↑ to save the frequency,
band and mode. Repeat for more
signals. Now press "Next" Ⓜ ➲,
to switch to the next saved signal, and press "Next" repeatedly to
cycle through the list. To delete a saved signal, first tune to
it with "Next" and then press "Delete" Ⓜ ☓ . If you save a large
number of signals, right click the "Next" button Ⓜ ➲, and you will get a
popup menu so you can jump directly to a station. The saved
stations will appear in the station window below the frequency X axis.
The saved stations can be on different bands.
<br><br>
Favorites button:
Quisk can save a list of favorite stations. Press
Config/Favorites to access the screen, and right-click the left label
to insert and delete stations, and to tune to them. The two
favorites buttons provide direct access to this screen. The
★ ↑ button enters the current station into the screen; just
provide a name. The ★ ↓ button jumps to the screen;
right click the left label and choose "Tune to". Favorite
stations will appear in the station window with a Star symbol ★ .
</p>
<h3 id="g0.0.4">Recording Sound and Samples</h3>
<p>
There are Record and Playback buttons to save and recall radio sound from a temporary buffer. Push
Record to start recording radio sound. The maximum time to record
can be configured, and after this limit
older sound is discarded. That is, the most recent sound is
retained up to some maximum time. Push Playback to play the sound. If you are
transmitting, the recorded sound is transmitted in place of mic
input. This sound is not speech processed, so it can be used to
give another station an accurate indication of how they sound.
To write this sound to a file, right-click the Playback button.
</p>
<p>
Quisk can also record the speaker audio, mic audio and the digital I/Q samples to WAV
files. Set the file names in the Config/Config screen. Enable the recording by checking
the box. Then Press
the "FileRecord" button to begin recording, and release it to stop.
If you press it again, it starts a new recording with a sequence number 001, 002, 003 etc.
Choose a directory (folder) to save these files because you will need to delete the ones you no longer need.
The speaker and mic audio are stored as 16-bit monophonic
samples at the audio play rate.
Both the audio play and mic rates should be 48 ksps.
The digital I/Q samples are stored as two
IEEE floating point samples at the sample rate.
It is possible to record all three sources at once, but this is not usually useful.
Normally you would record the mic to create a CQ message such as "CQ contest this is N2ADR".
You would record speaker audio to create a record of operations to review at a later time.
You would record IQ samples so you could record a slice of the band to tune in different
stations later.
Note that a WAV file has a maximum size of 4 gigabytes, but Quisk can record and play
files with an unlimited size.
</p>
<p>
Quisk can play the files it created, as well as other WAV files in the proper format.
Choose the file name of the source on the Config/Config screen, and check the box.
Then press "FilePlay" to begin playing and release it to stop.
Playing a file replaces the usual samples with the file samples, so it is necessary to
have working sound sources at the same sample rates as the recording.
Playing an audio file just plays the file on the speakers. When the file is finished,
normal speaker audio resumes. Playing a CQ message is similar, but will press the PTT button
as it plays. You can specify a repeat time to keep repeating the message. When a station
answers, press FilePlay or PTT to stop the message so you can answer.
</p>
<p>
To play an IQ sample file, first make sure the sample rate and VFO frequency are the same as
the recording. This will ensure the frequency readout is correct. It is a good idea to name files
with this information, such as "IQ192k7100.wav" for a file recorded at 192 ksps and a VFO (center)
frequency of 7100 kHz. Then press "FilePlay". The band samples will be replaced with the file samples,
and you will be able to tune around in the band and receive different stations. Do not press band up/down
as that will change the band center (VFO).
</p>
<h3 id="g0.0.4">Tuning in Stations</h3>
<p>
First select CW, USB etc. with the mode buttons. This is a master button on the small screen layout.
On the Graph or Waterfall screens, you tune in a CW signal by left-clicking
above the X axis directly on the signal. You tune in an SSB signal by clicking on the upper edge
(lower sideband) or the lower edge (upper sideband). That is, you always click
where the carrier goes. You can also click, hold down the
mouse button and drag the tuning line. The speed of tuning is lowest
close to the X axis, and increases as you move up. Try it.
If you hold the Shift key down when you click, the Rx filter will be centered at the frequency.
In this case, you would click in the center of an SSB signal.
</p>
<p>
If you click below the X axis, tuning will not jump to that frequency, but
you can still hold the button and drag. That is useful for small
adjustments. The mouse wheel will move the frequency up and down and round the
frequency to multiples of 50 Hertz.
</p>
<p>
If you right-click a signal, Quisk tunes to the signal as before, and also
changes the VFO to move the
signal to the center of the screen.
</p>
<h3 id="g0.0.6">Station Window and Dx Cluster </h3>
<p>
There is a station window below the frequency axis (X axis) to display
stations of interest. This feature was added by Christof,
DJ4CM. It consists of zero or more lines containing an M-Circle
symbol Ⓜ for memory stations, a Star symbol ★ for
favorite stations and a Dx symbol for Dx Cluster stations.
The default number of lines is one, but you can add more
lines if the display becomes too crowded. If you move your mouse
near the marked frequency, a popup window will appear with station
details. Left-click the symbol to tune to it.
There are Dx Cluster telnet servers available that provide real time
information on Dx station activity. You must configure the host name, the port
and your call sign to use this feature.
This feature will run continuously unless the host is the
null string. The Dx stations will appear in the station window as
they become available from the server.
</p>
<h3 id="g0.0.9">Configuration Screens </h3>
<p>
The Configuration screen shows a group of sub-screens that give status information,
and that adjust various Quisk control parameters.
The Status screen shows Quisk status information.
The Favorites screen allows you to enter frequencies and modes for
stations, nets, etc. To add a line, right-click the left label
and select Insert or Append. To tune to a station, select
Tune.
</p>
<p>
The Config screen controls features that apply to all radios. There is a band plan button
to control the colors shown for the CW and phone segmants of each band.
The Config screen can pop up an
amplitude and phase correction window for SoftRock and similar receivers.
If you use the sound card for input, you may need to correct for small errors
in the I and Q amplitude and phase. First change to the correct band, because
corrections are saved for each band. Press the button on the config
screen to bring up a correction screen. Then feed a test signal to your hardware
(or use a strong available signal) and look at the signal image. Adjust the
slider controls to reduce the image. The upper slider is the fine adjustment,
and the lower is the coarse. You will need to adjust both amplitude and phase
as they will interact. The amount of available adjustment range
can be configured. When
you have a final correction, it is a good idea to write it down.
The correction point is saved based on the VFO frequency, and you will
probably need two or three correction points per band.
</p>
<p>
The Tx Audio screen controls the transmit audio, and
enables you to record and play it back for test purposes.
To have good transmit audio, you must start start with a clean
audio source. You can plug in a USB mic or headset as a
source. Or you can connect an analog mic to the mic input of your
sound card. Or you can connect your mic to a preamp, and then to
the line input of your sound card. The mic should be directly in
front of your mouth when you speak. Try to avoid headsets that
cause you to speak across the mic. The idea is to make your voice
as loud as possible relative to the background noise in your shack.
</p>
<p>
You need to adjust the mic sound level as high as possible, but without
too much clipping. Speak normally and try to get a level a few dB below
zero (clipping). The peak level should be at least -20 dB, and
preferably above -10 dB. Infrequent clipping (above 0 dB) is OK,
because Quisk will clip the audio anyway when it processes it. It
may be difficult to figure out how to adjust the mic level. For
Linux, figure out the correct control number and use mixer_settings
or use one of the Linux mixer apps.
For Windows, use the level
control on the audio control screen in Control Panel, but be careful
that another application does not change it after you set it for
Quisk. Quisk will attempt to adjust the audio level with its AGC,
but it helps to start with a good level.
</p>
<p>
Refer to the "Tx Audio" tab. Clip is the amount of audio clipping
from zero to 20 dB. There is actually some clipping at zero dB
due to the mic AGC. Preemphasis boosts the high frequencies in
your voice. Zero is no boost, and 1.0 is 6 dB per octave.
Use the record and playback buttons to test for the best control
settings. Notice that your voice will become louder with more
clipping. Note that SSB, AM, FM and FDV each have their own settings,
so change to the correct mode before you start. Audio processing
is most useful for SSB, so if you are a DX enthusiast, use aggressive
settings. I only use AM for rag chewing, so I use zero
preemphasis and 4 dB clipping. For FM, I use zero clipping and
preemphasis.
<h3 id="g0.0.11">Digital Modes DGT-* </h3>
<p>
These modes are used for digital signals, and require an external
program such as Fldigi or WSJT-X to decode the signals.
First, connect your digital program
to quisk using Hamlib or XML-RPC so that frequency changes are recognized.
See "Hamlib Control" below. Next you need a way to transfer digital samples between the programs.
For Windows, install a Virtual Audio Cable (VAC) and connect Quisk to one end and the
digital program to the other. For Linux, Quisk can create a VAC itself by using PulseAudio.
Digital data is only sent when one of the "DGT-" modes is in use.
See the <a href="http://james.ahlstrom.name/quisk/docs.html">documentation</a> for more information.</p>
<h3 id="g0.0.12">Hamlib Control </h3>
<p>
Most digital and logging programs use Hamlib to control
a rig so that frequency changes in one program are recognized in the other.
Quisk has three options for external control: Hamlib, XML-RPC and a serial port emulating the "Flex" protocol.
See the <a href="http://james.ahlstrom.name/quisk/docs.html">documentation</a> for more information.</p>
</p>
<h3 id="g0.0.13">Multiple Receivers </h3>
<p>
Quisk can tune in two frequencies in the same band by using the "Split" button.
This is meant for working DX split, but it can be used anywhere in the band.
But if you want to receive in two different bands at once, you need SDR hardware that
can support multiple sub-receivers. The Hermes-Lite and other Hermes hardware can do this.
Press the "Add Rx" button to add additional receivers. You will get multiple receiver
screens with their own control buttons and menus. The main buttons in Quisk still control
the main receiver and transmitter. The menu button on the "Add Rx" button controls where
the sub-receiver sound goes. When a sub-receiver "Play" button is pressed, you can play the sound
alone, or together with the main receiver sound. There is an additional device on the sound screen
that can direct the sound from sub-receiver 1 to an external digital program.
There is a limit to how many sub-receivers your hardware can support. Quisk may not know this
limit, so please do not request more sub-receivers than actually exist.
</p>
<br><br>
</body>
</html>
+144
View File
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="description" content="Documentation for Quisk VNA"><meta name="author" content="James C. Ahlstrom"><meta name="keywords" content="quisk, vna, vector network analyzer, ham radio">
<title>QUISK Help File</title>
</head><body>
<h3>
Quisk VNA Help (December 2016)
</h3>
<p>This is the Help file for Quisk VNA, a program that turns the
Quisk2010 and HiQSDR transceiver and the Hermes-Lite transceiver into a Vector Network Analyzer
(VNA). This Help appears when you press the Help button. Quisk is written by Jim
Ahlstrom, N2ADR,
www.james.ahlstrom.name. Mail to jahlstr at gmail.com. To run the
Quisk VNA program, use "python quisk_vna.py" or set up a
shortcut. This program only works with hardware that is based
on UDP. It does not work with SoftRock hardware.
</p>
<p>For HiQSDR you need to update your firmware to Version 1.3 or later.
For Hermes-Lite update to 32 or later. The
new firmware locks the phase of the RF output to the phase of the RF
detector. There is a time delay in the path, but this is removed
by the calibration procedure. The calibration graphs will show a linear phase change with frequency due to this delay.
</p>
<p>There are two ways to use your hardware. You could connect the
RF output through attenuators, through a device under test and then
back to the RF input. This is called transmission mode. It is
used to plot the response of filters and to measure the electrical
length of cables. Or you could connect the RF input and output through
attenuators to
a resistive return loss bridge. This is called reflection mode. It is
used to measure SWR and impedance. You must choose a mode and
calibrate for that mode and hardware before you take any data.
The calibration is saved and will be restored the next time the
program starts. This is convenient when making repeated measurements,
but you should calibrate frequently for best accuracy. Hermes-Lite requires a power
on/off and a calibration before taking data, but the calibration is good until it is powered down.
</p>
<p>
The hardware generates RF output only when one of the "Calibrate"
buttons or the "Run" button is turned on.
The calibration routines save data every 15 kHz from zero
to sixty megahertz (thirty for Hermes-Lite). You can change the measurement frequency span
at any time without recalibration. Although you can set a
frequency span up to sixty (thirty) megahertz, my original hardware has a low
pass filter cutoff of 35 megahertz, so the upper frequency range is
smaller and will appear noisy.
Since the transmit and receive frequencies are equal, the data is at
DC, and is averaged and effectively low pass filtered. This
provides immunity from interference when measuring an antenna.
<h3 id="g0.0.1">Input Protection </h3>
Do not connect the RF output to the RF input without inserting
attenuators. The output will overload the input and result in
clipping in the ADC and possible damage. If your device under
test is an amplifier be especially careful to add additional
attenuation to avoid damage. Add enough attenuation to avoid
clipping, but not so much that you lose dynamic range. The
calibration screens show the ADC level. These attenuators also
help to stabilize the input and output impedance and increase
accuracy. I use the HAT series of attenuators from Mini-Circuits.
<h3 id="g0.0.2">Transmission Mode
</h3>
You must perform a calibration before you can take data. First
set the mode to "Transmission" and leave it there. Connect attenuators
and a cable between the RF input and output. Press the "Calibrate.." button.
With the cable connected press "Short". The "Short" calibration is
required for transmission mode. For the optional "Open" calibration, disconnect
the cable and press "Open". Then press "Calibrate" to save the calibration.
Connect the cable and press "Run" and
you should see a flat line at zero dB level and zero phase. Now
insert your test device in series. The test device could be a
filter, an amplifier (be careful) or an additional length of
cable. Press "Run" to take data.
<h3 id="g0.0.3">Reflection Mode
</h3>
<p>
This mode is used with a resistive return loss bridge.
Connect the RF
output to the generator terminal, and the RF input to the detector
terminal. Use attenuators on both. You must
perform a calibration before you can take data.
First set the
mode to "Reflection" and leave it there. Press the "Calibrate.." button.
Connect an open circuit (or nothing) to
the impedance
terminal of the bridge, and then press "Open". Connect a
short circuit to the bridge, and then press "Short". Connect
a 50 ohm termination to the bridge and then press "Load".
Then press "Calibrate" to save the calibration.
If you do not have a set of Open/Short/Load standards, you can just
use "Open" alone, but it is highly
recommended to use Open, Short and Load.
</p>
<p>
Now connect a 50 ohm termination to the bridge, and
press "Run". The graph will show the magnitude and phase of the
reflection coefficient, and the return loss is the drop in magnitude
below zero dB. Ideally your bridge will have a directivity of 30
dB or more. The phase may be noisy if the magnitude is very
small. Now connect an
unknown impedance to the bridge; for example, an antenna. The
graph will plot the return loss, reflection coefficient and SWR.
The status line will display the frequency, the reflection coefficient and SWR, the impedance, the equivalent
capacitance or inductance for that impedance, and the values for the parallel equivalent circuit.
</p>
<p>
You can attach any impedance, such as an unknown capacitor or inductor,
and read the value directly. The value may seem to vary with
frequency due to stray inductance, variation of permeability with
frequency, and bridge imperfections; so choose a reference frequency
wisely. Remember that the bridge measures the impedance relative
to fifty ohms, so accuracy suffers if the impedance is outside the
range of 5 to 500 ohms or so.
</p>
<h3 id="g0.0.4">Fun</h3>
<p>
In transmission mode, add an extra length of cable and see the phase
change. When the phase change is ninety degrees, that is a
quarter wave. The effect of velocity factor is included, and can
be measured. Use a bare wire (with attenuators) as the test
fixture, and then add a ferrite bead to the wire to measure its
properties. Insert a filter to see its response.
</p>
<p>
In reflection mode, measure your antenna from zero to sixty (thirty)
megahertz. If it is a dipole, you will see the drop in SWR at its fundamental and
third harmonic. Add a cable to the bridge to see its impedance at
a quarter and half wave length. Measure the length of your
transmission line by replacing your antenna with a 100 ohm
resister. The bridge will read 100 ohms at multiples of a half
wavelength. If you short out your antenna at the far end of your
transmission line, the bridge will read zero at half wavelengths, and
infinity at quarter wavelengths.
</p>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
This directory has files for the Hermes-Lite project versions 1 and 2.
Please perform all configuration with the Config screens in Quisk.
To start, go to Config/Radios and add a new radio with the general type "Hermes".
Quisk fully supports the Hermes-Lite project. But you may need a custom Widget or
Hardware file for special purposes. If you create these custom files, enter their
location (path) on the Config/radio/Hardware screen. Do not modify the files supplied
by Quisk, as these will be replaced with each new release.
# This is an example of a custom Widgets file. It changes the power calculations
# to that required by an external power bridge. Any methods (functions) defined here are used
# instead of the usual versions. See the original quisk_widgets.py to see what is available
# for replacement.
from hermes.quisk_widgets import BottomWidgets as BaseWidgets
class BottomWidgets(BaseWidgets): # Add extra widgets to the bottom of the screen
# This replaces the default version. You must alter the code to calculate watts for your
# external power meter that is connected to the Hermes-Lite power ADC.
def Code2FwdRevWatts(self): # Convert the HermesLite fwd/rev power code to watts forward and reverse
# volts = m * code + b # The N2ADR power circuit is linear in voltage
# power = (m**2 * code**2 + 2 * b * m * code + b**2) / 50
fwd = self.hardware.hermes_fwd_power # forward and reverse binary code
rev = self.hardware.hermes_rev_power
Vfwd = 3.26 * fwd / 4096.0 # forward and reverse volts
Vrev = 3.26 * rev / 4096.0
Pfwd = 2.493 * Vfwd**2 + 0.1165 * Vfwd # conversion from volts to power in watts
Prev = 2.493 * Vrev**2 + 0.1165 * Vrev
return Pfwd, Prev # return forward and reverse power in watts
# This is an example of a custom Hardware file:
from hermes.quisk_hardware import Hardware as BaseHardware
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.usingSpot = False # Use bit C2[7] as the Spot indicator
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
# The call to BaseHardware will set C2 according to the Hermes_BandDict{}
ret = BaseHardware.ChangeBand(self, band)
if self.usingSpot:
byte = self.GetControlByte(0, 2) # C0 index == 0, C2: user output
byte |= 0b10000000
self.SetControlByte(0, 2, byte)
return ret
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
ret = BaseHardware.OnSpot(self, level)
if level >= 0 and not self.usingSpot: # Spot was turned on
byte = self.GetControlByte(0, 2)
byte |= 0b10000000
self.SetControlByte(0, 2, byte)
self.usingSpot = True
elif level < 0 and self.usingSpot: # Spot was turned off
byte = self.GetControlByte(0, 2)
byte &= 0b01111111
self.SetControlByte(0, 2, byte)
self.usingSpot = False
return ret
+1
View File
@@ -0,0 +1 @@
#
+1
View File
@@ -0,0 +1 @@
# Configuration files are no longer used. Please use the radio Config screens instead.
+1
View File
@@ -0,0 +1 @@
# Configuration files are no longer used. Please use the radio Config screens instead.
File diff suppressed because it is too large Load Diff
+203
View File
@@ -0,0 +1,203 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import math, wx, time
from configure import ComboCtrl
class BottomWidgets: # Add extra widgets to the bottom of the screen
REG_ANTENNA_TUNER = 7
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
self.start_row = app.widget_row # The first available row
self.start_col = app.button_start_col # The start of the button columns
self.tuner_time = 0
self.Widgets_0x06(app, hardware, conf, frame, gbs, vertBox)
def Widgets_0x06(self, app, hardware, conf, frame, gbs, vertBox):
self.num_rows_added = 1
start_row = self.start_row
self.atu_ctrl = ComboCtrl(frame, "ATU", ["Tune", "Bypass"], True)
gbs.Add(self.atu_ctrl, (start_row, self.start_col), (1, 2), flag=wx.EXPAND)
bw, bh = self.atu_ctrl.GetMinSize()
frame.Bind(wx.EVT_COMBOBOX_CLOSEUP, self.OnAtu)
init = app.hermes_LNA_dB
self.sliderLNA = app.SliderBoxHH(frame, 'RfLna %d dB', init, -12, 48, self.OnLNA, True)
self.sliderLNA.idName = "RfLna"
app.midiControls["RfLna"] = (self.sliderLNA, self.OnLNA)
hardware.ChangeLNA(init)
gbs.Add(self.sliderLNA, (start_row, self.start_col + 2), (1, 8), flag=wx.EXPAND)
if conf.button_layout == "Small screen":
# Display four data items in a single window
self.text_temperature = app.QuiskText1(frame, '', bh)
self.text_pa_current = app.QuiskText1(frame, '', bh)
self.text_fwd_power = app.QuiskText1(frame, '', bh)
self.text_swr = app.QuiskText1(frame, '', bh)
self.text_data = self.text_temperature
self.text_pa_current.Hide()
self.text_fwd_power.Hide()
self.text_swr.Hide()
b = app.QuiskPushbutton(frame, self.OnTextDataMenu, '..')
szr = self.data_sizer = wx.BoxSizer(wx.HORIZONTAL)
szr.Add(self.text_data, 1, flag=wx.ALIGN_CENTER_VERTICAL)
szr.Add(b, 0, flag=wx.ALIGN_CENTER_VERTICAL)
gbs.Add(szr, (start_row, self.start_col + 10), (1, 2), flag=wx.EXPAND)
# Make a popup menu for the data window
self.text_data_menu = wx.Menu()
item = self.text_data_menu.Append(-1, 'Temperature')
app.Bind(wx.EVT_MENU, self.OnDataTemperature, item)
item = self.text_data_menu.Append(-1, 'PA Current')
app.Bind(wx.EVT_MENU, self.OnDataPaCurrent, item)
item = self.text_data_menu.Append(-1, 'Fwd Power')
app.Bind(wx.EVT_MENU, self.OnDataFwdPower, item)
item = self.text_data_menu.Append(-1, 'SWR')
app.Bind(wx.EVT_MENU, self.OnDataSwr, item)
else:
szr = wx.BoxSizer(wx.HORIZONTAL)
gbs.Add(szr, (start_row, self.start_col + 10), (1, 18), flag=wx.EXPAND)
text_temperature = wx.StaticText(frame, -1, ' Temp 100DC XX', style=wx.ST_NO_AUTORESIZE)
size = text_temperature.GetBestSize()
text_temperature.Destroy()
self.text_temperature = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_pa_current = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_fwd_power = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_swr = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL
szr.Add(self.text_temperature, 0, flag=flag)
szr.Add(self.text_pa_current, 0, flag=flag)
szr.Add(self.text_fwd_power, 0, flag=flag)
szr.Add(self.text_swr, 0, flag=flag)
def OnAtu(self, event):
if not self.hardware.io_board.have_IO_Board:
self.atu_ctrl.SetText("No ATU")
self.tuner_time = 0
elif self.atu_ctrl.GetValue() == "Tune":
self.atu_ctrl.SetText("Tuning")
self.tuner_time = time.time()
self.hardware.WriteI2C(0x7d, 0x1D, self.REG_ANTENNA_TUNER, 1)
else:
self.atu_ctrl.SetText("Bypass")
self.tuner_time = 0
self.hardware.WriteI2C(0x7d, 0x1D, self.REG_ANTENNA_TUNER, 2)
if self.application.spotButton.GetValue():
self.application.spotButton.SetValue(False, True)
self.sliderLNA.SetFocus()
def OnLNA(self, event=None):
value = self.sliderLNA.GetValue()
self.hardware.ChangeLNA(value)
self.application.hermes_LNA_dB = value
def Code2Temp(self): # Convert the HermesLite temperature code to the temperature
temp = self.hardware.hermes_temperature
# For best accuracy, 3.26 should be a user's measured 3.3V supply voltage.
temp = (3.26 * (temp/4096.0) - 0.5)/0.01
return temp
def Code2Current(self): # Convert the HermesLite PA current code to amps
current = self.hardware.hermes_pa_current
# 3.26 Ref voltage
# 4096 steps in ADC
# Gain of x50 for sense amp
# Sense resistor is 0.04 Ohms
current = ((3.26 * (current/4096.0))/50.0)/0.04
# Scale by resistor voltage divider 1000/(1000+270) at input of slow ADC
current = current / (1000.0/1270.0)
return current
def Code2FwdRevWatts(self, fwd, rev): # Convert the HermesLite fwd/rev power code to watts forward and reverse
#print (self.hardware.hermes_rev_power, self.hardware.hermes_fwd_power)
fwd = self.hardware.InterpolatePower(fwd)
rev = self.hardware.InterpolatePower(rev)
# Which voltage is forward and reverse depends on the polarity of the current sense transformer
if fwd >= rev:
return fwd, rev
else:
return rev, fwd
def UpdateText(self):
# Temperature
temp = self.Code2Temp()
temp = (" Temp %3.0f" % temp) + u'\u2103'
self.text_temperature.SetLabel(temp)
# power amp current
current = self.Code2Current()
current = " PA %4.0f ma" % (1000*current)
self.text_pa_current.SetLabel(current)
# forward and reverse peak power
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_peak, self.hardware.hermes_rev_peak)
# forward less reverse power
power = fwd - rev
if power < 0.0:
power = 0.0
text = " PEP %3.1f watts" % power
self.text_fwd_power.SetLabel(text)
# SWR based on average power
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_power, self.hardware.hermes_rev_power)
if fwd >= 0.05:
gamma = math.sqrt(rev / fwd)
if gamma < 0.98:
swr = (1.0 + gamma) / (1.0 - gamma)
else:
swr = 99.0
if swr < 9.95:
text = " SWR %4.2f" % swr
else:
text = " SWR %4.0f" % swr
else:
text = " SWR ---"
self.text_swr.SetLabel(text)
if self.tuner_time > 0:
ret = self.hardware.io_board.Receive(self.REG_ANTENNA_TUNER)
if ret:
code = ret[0]
if code == 0:
t = "ATU OK"
self.tuner_time = 0
elif code == 0xEE:
t = "ATU RF"
elif code >= 0xF0:
t = "Err 0x%X" % code
self.tuner_time = 0
else:
t = "ATU 0x%X" % code
self.atu_ctrl.SetText(t)
if code == 0xEE:
if not self.application.spotButton.GetValue():
self.application.spotButton.SetValue(True, True)
elif self.application.spotButton.GetValue():
self.application.spotButton.SetValue(False, True)
else:
code = -1
if code != 0 and self.tuner_time > 0 and time.time() - self.tuner_time > 20:
self.tuner_time = 0
self.atu_ctrl.SetText("ATU Err")
def OnTextDataMenu(self, event):
btn = event.GetEventObject()
btn.PopupMenu(self.text_data_menu, (0,0))
def OnDataTemperature(self, event):
self.data_sizer.Replace(self.text_data, self.text_temperature)
self.text_data.Hide()
self.text_data = self.text_temperature
self.text_data.Show()
self.data_sizer.Layout()
def OnDataPaCurrent(self, event):
self.data_sizer.Replace(self.text_data, self.text_pa_current)
self.text_data.Hide()
self.text_data = self.text_pa_current
self.text_data.Show()
self.data_sizer.Layout()
def OnDataFwdPower(self, event):
self.data_sizer.Replace(self.text_data, self.text_fwd_power)
self.text_data.Hide()
self.text_data = self.text_fwd_power
self.text_data.Show()
self.data_sizer.Layout()
def OnDataSwr(self, event):
self.data_sizer.Replace(self.text_data, self.text_swr)
self.text_data.Hide()
self.text_data = self.text_swr
self.text_data.Show()
self.data_sizer.Layout()
+1
View File
@@ -0,0 +1 @@
#
+33
View File
@@ -0,0 +1,33 @@
# This is a sample config file for the N2ADR 2010 transceiver hardware and for the
# improved version HiQSDR. If you use the HiQSDR you should upgrade your firmware
# to version 1.1.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from hiqsdr import quisk_hardware
add_imd_button = 1
add_fdx_button = 1
latency_millisecs = 50
# tx_level = {None:120, '60':52} # Use this to change your transmit level.
#use_rx_udp = 1 # Use this for the N2ADR 2010 hardware
use_rx_udp = 2 # Use this for the HiQSDR
rx_udp_ip = "192.168.2.196" # Sample source IP address
rx_udp_port = 0xBC77 # Sample source UDP port
rx_udp_clock = 122880000 # ADC sample rate in Hertz
rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
name_of_sound_capt = "" # We do not capture from the soundcard
name_of_sound_play = "hw:0"
data_poll_usec = 10000
playback_rate = 48000
microphone_name = "hw:1"
tx_ip = rx_udp_ip
key_method = "" # Use internal method
tx_audio_port = 0xBC79
mic_out_volume = 1.0
+426
View File
@@ -0,0 +1,426 @@
# This is a sample hardware file for UDP control. Use this file for my 2010 transceiver
# described in QEX and for the improved version HiQSDR. To turn on the extended
# features in HiQSDR, update your FPGA firmware to version 1.1 or later and use use_rx_udp = 2.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import struct, socket, math, traceback
import _quisk as QS
from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 0
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.got_udp_status = '' # status from UDP receiver
# want_udp_status is a 14-byte string with numbers in little-endian order:
# [0:2] 'St'
# [2:6] Rx tune phase
# [6:10] Tx tune phase
# [10] Tx output level 0 to 255
# [11] Tx control bits:
# 0x01 Enable CW transmit
# 0x02 Enable all other transmit
# 0x04 Use the HiQSDR extended IO pins not present in the 2010 QEX ver 1.0
# 0x08 The key is down (software key)
# bits 5 and 4: Transmit sample rate
# 0b00 48k
# 0b01 192k
# 0b10 480k
# 0b11 8k
# 0x40 odyssey: Spot button is in use
# 0x80 odyssey: Mic Boost 20dB
# [12] Rx control bits
# bits 5 through 0
# Second stage decimation less one, 1-39, six bits
# bits 7, 6
# 0b00 Prescaler 8, 3-byte samples I and Q; 1440 / 6 = 240 samples per UDP packet
# 0b01 Prescaler 2, 2-byte samples
# 0b10 Prescaler 40, 3-byte samples
# 0b11 Prescaler 2, 1-byte samples
# [13] zero or firmware version number
# The above is used for firmware version 1.0.
# Version 1.1 adds eight more bytes for the HiQSDR conntrol ports:
# [14] X1 connector: Preselect pins 69, 68, 65, 64; Preamp pin 63, Tx LED pin 57
# [15] Attenuator pins 84, 83, 82, 81, 80
# [16] More bits: AntSwitch pin 41 is 0x01
# [17:22] The remaining five bytes are sent as zero.
# Version 1.2 uses the same format as 1.1, but adds the "Qs" command (see below).
# Version 1.3 adds features needed by the new quisk_vna.py program:
# [17] The sidetone volume 0 to 255
# [18:20] This is vna_count, the number of VNA data points; or zero for normal operation
# [20] The CW delay as specified in the config file
# [21] Control bits:
# 0x01 Switch on tx mirror on rx for adaptive predistortion
# [22:24] Noise blanker level
# The "Qs" command is a two-byte UDP packet sent to the control port. It returns the hardware status
# as the above string, except that the string starts with "Qs" instead of "St". Do not send the "Qs" command
# from Quisk, as it interferes with the "St" command. The "Qs" command is meant to be used from an
# external program, such as HamLib or a logging program.
# When vna_count != 0, we are in VNA mode. The start frequency is rx_phase, and for each point tx_phase is added
# to advance the frequency. A zero sample is added to mark the blocks. The samples are I and Q averaged at DC.
self.rx_phase = 0
self.tx_phase = 0
self.tx_level = 0
self.tx_control = 0
self.rx_control = 0
QS.set_sample_bytes(3)
self.vna_count = 0 # VNA scan count; MUST be zero for non-VNA operation
self.cw_delay = conf.cw_delay
self.index = 0
self.mode = None
self.usingSpot = False
self.band = None
self.rf_gain = 0
self.sidetone_volume = 0 # sidetone volume 0 to 255
self.repeater_freq = None # original repeater output frequency
self.HiQSDR_Connector_X1 = 0
self.HiQSDR_Attenuator = 0
self.HiQSDR_Bits = 0
try:
if conf.radio_sound_mic_boost:
self.tx_control = 0x80
except:
pass
if conf.use_rx_udp == 2: # Set to 2 for the HiQSDR
self.rf_gain_labels = ('RF 0 dB', 'RF +10', 'RF -10', 'RF -20', 'RF -30')
self.antenna_labels = ('Ant 1', 'Ant 2')
self.firmware_version = None # firmware version is initially unknown
self.rx_udp_socket = None
self.vfo_frequency = 0 # current vfo frequency
self.tx_frequency = 0
self.decimations = [] # supported decimation rates
for dec in (40, 20, 10, 8, 5, 4, 2):
self.decimations.append(dec * 64)
self.decimations.append(80)
self.decimations.append(64)
if self.conf.fft_size_multiplier == 0:
self.conf.fft_size_multiplier = 6 # Set size needed by VarDecim
def open(self):
# Create the proper broadcast address for rx_udp_ip.
nm = self.conf.rx_udp_ip_netmask.split('.')
ip = self.conf.rx_udp_ip.split('.')
nm = list(map(int, nm))
ip = list(map(int, ip))
bc = ''
for i in range(4):
x = (ip[i] | ~ nm[i]) & 0xFF
bc = bc + str(x) + '.'
self.broadcast_addr = bc[:-1]
# This socket is used for the Simple Network Discovery Protocol by AE4JY
self.socket_sndp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket_sndp.setblocking(0)
self.socket_sndp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.sndp_request = bytearray(56)
self.sndp_request[0] = 56
self.sndp_request[1] = 0
self.sndp_request[2] = 0x5A
self.sndp_request[3] = 0xA5
self.sndp_active = self.conf.sndp_active
# conf.rx_udp_port is used for returning ADC samples
# conf.rx_udp_port + 1 is used for control
self.rx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rx_udp_socket.setblocking(0)
self.rx_udp_socket.connect((self.conf.rx_udp_ip, self.conf.rx_udp_port + 1))
return QS.open_rx_udp(self.conf.rx_udp_ip, self.conf.rx_udp_port)
def close(self):
if self.rx_udp_socket:
self.rx_udp_socket.close()
self.rx_udp_socket = None
def ReturnFrequency(self): # Return the current tuning and VFO frequency
return None, None # frequencies have not changed
def ReturnVfoFloat(self, freq=None): # Return the accurate VFO as a float
if freq is None:
rx_phase = self.rx_phase
else:
rx_phase = int(float(freq) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
return float(rx_phase) * self.conf.rx_udp_clock / 2.0**32
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
if vfo_freq != self.vfo_frequency:
self.vfo_frequency = vfo_freq
self.rx_phase = int(float(vfo_freq - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
if tx_freq and tx_freq > 0:
self.tx_frequency = tx_freq
self.tx_phase = int(float(tx_freq - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.NewUdpStatus()
return tx_freq, vfo_freq
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
if offset is None: # Return True if frequency change is complete
self.HeartBeat()
return self.want_udp_status == self.got_udp_status
if offset == 0: # Change back to the original frequency
if self.repeater_freq is None: # Frequency was already reset
return self.want_udp_status == self.got_udp_status
self.tx_frequency = self.repeater_freq
self.repeater_freq = None
else: # Shift to repeater input frequency
self.repeater_freq = self.tx_frequency
offset = int(offset * 1000) # Convert kHz to Hz
self.tx_frequency += offset
self.tx_phase = int(float(self.tx_frequency - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.NewUdpStatus(True)
return False
def ChangeMode(self, mode):
# mode is a string: "USB", "AM", etc.
self.mode = mode
self.tx_control &= ~0x03 # Erase last two bits
if self.vna_count:
pass
elif self.usingSpot:
self.tx_control |= 0x02
elif mode in ("CWL", "CWU"):
self.tx_control |= 0x01
else:
self.tx_control |= 0x02
self.SetTxLevel()
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
BaseHardware.ChangeBand(self, band)
self.band = band
self.HiQSDR_Connector_X1 &= ~0x0F # Mask in the last four bits
self.HiQSDR_Connector_X1 |= self.conf.HiQSDR_BandDict.get(band, 0) & 0x0F
self.SetTxLevel()
def SetTxLevel(self):
# As tx_level varies from 50 to 200, the output level changes from 263 to 752 mV
# So 0 to 255 is 100 to 931, or 1.0 to 9.31; v = 1.0 + 0.0326 * level
if not self.vna_count:
try:
self.tx_level = self.conf.tx_level[self.band]
except KeyError:
self.tx_level = self.conf.tx_level.get(None, 127) # The default
if self.mode[0:3] in ('DGT', 'FDV'): # Digital modes; change power by a percentage
reduc = self.application.digital_tx_level
else:
reduc = self.application.tx_level
level = 1.0 + self.tx_level * 0.0326
level *= math.sqrt(reduc / 100.0) # Convert from a power to an amplitude
self.tx_level = int((level - 1.0) / 0.0326 + 0.5)
if self.tx_level < 0:
self.tx_level = 0
elif self.tx_level > 255:
self.tx_level = 255
self.NewUdpStatus()
def OnButtonRfGain(self, event):
# The HiQSDR attenuator is five bits: 2, 4, 8, 10, 20 dB
btn = event.GetEventObject()
n = btn.index
self.HiQSDR_Connector_X1 &= ~0x10 # Mask in the preamp bit
if n == 0: # 0dB
self.HiQSDR_Attenuator = 0
self.rf_gain = 0
elif n == 1: # +10
self.HiQSDR_Attenuator = 0
self.HiQSDR_Connector_X1 |= 0x10
self.rf_gain = 10
elif n == 2: # -10
self.HiQSDR_Attenuator = 0x08
self.rf_gain = -10
elif n == 3: # -20
self.HiQSDR_Attenuator = 0x10
self.rf_gain = -20
elif n == 4: # -30
self.HiQSDR_Attenuator = 0x18
self.rf_gain = -30
else:
self.HiQSDR_Attenuator = 0
self.rf_gain = 0
print ('Unknown RfGain')
self.NewUdpStatus()
def OnButtonPTT(self, event):
# This feature requires firmware version 1.1 or higher
if self.firmware_version:
btn = event.GetEventObject()
if btn.GetValue(): # Turn the software key bit on or off
self.tx_control |= 0x08
else:
self.tx_control &= ~0x08
self.NewUdpStatus(True) # Prompt update for PTT
def OnButtonAntenna(self, event):
# This feature requires extended IO
btn = event.GetEventObject()
if btn.index:
self.HiQSDR_Bits |= 0x01
else:
self.HiQSDR_Bits &= ~0x01
self.NewUdpStatus()
def ChangeSidetone(self, value): # The sidetone volume changed
self.sidetone_volume = int(value * 255.1) # Change 0.0-1.0 to 0-255
self.NewUdpStatus()
def HeartBeat(self):
if self.sndp_active: # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address
try:
if DEBUG: print("Sndp send")
self.socket_sndp.sendto(self.sndp_request, (self.broadcast_addr, 48321))
data, ffrom = self.socket_sndp.recvfrom(1024)
if DEBUG: print("Sndp From", ffrom, "Data", repr(data))
except:
# traceback.print_exc()
pass
else:
data = bytearray(data)
if len(data) == 56 and data[5:14] == bytearray(b'HiQSDR-v1'):
ip = self.conf.rx_udp_ip.split('.')
t = data[0:4]
t.append(2)
t += data[5:37]
t.append(int(ip[3]))
t.append(int(ip[2]))
t.append(int(ip[1]))
t.append(int(ip[0]))
t += bytearray(12)
t.append(self.conf.rx_udp_port & 0xFF)
t.append(self.conf.rx_udp_port >> 8)
t.append(0)
if DEBUG: print("Sndp reply", repr(t))
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
try: # receive the old status if any
data = self.rx_udp_socket.recv(1024)
if DEBUG:
self.PrintStatus(' got ', data)
except:
pass
else:
data = bytearray(data)
if data[0:2] == b'St':
self.got_udp_status = data
if self.firmware_version is None: # get the firmware version
if self.want_udp_status[0:13] != self.got_udp_status[0:13]:
try:
self.rx_udp_socket.send(self.want_udp_status)
if DEBUG:
self.PrintStatus('Start', self.want_udp_status)
except:
pass
else: # We got a correct response.
self.firmware_version = self.got_udp_status[13] # Firmware version is returned here
if DEBUG:
print ('Got version', self.firmware_version)
if self.firmware_version > 0 and self.conf.use_rx_udp == 2:
self.tx_control |= 0x04 # Use extra control bytes
self.sndp_active = False
self.NewUdpStatus()
else:
if self.want_udp_status != self.got_udp_status:
if DEBUG:
self.PrintStatus('Have ', self.got_udp_status)
self.PrintStatus(' send', self.want_udp_status)
try:
self.rx_udp_socket.send(self.want_udp_status)
except:
pass
elif DEBUG:
self.rx_udp_socket.send(b'Qs')
def PrintStatus(self, msg, data):
print (msg, ' ', end=' ')
print (data[0:2], end=' ')
for c in data[2:]:
print ("%2X" % c, end=' ')
print ()
def GetFirmwareVersion(self):
return self.firmware_version
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
# The Spot button sets the mode to SSB-equivalent for CW so that the Spot level works.
if level >= 0 and not self.usingSpot: # Spot was turned on
self.usingSpot = True
self.tx_control |= 0x40
self.ChangeMode(self.mode)
elif level < 0 and self.usingSpot: # Spot was turned off
self.usingSpot = False
self.tx_control &= ~0x40
self.ChangeMode(self.mode)
def OnBtnFDX(self, is_fdx): # Status of FDX button, 0 or 1
if is_fdx:
self.HiQSDR_Connector_X1 |= 0x20 # Mask in the FDX bit
else:
self.HiQSDR_Connector_X1 &= ~0x20
self.NewUdpStatus()
def VarDecimGetChoices(self): # return text labels for the control
clock = self.conf.rx_udp_clock
l = [] # a list of sample rates
for dec in self.decimations:
l.append(str(int(float(clock) / dec / 1e3 + 0.5)))
return l
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate ksps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set decimation before the call to open()
rate = self.application.vardecim_set # May be None or from different hardware
try:
dec = int(float(self.conf.rx_udp_clock) / rate + 0.5)
self.index = self.decimations.index(dec)
except:
try:
self.index = self.decimations.index(self.conf.rx_udp_decimation)
except:
self.index = 0
else:
self.index = index
dec = self.decimations[self.index]
if dec >= 128:
self.rx_control = dec // 64 - 1 # Second stage decimation less one
QS.set_sample_bytes(3)
else:
self.rx_control = dec // 16 - 1 # Second stage decimation less one
self.rx_control |= 0b01000000 # Change prescaler to 2 (instead of 8)
QS.set_sample_bytes(2)
self.NewUdpStatus()
return int(float(self.conf.rx_udp_clock) / dec + 0.5)
def VarDecimRange(self):
return (48000, 960000)
def NewUdpStatus(self, do_tx=False):
s = bytearray(b'St')
s = s + struct.pack("<L", self.rx_phase)
s = s + struct.pack("<L", self.tx_phase)
s.append(self.tx_level & 0xFF)
s.append(self.tx_control & 0xFF)
s.append(self.rx_control & 0xFF)
if self.firmware_version: # Add the version
s.append(self.firmware_version & 0xFF) # The firmware version will be returned
if self.tx_control & 0x04: # Use extra HiQSDR control bytes
s.append(self.HiQSDR_Connector_X1 & 0xFF)
s.append(self.HiQSDR_Attenuator & 0xFF)
s.append(self.HiQSDR_Bits & 0xFF)
else:
s += bytearray(3)
s.append(self.sidetone_volume & 0xFF)
s = s + struct.pack("<H", self.vna_count)
s.append(self.cw_delay & 0xFF)
s.append(0)
else: # firmware version 0 or None
s.append(0) # assume version 0
self.want_udp_status = s
if do_tx:
try:
self.rx_udp_socket.send(s)
except:
pass
def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False):
if key_down is None:
pass
elif key_down:
self.tx_control |= 0x08
else:
self.tx_control &= ~0x08
if vna_count is not None:
self.vna_count = vna_count # Number of scan points
if vna_start is not None: # Set the start and stop frequencies. The tx_phase is the frequency delta.
self.rx_phase = int(float(vna_start) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.tx_phase = int(float(vna_stop - vna_start) / (self.vna_count - 1) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.tx_control &= ~0x03 # Erase last two bits
self.rx_control = 40 - 1
self.tx_level = 255
self.NewUdpStatus(do_tx)
start = int(float(self.rx_phase) * self.conf.rx_udp_clock / 2.0**32 + 0.5)
phase = self.rx_phase + self.tx_phase * (self.vna_count - 1)
stop = int(float(phase) * self.conf.rx_udp_clock / 2.0**32 + 0.5)
return start, stop # return the start and stop frequencies after integer rounding
+83
View File
@@ -0,0 +1,83 @@
/*
This module uses the Python CObject or Capsule interface to import pointers to
functions and variables defined in the _quisk Python extension module. These functions and
variables can then be used in another extension module. This is an alternative to linking
the other extension module to _quisk with the C linker. This interface is used by the SDR-IQ
extension module, and you can use that as a model.
This feature exists because of Maitland Bottoms, AA4HS, who requested the feature and provided patches.
To use this interface in your own extension module, first modify your setup.py or makefile so that
you are not linking in symbols from the _quisk module. Add import_quisk_api.c to your source files.
Then add this define before including quisk.h:
#define IMPORT_QUISK_API
#include "quisk.h"
Add this code after Py_InitModule() in your module init function (PyMODINIT_FUNC):
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
printf("Failure to import pointers from _quisk\n");
return; //Error
}
Use this new function to set your Start/Stop/Read functions (if used):
quisk_sample_source(&quisk_start_sdriq, &quisk_stop_sdriq, &quisk_read_sdriq);
Change references to quisk_sound_state to use the pointer pt_quisk_sound_state everywhere. For
example, replace this:
quisk_sound_state.read_error++;
with this:
pt_quisk_sound_state->read_error++;
*/
#include <Python.h>
void ** Quisk_API; // array of pointers to functions and variables from module _quisk
struct sound_conf * pt_quisk_sound_state; // pointer to quisk_sound_state
#if ( (PY_VERSION_HEX < 0x02070000) || ((PY_VERSION_HEX >= 0x03000000) && (PY_VERSION_HEX < 0x03010000)) )
// Old Python interface using CObject
int import_quisk_api(void)
{
PyObject *c_api_object;
PyObject *module;
module = PyImport_ImportModule("_quisk");
if (module == NULL) {
printf("Failure 1 to import Quisk_API\n");
return -1;
}
c_api_object = PyObject_GetAttrString(module, "QUISK_C_API");
if (c_api_object == NULL) {
Py_DECREF(module);
printf("Failure 2 to import Quisk_API\n");
return -1;
}
if (PyCObject_Check(c_api_object)) {
Quisk_API = (void **)PyCObject_AsVoidPtr(c_api_object);
}
else {
printf("Failure 3 to import Quisk_API\n");
Py_DECREF(c_api_object);
Py_DECREF(module);
return -1;
}
Py_DECREF(c_api_object);
Py_DECREF(module);
pt_quisk_sound_state = (struct sound_conf *)Quisk_API[0];
return 0;
}
#else
// New Python interface using Capsule
int import_quisk_api(void)
{
Quisk_API = (void **)PyCapsule_Import("_quisk.QUISK_C_API", 0);
if (Quisk_API == NULL) {
printf("Failure to import Quisk_API\n");
return -1;
}
pt_quisk_sound_state = (struct sound_conf *)Quisk_API[0];
return 0;
}
#endif
+240
View File
@@ -0,0 +1,240 @@
#include <Python.h> // used by quisk.h
#include <complex.h> // Used by quisk.h
#include "quisk.h"
// This module provides methods to access the state of the key.
// First call quisk_open_key(name) to choose a method and initialize.
// Subsequent key access uses the method chosen.
static int startup_error = -1; // -1: port not opened; 0: no error opening port; 1: error opening port
static int bit_cts, bit_dsr; // modem bits
static char use_cts, use_dsr; // use of CTS and DSR bits
static int reverse_cts, reverse_dsr; // opposite polarity for cts/dsr
static char port_name[QUISK_SC_SIZE]; // serial port name
static PyObject * start_up(void); // open the serial port
static void shut_down(void); // close the serial port
static void modem_status(void); // test modem bits
#define MSG_SIZE (50 + QUISK_SC_SIZE)
//int quisk_serial_key_errors = 0;
int quisk_serial_key_down; // The cts or dsr bit for CW key is asserted
int quisk_use_serial_port; // either cts or dsr is being used
int quisk_serial_ptt; // The cts or dsr bit for PTT is asserted
PyObject * quisk_open_key(PyObject * self, PyObject * args, PyObject * keywds)
{ // return a message for error, or "" for no error
static char * kwlist[] = {"port", "cts", "dsr", NULL} ;
PyObject * msg = NULL;
char * port = NULL;
char * cts = NULL;
char * dsr = NULL;
quisk_serial_key_down = 0;
quisk_serial_ptt = 0;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|sss", kwlist, &port, &cts, &dsr))
return NULL;
//quisk_serial_cts and dsr are "None", "CW", "PTT"; and "when high" or "when low"
if (cts) {
use_cts = * cts; // 'N', 'C', 'P'
reverse_cts = strstr(cts, "when low") != NULL;
}
if (dsr) {
use_dsr = * dsr; // 'N', 'C', 'P'
reverse_dsr = strstr(dsr, "when low") != NULL;
}
if (port) {
if (startup_error == 0) // port is open
shut_down();
strncpy(port_name, port, QUISK_SC_SIZE - 1);
port_name[QUISK_SC_SIZE - 1] = 0;
if (port_name[0])
msg = start_up();
}
if (startup_error == 0 && (use_cts != 'N' || use_dsr != 'N'))
quisk_use_serial_port = 1;
else
quisk_use_serial_port = 0;
if (msg == NULL)
msg = PyUnicode_FromString("");
return msg;
}
PyObject * quisk_close_key(PyObject * self, PyObject * args)
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
shut_down();
Py_INCREF (Py_None);
return Py_None;
}
void quisk_poll_hardware_key(void)
{ // call frequently to check the modem bits
if ( ! quisk_use_serial_port)
return;
modem_status();
if (use_cts == 'C')
quisk_serial_key_down = reverse_cts ? bit_cts == 0 : bit_cts != 0;
else if (use_cts == 'P')
quisk_serial_ptt = reverse_cts ? bit_cts == 0 : bit_cts != 0;
if (use_dsr == 'C')
quisk_serial_key_down = reverse_dsr ? bit_dsr == 0 : bit_dsr != 0;
else if (use_dsr == 'P')
quisk_serial_ptt = reverse_dsr ? bit_dsr == 0 : bit_dsr != 0;
}
#if defined(MS_WINDOWS)
#include <stdlib.h>
#include <sys/types.h>
#include <windows.h>
//#include <processthreadsapi.h>
//#include <timeapi.h>
#include <avrt.h>
static HANDLE hComm = INVALID_HANDLE_VALUE; // Windows handle to read the serial port
static void modem_status(void)
{
DWORD dwModemStatus;
if (hComm == INVALID_HANDLE_VALUE) {
bit_cts = bit_dsr = 0;
}
else {
if (!GetCommModemStatus(hComm, &dwModemStatus)) {
bit_cts = bit_dsr = 0; // Error in GetCommModemStatus;
}
else {
bit_cts = MS_CTS_ON & dwModemStatus;
bit_dsr = MS_DSR_ON & dwModemStatus;
}
}
}
static PyObject * start_up(void)
{
char msg[MSG_SIZE];
hComm = CreateFile(port_name, GENERIC_READ, 0, 0, OPEN_EXISTING,
0, 0);
//FILE_FLAG_OVERLAPPED, 0);
if (hComm == INVALID_HANDLE_VALUE) {
snprintf(msg, MSG_SIZE, "Open Morse key serial port %s failed.", port_name);
startup_error = 1;
return PyUnicode_FromString(msg);
}
startup_error = 0;
return PyUnicode_FromString("");
}
static void shut_down(void)
{
if (hComm != INVALID_HANDLE_VALUE)
CloseHandle(hComm);
hComm = INVALID_HANDLE_VALUE;
startup_error = -1;
quisk_serial_key_down = 0;
quisk_use_serial_port = 0;
quisk_serial_ptt = 0;
}
// Changes for MacOS support thanks to Mario, DL3LSM.
// Broken by N2ADR April, 2020.
#elif defined(__MACH__)
static PyObject * start_up(void)
{
startup_error = 0;
return PyUnicode_FromString("");
}
static void shut_down(void)
{
startup_error = -1;
quisk_serial_key_down = 0;
quisk_use_serial_port = 0;
quisk_serial_ptt = 0;
}
static void modem_status(void)
{
}
#else
// Not MS Windows and not __MACH__:
// Access the serial port. This code sets DTR high, and monitors DSR and CTS.
// When DSR is high set the RTS signal high. When DSR goes low set RTS low after a delay.
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
static int fdComm = -1; // File descriptor to read the serial port
static void modem_status(void)
{
int bits;
struct timeval tv;
double time;
static double time0=0; // time when the key was last down
if (fdComm >= 0) {
ioctl(fdComm, TIOCMGET, &bits); // read modem bits
bit_cts = bits & TIOCM_CTS;
bit_dsr = bits & TIOCM_DSR;
if (bit_dsr) {
if ( ! (bits & TIOCM_RTS)) { // set RTS
bits |= TIOCM_RTS;
ioctl(fdComm, TIOCMSET, &bits);
}
gettimeofday(&tv, NULL);
time0 = tv.tv_sec + tv.tv_usec / 1.0E6; // time is in seconds
}
else if (bits & TIOCM_RTS) { // clear RTS after a delay
gettimeofday(&tv, NULL);
time = tv.tv_sec + tv.tv_usec / 1.0E6;
if (time - time0 > pt_quisk_sound_state->quiskKeyupDelay * 1E-3) {
bits &= ~TIOCM_RTS;
ioctl(fdComm, TIOCMSET, &bits);
}
}
}
}
static PyObject * start_up(void)
{
int bits;
char msg[MSG_SIZE];
struct timespec tspec;
fdComm = open(port_name, O_RDWR | O_NOCTTY);
if (fdComm < 0) {
snprintf(msg, MSG_SIZE, "Open morse key serial port %s failed.", port_name);
startup_error = 1;
return PyUnicode_FromString(msg);
}
else {
ioctl(fdComm, TIOCMGET, &bits); // read modem bits
bits |= TIOCM_DTR; // Set DTR
bits &= ~TIOCM_RTS; // Clear RTS at first
ioctl(fdComm, TIOCMSET, &bits);
}
tspec.tv_sec = 0;
tspec.tv_nsec = 10000 * 1000;
nanosleep(&tspec, NULL);
startup_error = 0;
return PyUnicode_FromString("");
}
static void shut_down(void)
{
if (fdComm >= 0)
close(fdComm);
fdComm = -1;
startup_error = -1;
quisk_serial_key_down = 0;
quisk_use_serial_port = 0;
quisk_serial_ptt = 0;
}
#endif
BIN
View File
Binary file not shown.
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
Notes on libusb and pyusb
=========================
Libusb provides access to the USB bus from user space. It uses the
files in /dev/bus/usb/*/*. The following commands are useful on Linux:
List devices on the USB bus:
lsusb
Add -d 16c0:05dc for a specific device, -v for more information.
List file permissions for bus 001 device 005:
ls -l /dev/bus/usb/001/005
These permissions default to 660 group root.
List udev information for bus 001 device 005:
udevadm info --query=all --name=/dev/bus/usb/001/005 --attribute-walk
These items can be used in udev rules.
Default USB permissions do not allow a non-root user to write to the bus.
To change permissions, add a rule to /etc/udev/rules.d/local.rules like this:
SUBSYSTEM=="usb", ATTR{idVendor}=="16c0" , ATTR{idProduct}=="05dc", MODE="0666", GROUP="dialout"
Then notify udev with "udevadm control --reload_rules", or /etc/init.d/udev/restart. But
on my system, I need to plug in the SoftRock and reboot.
To install Libusb on Windows, follow the instructions in
http://sourceforge.net/apps/trac/libusb-win32/wiki.
Run the program libusb-win32*/bin/inf-wizard.exe.
On Windows, when libusb-win32 is properly installed, Device Manager reports a
top-level device "libusb-win32 devices" and a sub-device "DG8SAQ-I2C". Otherwise
it reports "Unknown Device" under "Universal Serial Bus controllers".
BIN
View File
Binary file not shown.
Binary file not shown.
+129
View File
@@ -0,0 +1,129 @@
This software is Copyright (C) 2007-2024 by James C. Ahlstrom, and is
licensed for use under the GNU General Public License (GPL).
See http://www.opensource.org.
Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
The GNU General Public License (GPL)
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
One line to give the program's name and a brief idea of what it does.
Copyright (C) <year> <name of author>
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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.
signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.
+18
View File
@@ -0,0 +1,18 @@
.PHONY: quisk
quisk:
python3 setup.py build_ext --force --inplace
@echo
quisk3:
python3 setup.py build_ext --force --inplace
@echo
soapy3:
(cd soapypkg; make soapy3)
afedrinet3:
(cd afedrinet; make afedrinet3)
perseus3:
(cd perseuspkg; make perseus3)
+1608
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
// Filters were moved to filter.c.
+161
View File
@@ -0,0 +1,161 @@
# This module implements Midi processing in Quisk. The OnReadMIDI() method is called for any Midi bytes
# received. Do not change this file. If you want to replace it with your own Midi handler, create a
# configuration file, copy this file into it and make any changes there. Quisk will use your configuration
# file MidiHandler instead of this one.
# Midi messages are generally three bytes long. The first byte is the status and has the most significant bit set.
# Subsequent bytes have the most significant bit zero. The status byte of a channel message is a 1 bit, three bits of message
# type and 4 bits of channel number. This is followed by two bytes of data.
# For Note On (status 0x9?) and Note Off (status 0x8?) the data bytes are the note number and velocity. Velocity
# indicates how hard the key was pressed. If the velocity of a Note On message is zero it is treated the same as Note Off.
# For a control change (status = 0xB?) the data bytes are the controller number and the controller value. For some controllers
# it only matters if the value is less than 64 (down) or 64 or greater (up). For other controllers the value 0 to 127
# is the actual control setting.
import traceback
class MidiHandler: # Quisk calls this to make the Midi handler instance.
tune_speed = {0:10, 1:20, 2:50, 3:100, 4:200, 5:500, 6:1000, 7:2000, 8:5000, 9:10000}
slider_speed = {0:1, 1:2, 2:3, 3:5, 4:7, 5:9, 6:12, 7:15, 8:18, 9:22}
def __init__(self, app, conf):
self.app = app # The application object
self.conf = conf # The configuration settings
self.midi_message = [] # Save Midi bytes until a whole message is received.
def OnReadMIDI(self, byts): # Quisk calls this for any Midi bytes received.
for byt in byts:
if byt & 0x80: # this is a status byte and the start of a new message
self.midi_message = [byt]
else:
self.midi_message.append(byt)
if len(self.midi_message) == 3:
#print ("0x%2X%02X %d" % tuple(self.midi_message))
status = self.midi_message[0]
status = status & 0xF0 # Ignore channel
if status == 0x90: # Note On
if self.midi_message[2] == 0: # Note On with zero velocity is the same as Note Off
self.NoteOff()
else:
self.NoteOn()
elif status == 0x80: # Note Off
self.NoteOff()
elif status == 0xB0: # Control Change
try:
name = self.app.local_conf.MidiNoteDict["0x%02X%02X" % (self.midi_message[0], self.midi_message[1])]
except:
pass
#traceback.print_exc()
else:
if len(name) > 3 and name[-3] == " " and name[-2] in "+-" and name[-1] in "0123456789":
self.JogWheel(name)
else:
self.ControlKnob(name)
def NoteOn(self):
try:
name = self.app.local_conf.MidiNoteDict["0x%02X%02X" % (self.midi_message[0], self.midi_message[1])]
btn = self.app.idName2Button[name]
except:
return
if btn.idName == 'PTT' and not self.conf.midi_ptt_toggle:
btn.SetValue(True, True)
else:
btn.Shortcut(None, name)
def NoteOff(self):
try: # Look up the Note On name
name = self.app.local_conf.MidiNoteDict["0x9%X%02X" % (self.midi_message[0] & 0xF, self.midi_message[1])]
btn = self.app.idName2Button[name]
except:
return
if hasattr(btn, "repeat_state"): # This is a QuiskRepeatbutton
btn.Shortcut(None, "_end_")
elif btn.idName == 'PTT' and not self.conf.midi_ptt_toggle:
btn.SetValue(False, True)
def ControlKnob(self, name):
if self.midi_message[2] == 64: # Mid control
dec_value = 0.5
else:
dec_value = self.midi_message[2] / 127.0
if name == "Tune":
tune = self.app.sample_rate * (dec_value - 0.5) * 0.98
tune = int(tune)
self.app.ChangeHwFrequency(tune, self.app.VFO, 'FreqEntry')
elif name == "Rit": # Offset values by the CW tone frequency
ctrl, func = self.app.midiControls[name]
value = self.midi_message[2] - 64 # Center value
if self.app.mode == 'CWU':
offset = - self.conf.cwTone
value = value * 1000 // 63 + offset
elif self.app.mode == 'CWL':
offset = self.conf.cwTone
value = value * 1000 // 63 + offset
else:
offset = 0
value = value * 2000 // 63 + offset
if value < ctrl.themin:
value = ctrl.themin
elif value > ctrl.themax:
value = ctrl.themax
ctrl.SetValue(value)
if self.app.remote_control_head:
self.app.Hardware.RemoteCtlSend(f'{ctrl.idName};{ctrl.GetValue()}\n')
func()
elif name in self.app.midiControls:
ctrl, func = self.app.midiControls[name]
if ctrl:
ctrl.SetDecValue(dec_value, False)
if self.app.remote_control_head:
self.app.Hardware.RemoteCtlSend(f'{ctrl.idName};{ctrl.GetValue()}\n')
func()
else: # Try to treat as Note On/Off
try:
btn = self.app.idName2Button[name]
except:
return
if self.midi_message[2] == 0: # Note On with zero velocity is the same as Note Off
if hasattr(btn, "repeat_state"): # This is a QuiskRepeatbutton
btn.Shortcut(None, "_end_")
else: # Note On
btn.Shortcut(None, name)
def JogWheel(self, name):
speed = int(name[-1])
if name[-2] == '+':
direction = +1
else:
direction = -1
name = name[0:-3]
if name == "Tune":
freq = self.app.txFreq + self.app.VFO
delta = self.tune_speed[speed]
if self.midi_message[2] < 64:
freq += direction * delta
else:
freq -= direction * delta
freq = ((freq + delta // 2) // delta) * delta
tune = freq - self.app.VFO
d = self.app.sample_rate * 45 // 100
if -d <= tune <= d: # Frequency is on-screen
vfo = self.app.VFO
else: # Change the VFO
vfo = (freq // 5000) * 5000 - 5000
tune = freq - vfo
self.app.ChangeHwFrequency(tune, vfo, 'FreqEntry')
elif name in self.app.midiControls:
ctrl, func = self.app.midiControls[name]
self.AdjSlider(ctrl, direction, speed)
if self.app.remote_control_head:
self.app.Hardware.RemoteCtlSend(f'{ctrl.idName};{ctrl.GetValue()}\n')
func()
else:
pass #print ("Unknown jog name", name)
def AdjSlider(self, ctrl, direction, speed):
value = ctrl.GetValue()
if self.midi_message[2] < 64:
value += direction * self.slider_speed[speed]
else:
value -= direction * self.slider_speed[speed]
if value < ctrl.themin:
value = ctrl.themin
elif value > ctrl.themax:
value = ctrl.themax
ctrl.SetValue(value)
+1
View File
@@ -0,0 +1 @@
#
+13
View File
@@ -0,0 +1,13 @@
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path
settings_file_path = "../quisk_settings.json"
name_of_sound_play = n2adr_sound_usb_play
microphone_name = n2adr_sound_usb_mic
digital_input_name = ""
digital_output_name = ""
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.1.196" # Sample source IP address
rx_udp_port = 0xBC77 # Sample source UDP port
graph_width = 0.80
+20
View File
@@ -0,0 +1,20 @@
# This is a second config file that I use to test various hardware configurations.
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic
from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path
settings_file_path = "../quisk_settings.json"
name_of_sound_play = n2adr_sound_usb_play
name_of_sound_capt = n2adr_sound_pc_capt
sdriq_name = "/dev/ttyUSB0" # Name of the SDR-IQ device to open
default_screen = 'WFall'
waterfall_y_scale = 80
waterfall_y_zero = 40
waterfall_graph_y_scale = 40
waterfall_graph_y_zero = 90
waterfall_graph_size = 160
display_fraction = 1.00 # The edges of the full bandwidth are not valid
+13
View File
@@ -0,0 +1,13 @@
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path
name_of_sound_play = n2adr_sound_usb_play
microphone_name = n2adr_sound_usb_mic
settings_file_path = "../quisk_settings.json"
rx_udp_clock = 73728000 - 102
rx_udp_ip = "192.168.1.213" # Sample source IP address "" for DHCP
do_repeater_offset = True
#bandTransverterOffset = {'10' : 300000}
spot_button_keys_tx = True
+5
View File
@@ -0,0 +1,5 @@
from n2adr import conf3
settings_file_path = "../quisk_settings.json"
microphone_name = ""
+40
View File
@@ -0,0 +1,40 @@
# This is a config file to test the microphone by sending microphone Tx playback to the audio out.
# Set the frequency to zero, and press FDX and PTT.
# Set these values for DEBUG_MIC in sound.c:
# 0: Normal FFT.
# 1: Send filtered Tx audio to the FFT.
# 2: Send mic playback to the FFT.
# 3: Send unfiltered mono mic audio to the FFT.
import sys
from quisk_hardware_model import Hardware as BaseHardware
import _quisk as QS
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic
from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path
from n2adr.quisk_conf import mixer_settings
settings_file_path = "../quisk_settings.json"
name_of_sound_capt = n2adr_sound_pc_capt
name_of_sound_play = ''
microphone_name = n2adr_sound_usb_mic
name_of_mic_play = n2adr_sound_usb_play
graph_y_scale = 160
mic_sample_rate = 48000
sample_rate = 48000
mic_playback_rate = 48000
mic_out_volume = 0.6
add_fdx_button = 1
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
def OnButtonPTT(self, event):
if event.GetEventObject().GetValue():
QS.set_key_down(1)
else:
QS.set_key_down(0)
+15
View File
@@ -0,0 +1,15 @@
import sys
settings_file_path = "../quisk_settings.json"
#hamlib_port = 4575 # Standard port for Quisk control. Set the port in Hamlib to 4575 too.
hamlib_port = 4532 # Default port for rig 2. Use this if you can not set the Hamlib port.
if sys.platform == "win32":
pass
elif 0:
digital_input_name = 'pulse'
digital_output_name =''
else:
digital_input_name = 'hw:Loopback,0'
digital_output_name = digital_input_name
+30
View File
@@ -0,0 +1,30 @@
# This is a second config file to test the softrock radios.
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic
from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path
from n2adr.quisk_conf import mixer_settings
settings_file_path = "../quisk_settings.json"
name_of_sound_capt = n2adr_sound_pc_capt
name_of_sound_play = n2adr_sound_usb_play
default_screen = 'WFall'
waterfall_y_scale = 80
waterfall_y_zero = 40
waterfall_graph_y_scale = 40
waterfall_graph_y_zero = 90
waterfall_graph_size = 160
display_fraction = 1.00
sample_rate = 48000
playback_rate = 48000
do_repeater_offset = True
#bandTransverterOffset = {'40' : 300000}
# Microphone capture and playback:
microphone_name = n2adr_sound_usb_mic
name_of_mic_play = n2adr_sound_pc_play
mic_playback_rate = sample_rate
mic_out_volume = 0.6
+14
View File
@@ -0,0 +1,14 @@
import sys
settings_file_path = "../quisk_settings.json"
if sys.platform == "win32":
digital_output_name = 'CABLE-A Input'
elif 0:
digital_input_name = 'pulse'
digital_output_name =''
else:
digital_output_name = 'hw:Loopback,0'
name_of_sound_play = ''
microphone_name = ''
+72
View File
@@ -0,0 +1,72 @@
# This is the hardware control file for my shack.
# It is for the Hermes-Lite2 5 watt output which uses only the antenna tuner.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from hermes.quisk_hardware import Hardware as BaseHw
from n2adr import station_hardware
class Hardware(BaseHw):
def __init__(self, app, conf):
BaseHw.__init__(self, app, conf)
self.GUI = None
self.vfo_frequency = 0 # current vfo frequency
# Other hardware
self.anttuner = station_hardware.AntennaTuner(app, conf) # Control the antenna tuner
self.controlbox = station_hardware.ControlBox(app, conf) # Control my Station Control Box
self.v2filter = station_hardware.FilterBoxV2(app, conf) # Control V2 filter box
def open(self):
if False:
from n2adr.station_hardware import StationControlGUI
self.GUI = StationControlGUI(self.application.main_frame, self, self.application, self.conf)
self.GUI.Show()
self.anttuner.open()
return BaseHw.open(self)
def close(self):
self.anttuner.close()
self.controlbox.close()
return BaseHw.close(self)
def ChangeFilterFrequency(self, tx_freq):
if tx_freq and tx_freq > 0:
if self.GUI:
self.GUI.SetTxFreq(tx_freq)
self.GUI.freq_entry.ChangeValue("%.3f" % (tx_freq * 1E-6))
else:
self.anttuner.SetTxFreq(tx_freq)
self.v2filter.SetTxFreq(tx_freq)
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.ChangeFilterFrequency(tx_freq)
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
ret = BaseHw.ChangeBand(self, band)
self.anttuner.ChangeBand(band)
#self.lpfilter.ChangeBand(band)
#self.hpfilter.ChangeBand(band)
self.v2filter.ChangeBand(band)
self.CorrectSmeter()
return ret
def HeartBeat(self): # Called at about 10 Hz by the main
self.anttuner.HeartBeat()
self.v2filter.HeartBeat()
self.controlbox.HeartBeat()
return BaseHw.HeartBeat(self)
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
self.anttuner.OnSpot(level)
return BaseHw.OnSpot(self, level)
def OnButtonRfGain(self, event):
self.v2filter.OnButtonRfGain(event)
self.CorrectSmeter()
def CorrectSmeter(self): # S-meter correction can change with band or RF gain
return
if self.band == '40': # Basic S-meter correction by band
self.correct_smeter = 20.5
else:
self.correct_smeter = 20.5
#self.correct_smeter -= self.rf_gain / 6.0 # Correct S-meter for RF gain
#self.application.waterfall.ChangeRfGain(self.rf_gain) # Waterfall colors are constant
def OnButtonPTT(self, event):
self.controlbox.OnButtonPTT(event)
return BaseHw.OnButtonPTT(self, event)
+47
View File
@@ -0,0 +1,47 @@
# This is the config file from my shack, which controls various hardware.
# The files to control my 2010 transceiver and for the improved version HiQSDR
# are in the package directory HiQSDR.
import sys
sample_rate = 48000
if sys.platform == 'win32':
favorites_file_path = "C:/pub/quisk_favorites.txt"
settings_file_path = "C:/pub/quisk_settings.json"
name_of_sound_play = ""
name_of_sound_capt = "Primary"
microphone_name = ""
name_of_mic_play = ""
else:
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
settings_file_path = "/home/jim/pub/quisk_settings.json"
name_of_sound_play = ""
name_of_sound_capt = "hw:0"
microphone_name = ""
name_of_mic_play = ""
#lin_microphone_name = "ALC1150 Analog"
#lin_name_of_mic_play = "USB Sound Device"
# These are for CM106 like sound device
#n2adr_sound_usb_mic = 'alsa:USB Sound Device'
#microphone_name = n2adr_sound_usb_mic
#mixer_settings = [
# (microphone_name, 16, 1), # PCM capture from line
# (microphone_name, 14, 0), # PCM capture switch
# (microphone_name, 11, 1), # line capture switch
# (microphone_name, 12, 0.70), # line capture volume
# (microphone_name, 3, 0), # mic playback switch
# (microphone_name, 9, 0), # mic capture switch
# ]
# These are for Asus internal sound
n2adr_sound_usb_mic = 'alsa:ALC1150 Analog'
microphone_name = n2adr_sound_usb_mic
mixer_settings = [
(microphone_name, 19, 2), # PCM capture from line
(microphone_name, 24, 0), # PCM capture switch
(microphone_name, 22, 1), # line capture switch
(microphone_name, 27, 0), # line capture volume boost
(microphone_name, 21, 0.70), # line capture volume
]
+202
View File
@@ -0,0 +1,202 @@
# These are the configuration parameters for receiving the
# 10.7 MHz IF output of the AOR AR8600 receiver with my
# transceiver. This results in a 100 kHz to 3 GHz
# wide range receiver with pan adapter.
#
# Due to noise starting at 11.18 MHz when tuned to 449.0 MHz, we tune to 10.5 MHz center.
#
# Note: The AR8600 IF output in WFM mode seems to tune in 10kHz increments
# no matter what the step size, even though the display reads a
# different frequency.
# The AR8600 inverts the spectrum of these bands: 10, 2, 220, 440, 900
# The AR8600 does not invert these bands: 1240
# The change from inverted to non-inverted is about 1040 MHz.
# Please do not change this sample file.
# Instead copy it to your own .quisk_conf.py and make changes there.
# See quisk_conf_defaults.py for more information.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import time, traceback, os
import _quisk as QS
import serial # From the pyserial package
from n2adr.quisk_conf import *
from n2adr import scanner_widgets as quisk_widgets
settings_file_path = "../quisk_settings.json"
bandLabels = [ ('60',) * 5, '40', '20',
'15', '12', '10', '2', '220', '440', '900', '1240', ('Time',) * len(bandTime)]
# Define the Hardware class in this config file instead of a separate file.
from hiqsdr.quisk_hardware import Hardware as BaseHardware
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.ar8600_frequency = 0 # current AR8600 tuning frequency
self.hware_frequency = 0 # current hardware VFO frequency
self.vfo_frequency = 0 # current Quisk VFO frequency
self.invert = 1 # The frequency spectrum is backwards
self.serial = None # the open serial port
self.timer = 0.02 # time between AR8600 commands in seconds
self.time0 = 0 # time of last AR8600 command
self.serial_out = [] # send commands slowly
self.offset = 10700000 # frequency offset from AR8600 tuning freq to IF output
self.tx_freq = 0 # current frequency
conf.BandEdge['220'] = (222000000, 225000000)
conf.BandEdge['440'] = (420000000, 450000000)
conf.BandEdge['900'] = (902000000, 928000000)
conf.BandEdge['1240'] = (1240000000, 1300000000)
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
rpt_file = os.path.join(rpt_file, 'MetroCor.txt')
fp = open(rpt_file, 'r')
self.repeaters = {}
for line in fp:
line = line.strip()
if line and line[0] != '#':
line = line.split('\t')
fout = int(float(line[0]) * 1000000 + 0.1)
text = "%s %s, %s" % (line[2], line[3], line[5])
if fout in self.repeaters:
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
else:
self.repeaters[fout] = text
fp.close()
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
rpt_file = os.path.join(rpt_file, 'ARCC.csv')
fp = open(rpt_file, 'r')
for line in fp:
line = line.strip()
if line and line[0] != '#':
line = line.split(',')
fout = float(line[3])
if fout >= 2000.0:
continue
fout = int(fout * 1000000 + 0.1)
text = "%s %s, %s" % (line[5], line[2], line[0])
if fout in self.repeaters:
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
else:
self.repeaters[fout] = text
fp.close()
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
rpt_file = os.path.join(rpt_file, 'Repeaters.csv')
fp = open(rpt_file, 'r')
for line in fp:
line = line.strip()
if line and line[0] != '#':
line = line.split(',')
fout = float(line[3])
if fout >= 2000.0:
continue
fout = int(fout * 1000000 + 0.1)
if line[0]:
text = "%s %s, %s" % (line[5], line[2], line[0])
else:
text = line[5]
if fout in self.repeaters:
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
else:
self.repeaters[fout] = text
fp.close()
for freq, text in list(self.repeaters.items()):
if len(text) > 80:
t =''
stations = text.split(';')
for s in stations:
s = s.strip()
t = t + s.split()[0] + ' ' + s.split(',')[1] + '; '
self.repeaters[freq] = t
self.rpt_freq_list = list(self.repeaters)
self.rpt_freq_list.sort()
def OpenPort(self):
if sys.platform == "win32":
tty_list = ("COM7", "COM8")
else:
tty_list = ("/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2")
for tty_name in tty_list:
try:
port = serial.Serial(port=tty_name, baudrate=9600,
stopbits=serial.STOPBITS_TWO, xonxoff=1, timeout=0)
except:
#traceback.print_exc()
pass
else:
time.sleep(0.1)
for i in range(3):
port.write('VR\r')
time.sleep(0.1)
chars = port.read(1024)
if "VR0101" in chars:
self.serial = port
port.write('MD0\r') # set WFM mode so the IF output is available
break
if self.serial:
break
else:
port.close()
def open(self):
self.OpenPort()
QS.invert_spectrum(self.invert)
t = BaseHardware.open(self) # save the message
return t
def close(self):
BaseHardware.close(self)
if self.serial:
self.serial.write('EX\r')
time.sleep(1) # wait for output to drain, but don't block
self.serial.close()
self.serial = None
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.tx_freq = tx_freq
try:
rpt = self.repeaters[tx_freq]
except KeyError:
self.application.bottom_widgets.UpdateText('')
else:
self.application.bottom_widgets.UpdateText(rpt)
if vfo_freq != self.vfo_frequency and vfo_freq >= 10000:
self.vfo_frequency = vfo_freq
# Calculate new AR8600 and hardware frequencies
ar8600 = (vfo_freq + 50000) // 100000 * 100000 - 200000
if self.ar8600_frequency != ar8600:
self.ar8600_frequency = ar8600
self.SendAR8600('RF%010d\r' % ar8600)
if ar8600 < 1040000000:
self.invert = 1
else:
self.invert = 0
QS.invert_spectrum(self.invert)
if self.invert:
hware = self.offset - vfo_freq + self.ar8600_frequency
else:
hware = self.offset + vfo_freq - self.ar8600_frequency
if self.hware_frequency != hware:
self.hware_frequency = hware
BaseHardware.ChangeFrequency(self, 0, hware)
#print 'AR8600 Hware', self.ar8600_frequency, self.hware_frequency
return tx_freq, vfo_freq
def SendAR8600(self, msg): # Send commands to the AR8600, but not too fast
if self.serial:
if time.time() - self.time0 > self.timer:
self.serial.write(msg) # send message now
self.time0 = time.time()
else:
self.serial_out.append(msg) # send message later
def HeartBeat(self): # Called at about 10 Hz by the main
BaseHardware.HeartBeat(self)
if self.serial:
chars = self.serial.read(1024)
#if chars:
# print chars
if self.serial_out and time.time() - self.time0 > self.timer:
self.serial.write(self.serial_out[0])
self.time0 = time.time()
del self.serial_out[0]
+80
View File
@@ -0,0 +1,80 @@
# This is the hardware file from my shack, which controls various hardware.
# The files to control my 2010 transceiver and for the improved version HiQSDR
# are in the package directory HiQSDR.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from hiqsdr.quisk_hardware import Hardware as BaseHw
from n2adr import station_hardware
class Hardware(BaseHw):
def __init__(self, app, conf):
BaseHw.__init__(self, app, conf)
self.GUI = None
self.vfo_frequency = 0 # current vfo frequency
self.rf_gain_labels = ('RF 0 dB', 'RF +16')
self.rf_gain = 0 # Preamp or attenuation in dB; changed via app.Hardware
# Other hardware
self.anttuner = station_hardware.AntennaTuner(app, conf) # Control the antenna tuner
#self.lpfilter = station_hardware.LowPassFilter(app, conf) # Control LP filter box
#self.hpfilter = station_hardware.HighPassFilter(app, conf) # Control HP filter box
self.controlbox = station_hardware.ControlBox(app, conf) # Control my Station Control Box
self.v2filter = station_hardware.FilterBoxV2(app, conf) # Control V2 filter box
def open(self):
if False:
from n2adr.station_hardware import StationControlGUI
self.GUI = StationControlGUI(self.application.main_frame, self, self.application, self.conf)
self.GUI.Show()
self.anttuner.open()
return BaseHw.open(self)
def close(self):
self.anttuner.close()
self.controlbox.close()
return BaseHw.close(self)
def ChangeFilterFrequency(self, tx_freq):
if tx_freq and tx_freq > 0:
if self.GUI:
self.GUI.SetTxFreq(tx_freq)
self.GUI.freq_entry.ChangeValue("%.3f" % (tx_freq * 1E-6))
else:
self.anttuner.SetTxFreq(tx_freq)
self.v2filter.SetTxFreq(tx_freq)
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.ChangeFilterFrequency(tx_freq)
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
ret = BaseHw.ChangeBand(self, band)
self.anttuner.ChangeBand(band)
#self.lpfilter.ChangeBand(band)
#self.hpfilter.ChangeBand(band)
self.v2filter.ChangeBand(band)
self.CorrectSmeter()
return ret
def HeartBeat(self): # Called at about 10 Hz by the main
self.anttuner.HeartBeat()
#self.lpfilter.HeartBeat()
#self.hpfilter.HeartBeat()
self.v2filter.HeartBeat()
self.controlbox.HeartBeat()
return BaseHw.HeartBeat(self)
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
self.anttuner.OnSpot(level)
return BaseHw.OnSpot(self, level)
def OnButtonRfGain(self, event):
#self.hpfilter.OnButtonRfGain(event)
self.v2filter.OnButtonRfGain(event)
self.CorrectSmeter()
def CorrectSmeter(self): # S-meter correction can change with band or RF gain
if self.band == '40': # Basic S-meter correction by band
self.correct_smeter = 20.5
else:
self.correct_smeter = 20.5
self.correct_smeter -= self.rf_gain / 6.0 # Correct S-meter for RF gain
self.application.waterfall.ChangeRfGain(self.rf_gain) # Waterfall colors are constant
def OnButtonPTT(self, event):
self.controlbox.OnButtonPTT(event)
return BaseHw.OnButtonPTT(self, event)
+56
View File
@@ -0,0 +1,56 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import wx, time
import _quisk as QS
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
#self.config = conf
#self.hardware = hardware
#self.application = app
#start_row = app.widget_row # The first available row
#start_col = app.button_start_col # The start of the button columns
#b = app.QuiskCycleCheckbutton(frame, self.OnAntTuner, ('Antenna', 'Ant 0', 'Ant 1'))
#bw, bh = b.GetMinSize()
#gbs.Add(b, (start_row, start_col), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L+')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 2), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L-')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 4), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C+')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 6), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C-')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 8), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'Save')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 10), (1, 2), flag=wx.EXPAND)
#self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
#gbs.Add(self.swr_label, (start_row, start_col + 2), (1, 10), flag=wx.EXPAND)
#b = app.QuiskCheckbutton(frame, None, text='')
#gbs.Add(b, (start_row, start_col + 25), (1, 2), flag=wx.EXPAND)
# Example of a horizontal slider:
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
# def OnPreamp(self, event):
# print event.GetPosition()
self.num_rows_added = 0
#def OnAntTuner(self, event):
# btn = event.GetEventObject()
# text = btn.GetLabel()
# self.hardware.OnAntTuner(text)
#def UpdateText(self, text):
# self.swr_label.SetLabel(text)
+140
View File
@@ -0,0 +1,140 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import wx, time
import _quisk as QS
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
row = 4 # The next available row
b = app.QuiskPushbutton(frame, None, 'Tune')
bw, bh = b.GetMinSize()
b.Enable(0)
gbs.Add(b, (row, 0), (1, 2), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, None, '')
gbs.Add(b, (row, 2), (1, 2), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, None, '')
gbs.Add(b, (row, 4), (1, 2), flag=wx.EXPAND)
b = self.btnScanner = app.QuiskCheckbutton(frame, self.OnBtnScanner, text='Scanner', use_right=True)
self.scan_timer = wx.Timer(b) # timed events for the scanner
b.Bind(wx.EVT_TIMER, self.OnTimerEvent)
gbs.Add(b, (row, 6), (1, 2), flag=wx.EXPAND)
b = self.btnNext = app.QuiskPushbutton(frame, self.OnBtnNext, 'Next', True)
gbs.Add(b, (row, 8), (1, 2), flag=wx.EXPAND)
b = app.QuiskCheckbutton(frame, self.OnBtnRptr, text='Rptr')
b.SetValue(True, True)
gbs.Add(b, (row, 10), (1, 2), flag=wx.EXPAND)
self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
gbs.Add(self.swr_label, (row, 15), (1, 12), flag=wx.EXPAND)
# Example of a horizontal slider:
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
# def OnPreamp(self, event):
# print event.GetPosition()
def UpdateText(self, text):
self.swr_label.SetLabel(text)
def OnBtnRptr(self, event):
btn = event.GetEventObject()
if btn.GetValue():
self.config.freq_spacing = 5000
else:
self.config.freq_spacing = 0
def OnBtnNext(self, event):
self.direction = self.btnNext.direction # +1 for left -> go up; -1 for down
self.keep_going = wx.GetKeyState(wx.WXK_SHIFT) # if Shift is down, move to next band
self.scanner = False
if self.keep_going:
if not self.ScanScreen(event):
self.MoveVfo(event)
self.scan_timer.Start(500)
else:
self.ScanScreen(event)
def ScanScreen(self, event): # Look for signals on the current screen
lst = self.hardware.rpt_freq_list
app = self.application
vfo = app.VFO
tx_freq = vfo + app.txFreq
sample_rate = app.sample_rate
limit = int(sample_rate / 2.0 * self.config.display_fraction * 0.95) # edge of screen
self.scan_n1 = None
self.scan_n = None
for n in range(len(lst)):
if lst[n] > vfo - limit and self.scan_n1 is None:
self.scan_n1 = n # inclusive
if lst[n] >= tx_freq and self.scan_n is None:
self.scan_n = n
if lst[n] > vfo + limit:
break
self.scan_n2 = n # inclusive
if self.scan_n is None:
self.scan_n = self.scan_n1
if self.direction > 0: # left click; go up
seq = list(range(self.scan_n + 1, self.scan_n2 + 1))
if not self.keep_going:
seq += list(range(self.scan_n1, self.scan_n))
else: # right click; go down
seq = list(range(self.scan_n - 1, self.scan_n1 - 1, -1))
if not self.keep_going:
seq += list(range(self.scan_n2, self.scan_n, -1))
for n in seq:
freq = lst[n]
if not QS.get_squelch(freq - vfo):
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
return True # frequency was changed
return False # frequency was not changed
def MoveVfo(self, event): # Move the VFO to look for further signals
lst = self.hardware.rpt_freq_list
app = self.application
vfo = app.VFO
tx_freq = vfo + app.txFreq
sample_rate = app.sample_rate
if self.direction > 0: # left click; go up
n = self.scan_n2 + 1
if n >= len(lst):
n = 0
freq = lst[n]
vfo = freq + sample_rate * 4 // 10
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
else: # right click; go down
n = self.scan_n1 - 1
if n < 0:
n = len(lst) - 1
freq = lst[n]
vfo = freq - sample_rate * 4 // 10
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
def OnBtnScanner(self, event):
self.direction = self.btnScanner.direction # +1 for left -> go up; -1 for down
self.keep_going = wx.GetKeyState(wx.WXK_SHIFT) # if Shift is down, move to next band
self.scanner = True
if self.btnScanner.GetValue():
self.btnNext.Enable(0)
if self.keep_going:
if not self.ScanScreen(event):
self.MoveVfo(event)
else:
self.ScanScreen(event)
self.scan_timer.Start(500)
else:
self.btnNext.Enable(1)
self.scan_timer.Stop()
def OnTimerEvent(self, event):
if QS.get_squelch(self.application.txFreq):
if self.keep_going:
if not self.ScanScreen(event):
self.MoveVfo(event)
else:
self.ScanScreen(event)
elif not self.scanner:
self.scan_timer.Stop()
+92
View File
@@ -0,0 +1,92 @@
#! /usr/bin/python
# All QUISK software is Copyright (C) 2006-2011 by James C. Ahlstrom.
# This free software is licensed for use under the GNU General Public
# License (GPL), see http://www.opensource.org.
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
"Select the desired hardware, and start Quisk"
import sys, wx, subprocess, os
Choices = [
(' My Transceiver', 'n2adr/quisk_conf.py', ''),
(' VHF/UHF Receiver', 'n2adr/uhfrx_conf.py', ''),
(' Softrock Rx Ensemble', 'softrock/conf_rx_ensemble2.py', 'n2adr/conf2.py'),
(' Softrock Rx/Tx Ensemble', 'softrock/conf_rx_tx_ensemble.py', 'n2adr/conf6.py'),
(' Plain Sound Card, Rx only', 'n2adr/conf2.py', ''),
(' Test microphone sound', 'n2adr/conf4.py', ''),
(' SDR-IQ, receive only, antenna to RF input', 'quisk_conf_sdriq.py', 'n2adr/conf2.py'),
(' AOR AR8600 with IF to my hardware', 'n2adr/quisk_conf_8600.py', ''),
(' AOR AR8600 with IF to SDR-IQ', 'quisk_conf_sdr8600.py', 'n2adr/conf2.py'),
(' Fldigi with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf5.py'),
(' Freedv.org Rx with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf7.py'),
(' Hermes-Lite', 'hermes/quisk_conf.py', 'n2adr/conf3.py'),
(' Odyssey', 'odyssey/quisk_conf.py', 'n2adr/conf1.py'),
(' My Transceiver to Hermes-Lite', 'Quisk2Hermes', ''),
]
if sys.platform == 'win32':
os.chdir('C:\\pub\\quisk')
exe = "C:\\python27\\pythonw.exe"
else:
os.chdir('/home/jim/pub/quisk')
exe = "/usr/bin/python"
class ListBoxFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Select Hardware')
font = wx.Font(14, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL)
self.SetFont(font)
charx = self.GetCharWidth()
chary = self.GetCharHeight()
width = 0
height = chary * 2
tlist = []
for txt, conf1, conf2 in Choices:
text = "%s, %s" % (txt, conf1)
if conf2:
text = "%s, %s" % (text, conf2)
tlist.append(text)
w, h = self.GetTextExtent(text)
width = max(width, w)
height += h
width += 3 * chary
lb = wx.ListBox(self, -1, (0, 0), (width, height), tlist, wx.LB_SINGLE)
lb.SetSelection(0)
lb.SetFont(font)
lb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnDClick, lb)
lb.Bind(wx.EVT_KEY_DOWN, self.OnChar)
self.SetClientSize((width, height))
def OnDClick(self, event):
lb = event.GetEventObject()
index = lb.GetSelection()
text, conf1, conf2 = Choices[index]
if conf1 == "Quisk2Hermes":
subprocess.Popen([exe, 'quisk.py', '-c', 'n2adr/quisk_conf.py', '--local', 'Q2H'])
subprocess.Popen([exe, 'quisk.py', '-c', 'hermes/quisk_conf.py', '--config2', 'n2adr/conf3A.py', '--local', 'Q2H'])
else:
cmd = [exe, 'quisk.py', '-c', conf1]
if conf2:
cmd = cmd + ['--config2', conf2]
subprocess.Popen(cmd)
self.Destroy()
def OnChar(self, event):
if event.GetKeyCode() == 13:
self.OnDClick(event)
else:
event.Skip()
class App(wx.App):
def __init__(self):
if sys.stdout.isatty():
wx.App.__init__(self, redirect=False)
else:
wx.App.__init__(self, redirect=True)
def OnInit(self):
frame = ListBoxFrame()
frame.Show()
return True
app = App()
app.MainLoop()
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
# This is the config file for the VHF/UHF receiver and transmitter.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, struct, socket, traceback
settings_file_path = "../quisk_settings.json"
DEBUG = 0
if sys.platform == "win32":
n2adr_sound_pc_capt = 'Line In (Realtek High Definition Audio)'
n2adr_sound_pc_play = 'Speakers (Realtek High Definition Audio)'
n2adr_sound_usb_play = 'Primary'
n2adr_sound_usb_mic = 'Primary'
latency_millisecs = 150
data_poll_usec = 20000
favorites_file_path = "C:/pub/quisk_favorites.txt"
elif 0: # portaudio devices
name_of_sound_play = 'portaudio:CODEC USB'
microphone_name = "portaudio:AK5370"
latency_millisecs = 150
data_poll_usec = 5000
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
else: # alsa devices
n2adr_sound_pc_capt = 'alsa:ALC1150 Analog'
n2adr_sound_pc_play = 'alsa:ALC1150 Analog'
n2adr_sound_usb_play = 'alsa:USB Sound Device'
n2adr_sound_usb_mic = 'alsa:USB Sound Device'
latency_millisecs = 150
data_poll_usec = 5000
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
name_of_sound_capt = ""
name_of_sound_play = n2adr_sound_pc_play
microphone_name = n2adr_sound_pc_capt
playback_rate = 48000
agc_off_gain = 80
do_repeater_offset = True
station_display_lines = 1
# DX cluster telent login data, thanks to DJ4CM.
dxClHost = ''
#dxClHost = 'dxc.w8wts.net'
dxClPort = 7373
user_call_sign = 'n2adr'
bandLabels = ['6', '2', '1.25', '70cm', '33cm', '23cm', 'WWV']
bandState['WWV'] = (19990000, 10000, 'AM')
BandEdge['WWV'] = (19500000, 20500000)
use_rx_udp = 17 # Get ADC samples from UDP
rx_udp_ip = "192.168.1.199" # Sample source IP address
rx_udp_port = 0xAA53 # Sample source UDP port
#rx_clk38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal
#rx_udp_clock = rx_clk38 * 32 // 2 // 9 # ADC sample rate in Hertz
sample_rate = 96000 # 96, 192, 384, 768, 1152 (for 69120/3/10)
display_fraction = 1.00
fft_size_multiplier = 16
tx_ip = "192.168.1.201"
tx_audio_port = 0xBC79
add_imd_button = 1
add_fdx_button = 1
+498
View File
@@ -0,0 +1,498 @@
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, struct, socket, traceback
from quisk_hardware_model import Hardware as BaseHardware
import _quisk as QS
DEBUG = 0
class Adf4351: # class to hold adf4351 attributes
def __init__(self, receiver, clock, r_counter):
self.receiver = receiver
self.clock = clock
self.r_counter = r_counter
self.int_mode = 1 # integer one, fractional zero
self.band_sel_clock_div = 40
self.aux_rf_out = 0b000 # enable 1/0, power 00 to 11
self.frac_value = 0
self.modulus = 23
self.changed = 0
class Preamp: # Lone Wire Bus control of preamp for 2 meters and 70 cm
def __init__(self):
self.IP = '192.168.1.194'
self.PORT = 0x3A00 + 67
# Create a socket for the Lone Wire Bus control of the preamp
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.connect((self.IP, self.PORT))
self.socket.settimeout(0)
self.want_preamp = self.have_preamp = '\000'
def ChangeBand(self, band):
if band == '2':
self.want_preamp = '\001'
elif band == '70cm':
self.want_preamp = '\002'
else:
self.want_preamp = '\000'
#print ("ChangeBand", band)
def HeartBeat(self):
try:
data = self.socket.recv(4096)
except:
pass
else:
if len(data) == 1:
self.have_preamp = data
#print ("Got data 0x%X" % ord(data))
if self.want_preamp != self.have_preamp:
self.socket.send(self.want_preamp)
#print ("Send data 0x%X" % ord(self.want_preamp))
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.vfo_frequency = 52000000
self.vfo_sample_rate = conf.sample_rate
self.vfo_test = 0 # JIM
self.tx_frequency = 0
self.CorrectTxDc = {
'23cm':(1270.0, 0.167081, 0.150557),
'2':(146.0, 0.018772, 0.038658),
'33cm':(915.0, 0.140150, 0.051967),
'6':(52.0, 0.020590, 0.024557),
'70cm':(435.0, 0.004495, 0.096879),
'1.25':(223.5, 0.042958, 0.055212),
}
self.rx_clock38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal
#rx_udp_clock = rx_clock38 * 32 // 2 // 9 # ADC sample rate in Hertz
self.rx_udp_clock_nominal = 69120000 # rate to display
self.tx_clock80 = 80000000 + 14
self.firmware_version = None # firmware version is initially unknown
self.rx_udp_socket = None
self.tx_udp_socket = None
self.got_rx_udp_status = ''
self.got_tx_udp_status = ''
self.band = ''
self.rx_phase0 = self.rx_phase1 = 0
self.tx_phase = 0
self.button_PTT = 0
self.mode_is_cw = 0
self.scan_enable = 0
self.scan_blocks = 0
self.scan_samples = 1
self.scan_phase = 0
self.fft_scan_valid = 0.84
self.preamp = Preamp()
self.Rx4351 = Adf4351(True, self.rx_clock38, 8)
self.Tx4351 = Adf4351(False, 10700000, 2)
self.Tx4351.aux_rf_out = 0b000 # enable aux RF out 0b111 or turn off 0b000
self.decim3 = 10
self.SetDecim(192000)
self.var_rates = ['31X', '19X', '9X', '5X', '3X', '2X', '1728', '1152', '768', '384', '192', '96', '48'] # supported sample rates as strings
self.index = 0
self.repeater_freq = None
self.DcI, self.DcQ = (0.0, 0.0)
self.NewAdf4351(self.Rx4351, 146E6)
self.NewAdf4351(self.Tx4351, 146E6)
self.NewAd9951(52e6)
self.NewUdpStatus()
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.tx_frequency = tx_freq
if not self.Rx4351.frequency - 3E6 < vfo_freq < self.Rx4351.frequency + 3E6:
self.NewAdf4351(self.Rx4351, vfo_freq)
self.vfo_frequency = -1
self.NewAd9951(tx_freq)
if abs(self.ad9951_freq - 10.7e6) > 15000:
self.NewAdf4351(self.Tx4351, tx_freq)
self.NewAd9951(tx_freq)
self.NewAd9951(tx_freq)
if self.vfo_frequency != vfo_freq:
self.vfo_frequency = vfo_freq
self.scan_deltaf = int(1152E3 * self.fft_scan_valid + 0.5)
self.scan_phase = int(1152.E3 * self.fft_scan_valid / self.conf.rx_udp_clock * 2.0**32 + 0.5)
self.scan_vfo0 = vfo_freq
rx_phase1 = int((vfo_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
if self.scan_enable:
self.scan_vfo0 = self.scan_vfo0 - self.scan_deltaf * (self.scan_blocks - 1) // 2
rx_phase1 = rx_phase1 - int(self.scan_phase * (self.scan_blocks - 1) / 2.0 + 0.5)
self.rx_phase1 = rx_phase1 & 0xFFFFFFFF
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, vfo_freq)
QS.change_scan(self.scan_blocks, 1152000, self.fft_scan_valid, self.scan_vfo0, self.scan_deltaf)
if DEBUG:
#print( "vfo", vfo_freq, "adf4351", self.Rx4351.frequency, "phase", rx_phase1, "rx_tune", self.Rx4351.frequency - vfo_freq, rx_tune_freq)
#print ("VFO", self.Rx4351.frequency + rx_tune_freq)
print ("Change to Tx %d Vfo %d; VFO %.0f = adf4351_freq %.0f + rx_tune_freq %.0f" % (tx_freq, vfo_freq,
self.Rx4351.frequency + rx_tune_freq, self.Rx4351.frequency, rx_tune_freq))
#print ("scan_enable %d, scan_blocks %d, scan_vfo0 %d, scan_deltaf %d" % (self.scan_enable, self.scan_blocks, self.scan_vfo0, self.scan_deltaf))
else:
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, self.vfo_frequency)
rx_phase0 = int((tx_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
self.rx_phase0 = rx_phase0 & 0xFFFFFFFF
self.NewUdpStatus()
if self.application.bottom_widgets:
Rx1 = self.Rx4351.frequency * 1e-6
Rx2 = (self.ReturnVfoFloat() - self.Rx4351.frequency) * 1e-6
t = "Rx Div %d; ADF4351 %.6f + rx_tune %.6f = %.6f Tx Adf4351 %.6f AD9951 %.6f" % (
2**self.Rx4351.rf_divider, Rx1, Rx2, Rx1 + Rx2, self.Tx4351.frequency * 1e-6, self.ad9951_freq * 1e-6)
self.application.bottom_widgets.UpdateText(t)
return tx_freq, vfo_freq
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
if offset is None: # Return True if frequency change is complete
self.HeartBeat()
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
if offset == 0: # Change back to the original frequency
if self.repeater_freq is None: # Frequency was already reset
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
self.ChangeFrequency(self.repeater_freq, self.vfo_frequency)
self.repeater_freq = None
else: # Shift to repeater input frequency
self.repeater_freq = self.tx_frequency
offset = int(offset * 1000) # Convert kHz to Hz
self.ChangeFrequency(self.tx_frequency + offset, self.vfo_frequency)
return False
def ReturnVfoFloat(self): # Return the accurate VFO as a float
rx_phase1 = int((self.vfo_frequency - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
return self.Rx4351.frequency + rx_tune_freq
def open(self):
##self.application.config_screen.config.tx_phase.Enable(1)
# Create the proper broadcast address for rx_udp_ip.
nm = self.conf.rx_udp_ip_netmask.split('.')
ip = self.conf.rx_udp_ip.split('.')
nm = list(map(int, nm))
ip = list(map(int, ip))
bc = ''
for i in range(4):
x = (ip[i] | ~ nm[i]) & 0xFF
bc = bc + str(x) + '.'
self.broadcast_addr = bc[:-1]
# This socket is used for the Simple Network Discovery Protocol by AE4JY
self.socket_sndp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket_sndp.setblocking(0)
self.socket_sndp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.sndp_request = chr(56) + chr(0) + chr(0x5A) + chr(0xA5) + chr(0) * 52
self.sndp_rx_active = True
# conf.rx_udp_port is used for returning ADC samples
# conf.rx_udp_port + 1 is used for control
self.rx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rx_udp_socket.setblocking(0)
self.rx_udp_socket.connect((self.conf.rx_udp_ip, self.conf.rx_udp_port + 1))
# conf.tx_audio_port + 1 is used for control
if self.conf.tx_ip:
self.sndp_tx_active = True
self.tx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.tx_udp_socket.setblocking(0)
self.tx_udp_socket.connect((self.conf.tx_ip, self.conf.tx_audio_port + 1))
else:
self.sndp_tx_active = False
QS.change_rates(96000, 0, 96000, 0)
self.application.test1Button.Enable(0)
return QS.open_rx_udp(self.conf.rx_udp_ip, self.conf.rx_udp_port)
def close(self):
if self.rx_udp_socket:
self.rx_udp_socket.close()
self.rx_udp_socket = None
if self.tx_udp_socket:
self.tx_udp_socket.close()
self.tx_udp_socket = None
def PrintStatus(self, msg, string):
print (msg, ' ', end=' ')
print (string[0:2], end=' ')
for c in string[2:]:
print ("%2X" % ord(c), end=' ')
print ()
def GetFirmwareVersion(self):
return self.firmware_version
def ChangeMode(self, mode):
# mode is a string: "USB", "AM", etc.
if mode in ("CWL", "CWU"):
self.mode_is_cw = 1
else:
self.mode_is_cw = 0
self.NewUdpStatus()
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
self.band = band
try:
freq, DcI, DcQ = self.CorrectTxDc[band]
except KeyError:
DcI, DcQ = (0.0, 0.0)
self.NewUdpCorrect(DcI, DcQ)
self.preamp.ChangeBand(band)
def NewUdpCorrect(self, DcI, DcQ):
self.DcI = DcI
self.DcQ = DcQ
QS.set_udp_tx_correct(DcI, DcQ, 0.828)
self.NewUdpStatus()
def PrintUdpCorrect(self):
for band in self.CorrectTxDc:
freq, DcI, DcQ = self.CorrectTxDc[band]
print ("'%s':(%.1f, %.6f, %.6f)," % (band, freq, DcI, DcQ))
def OnButtonPTT(self, event):
btn = event.GetEventObject()
if btn.GetValue(): # Turn the software key bit on or off
self.button_PTT = 1
else:
self.button_PTT = 0
QS.set_key_down(self.button_PTT)
self.NewUdpStatus()
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
pass
def Sndp(self): # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address
try:
self.socket_sndp.sendto(self.sndp_request, (self.broadcast_addr, 48321))
except:
if DEBUG:
traceback.print_exc()
return
for i in range(5):
try:
data = self.socket_sndp.recv(1024)
except:
break
if len(data) != 56:
continue
if data[5:17] == 'QuiskUHFR-v1':
ip = self.conf.rx_udp_ip.split('.')
ip = list(map(int, ip))
ip = list(map(chr, ip))
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
self.sndp_rx_active = False
if DEBUG: print("SNDP success for Rx")
else:
t = (data[0:4] + chr(2) + data[5:37] + ip[3] + ip[2] + ip[1] + ip[0]
+ chr(0) * 12 + chr(self.conf.rx_udp_port & 0xFF) + chr(self.conf.rx_udp_port >> 8) + chr(0))
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
elif data[5:17] == 'QuiskUHFT-v1':
if self.conf.tx_ip:
ip = self.conf.tx_ip.split('.')
ip = list(map(int, ip))
ip = list(map(chr, ip))
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
self.sndp_tx_active = False
if DEBUG: print("SNDP success for Tx")
else:
t = (data[0:4] + chr(2) + data[5:37] + ip[3] + ip[2] + ip[1] + ip[0]
+ chr(0) * 12 + chr(self.conf.tx_audio_port & 0xFF) + chr(self.conf.tx_audio_port >> 8) + chr(0))
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
def HeartBeat(self):
if self.sndp_rx_active or self.sndp_tx_active:
self.Sndp()
return # SNDP is required
for i in range(10):
try: # receive the Rx status if any
data = self.rx_udp_socket.recv(1024)
if DEBUG > 1:
self.PrintStatus(' gotRx ', data)
except:
break
else:
if data[0:2] == 'Sx':
self.got_rx_udp_status = data
if self.tx_udp_socket:
for i in range(10):
try: # receive the Tx status if any
data = self.tx_udp_socket.recv(1024)
if DEBUG > 1:
self.PrintStatus(' gotTx ', data)
except:
break
else:
if data[0:2] == 'Sx':
self.got_tx_udp_status = data
if self.want_rx_udp_status[16:] == self.got_rx_udp_status[16:]: # The first part returns information from the hardware
self.firmware_version = ord(self.got_rx_udp_status[2]) # Firmware version is returned here
self.Rx4351.changed = 0
else:
if DEBUG > 1:
self.PrintStatus('HaveRx', self.got_rx_udp_status[0:20])
self.PrintStatus('sendRx', self.want_rx_udp_status[0:20])
try:
self.rx_udp_socket.send(self.want_rx_udp_status)
except:
#traceback.print_exc()
pass
if not self.tx_udp_socket:
pass
elif self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]: # The first part returns information from the hardware
self.Tx4351.changed = 0
self.Tx9951_changed = 0
else:
if DEBUG > 1:
self.PrintStatus('HaveTx', self.got_rx_udp_status[0:20])
self.PrintStatus('sendTx', self.want_rx_udp_status[0:20])
try:
self.tx_udp_socket.send(self.want_rx_udp_status)
except:
#traceback.print_exc()
pass
if 0:
self.rx_udp_socket.send('Qs')
self.preamp.HeartBeat()
def VarDecimGetChoices(self): # return text labels for the control
return self.var_rates
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate ksps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimRange(self):
return (48000, 1152000)
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set decimation before the call to open()
rate = self.application.vardecim_set # May be None or from different hardware
try:
rate = rate // 1000
if rate > 1152:
rate = 1152
index = self.var_rates.index(str(rate))
except:
rate = 192
index = self.var_rates.index(str(rate))
self.index = index
rate = self.var_rates[index]
if rate[-1] == 'X':
self.scan_enable = 1
self.scan_blocks = int(rate[0:-1])
self.scan_samples = self.application.fft_size
self.decim1 = 2
self.decim2 = 3
rate = 1152000 * self.scan_blocks
else:
self.scan_enable = 0
self.scan_blocks = 0
rate = int(rate)
rate = rate * 1000
self.SetDecim(rate)
vfo = self.vfo_frequency
self.vfo_frequency = -1
self.vfo_sample_rate = rate
self.ChangeFrequency(self.tx_frequency, vfo)
self.NewUdpStatus()
return rate
def SetDecim(self, rate):
# self.decim1, decim2, decim3 are the first, second, third decimations in the hardware
if rate >= 1152000:
self.decim1 = 2
elif rate >= 192000:
self.decim1 = 3
elif rate == 96000:
self.decim1 = 6
else:
self.decim1 = 12
self.decim2 = self.rx_udp_clock_nominal // rate // self.decim1 // self.decim3
def NewUdpStatus(self):
# Start of 16 bytes sent to the hardware:
s = "Sx" # 0:2 Fixed string
s += chr(0) # 2 Version number is returned here
s += chr(0) # 3
s += chr(0) * 12 # 4:16
# Start of 80 bytes of data sent to the hardware:
s += chr( 6 - 1) # 0 Variable decimation less one channel 0 first
s += chr(12 - 1) # 1 Variable decimation less one channel 0 second
s += struct.pack("<L", self.rx_phase0) # 2: 6 Channel zero Rx tune phase
s += struct.pack("<L", self.rx_phase1) # 6:10 Channel one Rx tune phase)
s += chr(0x3 | self.scan_enable << 2 | self.Rx4351.changed << 3 |
self.Tx4351.changed << 4 | self.Tx9951_changed << 5 |
self.button_PTT << 6 | self.mode_is_cw << 7) # 10 Flags
# 0: enable samples on channel 0
# 1: enable samples on channel 1
# 2: enable scan on channel 1
# 3: the receive adf4351 registers have changed
# 4: the transmit adf4351 registers have changed
# 5: the transmit ad9951 register changed
# 6: the PTT button is down
# 7: the mode is CWU or CWL
s += chr(self.scan_blocks) # 11 For scan, the number of frequency blocks
s += struct.pack("<H", self.scan_samples) # 12:14 For scan, the number of samples per block
s += struct.pack("<L", self.scan_phase) # 14:18 For scan, the tuning phase increment
s += chr(self.decim1 - 1) # 18 Variable decimation less one channel 1 first
s += chr(self.decim2 - 1) # 19 Variable decimation less one channel 1 second
s += self.Rx4351.regs # 20:44 Receive adf4351; six 32-bit registers, 24 bytes
s += self.Tx4351.regs # 44:68 Transmit adf4351; six 32-bit registers, 24 bytes
s += self.ad9951_data # 68:74 Transmit ad9951: data length, instruction, 4 bytes of data
DcI = int(self.DcI * 32767.0)
DcQ = int(self.DcQ * 32767.0)
s += struct.pack("<h", DcI) # 74:76 Transmit DC correction for I channel
s += struct.pack("<h", DcQ) # 76:78 Transmit DC correction for Q channel
s += chr(0) * (96 - len(s)) # Fixed length message 16 + 80
self.want_rx_udp_status = s
def NewAd9951(self, tx_freq):
# Fpfd = Fref / 2 / R = Fvco / N
# Fout = Fvco / 2**D and is twice the transmit frequency
# Fref = 2R(2**d)Fout / N
# Fout = Fref * N / 2 / R / 2**D
adf = self.Tx4351
freq = 2.0 * adf.r_counter * (2 ** adf.rf_divider) / adf.int_value * (tx_freq * 2.0)
phase = int(freq / self.tx_clock80 * 2.0**32 + 0.5)
try:
self.ad9951_data = chr(40) + struct.pack("<L", phase ) + chr(4)
except struct.error:
self.ad9951_data = chr(0) * 6
self.ad9951_freq = float(phase) * self.tx_clock80 / 2 ** 32
##adf.frequency = 0.5 * self.ad9951_freq * adf.int_value / 2.0 / adf.r_counter / (2 ** adf.rf_divider)
self.Tx9951_changed = 1
def NewAdf4351(self, adf, vfo_freq):
# Set the adf4351 to the nearest integer-mode frequency
Fpfd = adf.clock / 2.0 / adf.r_counter
if adf.receiver:
vfo_freq += Fpfd * self.vfo_test / 8 # test the VFO at an offset from the center
vfo = vfo_freq * 2 # Local oscillator runs at 2X frequency
for div in range(0, 7):
Fvco = vfo * 2 ** div
if 2200E6 <= Fvco < 4400E6:
adf.rf_divider = div
adf.int_value = int(vfo * 2 ** div / Fpfd + 0.5)
break
else:
if vfo < 500e6:
adf.rf_divider = 6
adf.int_value = int(2200E6 / Fpfd)
else:
adf.rf_divider = 0
adf.int_value = int(4400E6 / Fpfd)
adf.frequency = 0.5 * Fpfd * adf.int_value / 2 ** div
if DEBUG:
print ("int, div, Fvco, ADF4351", adf.int_value, div, int(vfo * 2 ** div / 1e6), adf.frequency)
print ("New adf4351_freq", adf.frequency)
# Number of bits for each field:
# intNmode 1
# aux_rf_out 3
# rf_divider 3
# band_sel_clock_div 8
# r_counter 10 Fpfd = Fref / 2 / r_counter
# int_value 16 Fvco = Fpfd * (int_value + frac_value / modulus)
# frac_value 12
# modulus 12
reg = 0b00000000000000000000000000000000 # Register 0
reg = reg | adf.int_value << 15 | adf.frac_value << 3
s = struct.pack("<L", reg)
reg = 0b00001000000000001000000000000001 # Register 1
reg = reg | adf.modulus << 3
s += struct.pack("<L", reg)
reg = 0b00000001000000000001111001000010 # Register 2
reg = reg | adf.r_counter << 14 | adf.int_mode << 8 | adf.int_mode << 7
s += struct.pack("<L", reg)
reg = 0b00000000000001000000000000000011 # Register 3
reg = reg | adf.int_mode << 22 | adf.int_mode << 21
s += struct.pack("<L", reg)
reg = 0b00000000100000000000010000111100 # Register 4
reg = reg | adf.rf_divider << 20 | adf.band_sel_clock_div << 12 | adf.aux_rf_out << 6
s += struct.pack("<L", reg)
reg = 0b00000000010110000000000000000101 # Register 5
s += struct.pack("<L", reg)
adf.regs = s
adf.changed = 1
def TestVfoPlus(self, event):
self.vfo_test += 1
self.Rx4351.frequency = 1
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
def TestVfoMinus(self, event):
self.vfo_test -= 1
self.Rx4351.frequency = 1
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
+526
View File
@@ -0,0 +1,526 @@
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, struct, socket, traceback
from quisk_hardware_model import Hardware as BaseHardware
import _quisk as QS
DEBUG = 0
class Adf4351: # class to hold adf4351 attributes
def __init__(self, receiver, clock, r_counter):
self.receiver = receiver
self.clock = clock
self.r_counter = r_counter
self.int_mode = 1 # integer one, fractional zero
self.band_sel_clock_div = 40
self.aux_rf_out = 0b000 # enable 1/0, power 00 to 11
self.frac_value = 0
self.modulus = 23
self.changed = 0
class Preamp: # Lone Wire Bus control of preamp for 2 meters and 70 cm
def __init__(self):
self.IP = '192.168.1.194'
self.PORT = 0x3A00 + 67
# Create a socket for the Lone Wire Bus control of the preamp
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.connect((self.IP, self.PORT))
self.socket.settimeout(0)
self.want_preamp = self.have_preamp = b'\000'
def ChangeBand(self, band):
if band == '2':
self.want_preamp = b'\001'
elif band == '70cm':
self.want_preamp = b'\002'
else:
self.want_preamp = b'\000'
#print ("Preamp ChangeBand", band)
def HeartBeat(self):
try:
data = self.socket.recv(4096)
except:
pass
else:
if len(data) == 1:
self.have_preamp = data
#print ("Got data 0x%X" % ord(data))
if self.want_preamp != self.have_preamp:
self.socket.send(self.want_preamp)
#print ("Send data 0x%X" % ord(self.want_preamp))
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.vfo_frequency = 52000000
self.vfo_sample_rate = conf.sample_rate
self.vfo_test = 0 # JIM
self.tx_frequency = 0
self.CorrectTxDc = {
'23cm':(1270.0, 0.167081, 0.150557),
'2':(146.0, 0.018772, 0.038658),
'33cm':(915.0, 0.140150, 0.051967),
'6':(52.0, 0.020590, 0.024557),
'70cm':(435.0, 0.004495, 0.096879),
'1.25':(223.5, 0.042958, 0.055212),
}
self.rx_clock38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal
#rx_udp_clock = rx_clock38 * 32 // 2 // 9 # ADC sample rate in Hertz
self.rx_udp_clock_nominal = 69120000 # rate to display
self.tx_clock80 = 80000000 + 14
self.firmware_version = None # firmware version is initially unknown
self.rx_udp_socket = None
self.tx_udp_socket = None
self.got_rx_udp_status = bytearray()
self.got_tx_udp_status = bytearray()
self.band = ''
self.rx_phase0 = self.rx_phase1 = 0
self.tx_phase = 0
self.button_PTT = 0
self.mode_is_cw = 0
self.scan_enable = 0
self.scan_blocks = 0
self.scan_samples = 1
self.scan_phase = 0
self.fft_scan_valid = 0.84
self.preamp = Preamp()
self.Rx4351 = Adf4351(True, self.rx_clock38, 8)
self.Tx4351 = Adf4351(False, 10700000, 2)
self.Tx4351.aux_rf_out = 0b000 # enable aux RF out 0b111 or turn off 0b000
self.decim3 = 10
self.SetDecim(192000)
self.var_rates = ['31X', '19X', '9X', '5X', '3X', '2X', '1728', '1152', '768', '384', '192', '96', '48'] # supported sample rates as strings
self.index = 0
self.repeater_freq = None
self.DcI, self.DcQ = (0.0, 0.0)
self.NewAdf4351(self.Rx4351, 146E6)
self.NewAdf4351(self.Tx4351, 146E6)
self.NewAd9951(52e6)
self.NewUdpStatus()
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.tx_frequency = tx_freq
if not self.Rx4351.frequency - 3E6 < vfo_freq < self.Rx4351.frequency + 3E6:
self.NewAdf4351(self.Rx4351, vfo_freq)
self.vfo_frequency = -1
self.NewAd9951(tx_freq)
if abs(self.ad9951_freq - 10.7e6) > 15000:
self.NewAdf4351(self.Tx4351, tx_freq)
self.NewAd9951(tx_freq)
self.NewAd9951(tx_freq)
if self.vfo_frequency != vfo_freq:
self.vfo_frequency = vfo_freq
self.scan_deltaf = int(1152E3 * self.fft_scan_valid + 0.5)
self.scan_phase = int(1152.E3 * self.fft_scan_valid / self.conf.rx_udp_clock * 2.0**32 + 0.5)
self.scan_vfo0 = vfo_freq
rx_phase1 = int((vfo_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
if self.scan_enable:
self.scan_vfo0 = self.scan_vfo0 - self.scan_deltaf * (self.scan_blocks - 1) // 2
rx_phase1 = rx_phase1 - int(self.scan_phase * (self.scan_blocks - 1) / 2.0 + 0.5)
self.rx_phase1 = rx_phase1 & 0xFFFFFFFF
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, vfo_freq)
QS.change_scan(self.scan_blocks, 1152000, self.fft_scan_valid, self.scan_vfo0, self.scan_deltaf)
if DEBUG:
#print( "vfo", vfo_freq, "adf4351", self.Rx4351.frequency, "phase", rx_phase1, "rx_tune", self.Rx4351.frequency - vfo_freq, rx_tune_freq)
#print ("VFO", self.Rx4351.frequency + rx_tune_freq)
print ("Change to Tx %d Vfo %d; VFO %.0f = adf4351_freq %.0f + rx_tune_freq %.0f" % (tx_freq, vfo_freq,
self.Rx4351.frequency + rx_tune_freq, self.Rx4351.frequency, rx_tune_freq))
#print ("scan_enable %d, scan_blocks %d, scan_vfo0 %d, scan_deltaf %d" % (self.scan_enable, self.scan_blocks, self.scan_vfo0, self.scan_deltaf))
else:
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, self.vfo_frequency)
rx_phase0 = int((tx_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
self.rx_phase0 = rx_phase0 & 0xFFFFFFFF
self.NewUdpStatus()
if self.application.bottom_widgets:
Rx1 = self.Rx4351.frequency * 1e-6
Rx2 = (self.ReturnVfoFloat() - self.Rx4351.frequency) * 1e-6
t = "Rx Div %d; ADF4351 %.6f + rx_tune %.6f = %.6f Tx Adf4351 %.6f AD9951 %.6f" % (
2**self.Rx4351.rf_divider, Rx1, Rx2, Rx1 + Rx2, self.Tx4351.frequency * 1e-6, self.ad9951_freq * 1e-6)
self.application.bottom_widgets.UpdateText(t)
return tx_freq, vfo_freq
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
if offset is None: # Return True if frequency change is complete
self.HeartBeat()
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
if offset == 0: # Change back to the original frequency
if self.repeater_freq is None: # Frequency was already reset
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
self.ChangeFrequency(self.repeater_freq, self.vfo_frequency)
self.repeater_freq = None
else: # Shift to repeater input frequency
self.repeater_freq = self.tx_frequency
offset = int(offset * 1000) # Convert kHz to Hz
self.ChangeFrequency(self.tx_frequency + offset, self.vfo_frequency)
return False
def ReturnVfoFloat(self): # Return the accurate VFO as a float
rx_phase1 = int((self.vfo_frequency - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
return self.Rx4351.frequency + rx_tune_freq
def open(self):
##self.application.config_screen.config.tx_phase.Enable(1)
# Create the proper broadcast address for rx_udp_ip.
nm = self.conf.rx_udp_ip_netmask.split('.')
ip = self.conf.rx_udp_ip.split('.')
nm = list(map(int, nm))
ip = list(map(int, ip))
bc = ''
for i in range(4):
x = (ip[i] | ~ nm[i]) & 0xFF
bc = bc + str(x) + '.'
self.broadcast_addr = bc[:-1]
# This socket is used for the Simple Network Discovery Protocol by AE4JY
self.socket_sndp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket_sndp.setblocking(0)
self.socket_sndp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.sndp_request = bytearray()
self.sndp_request.append(56)
self.sndp_request.append(0)
self.sndp_request.append(0x5A)
self.sndp_request.append(0xA5)
self.sndp_request += bytearray(52)
self.sndp_rx_active = True
# conf.rx_udp_port is used for returning ADC samples
# conf.rx_udp_port + 1 is used for control
self.rx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rx_udp_socket.setblocking(0)
self.rx_udp_socket.connect((self.conf.rx_udp_ip, self.conf.rx_udp_port + 1))
# conf.tx_audio_port + 1 is used for control
if self.conf.tx_ip:
self.sndp_tx_active = True
self.tx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.tx_udp_socket.setblocking(0)
self.tx_udp_socket.connect((self.conf.tx_ip, self.conf.tx_audio_port + 1))
else:
self.sndp_tx_active = False
QS.change_rates(96000, 0, 96000, 0)
self.application.test1Button.Enable(0)
return QS.open_rx_udp(self.conf.rx_udp_ip, self.conf.rx_udp_port)
def close(self):
if self.rx_udp_socket:
self.rx_udp_socket.close()
self.rx_udp_socket = None
if self.tx_udp_socket:
self.tx_udp_socket.close()
self.tx_udp_socket = None
def PrintStatus(self, msg, string):
print (msg, ' ', end=' ')
print (string[0:2], end=' ')
for c in string[2:]:
print ("%2X" % ord(c), end=' ')
print ()
def GetFirmwareVersion(self):
return self.firmware_version
def ChangeMode(self, mode):
# mode is a string: "USB", "AM", etc.
if mode in ("CWL", "CWU"):
self.mode_is_cw = 1
else:
self.mode_is_cw = 0
self.NewUdpStatus()
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
self.band = band
try:
freq, DcI, DcQ = self.CorrectTxDc[band]
except KeyError:
DcI, DcQ = (0.0, 0.0)
self.NewUdpCorrect(DcI, DcQ)
self.preamp.ChangeBand(band)
def NewUdpCorrect(self, DcI, DcQ):
self.DcI = DcI
self.DcQ = DcQ
QS.set_udp_tx_correct(DcI, DcQ, 0.828)
self.NewUdpStatus()
def PrintUdpCorrect(self):
for band in self.CorrectTxDc:
freq, DcI, DcQ = self.CorrectTxDc[band]
print ("'%s':(%.1f, %.6f, %.6f)," % (band, freq, DcI, DcQ))
def OnButtonPTT(self, event):
btn = event.GetEventObject()
if btn.GetValue(): # Turn the software key bit on or off
self.button_PTT = 1
else:
self.button_PTT = 0
QS.set_key_down(self.button_PTT)
self.NewUdpStatus()
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
pass
def Sndp(self): # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address
try:
self.socket_sndp.sendto(self.sndp_request, (self.broadcast_addr, 48321))
except:
if DEBUG:
traceback.print_exc()
return
for i in range(5):
try:
data = self.socket_sndp.recv(1024)
except:
break
if len(data) != 56:
continue
if data[5:17] == b'QuiskUHFR-v1':
ip = self.conf.rx_udp_ip.split('.')
ip = list(map(int, ip))
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
self.sndp_rx_active = False
if DEBUG: print("SNDP success for Rx")
else:
t = bytearray()
t += data[0:4]
t.append(2)
t += data[5:37]
t.append(ip[3])
t.append(ip[2])
t.append(ip[1])
t.append(ip[0])
t += bytearray(12)
t.append(self.conf.rx_udp_port & 0xFF)
t.append(self.conf.rx_udp_port >> 8)
t.append(0)
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
elif data[5:17] == b'QuiskUHFT-v1':
if self.conf.tx_ip:
ip = self.conf.tx_ip.split('.')
ip = list(map(int, ip))
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
self.sndp_tx_active = False
if DEBUG: print("SNDP success for Tx")
else:
t = bytearray()
t += data[0:4]
t.append(2)
t += data[5:37]
t.append(ip[3])
t.append(ip[2])
t.append(ip[1])
t.append(ip[0])
t += bytearray(12)
t.append(self.conf.tx_audio_port & 0xFF)
t.append(self.conf.tx_audio_port >> 8)
t.append(0)
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
def HeartBeat(self):
if self.sndp_rx_active or self.sndp_tx_active:
if DEBUG: print ("Try SNDP", self.sndp_rx_active, self.sndp_tx_active)
self.Sndp()
return # SNDP is required
for i in range(10):
try: # receive the Rx status if any
data = self.rx_udp_socket.recv(1024)
if DEBUG > 1:
self.PrintStatus(' gotRx ', data)
except:
break
else:
if data[0:2] == b'Sx':
self.got_rx_udp_status = data
if self.tx_udp_socket:
for i in range(10):
try: # receive the Tx status if any
data = self.tx_udp_socket.recv(1024)
if DEBUG > 1:
self.PrintStatus(' gotTx ', data)
except:
break
else:
if data[0:2] == b'Sx':
self.got_tx_udp_status = data
if self.want_rx_udp_status[16:] == self.got_rx_udp_status[16:]: # The first part returns information from the hardware
self.firmware_version = self.got_rx_udp_status[2] # Firmware version is returned here
self.Rx4351.changed = 0
else:
if DEBUG > 1:
self.PrintStatus('HaveRx', self.got_rx_udp_status[0:20])
self.PrintStatus('sendRx', self.want_rx_udp_status[0:20])
try:
self.rx_udp_socket.send(self.want_rx_udp_status)
except:
#traceback.print_exc()
pass
if not self.tx_udp_socket:
pass
elif self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]: # The first part returns information from the hardware
self.Tx4351.changed = 0
self.Tx9951_changed = 0
else:
if DEBUG > 1:
self.PrintStatus('HaveTx', self.got_rx_udp_status[0:20])
self.PrintStatus('sendTx', self.want_rx_udp_status[0:20])
try:
self.tx_udp_socket.send(self.want_rx_udp_status)
except:
#traceback.print_exc()
pass
if 0:
self.rx_udp_socket.send(b'Qs')
self.preamp.HeartBeat()
def VarDecimGetChoices(self): # return text labels for the control
return self.var_rates
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate ksps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimRange(self):
return (48000, 1152000)
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set decimation before the call to open()
rate = self.application.vardecim_set # May be None or from different hardware
try:
rate = rate // 1000
if rate > 1152:
rate = 1152
index = self.var_rates.index(str(rate))
except:
rate = 192
index = self.var_rates.index(str(rate))
self.index = index
rate = self.var_rates[index]
if rate[-1] == 'X':
self.scan_enable = 1
self.scan_blocks = int(rate[0:-1])
self.scan_samples = self.application.fft_size
self.decim1 = 2
self.decim2 = 3
rate = 1152000 * self.scan_blocks
else:
self.scan_enable = 0
self.scan_blocks = 0
rate = int(rate)
rate = rate * 1000
self.SetDecim(rate)
vfo = self.vfo_frequency
self.vfo_frequency = -1
self.vfo_sample_rate = rate
self.ChangeFrequency(self.tx_frequency, vfo)
self.NewUdpStatus()
return rate
def SetDecim(self, rate):
# self.decim1, decim2, decim3 are the first, second, third decimations in the hardware
if rate >= 1152000:
self.decim1 = 2
elif rate >= 192000:
self.decim1 = 3
elif rate == 96000:
self.decim1 = 6
else:
self.decim1 = 12
self.decim2 = self.rx_udp_clock_nominal // rate // self.decim1 // self.decim3
def NewUdpStatus(self):
# Start of 16 bytes sent to the hardware:
s = bytearray()
s += b"Sx" # 0:2 Fixed string
s.append(0) # 2 Version number is returned here
s.append(0) # 3
s += bytes(12) # 4:16
# Start of 80 bytes of data sent to the hardware:
s.append( 6 - 1) # 0 Variable decimation less one channel 0 first
s.append(12 - 1) # 1 Variable decimation less one channel 0 second
s += struct.pack("<L", self.rx_phase0) # 2: 6 Channel zero Rx tune phase
s += struct.pack("<L", self.rx_phase1) # 6:10 Channel one Rx tune phase)
s.append(0x3 | self.scan_enable << 2 | self.Rx4351.changed << 3 |
self.Tx4351.changed << 4 | self.Tx9951_changed << 5 |
self.button_PTT << 6 | self.mode_is_cw << 7) # 10 Flags
# 0: enable samples on channel 0
# 1: enable samples on channel 1
# 2: enable scan on channel 1
# 3: the receive adf4351 registers have changed
# 4: the transmit adf4351 registers have changed
# 5: the transmit ad9951 register changed
# 6: the PTT button is down
# 7: the mode is CWU or CWL
s.append(self.scan_blocks) # 11 For scan, the number of frequency blocks
s += struct.pack("<H", self.scan_samples) # 12:14 For scan, the number of samples per block
s += struct.pack("<L", self.scan_phase) # 14:18 For scan, the tuning phase increment
s.append(self.decim1 - 1) # 18 Variable decimation less one channel 1 first
s.append(self.decim2 - 1) # 19 Variable decimation less one channel 1 second
s += self.Rx4351.regs # 20:44 Receive adf4351; six 32-bit registers, 24 bytes
s += self.Tx4351.regs # 44:68 Transmit adf4351; six 32-bit registers, 24 bytes
s += self.ad9951_data # 68:74 Transmit ad9951: data length, instruction, 4 bytes of data
DcI = int(self.DcI * 32767.0)
DcQ = int(self.DcQ * 32767.0)
s += struct.pack("<h", DcI) # 74:76 Transmit DC correction for I channel
s += struct.pack("<h", DcQ) # 76:78 Transmit DC correction for Q channel
s += bytearray(96 - len(s)) # Fixed length message 16 + 80
self.want_rx_udp_status = s
def NewAd9951(self, tx_freq):
# Fpfd = Fref / 2 / R = Fvco / N
# Fout = Fvco / 2**D and is twice the transmit frequency
# Fref = 2R(2**d)Fout / N
# Fout = Fref * N / 2 / R / 2**D
adf = self.Tx4351
freq = 2.0 * adf.r_counter * (2 ** adf.rf_divider) / adf.int_value * (tx_freq * 2.0)
phase = int(freq / self.tx_clock80 * 2.0**32 + 0.5)
try:
self.ad9951_data = bytearray()
self.ad9951_data.append(40)
self.ad9951_data += struct.pack("<L", phase )
self.ad9951_data.append(4)
except struct.error:
self.ad9951_data = bytearray(6)
self.ad9951_freq = float(phase) * self.tx_clock80 / 2 ** 32
##adf.frequency = 0.5 * self.ad9951_freq * adf.int_value / 2.0 / adf.r_counter / (2 ** adf.rf_divider)
self.Tx9951_changed = 1
def NewAdf4351(self, adf, vfo_freq):
# Set the adf4351 to the nearest integer-mode frequency
Fpfd = adf.clock / 2.0 / adf.r_counter
if adf.receiver:
vfo_freq += Fpfd * self.vfo_test / 8 # test the VFO at an offset from the center
vfo = vfo_freq * 2 # Local oscillator runs at 2X frequency
for div in range(0, 7):
Fvco = vfo * 2 ** div
if 2200E6 <= Fvco < 4400E6:
adf.rf_divider = div
adf.int_value = int(vfo * 2 ** div / Fpfd + 0.5)
break
else:
if vfo < 500e6:
adf.rf_divider = 6
adf.int_value = int(2200E6 / Fpfd)
else:
adf.rf_divider = 0
adf.int_value = int(4400E6 / Fpfd)
adf.frequency = 0.5 * Fpfd * adf.int_value / 2 ** div
if DEBUG:
print ("int, div, Fvco, ADF4351", adf.int_value, div, int(vfo * 2 ** div / 1e6), adf.frequency)
print ("New adf4351_freq", adf.frequency)
# Number of bits for each field:
# intNmode 1
# aux_rf_out 3
# rf_divider 3
# band_sel_clock_div 8
# r_counter 10 Fpfd = Fref / 2 / r_counter
# int_value 16 Fvco = Fpfd * (int_value + frac_value / modulus)
# frac_value 12
# modulus 12
reg = 0b00000000000000000000000000000000 # Register 0
reg = reg | adf.int_value << 15 | adf.frac_value << 3
s = struct.pack("<L", reg)
reg = 0b00001000000000001000000000000001 # Register 1
reg = reg | adf.modulus << 3
s += struct.pack("<L", reg)
reg = 0b00000001000000000001111001000010 # Register 2
reg = reg | adf.r_counter << 14 | adf.int_mode << 8 | adf.int_mode << 7
s += struct.pack("<L", reg)
reg = 0b00000000000001000000000000000011 # Register 3
reg = reg | adf.int_mode << 22 | adf.int_mode << 21
s += struct.pack("<L", reg)
reg = 0b00000000100000000000010000111100 # Register 4
reg = reg | adf.rf_divider << 20 | adf.band_sel_clock_div << 12 | adf.aux_rf_out << 6
s += struct.pack("<L", reg)
reg = 0b00000000010110000000000000000101 # Register 5
s += struct.pack("<L", reg)
adf.regs = s
adf.changed = 1
def TestVfoPlus(self, event):
self.vfo_test += 1
self.Rx4351.frequency = 1
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
def TestVfoMinus(self, event):
self.vfo_test -= 1
self.Rx4351.frequency = 1
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
+145
View File
@@ -0,0 +1,145 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import wx, time
import _quisk as QS
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
self.correct_screen = None
self.num_rows_added = 1
start_row = app.widget_row # The first available row
start_col = app.button_start_col # The start of the button columns
b = app.QuiskPushbutton(frame, hardware.TestVfoPlus, 'Adf+')
bw, bh = b.GetMinSize()
gbs.Add(b, (start_row, start_col), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, hardware.TestVfoMinus, 'Adf-')
gbs.Add(b, (start_row, start_col + 1), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, self.OnBtnCorrect, 'Corr')
gbs.Add(b, (start_row, start_col + 2), flag=wx.EXPAND)
self.status_label = app.QuiskText(frame, 'Ready', bh)
gbs.Add(self.status_label, (start_row, start_col + 3), (1, 24), flag=wx.EXPAND)
def UpdateText(self, text):
self.status_label.SetLabel(text)
def OnBtnCorrect(self, event):
if self.correct_screen:
self.correct_screen.Raise()
else:
self.correct_screen = QCorrect(self, self.application.width)
class QCorrect(wx.Frame):
"""Create a window with DC adjustment controls"""
f_DcI = "DC adjustment for In-Phase %.6f"
f_DcQ = "DC adjustment for Quadrature %.6f"
def __init__(self, parent, width):
self.parent = parent
self.application = parent.application
self.config = parent.config
self.hardware = parent.hardware
t = "DC Null Adjustment"
wx.Frame.__init__(self, self.application.main_frame, -1, t, pos=(50, 100), style=wx.CAPTION)
panel = wx.Panel(self)
self.MakeControls(panel, width)
self.Show()
def MakeControls(self, panel, width): # Make controls for DC adjustment
self.old_DcI = self.DcI = self.hardware.DcI
self.old_DcQ = self.DcQ = self.hardware.DcQ
self.hardware.NewUdpCorrect(self.DcI, self.DcQ)
sl_max = width * 4 // 10 # maximum +/- value for slider
self.dc_scale = float(0.3) / sl_max
font = wx.Font(self.config.default_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
wx.FONTWEIGHT_NORMAL, False, self.config.quisk_typeface)
chary = self.GetCharHeight()
y = chary * 3 // 10
self.t_DcI = wx.StaticText(panel, -1, self.f_DcI % self.old_DcI, pos=(0, y))
self.t_DcI.SetFont(font)
y += self.t_DcI.GetSize().GetHeight()
self.DcI1 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcI1.GetSize().GetHeight()
self.DcI2 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcI2.GetSize().GetHeight()
self.PositionDcI(self.old_DcI)
self.t_DcQ = wx.StaticText(panel, -1, self.f_DcQ % self.old_DcQ, pos=(0, y))
self.t_DcQ.SetFont(font)
y += self.t_DcQ.GetSize().GetHeight()
self.DcQ1 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcQ1.GetSize().GetHeight()
self.DcQ2 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcQ2.GetSize().GetHeight()
sv = self.application.QuiskPushbutton(panel, self.OnBtnSave, 'Save')
cn = self.application.QuiskPushbutton(panel, self.OnBtnCancel, 'Cancel')
w, h = cn.GetSize().Get()
sv.SetSize((w, h))
y += h // 4
x = (width - w * 3) // 4
sv.SetPosition((x, y))
cn.SetPosition((x*3 + w*2, y))
sv.SetBackgroundColour('light blue')
cn.SetBackgroundColour('light blue')
y += h
y += h // 4
self.DcI1.SetBackgroundColour('aquamarine')
self.DcI2.SetBackgroundColour('orange')
self.DcQ1.SetBackgroundColour('aquamarine')
self.DcQ2.SetBackgroundColour('orange')
self.PositionDcQ(self.old_DcQ)
self.SetClientSize(wx.Size(width, y))
self.DcI1.Bind(wx.EVT_SCROLL, self.OnChange)
self.DcI2.Bind(wx.EVT_SCROLL, self.OnFineDcI)
self.DcQ1.Bind(wx.EVT_SCROLL, self.OnChange)
self.DcQ2.Bind(wx.EVT_SCROLL, self.OnFineDcQ)
def PositionDcI(self, dc): # set pos1, pos2 for I
pos2 = round(dc / self.dc_scale)
remain = dc - pos2 * self.dc_scale
pos1 = round(remain / self.dc_scale * 50.0)
self.DcI1.SetValue(pos1)
self.DcI2.SetValue(pos2)
def PositionDcQ(self, dc): # set pos1, pos2 for Q
pos2 = round(dc / self.dc_scale)
remain = dc - pos2 * self.dc_scale
pos1 = round(remain / self.dc_scale * 50.0)
self.DcQ1.SetValue(pos1)
self.DcQ2.SetValue(pos2)
def OnChange(self, event):
dc = self.dc_scale * self.DcI1.GetValue() / 50.0 + self.dc_scale * self.DcI2.GetValue()
if abs(dc) < self.dc_scale * 3.0 / 50.0:
dc = 0.0
self.t_DcI.SetLabel(self.f_DcI % dc)
self.DcI = dc
dc = self.dc_scale * self.DcQ1.GetValue() / 50.0 + self.dc_scale * self.DcQ2.GetValue()
if abs(dc) < self.dc_scale * 3.0 / 50.0:
dc = 0.0
self.t_DcQ.SetLabel(self.f_DcQ % dc)
self.DcQ = dc
self.hardware.NewUdpCorrect(self.DcI, self.DcQ)
def OnFineDcI(self, event): # re-center the fine slider when the coarse slider is adjusted
dc = self.dc_scale * self.DcI1.GetValue() / 50.0 + self.dc_scale * self.DcI2.GetValue()
self.PositionDcI(dc)
self.OnChange(event)
def OnFineDcQ(self, event): # re-center the fine slider when the coarse slider is adjusted
dc = self.dc_scale * self.DcQ1.GetValue() / 50.0 + self.dc_scale * self.DcQ2.GetValue()
self.PositionDcQ(dc)
self.OnChange(event)
def OnBtnSave(self, event):
self.hardware.CorrectTxDc[self.hardware.band] = (self.hardware.tx_frequency * 1E-6, self.DcI, self.DcQ)
self.hardware.PrintUdpCorrect()
self.parent.correct_screen = None
self.Destroy()
def OnBtnCancel(self, event=None):
self.hardware.NewUdpCorrect(self.old_DcI, self.old_DcQ)
self.parent.correct_screen = None
self.Destroy()
+10
View File
@@ -0,0 +1,10 @@
Microtelecom Perseus HF receiver
Prerequisite are:
libusb-1.0-0-dev (found in all major Linux distro)
libperseus-sdr as found in https://github.com/Microtelecom/libperseus-sdr/archive/master.zip
+1
View File
@@ -0,0 +1 @@
#
+7
View File
@@ -0,0 +1,7 @@
perseus2:
python2 setup.py build_ext --force --inplace
perseus3:
python3 setup.py build_ext --force --inplace
+505
View File
@@ -0,0 +1,505 @@
/*
*
* Microtelecom perseus HF receiver
*
* access module: exposes Python functions needed in quisk_hardware.py
* to control hardware
*
*/
#include <Python.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <complex.h>
#include <perseus-sdr.h>
#define IMPORT_QUISK_API
#include "quisk.h"
#include "filter.h"
// This module was written by Andrea Montefusco IW0HDV.
typedef union {
struct {
int32_t i;
int32_t q;
} __attribute__((__packed__)) iq;
struct {
uint8_t i1;
uint8_t i2;
uint8_t i3;
uint8_t i4;
uint8_t q1;
uint8_t q2;
uint8_t q3;
uint8_t q4;
} __attribute__((__packed__)) ;
} iq_sample;
// buffer size for libperseus-sdr
const static int nb = 6;
const static int bs = 1024;
// This module uses the Python interface to import symbols from the parent _quisk
// extension module. It must be linked with import_quisk_api.c. See the documentation
// at the start of import_quisk_api.c.
#define DEBUG 1
static int num_perseus = 0;
static perseus_descr *descr = 0;
static int sr = 48000;
static float freq = 7050000.0;
static int adc_dither = 0;
static int adc_preamp = 0;
static void quisk_stop_samples(void);
static const char *fname = "/tmp/quiskperseus";
static int rfd = 0;
static int wfd = 0;
static int running = 0;
static int wb_filter = 0;
// Called in a loop to read samples; called from the sound thread.
static int quisk_read_samples(complex double * cSamples)
{
//fprintf (stderr, "r"); fflush(stderr);
int n = read(rfd, cSamples, sizeof(complex double)*SAMP_BUFFER_SIZE);
//fprintf(stderr, "%d ", n);
if (n >= 0)
return n/sizeof(complex double); // return number of samples
else
return 0;
}
// Called in a loop to write samples; called from the sound thread.
static int quisk_write_samples(complex double * cSamples, int nSamples)
{
return 0;
}
//
// callback that writes in the output pipe IQ values as
// complex floating point
//
static int user_data_callback_c_f(void *buf, int buf_size, void *extra)
{
// The buffer received contains 24-bit IQ samples (6 bytes per sample)
// Here we save the received IQ samples as 32 bit
// (msb aligned) integer IQ samples.
uint8_t *samplebuf = (uint8_t*)buf;
int nSamples = buf_size/6;
int k;
iq_sample s;
// the 24 bit data is scaled to a 32bit value (so that the machine's
// natural signed arithmetic will work)
for (k=0; k < nSamples; k++) {
s.i1 = s.q1 = 0;
s.i2 = *samplebuf++;
s.i3 = *samplebuf++;
s.i4 = *samplebuf++;
s.q2 = *samplebuf++;
s.q3 = *samplebuf++;
s.q4 = *samplebuf++;
// move I/Q to complex number
complex double x = (double)(s.iq.i)*10 + (double)(s.iq.q)*10 * _Complex_I;
if (wfd > 0) {
int n = write(wfd, &x, sizeof(complex double));
if (n<0 && ! -EAGAIN )
fprintf(stderr, "perseus c: Can't write output file: %s, descriptor: %d\n", strerror(errno), wfd);
}
}
return 0;
}
// Start sample capture; called from the sound thread.
static void quisk_start_samples(void)
{
if (DEBUG) { fprintf (stderr, "perseus c: quisk_start_samples\n"); fflush(stderr); }
int rc = mkfifo(fname, 0666);
if ((rc == -1) && (errno != EEXIST)) {
perror("perseus c: Error creating the named pipe");
}
rfd = open(fname, O_RDONLY|O_NONBLOCK);
if (rfd < 0) {
fprintf(stderr, "perseus c: Can't open read FIFO (%s)\n", strerror(errno));
} else {
if (DEBUG) fprintf(stderr, "perseus c: read FIFO (%d)\n", rfd);
}
wfd = open(fname, O_WRONLY|O_NONBLOCK);
if (wfd < 0) {
fprintf(stderr, "perseus c: Can't open write FIFO (%s)\n", strerror(errno));
} else {
if (DEBUG) fprintf(stderr, "perseus c: write FIFO (%d)\n", wfd);
}
if (perseus_set_sampling_rate(descr, sr) < 0) { // specify the sampling rate value in Samples/second
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
} else {
if (DEBUG) fprintf(stderr, "perseus c: sampling rate set to: %d\n", sr);
// Re-enable preselection filters (WB_MODE Off)
perseus_set_ddc_center_freq(descr, freq, wb_filter);
// start sampling ops
if (perseus_start_async_input(descr, nb*bs, user_data_callback_c_f, 0)<0) {
fprintf(stderr, "perseus c: start async input error: %s\n", perseus_errorstr());
} else {
if (DEBUG) fprintf(stderr, "perseus c: start async input\n");
}
running = 1;
}
}
// Stop sample capture; called from the sound thread.
static void quisk_stop_samples(void)
{
if (DEBUG) { fprintf (stderr, "perseus c: quisk_stop_samples\n"); fflush(stderr); }
// We stop the acquisition...
if (DEBUG) fprintf(stderr, "perseus c: stopping async data acquisition...\n");
perseus_stop_async_input(descr);
running = 0;
// clearing FIFO...
close(rfd);
close(wfd);
unlink(fname);
}
// Called to close the sample source; called from the GUI thread.
static PyObject * close_device(PyObject * self, PyObject * args)
{
if (DEBUG) fprintf (stderr, "perseus c: close_device\n");
int sample_device; // for now one only Perseus can be managed
if (!PyArg_ParseTuple (args, "i", &sample_device))
return NULL;
if (descr) {
// We stop the acquisition...
if (running) {
perseus_stop_async_input(descr);
running = 0;
}
perseus_close(descr);
descr = 0;
}
Py_INCREF (Py_None);
return Py_None;
}
// Called to open the Perseus SDR device; called from the GUI thread.
static PyObject * open_device(PyObject * self, PyObject * args)
{
char buf128[128] = "Capture Microtelecom Perseus HF receiver";
eeprom_prodid prodid;
if (DEBUG) { fprintf (stderr, "perseus c: open device (%d)\n", num_perseus); fflush(stderr); }
// Check how many Perseus receivers are connected to the system
if (num_perseus == 0) num_perseus = perseus_init();
if (DEBUG) fprintf(stderr, "perseus c: %d Perseus receivers found\n",num_perseus);
if (num_perseus == 0) {
sprintf(buf128, "No Perseus receivers detected\n");
perseus_exit();
goto main_cleanup;
}
// Open the first one...
if ((descr = perseus_open(0)) == NULL) {
sprintf(buf128, "error: %s\n", perseus_errorstr());
fprintf(stderr, "perseus c: open error: %s\n", perseus_errorstr());
goto main_cleanup;
}
// Download the standard firmware to the unit
if (DEBUG) fprintf(stderr, "perseus c: Downloading firmware...\n");
if (perseus_firmware_download(descr,NULL)<0) {
sprintf(buf128, "perseus c: firmware download error: %s", perseus_errorstr());
goto main_cleanup;
}
// Dump some information about the receiver (S/N and HW rev)
if (perseus_is_preserie(descr, 0) == PERSEUS_SNNOTAVAILABLE) {
fprintf(stderr, "perseus c: The device is a preserie unit");
} else {
if (perseus_get_product_id(descr,&prodid)<0) {
fprintf(stderr, "perseus c: get product id error: %s", perseus_errorstr());
} else {
if (DEBUG) {
fprintf(stderr, "perseus c: Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n",
(uint16_t) prodid.sn,
(uint16_t) prodid.signature[5],
(uint16_t) prodid.signature[4],
(uint16_t) prodid.signature[3],
(uint16_t) prodid.signature[2],
(uint16_t) prodid.signature[1],
(uint16_t) prodid.signature[0],
(uint16_t) prodid.hwrel,
(uint16_t) prodid.hwver);
}
}
}
// Printing all sampling rates available .....
{
int buf[BUFSIZ];
if (perseus_get_sampling_rates (descr, buf, sizeof(buf)/sizeof(buf[0])) < 0) {
fprintf(stderr, "perseus c: get sampling rates error: %s\n", perseus_errorstr());
goto main_cleanup;
} else {
int i = 0;
while (buf[i]) {
if (DEBUG) fprintf(stderr, "perseus c: #%d: sample rate: %d\n", i, buf[i]);
i++;
}
}
}
// Configure the receiver for 2 MS/s operations
if (DEBUG) fprintf(stderr, "perseus c: Configuring FPGA...\n");
if (perseus_set_sampling_rate(descr, sr) < 0) { // specify the sampling rate value in Samples/second
//if (perseus_set_sampling_rate_n(descr, 0)<0) // specify the sampling rate value as ordinal in the vector
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
goto main_cleanup;
}
// ADC settings
perseus_set_adc (descr, adc_dither, adc_preamp);
// Disable preselection filters (WB_MODE On)
//perseus_set_ddc_center_freq(descr, freq, 0);
//sleep(1);
// Re-enable preselection filters (WB_MODE Off)
perseus_set_ddc_center_freq(descr, freq, wb_filter);
quisk_sample_source4(&quisk_start_samples, &quisk_stop_samples, &quisk_read_samples, &quisk_write_samples);
if (DEBUG) { fprintf (stderr, "perseus c: quisk sample source callbacks established\n"); fflush(stderr); }
goto exit_success;
main_cleanup:
return PyString_FromString("ERROR");
exit_success:
return PyString_FromString(buf128);
}
static PyObject * set_frequency(PyObject * self, PyObject * args) // Called from GUI thread
{
float param;
if (!PyArg_ParseTuple (args, "f", &param))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: set DDC frequency %lf WB filter:%d\n", param, wb_filter);
freq= param;
if (descr) perseus_set_ddc_center_freq(descr, freq, wb_filter);
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_input_filter(PyObject * self, PyObject * args) // Called from GUI thread
{
int param;
if (!PyArg_ParseTuple (args, "i", &param))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: set input filter %d\n", param);
wb_filter = param;
if (descr) perseus_set_ddc_center_freq(descr, freq, wb_filter);
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_sampling_rate(PyObject * self, PyObject * args) // Called from GUI thread
{
int param;
if (!PyArg_ParseTuple (args, "i", &param))
return NULL;
if (DEBUG) fprintf (stderr, "perseus c: Set sampling rate %d\n", param);
if (param < 48000) sr = param * 1000;
else sr = param;
if (descr) {
if (running) {
if (DEBUG) fprintf(stderr, "perseus c: stop async input\n");
perseus_stop_async_input(descr);
}
// specify the sampling rate value in Samples/secon
if (perseus_set_sampling_rate(descr, sr) < 0) {
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
}
if (running) {
if (perseus_start_async_input(descr, nb*bs, user_data_callback_c_f, 0)<0) {
fprintf(stderr, "perseus c: start async input error: %s\n", perseus_errorstr());
} else {
if (DEBUG) fprintf(stderr, "perseus c: start async input @%d\n", sr);
}
}
} else {
fprintf(stderr, "perseus c: trying to start async input with no device open\n");
}
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_attenuator(PyObject * self, PyObject * args) // Called from GUI thread
{ int param;
if (!PyArg_ParseTuple (args, "i", &param))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: Set attenuator %d\n", param);
if (descr) {
// specify the sampling rate value in Samples/secon
if (perseus_set_attenuator_n(descr, (int)(param / -10)) < 0) {
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
}
}
Py_INCREF (Py_None);
return Py_None;
}
// Enable ADC Dither, Disable ADC Preamp
// perseus_set_adc(descr, true, false);
static PyObject * set_adc_dither (PyObject * self, PyObject * args) // Called from GUI thread
{ int dither_;
if (!PyArg_ParseTuple (args, "i", &dither_))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: Set ADC: dither %d\n", dither_);
adc_dither = dither_;
if (descr) {
// specify the ADC dithering
if (perseus_set_adc(descr, adc_dither == 1, adc_preamp == 1) < 0) {
fprintf(stderr, "perseus c: ADC configuration error: %s\n", perseus_errorstr());
}
}
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_adc_preamp (PyObject * self, PyObject * args) // Called from GUI thread
{ int preamp_;
if (!PyArg_ParseTuple (args, "i", &preamp_))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: Set ADC: preamp: %d\n", preamp_);
adc_preamp = preamp_;
if (descr) {
// specify the sampling rate value in Samples/secon
if (perseus_set_adc(descr, adc_dither == 1, adc_preamp == 1) < 0) {
fprintf(stderr, "perseus c: ADC configuration error: %s\n", perseus_errorstr());
}
}
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * deinit(PyObject * self, PyObject * args) // Called from dctor
{
perseus_exit();
Py_INCREF (Py_None);
return Py_None;
}
// Functions callable from Python are listed here:
static PyMethodDef QuiskMethods[] = {
{"open_device", open_device, METH_VARARGS, "Open the hardware."},
{"close_device", close_device, METH_VARARGS, "Close the hardware"},
{"set_frequency", set_frequency, METH_VARARGS, "set frequency"},
{"set_input_filter", set_input_filter, METH_VARARGS, "set input filter"},
{"set_sampling_rate", set_sampling_rate, METH_VARARGS, "set sampling rate"},
{"set_attenuator", set_attenuator, METH_VARARGS, "set attenuator"},
{"set_adc_dither", set_adc_dither, METH_VARARGS, "set ADC dither"},
{"set_adc_preamp", set_adc_preamp, METH_VARARGS, "set ADC preamplifier"},
{"deinit", deinit, METH_VARARGS, "deinit"},
// {"get_device_list", get_device_list, METH_VARARGS, "Return a list of Perseus SDR devices"},
{NULL, NULL, 0, NULL} /* Sentinel */
};
#if PY_MAJOR_VERSION < 3
// Python 2.7:
// Initialization, and registration of public symbol "initperseus":
PyMODINIT_FUNC initperseus (void)
{
if (Py_InitModule ("perseus", QuiskMethods) == NULL) {
fprintf(stderr, "perseus c: Py_InitModule failed!\n");
return;
}
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
fprintf(stderr, "perseus c: Failure to import pointers from _quisk\n");
return; //Error
}
}
// Python 3:
#else
static struct PyModuleDef perseusmodule = {
PyModuleDef_HEAD_INIT,
"perseus",
NULL,
-1,
QuiskMethods
} ;
PyMODINIT_FUNC PyInit_perseus(void)
{
PyObject * m;
m = PyModule_Create(&perseusmodule);
if (m == NULL)
return NULL;
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
fprintf(stderr, "perseus c: Failure to import pointers from _quisk\n");
return m; //Error
}
return m;
}
#endif
+189
View File
@@ -0,0 +1,189 @@
# This is the hardware file to support radios accessed by the PerseusSDR interface.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import socket, traceback, time, math
import _quisk as QS
try:
from perseuspkg import perseus
except:
#traceback.print_exc()
perseus = None
print ("Error: Perseus package not found.\n")
from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 1
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
################ Receivers PerseusSDR, The PerseusSDR interface to multiple hardware SDRs.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'perseuspkg/quisk_hardware.py'
## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = 'perseuspkg/quisk_widgets.py'
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.rf_gain_labels = ('RF +0', 'RF -10', 'RF -20', 'RF -30')
self.antenna_labels = ('Wide Band', 'Band Filter')
self.vardecim_index = 0
self.fVFO = 0.0 # Careful, this is a float
if DEBUG: print ("__init__: %s" % conf)
self.rates = [ 48000, \
95000, \
96000, \
125000, \
192000, \
250000, \
500000, \
1000000, \
1600000, \
2000000 \
]
self.current_rate = 192000
self.att = 0;
self.wb = 0
def __del__(self):
# try to clear hardware
if perseus:
perseus.close()
perseus.deinit()
def get_hw (self):
return perseus
def pre_open(self):
if DEBUG: print ("pre_open")
pass
def set_parameter(self, *args):
pass
def open(self): # Called once to open the Hardware
if not perseus:
return "Perseus module not available"
txt = perseus.open_device("perseus",2,3)
if DEBUG: print ("perseus hardware: open")
return txt
def close(self): # Called once to close the Hardware
if DEBUG: print ("perseus hardware: close")
if perseus:
perseus.close_device(1)
def ChangeGain(self, rxtx): # rxtx is '_rx' or '_tx'
if not perseus:
return
if DEBUG: print ("perseus hardware: ChangeGain", rxtx)
pass
def OnButtonRfGain(self, event):
#btn = event.GetEventObject()
n = event.GetEventObject().index
self.att = n * -10
if DEBUG: print ("perseus hardware: OnButtonRfGain: %d new attenuation: %d" % (n, self.att))
perseus.set_attenuator (self.att)
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
fVFO = float(vfo)
if self.fVFO != fVFO:
self.fVFO = fVFO
perseus.set_frequency(fVFO)
return tune, vfo
def ReturnFrequency(self):
# Return the current tuning and VFO frequency. If neither have changed,
# you can return (None, None). This is called at about 10 Hz by the main.
# return (tune, vfo) # return changed frequencies
return None, None # frequencies have not changed
def ReturnVfoFloat(self):
# Return the accurate VFO frequency as a floating point number.
# You can return None to indicate that the integer VFO frequency is valid.
return self.fVFO
# def OnBtnFDX(self, fdx): # fdx is 0 or 1
# pass
#
# def OnButtonPTT(self, event):
# pass
#
# def OnSpot(self, level):
# # level is -1 for Spot button Off; else the Spot level 0 to 1000.
# pass
#
# def ChangeMode(self, mode): # Change the tx/rx mode
# # mode is a string: "USB", "AM", etc.
# pass
#
# def ChangeBand(self, band):
# pass
#
# def HeartBeat(self): # Called at about 10 Hz by the main
# pass
def ImmediateChange(self, name, value):
if DEBUG: print ("perseus hardware: ImmediateChange: perseus: name: %s value: %s" % (name, value))
if name == 'perseus_setSampleRate_rx':
value = int(value)
self.application.OnBtnDecimation(rate=value)
perseus.set_sampling_rate(value)
self.curren_dec = value
def VarDecimGetChoices(self): # Not used to set sample rate
if DEBUG: print ("perseus hardware: VarDecimGetChoices")
return list(map(str, self.rates)) # convert integer to string
def VarDecimGetLabel(self): # Return a text label for the decimation control.
return 'Sample rates: '
def VarDecimGetIndex(self): # Return the index 0, 1, ... of the current decimation.
for i in range(len(self.rates)):
if self.rates[i] == self.current_rate:
return i
return 0
def VarDecimSet(self, index=None): # Called when the control is operated; if index==None, called on startup.
print ("perseus hardware: VarDecimSet: index: %s" % (index))
if index == None:
if DEBUG: print ("perseus hardware: VarDecimSet: current sampling rate: %d" % self.current_rate)
new_rate = self.current_rate = self.application.vardecim_set
else:
new_rate = self.rates[index]
if DEBUG: print ("perseus hardware: VarDecimSet: New sampling rate: %d" % new_rate)
perseus.set_sampling_rate(int(new_rate))
self.current_rate = int(new_rate)
return int(new_rate)
def VarDecimRange(self): # Return the lowest and highest sample rate.
if DEBUG: print ("perseus hardware: VarDecimRange: %s" % self.rates)
return (self.rates[0], self.rates[-1])
def OnButtonAntenna(self, event):
btn = event.GetEventObject()
n = btn.index
if DEBUG: print ("OnButtonAntenna: %d status: %d" % (n, self.wb))
self.wb = n
perseus.set_input_filter (self.wb)
# def StartSamples(self): # called by the sound thread
# print("perseus hardware: StartSamples")
# def StopSamples(self): # called by the sound thread
# print("perseus hardware: StopSamples")
+37
View File
@@ -0,0 +1,37 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import math, wx
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
self.start_row = app.widget_row # The first available row
self.start_col = app.button_start_col # The start of the button columns
self.Widgets_0x06(app, hardware, conf, frame, gbs, vertBox)
def Widgets_0x06(self, app, hardware, conf, frame, gbs, vertBox):
self.num_rows_added = 1
start_row = self.start_row
b1 = app.QuiskCheckbutton(frame, self.OnADC_dither, 'ADC Dither')
gbs.Add(b1, (start_row, self.start_col), (1, 2), flag=wx.EXPAND)
b2 = app.QuiskCheckbutton(frame, self.OnADC_preamp, 'ADC Preamp')
gbs.Add(b2, (start_row, self.start_col + 2), (1, 2), flag=wx.EXPAND)
def OnADC_dither(self, event):
btn = event.GetEventObject()
value = btn.GetValue()
self.hardware.get_hw().set_adc_dither (value)
def OnADC_preamp(self, event):
btn = event.GetEventObject()
value = btn.GetValue()
self.hardware.get_hw().set_adc_preamp (value)
+48
View File
@@ -0,0 +1,48 @@
from distutils.core import setup, Extension
import sys
module2 = Extension ('perseus',
libraries = ['m', 'perseus-sdr'],
sources = ['../import_quisk_api.c', 'perseus.c'],
include_dirs = ['.', '..'],
)
modulew2 = Extension ('perseus',
sources = ['../import_quisk_api.c', 'perseus.c'],
include_dirs = ['.', '..'],
libraries = ['WS2_32', 'perseus-sdr'],
)
if sys.platform == "win32":
Modules = [modulew2]
else:
Modules = [module2]
setup (name = 'perseus',
version = '0.1',
description = 'perseus is an extension to Quisk to support Microtelecom Perseus SDR hardware',
long_description = """Microtelecom Perseus SDR HF receiver.
""",
author = 'Andrea Montefusco IW0HDV',
author_email = 'andrew@montefusco.com',
url = 'http://www.montefusco.com',
download_url = 'http://james.ahlstrom.name/quisk/',
packages = ['quisk.perseuspkg'],
package_dir = {'perseus' : '.'},
ext_modules = Modules,
classifiers = [
'Development Status :: 6 - Mature',
'Environment :: X11 Applications',
'Environment :: Win32 (MS Windows)',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: English',
'Operating System :: POSIX :: Linux',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python',
'Programming Language :: C',
'Topic :: Communications :: Ham Radio',
],
)
+85
View File
@@ -0,0 +1,85 @@
#! /usr/bin/python
# Test for PortAudio devices using ctypes
from __future__ import print_function
import ctypes, ctypes.util
class PaDeviceInfo (ctypes.Structure):
_fields_ = [
('structVersion', ctypes.c_int),
('name', ctypes.c_char_p),
('hostApi', ctypes.c_int), # PaHostApiIndex
('maxInputChannels', ctypes.c_int),
('maxOutputChannels', ctypes.c_int),
('defaultLowInputLatency', ctypes.c_double), # PaTime
('defaultLowOutputLatency', ctypes.c_double), # PaTime
('defaultHighInputLatency', ctypes.c_double), # PaTime
('defaultHighOutputLatency', ctypes.c_double), # PaTime
('defaultSampleRate', ctypes.c_double),
]
class PaHostApiInfo (ctypes.Structure):
_fields_ = [
('structVersion', ctypes.c_int),
('type', ctypes.c_int), # enum PaHostApiTypeId
('name', ctypes.c_char_p),
('deviceCount', ctypes.c_int),
('defaultInputDevice', ctypes.c_int),
('defaultOutputDevice', ctypes.c_int),
]
class PaStreamParameters (ctypes.Structure):
_fields_ = [
('device', ctypes.c_int), #PaDeviceIndex
('channelCount', ctypes.c_int),
('sampleFormat', ctypes.c_ulong), #PaSampleFormat
('suggestedLatency', ctypes.c_double), # PaTime
('hostApiSpecificStreamInfo', ctypes.c_void_p),
]
pa_name = ctypes.util.find_library("portaudio")
pa = ctypes.CDLL(pa_name)
pa.Pa_GetDeviceInfo.restype = ctypes.POINTER(PaDeviceInfo)
pa.Pa_GetHostApiInfo.restype = ctypes.POINTER(PaHostApiInfo)
pa.Pa_GetVersionText.restype = ctypes.c_char_p
inputParameters = PaStreamParameters (device=0, channelCount=2,
sampleFormat=2, suggestedLatency=0, # format 2 is paInt32
hostApiSpecificStreamInfo=ctypes.c_void_p() )
outputParameters = PaStreamParameters (device=0, channelCount=2,
sampleFormat=2, suggestedLatency=0, # format 2 is paInt32
hostApiSpecificStreamInfo=ctypes.c_void_p() )
print('Open', pa.Pa_Initialize())
try:
print('Version', pa.Pa_GetVersion())
print('Version Text', pa.Pa_GetVersionText())
count = pa.Pa_GetDeviceCount()
print('NumDev', count)
for i in range(count):
pt_info = pa.Pa_GetDeviceInfo(i)
info = pt_info.contents
print("Device %2d, host api %s" % (i, pa.Pa_GetHostApiInfo(info.hostApi).contents.name))
print(" Name %s" % info.name)
print(" Max inputs %d, Max outputs %d" % (info.maxInputChannels, info.maxOutputChannels))
inputParameters.device = i
outputParameters.device = i
if info.maxInputChannels >= 2:
ptIn = ctypes.pointer(inputParameters)
else:
ptIn = ctypes.c_void_p()
if info.maxOutputChannels >= 2:
ptOut = ctypes.pointer(outputParameters)
else:
ptOut = ctypes.c_void_p()
print(" Speeds for 2-channel paInt32:", end=' ')
for speed in (44100, 48000, 96000, 192000):
if pa.Pa_IsFormatSupported(ptIn, ptOut, ctypes.c_double(speed)) == 0:
print(" %d" % speed, end=' ')
print()
finally:
print('Close', pa.Pa_Terminate())

Some files were not shown because too many files have changed in this diff Show More