Quisk version 4.2.28
This commit is contained in:
+1989
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#Quisk version 4.2.29
|
||||||
|
from .quisk import main
|
||||||
@@ -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.
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
@@ -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)))
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
"""
|
||||||
@@ -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 *)ξ
|
||||||
|
// 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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
afedrinet2:
|
||||||
|
python2 setup.py build_ext --force --inplace
|
||||||
|
|
||||||
|
afedrinet3:
|
||||||
|
python3 setup.py build_ext --force --inplace
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
|
##########################################################
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
@@ -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',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||||
+141
@@ -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
@@ -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
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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];
|
||||||
+770
@@ -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],
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
@@ -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>
|
||||||
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Configuration files are no longer used. Please use the radio Config screens instead.
|
||||||
@@ -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
@@ -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()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
Binary file not shown.
Binary file not shown.
+30
@@ -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
Binary file not shown.
Binary file not shown.
+129
@@ -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.
|
||||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
// Filters were moved to filter.c.
|
||||||
+161
@@ -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)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from n2adr import conf3
|
||||||
|
|
||||||
|
settings_file_path = "../quisk_settings.json"
|
||||||
|
|
||||||
|
microphone_name = ""
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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 = ''
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
]
|
||||||
@@ -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]
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
perseus2:
|
||||||
|
python2 setup.py build_ext --force --inplace
|
||||||
|
|
||||||
|
perseus3:
|
||||||
|
python3 setup.py build_ext --force --inplace
|
||||||
|
|
||||||
@@ -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", ¶m))
|
||||||
|
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", ¶m))
|
||||||
|
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", ¶m))
|
||||||
|
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", ¶m))
|
||||||
|
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
|
||||||
|
|
||||||
@@ -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")
|
||||||
@@ -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)
|
||||||
@@ -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',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -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
Reference in New Issue
Block a user