Initial commit of a local repository for Quisk.

This commit is contained in:
Rob French 2020-02-23 20:57:24 -06:00
commit 91aa2689ce
148 changed files with 43648 additions and 0 deletions

1316
CHANGELOG.txt Executable file

File diff suppressed because it is too large Load Diff

1
Extensions.txt Executable file
View File

@ -0,0 +1 @@
The information from this file is now in docs.html.

10
MANIFEST.in Executable file
View File

@ -0,0 +1,10 @@
include *.c *.h *.py *.pyw *.txt *.html *.dll *.pyd makefile quisk MANIFEST.in
graft afedrinet
graft freedvpkg
graft hermes
graft hiqsdr
graft n2adr
graft sdriqpkg
graft softrock
graft soapypkg
global-exclude *.pyc

32
PKG-INFO Executable file
View File

@ -0,0 +1,32 @@
Metadata-Version: 1.1
Name: quisk
Version: 4.1.52
Summary: QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.
Home-page: http://james.ahlstrom.name/quisk/
Author: James C. Ahlstrom
Author-email: jahlstr@gmail.com
License: UNKNOWN
Description: QUISK is a Software Defined Radio (SDR) transceiver.
You supply radio hardware that converts signals at the antenna to complex (I/Q) data at an
intermediate frequency (IF). Data can come from a sound card, Ethernet or USB. Quisk then filters and
demodulates the data and sends the audio to your speakers or headphones. For transmit, Quisk takes
the microphone signal, converts it to I/Q data and sends it to the hardware.
Quisk can be used with SoftRock, Hermes Lite 2, HiQSDR, Odyssey and many radios that use the Hermes protocol.
Quisk can connect to digital programs like Fldigi and WSJT-X. Quisk can be connected to other software like
N1MM+ and software that uses Hamlib.
Platform: UNKNOWN
Classifier: Development Status :: 6 - Mature
Classifier: Environment :: X11 Applications
Classifier: Environment :: Win32 (MS Windows)
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Natural Language :: English
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C
Classifier: Topic :: Communications :: Ham Radio
Provides: quisk

1
README.txt Executable file
View File

@ -0,0 +1 @@
The information from this file is now in docs.html.

66
WinEdit.pyw Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/python
import wx
from wx import py
from wx import stc
import os, sys, webbrowser
# Change to the directory of quisk.py.
os.chdir(os.path.normpath(os.path.dirname(__file__)))
# Command line parsing: be able to specify the config file.
from optparse import OptionParser
parser = OptionParser()
parser.add_option('-c', '--config', dest='config_file_path',
help='Specify the configuration file path')
parser.add_option('-e', '--edit', action='store_true', dest='edit',
help='Edit the config file')
parser.add_option('-d', '--defaults', action='store_true', dest='defaults',
help='Show a window with the configuration defaults')
parser.add_option('-r', '--docs', action='store_true', dest='docs',
help='Show a window with the docs.html file')
argv_options = parser.parse_args()[0]
ConfigPath = argv_options.config_file_path # Get config file path
if not ConfigPath: # Use default path; Duplicated from quisk.py
if sys.platform == 'win32':
ConfigPath = os.getenv('HOMEDRIVE', '') + os.getenv('HOMEPATH', '')
ConfigPath = os.path.join(ConfigPath, "My Documents")
ConfigPath = os.path.join(ConfigPath, "quisk_conf.py")
if not os.path.isfile(ConfigPath): # See if the user has a config file
try:
import shutil # Try to create an initial default config file
shutil.copyfile('quisk_conf_win.py', ConfigPath)
except:
pass
else:
ConfigPath = os.path.expanduser('~/.quisk_conf.py')
class EditApp(wx.App):
def __init__(self):
wx.App.__init__(self, redirect=False)
def OnInit(self):
wx.InitAllImageHandlers()
frame = None
if argv_options.defaults:
frame = py.editor.EditorFrame(filename="quisk_conf_defaults.py")
frame.editor.window.SetReadOnly(True)
frame.SetTitle("Configuration defaults quisk_conf_defaults.py")
frame.Show()
if argv_options.edit:
frame = py.editor.EditorFrame(filename=ConfigPath)
frame.SetTitle(ConfigPath)
frame.Show()
if argv_options.docs:
webbrowser.open("docs.html", new=2)
if frame:
self.SetTopWindow(frame)
return True
def main():
app = EditApp()
app.MainLoop()
if __name__ == '__main__':
main()

8
WinQuisk.pyw Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/python
import quisk # May be quisk.py or package quisk
if quisk.__file__.find('__init__') >= 0: # quisk is the package
import quisk.quisk as quisk
quisk.main()

10
WinQuiskVna.pyw Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/python
import quisk # May be quisk.py or package quisk
if quisk.__file__.find('__init__') >= 0: # quisk is the package
import quisk.quisk_vna as quisk_vna
else:
import quisk_vna
quisk_vna.main()

1
__init__.py Executable file
View File

@ -0,0 +1 @@
#Quisk version 4.1.52

8
__main__.py Executable file
View File

@ -0,0 +1,8 @@
def main():
import quisk
if quisk.__file__.find('__init__') >= 0: # quisk is the package
import quisk.quisk as quisk
quisk.main()
if __name__ == "__main__":
main()

BIN
_quisk.pyd Executable file

Binary file not shown.

3
afedrinet/SOURCE.txt Executable file
View File

@ -0,0 +1,3 @@
The files in this afedrinet directory came from http://www.afedri-sdr.com. Only slight
modifications were made by N2ADR. Thanks Alex!!!
N2ADR: Changes made 26 January 2019.

1
afedrinet/__init__.py Executable file
View File

@ -0,0 +1 @@
#

1
afedrinet/af_comp.bat Executable file
View File

@ -0,0 +1 @@
gcc -o afedrinet_io.pyd --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -O3 -I"../" -I"C:\Programs\Python27\include" -L"C:\Programs\Python27\libs" -lws2_32 -lpython27

4
afedrinet/afe_library Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
#replace include path and library name to right one for your system
# for example for Python 2.7 it will look like -I"/usr/include/python2.7/" -lpython2.7
gcc -o afedrinet_io.so --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -fPIC -O3 -I"../" -I"/usr/include/python2.7/" -lpython2.7

5
afedrinet/afe_library.mac Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
#replace include path and library name to right one for your system
# for example for Python 2.7 it will look like -I"/usr/include/python2.7/" -lpython2.7
#/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7
gcc -o afedrinet_io.so --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -I"../" -I"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/" -L"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/" -lpython2.7

211
afedrinet/afedri.py Executable file
View File

@ -0,0 +1,211 @@
#!/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
##################################################
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 = "\x0a\x00\x20\x00\x00" + __next_freq[:5]
self.s.send(__set_freq_cmd)
__data = self.s.recv(10)
__freq = __data[5:] + "\0" * 3
__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 = "\x09\x00\xB8\x00\x00" + __samp_rate[:4]
self.s.send(__set_rate_cmd)
__data = self.s.recv(9)
__samp = __data[5:] + "\0" * 4
__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 = "\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 = "\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 = "\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 = "\x09\xE0\x02\x55\x00\x00\x00\x00\x00"
__get_hword_cmd = "\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="\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="\x04\x20\x01\x00"
self.s.send(__start_cmd)
__data = self.s.recv(16)
return __data
def stop_capture(self):
if not self.s: return 1
__stop_cmd="\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="\x38\x00\x5a\xa5" # magic discovery packet
__data=__data.ljust(56,"\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]
__sn=__msg[21:36]
__ip=inet_ntoa(__msg[40:36:-1])
__port=struct.unpack("<H",__msg[53:55])[0]
self.s.close()
self.sin.close()
print ("found ", __devname, __sn, __ip, __port)
return (__ip,__port)
except timeout:
print ("No response from AFEDRI on the LAN")
##sys.exit()
return None, None
def __del__(self):
self.stop_capture()
if self.s: self.s.close()
"""
# verify and correct sampling rate according to the main clock speed
# Alex Trushkin code 4z5lv:
fe_main_clock_freq = a.get_fe_clock()
tmp_div = fe_main_clock_freq / (4 * samp_rate)
floor_div = math.floor(tmp_div)
if (tmp_div - floor_div >= 0.5):
floor_div += 1
if floor_div < 15:
floor_div = 15
#print ("Warning: Max supported sampling rate is", math.floor(fe_main_clock_freq / (4 * floor_div)))
elif floor_div > 625:
floor_div = 625
#print ("Warning: Min supported sampling rate is", math.floor(fe_main_clock_freq / (4 * floor_div)))
dSR = fe_main_clock_freq / (4 * floor_div)
floor_SR = math.floor(dSR)
if (dSR - floor_SR >= 0.5):
floor_SR += 1
if floor_SR != samp_rate:
print ("Warning: invalid sample rate selected for the AFEDRI main clock (", fe_main_clock_freq, "Hz )")
print (" setting to the next valid value", samp_rate, " => ", floor_SR)
samp_rate = floor_SR
"""

399
afedrinet/afedrinet_io.c Executable file
View File

@ -0,0 +1,399 @@
#include <Python.h>
#ifdef MS_WINDOWS
#include <Winsock2.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#endif
#ifdef MS_WINDOWS
#define QUISK_SHUT_RD SD_RECEIVE
#define QUISK_SHUT_BOTH SD_BOTH
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#define SOCKET int
#define INVALID_SOCKET -1
#define QUISK_SHUT_RD SHUT_RD
#define QUISK_SHUT_BOTH SHUT_RDWR
#endif
#include <complex.h>
#define IMPORT_QUISK_API
#include "quisk.h"
//#include "sdriq.h"
static SOCKET rx_udp_socket = INVALID_SOCKET; // Socket for receiving ADC samples from UDP
static int rx_udp_started = 0; // Have we received any data yet?
static int rx_udp_read_blocks = 0; // Number of blocks to read for each read call
static double rx_udp_gain_correct = 1; // For decimation by 5, correct by 4096 / 5**5
static int use_remove_dc=0; // Remove DC from samples
//static int quisk_using_udp = 0;
// This module provides access to the SDR-IQ by RfSpace. It is the source
// for the Python extension module sdriq. It can be used as a model for an
// extension module for other hardware. Read the end of this file for more
// information. This module was written by James Ahlstrom, N2ADR.
// This module uses the Python interface to import symbols from the parent _quisk
// extension module. It must be linked with import_quisk_api.c. See the documentation
// at the start of import_quisk_api.c.
// Start of SDR-IQ specific code:
//
#define DEBUG 0
// Type field for the message block header; upper 3 bits of byte
#define TYPE_HOST_SET 0
#define TYPE_HOST_GET (1 << 5)
#define NAME_SIZE 16
//#define UDP_BROADCAST
#ifdef UDP_BROADCAST
#define FIRST_IQ_DATA_IDX 20
#define RX_UDP_SIZE 1044 // Expected size of UDP samples packet
#else
#define RX_UDP_SIZE 1028 // Expected size of UDP samples packet
#define FIRST_IQ_DATA_IDX 4
#endif
#define BROADCAST_HEADER_SIZE 16
#define UDP_PROTCOL_ID1 0x04
#define UDP_PROTCOL_ID2 0x18
#ifdef DEBUG_IO
#undef DEBUG_IO
#define DEBUG_IO 1
#endif
static PyObject * open_rx_udp(const char * ip, int port)
{
// const char * ip;
// int port;
char buf[128];
struct sockaddr_in Addr;
int recvsize;
char optval;
#if DEBUG_IO
int intbuf;
#ifdef MS_WINDOWS
int bufsize = sizeof(int);
#else
socklen_t bufsize = sizeof(int);
#endif
#endif
#ifdef MS_WINDOWS
WORD wVersionRequested;
WSADATA wsaData;
#endif
// if (!PyArg_ParseTuple (args, "si", &ip, &port))
// return NULL;
// port = 50000;
#ifdef MS_WINDOWS
wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
sprintf(buf, "Failed to initialize Winsock (WSAStartup)");
return PyString_FromString(buf);
}
#endif
// quisk_using_udp = 1;
rx_udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (rx_udp_socket != INVALID_SOCKET)
{
optval=1;
setsockopt( rx_udp_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval) );
recvsize = 256000;
setsockopt(rx_udp_socket, SOL_SOCKET, SO_RCVBUF, (char *)&recvsize, sizeof(recvsize));
memset(&Addr, 0, sizeof(Addr));
Addr.sin_family = AF_INET;
Addr.sin_port = htons(port);
Addr.sin_addr.s_addr = htonl(INADDR_ANY);//inet_addr("192.168.0.8");
// Addr.sin_addr.S_un.S_addr = inet_addr(ip);
// if (connect(rx_udp_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0)
if (bind(rx_udp_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0)
{
shutdown(rx_udp_socket, QUISK_SHUT_BOTH);
close(rx_udp_socket);
rx_udp_socket = INVALID_SOCKET;
sprintf(buf, "Failed to connect to UDP %s port %u", ip, port);
}
else {
sprintf(buf, "Capture from UDP %s port %u", ip, port);
#if DEBUG_IO
if (getsockopt(rx_udp_socket, SOL_SOCKET, SO_RCVBUF, (char *)&intbuf, &bufsize) == 0)
{
printf("UDP socket receive buffer size %d\n", intbuf);
printf("address %s port %u\n", ip, port);
}
else
printf ("Failure SO_RCVBUF\n");
#endif
}
}
else {
sprintf(buf, "Failed to open socket");
}
return PyString_FromString(buf);
}
static PyObject * close_rx_udp(PyObject * self, PyObject * args)
{
short msg = 0x7373; // shutdown
if (!PyArg_ParseTuple (args, ""))
return NULL;
if (rx_udp_socket != INVALID_SOCKET) {
shutdown(rx_udp_socket, QUISK_SHUT_RD);
send(rx_udp_socket, (char *)&msg, 2, 0);
send(rx_udp_socket, (char *)&msg, 2, 0);
QuiskSleepMicrosec(3000000);
close(rx_udp_socket);
rx_udp_socket = INVALID_SOCKET;
}
rx_udp_started = 0;
// if (quisk_using_udp) {
// quisk_using_udp = 0;
#ifdef MS_WINDOWS
WSACleanup();
#endif
// }
Py_INCREF (Py_None);
return Py_None;
}
int afedri_read_rx_udp(complex * samp) // Read samples from UDP
{ // Size of complex sample array is SAMP_BUFFER_SIZE
ssize_t bytes;
//int SR = 0;
static int sample_rate = 0; // Sample rate such as 48000, 96000, 192000
unsigned char buf[1500]; // Maximum Ethernet is 1500 bytes.
static unsigned short seq0; // must be 8 bits
unsigned short seq_curr = 0;
#ifdef MS_WINDOWS
__int32 i, count, nSamples, xr, xi, index;
#else
int32_t i, count, nSamples, xr, xi, index;
#endif
unsigned char * ptxr, * ptxi;
static complex dc_average = 0; // Average DC component in samples
static complex dc_sum = 0;
static int dc_count = 0;
static int dc_key_delay = 0;
// Data from the receiver is little-endian
// if ( !rx_udp_read_blocks)
if(sample_rate != pt_quisk_sound_state->sample_rate)
{
sample_rate = pt_quisk_sound_state->sample_rate;
// "rx_udp_read_blocks" is the number of UDP blocks to read at once
rx_udp_read_blocks = (int)(pt_quisk_sound_state->data_poll_usec * 1e-6 * sample_rate + 0.5);
rx_udp_read_blocks = (rx_udp_read_blocks + (RX_UDP_SIZE / 12)) / (RX_UDP_SIZE / 6); // 6 bytes per sample
if (rx_udp_read_blocks < 1)
rx_udp_read_blocks = 1;
#if DEBUG_IO
printf("read_rx_udp: rx_udp_read_blocks %d\n", rx_udp_read_blocks);
#endif
}
/* if ( ! rx_udp_gain_correct) {
int dec;
dec = (int)(rx_udp_clock / sample_rate + 0.5);
if ((dec / 5) * 5 == dec) // Decimation by a factor of 5
rx_udp_gain_correct = 1.31072;
else // Decimation by factors of two
rx_udp_gain_correct = 1.0;
}
*/
nSamples = 0;
for (count = 0; count < rx_udp_read_blocks; count++)
{ // read several UDP blocks
#if DEBUG_IO
// printf("Data RX Process Begin %u\n",count);
#endif
bytes = recv(rx_udp_socket, (char *)buf, RX_UDP_SIZE, 0); // blocking read
if (bytes != RX_UDP_SIZE) { // Known size of sample block
pt_quisk_sound_state->read_error++;
#if DEBUG_IO
printf("read_rx_udp: Bad block size %i\n", (int)bytes);
#endif
continue;
}
// buf[0] is the sequence number
// buf[1] is the status:
// bit 0: key up/down state
// bit 1: set for ADC overrange (clip)
seq_curr = buf[2] | (buf[3] << 8);
if (seq_curr != seq0) {
#if DEBUG_IO
printf("read_rx_udp: Bad sequence want %3d got %3d at block %d of %d\n",
(unsigned int)seq0, (unsigned int)buf[0], count, rx_udp_read_blocks);
#endif
pt_quisk_sound_state->read_error++;
}
seq0 = seq_curr + 1; // Next expected sequence number
// quisk_set_key_down(buf[1] & 0x01); // bit zero is key state
// if (buf[1] & 0x02) // bit one is ADC overrange
// quisk_sound_state.overrange++;
index = FIRST_IQ_DATA_IDX;
ptxr = (unsigned char *)&xr;
ptxi = (unsigned char *)&xi;
// convert 24-bit samples to 32-bit samples; int must be 32 bits.
while (index < bytes)
{
xr = xi = 0;
memcpy (ptxr + 2, buf + index, 2);
index += 2;
memcpy (ptxi + 2, buf + index, 2);
index += 2;
samp[nSamples++] = (xr + xi * I) * rx_udp_gain_correct;
xr = xi = 0;
memcpy (ptxr + 2, buf + index, 2);
index += 2;
memcpy (ptxi + 2, buf + index, 2);
index += 2;
samp[nSamples++] = (xr + xi * I) * rx_udp_gain_correct;;
//if (nSamples == 2) printf("%12d %12d\n", xr, xi);
}
}
if (quisk_is_key_down()) {
dc_key_delay = 0;
dc_sum = 0;
dc_count = 0;
}
else if (dc_key_delay < pt_quisk_sound_state->sample_rate) {
dc_key_delay += nSamples;
}
else {
dc_count += nSamples;
for (i = 0; i < nSamples; i++) // Correction for DC offset in samples
dc_sum += samp[i];
if (dc_count > pt_quisk_sound_state->sample_rate * 2) {
dc_average = dc_sum / dc_count;
dc_sum = 0;
dc_count = 0;
//printf("dc average %lf %lf %d\n", creal(dc_average), cimag(dc_average), dc_count);
//printf("dc polar %.0lf %d\n", cabs(dc_average),
// (int)(360.0 / 2 / M_PI * atan2(cimag(dc_average), creal(dc_average))));
}
}
if (use_remove_dc)
for (i = 0; i < nSamples; i++) // Correction for DC offset in samples
samp[i] -= dc_average;
// printf("%u\n", pt_quisk_sound_state->sample_rate);
return nSamples;
}
// End of most AFEDRI specific code.
///////////////////////////////////////////////////////////////////////////
// The API requires at least two Python functions for Open and Close, plus
// additional Python functions as needed. And it requires exactly three
// C funcions for Start, Stop and Read samples. Quisk runs in two threads,
// a GUI thread and a sound thread. You must not call the GUI or any Python
// code from the sound thread. You must return promptly from functions called
// by the sound thread.
//
// The calling sequence is Open, Start, then repeated calls to Read, then
// Stop, then Close.
// Start of Application Programming Interface (API) code:
// Called to close the sample source; called from the GUI thread.
static PyObject * close_samples(PyObject * self, PyObject * args)
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
close_rx_udp(self, args);
Py_INCREF (Py_None);
return Py_None;
}
// Called to open the sample source; called from the GUI thread.
static PyObject * open_samples(PyObject * self, PyObject * args)
{
const char * ip;
int port;
// const char * name;
// char buf[128];
if (!PyArg_ParseTuple (args, "si", &ip, &port))
return NULL;
// name = QuiskGetConfigString("sdriq_name", "NoName");
// sdriq_clock = QuiskGetConfigDouble("sdriq_clock", 66666667.0);
// Record our C-language Start/Stop/Read functions for use by sound.c.
quisk_sample_source(NULL, NULL, &afedri_read_rx_udp);
//////////////
return open_rx_udp(ip, port); // AFEDRI specific
// return PyString_FromString(buf); // return a string message
}
// Miscellaneous functions needed by the SDR-IQ; called from the GUI thread as
// a result of button presses.
// Functions callable from Python are listed here:
static PyMethodDef QuiskMethods[] = {
{"open_samples", open_samples, METH_VARARGS, "Open the AFEDRI SDR-Net."},
{"close_samples", close_samples, METH_VARARGS, "Close the AFEDRI SDR-Net."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
#if PY_MAJOR_VERSION < 3
// Python 2.7:
PyMODINIT_FUNC initafedrinet_io (void)
{
if (Py_InitModule ("afedrinet_io", QuiskMethods) == NULL) {
printf("Py_InitModule failed!\n");
return;
}
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
printf("Failure to import pointers from _quisk\n");
return; //Error
}
}
// Python 3:
#else
static struct PyModuleDef afedrinet_iomodule = {
PyModuleDef_HEAD_INIT,
"afedrinet_io",
NULL,
-1,
QuiskMethods
} ;
PyMODINIT_FUNC PyInit_afedrinet_io(void)
{
PyObject * m;
m = PyModule_Create(&afedrinet_iomodule);
if (m == NULL)
return NULL;
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
printf("Failure to import pointers from _quisk\n");
return m; //Error
}
return m;
}
#endif

BIN
afedrinet/afedrinet_io.pyd Executable file

Binary file not shown.

61
afedrinet/quisk_conf.py Executable file
View File

@ -0,0 +1,61 @@
# This is a sample quisk_conf.py configuration file for Microsoft Windows.
# For Windows, your default config file name is "My Documents/quisk_conf.py",
# but you can use a different config file by using -c or --config. Quisk creates
# an initial default config file if there is none. To control Quisk, edit
# "My Documents/quisk_conf.py" using any text editor; for example WordPad (not Notepad).
# In Windows you can see what sound devices you have, and you can set the Primary
# Device for capture and playback by using Control Panel/Sounds and Audio Devices.
# If you have only one sound device, it should be set as "Primary". If you have
# several, find the names by using Control Panel/Sounds and Audio Devices; for
# example, you may have "SoundMAX HD Audio" in the list for "Sound playback" and
# "Sound recording". To specify this device for capture (recording) or playback,
# enter a unique part of its name using exact upper/lower case. For example:
# name_of_sound_capture = "SoundMAX"
# name_of_sound_play = "SoundMAX"
# There are many possible options for your config file. Copy the ones you want
# from the master file quisk_conf_defaults.py (but don't change the master file).
# The master config file is located in the site-packages/quisk folder for Python 2.7.
# This file is Python code and the comment character is "#". To ignore a line,
# start it with "#". To un-ignore a line, remove the "#". Generally you must start
# lines in column one (the left edge) except for logic blocks.
from afedrinet import quisk_hardware # Use different hardware file
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.0.8" # Sample source IP address
rx_udp_port = 50000 # Sample source UDP port
rx_udp_clock = 79998382 # ADC sample rate in Hertz
#rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
#sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
data_poll_usec = 10000
#sample_rate = 192000 # ADC hardware sample rate in Hertz
sample_rate = 740740 # ADC hardware sample rate in Hertz
playback_rate = 48000 # Radio sound play rate
name_of_sound_capt = ""#AFEDRI-SDR-Net Audio" # Name of soundcard capture hardware device.
name_of_sound_play = "Buil-in Output" # Use the same device for play back.
#name_of_sound_play = "Line 1"#Virtual Audio Cable" # Use the same device for play back.
latency_millisecs = 50 # latency time in milliseconds
display_fraction = 0.92 # The edges of the full bandwidth are not valid
default_rf_gain = 11
# Select the default screen when Quisk starts:
#default_screen = 'Graph'
default_screen = 'WFall'
# If you use hardware with a fixed VFO (crystal controlled SoftRock) un-comment the following:
# import quisk_hardware_fixed as quisk_hardware
# fixed_vfo_freq = 7056000
# If you use an SDR-IQ for capture, first install the SpectraView software
# that came with the SDR-IQ. This will install the USB driver. Then set these parameters:
# import quisk_hardware_sdriq as quisk_hardware # Use different hardware file
# use_sdriq = 1 # Capture device is the SDR-IQ
# sdriq_name = "SDR-IQ" # Name of the SDR-IQ device to open
# sdriq_clock = 66666667.0 # actual sample rate (66666667 nominal)
# sdriq_decimation = 500 # Must be 360, 500, 600, or 1250
# sample_rate = int(float(sdriq_clock) / sdriq_decimation + 0.5) # Don't change this
# name_of_sound_capt = "" # We do not capture from the soundcard
# playback_rate = 48000 # Radio sound play rate, default 48000
# display_fraction = 0.85 # The edges of the full bandwidth are not valid

54
afedrinet/quisk_conf_linux.py Executable file
View File

@ -0,0 +1,54 @@
# This is a sample quisk_conf.py configuration file for Linux
# For Windows, your default config file name is "/home/user/quisk_conf.py"
# but you can use a different config file by using -c or --config. Quisk creates
# an initial default config file if there is none. To control Quisk, edit
# "/home/user/quisk_conf.py" using any text editor; for example WordPad (not Notepad).
# In Windows you can see what sound devices you have, and you can set the Primary
# Device for capture and playback by using Control Panel/Sounds and Audio Devices.
# If you have only one sound device, it should be set as "Primary". If you have
# several, find the names by using Control Panel/Sounds and Audio Devices; for
# example, you may have "SoundMAX HD Audio" in the list for "Sound playback" and
# "Sound recording". To specify this device for capture (recording) or playback,
# enter a unique part of its name using exact upper/lower case. For example:
# name_of_sound_capture = "SoundMAX"
# name_of_sound_play = "SoundMAX"
# There are many possible options for your config file. Copy the ones you want
# from the master file quisk_conf_defaults.py (but don't change the master file).
# The master config file is located in the site-packages/quisk folder for Python 2.7.
# This file is Python code and the comment character is "#". To ignore a line,
# start it with "#". To un-ignore a line, remove the "#". Generally you must start
# lines in column one (the left edge) except for logic blocks.
from afedrinet import quisk_hardware # Use different hardware file
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.0.8" # Sample source IP address
rx_udp_port = 50000 # Sample source UDP port
rx_udp_clock = 80000000 # ADC sample rate in Hertz
#rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
#sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
data_poll_usec = 10000
#sample_rate = 192000 # ADC hardware sample rate in Hertz
sample_rate = 740740 # ADC hardware sample rate in Hertz
playback_rate = 48000 # Radio sound play rate
name_of_sound_capt = ""#AFEDRI-SDR-Net Audio" # Name of soundcard capture hardware device.
name_of_sound_play = "portaudiodefault"#hw:0,0" # Use the same device for play back.
#name_of_sound_play = "Line 1"#Virtual Audio Cable" # Use the same device for play back.
latency_millisecs = 50 # latency time in milliseconds
display_fraction = 0.92 # The edges of the full bandwidth are not valid
default_rf_gain = 14
# Select the default screen when Quisk starts:
#default_screen = 'Graph'
default_screen = 'WFall'
# If you use hardware with a fixed VFO (crystal controlled SoftRock) un-comment the following:
# import quisk_hardware_fixed as quisk_hardware
# fixed_vfo_freq = 7056000
# If you use an SDR-IQ for capture, first install the SpectraView software
# that came with the SDR-IQ. This will install the USB driver. Then set these parameters:
# import quisk_hardware_sdriq as quisk_hardware # Use different hardware file
# use_sdriq = 1 # Capture device is the SDR-IQ
# sdriq_name = "SDR-IQ" # Name of the SDR-IQ device to open
# sdriq_clock = 66666667.0 # actual sample rate (66666667 nominal)
# sdriq_decimation = 500 # Must be 360, 500, 600, or 1250
# sample_rate = int(float(sdriq_clock) / sdriq_decimation + 0.5) # Don't change this
# name_of_sound_capt = "" # We do not capture from the soundcard
# playback_rate = 48000 # Radio sound play rate, default 48000
# display_fraction = 0.85 # The edges of the full bandwidth are not valid

54
afedrinet/quisk_conf_mac.py Executable file
View File

@ -0,0 +1,54 @@
# This is a sample quisk_conf.py configuration file for Linux
# For Windows, your default config file name is "/home/user/quisk_conf.py"
# but you can use a different config file by using -c or --config. Quisk creates
# an initial default config file if there is none. To control Quisk, edit
# "/home/user/quisk_conf.py" using any text editor; for example WordPad (not Notepad).
# In Windows you can see what sound devices you have, and you can set the Primary
# Device for capture and playback by using Control Panel/Sounds and Audio Devices.
# If you have only one sound device, it should be set as "Primary". If you have
# several, find the names by using Control Panel/Sounds and Audio Devices; for
# example, you may have "SoundMAX HD Audio" in the list for "Sound playback" and
# "Sound recording". To specify this device for capture (recording) or playback,
# enter a unique part of its name using exact upper/lower case. For example:
# name_of_sound_capture = "SoundMAX"
# name_of_sound_play = "SoundMAX"
# There are many possible options for your config file. Copy the ones you want
# from the master file quisk_conf_defaults.py (but don't change the master file).
# The master config file is located in the site-packages/quisk folder for Python 2.7.
# This file is Python code and the comment character is "#". To ignore a line,
# start it with "#". To un-ignore a line, remove the "#". Generally you must start
# lines in column one (the left edge) except for logic blocks.
from afedrinet import quisk_hardware # Use different hardware file
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.0.8" # Sample source IP address
rx_udp_port = 50000 # Sample source UDP port
rx_udp_clock = 80000000 # ADC sample rate in Hertz
#rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
#sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
data_poll_usec = 10000
#sample_rate = 192000 # ADC hardware sample rate in Hertz
sample_rate = 740740 # ADC hardware sample rate in Hertz
playback_rate = 48000 # Radio sound play rate
name_of_sound_capt = ""#AFEDRI-SDR-Net Audio" # Name of soundcard capture hardware device.
name_of_sound_play = "portaudiodefault"#hw:0,0" # Use the same device for play back.
#name_of_sound_play = "Line 1"#Virtual Audio Cable" # Use the same device for play back.
latency_millisecs = 50 # latency time in milliseconds
display_fraction = 0.92 # The edges of the full bandwidth are not valid
default_rf_gain = 14
# Select the default screen when Quisk starts:
#default_screen = 'Graph'
default_screen = 'WFall'
# If you use hardware with a fixed VFO (crystal controlled SoftRock) un-comment the following:
# import quisk_hardware_fixed as quisk_hardware
# fixed_vfo_freq = 7056000
# If you use an SDR-IQ for capture, first install the SpectraView software
# that came with the SDR-IQ. This will install the USB driver. Then set these parameters:
# import quisk_hardware_sdriq as quisk_hardware # Use different hardware file
# use_sdriq = 1 # Capture device is the SDR-IQ
# sdriq_name = "SDR-IQ" # Name of the SDR-IQ device to open
# sdriq_clock = 66666667.0 # actual sample rate (66666667 nominal)
# sdriq_decimation = 500 # Must be 360, 500, 600, or 1250
# sample_rate = int(float(sdriq_clock) / sdriq_decimation + 0.5) # Don't change this
# name_of_sound_capt = "" # We do not capture from the soundcard
# playback_rate = 48000 # Radio sound play rate, default 48000
# display_fraction = 0.85 # The edges of the full bandwidth are not valid

95
afedrinet/quisk_hardware.py Executable file
View File

@ -0,0 +1,95 @@
# 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.use_sidetone = 0
self.clock = 80000000
self.old_LO_freq = 0
self.rf_gain_labels = ('RF -10','RF -7','RF -4','RF -1','+2','+5','+8','+11','+14','+17','+20','+23','+26','+29','+32','+35')
self.conf = conf
self.plugin = Control(self.conf.rx_udp_ip, self.conf.rx_udp_port)
self.app = app
self.decimations = [] # supported decimation rates
for dec in (53333, 96000, 133333, 185185, 192000, 370370, 740740, 1333333):
self.decimations.append(dec)
def open(self):
self.plugin.OpenHW() # Return a config message
RF_Gain_idx = int((10 + self.conf.default_rf_gain) / 3)
if not 0 <= RF_Gain_idx < len(self.rf_gain_labels):
RF_Gain_idx = 0
self.plugin.SetAttenuator(RF_Gain_idx)
self.app.BtnRfGain.SetIndex(RF_Gain_idx)
#print ("RF Gain %i" % self.conf.default_rf_gain)
return AF.open_samples(self.conf.rx_udp_ip, self.conf.rx_udp_port)
def close(self):
self.plugin.CloseHW()
def OnButtonRfGain(self, event):
btn = event.GetEventObject()
n = btn.index
if n > -1 or n < 16 :
self.plugin.SetAttenuator(n)
else:
print ('Unknown RfGain')
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
self.local_tune = tune
if vfo:
self.plugin.SetHWLO(vfo)
return tune, vfo
def ReturnFrequency(self): # Return the current tuning and VFO frequency
return (None, None) # Return LO frequency
def GetFirmwareVersion(self):
return 226
def HeartBeat(self):
# self.PrintStatus('Start', 'AFEDRI')
return
def VarDecimGetChoices(self): # Return a list/tuple of strings for the decimation control.
l = [] # a list of sample rates
for dec in self.decimations:
l.append(str( dec ))
return l
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate sps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set decimation before the call to open()
rate = self.application.vardecim_set # May be None or from different hardware
try:
dec = rate #int(float(self.conf.rx_udp_clock / rate + 0.5))
self.index = self.decimations.index(dec)
except:
try:
self.index = self.decimations.index(self.conf.sample_rate)
except:
self.index = 0
else:
self.index = index
dec = self.decimations[self.index]
self.plugin.SetHWSR(dec) # Return a config message
return dec

38
afedrinet/readme.txt Executable file
View File

@ -0,0 +1,38 @@
Hello All!
I want to publish here some information about possibility to use Quisk application on MAC computers running MAC OSX.
It is for experienced MAC OS users that know what is Terminal and command shell tools and how to compile executable from source code:
1. You should download Quisk package from author's web page
2. Xcode SDK and command tools
3. Install macports to manage ports that required to be used with Quisk
4. You should install the next packages:
python27, py27-wxpython-2.8, fftw-3.0 , portaudio, pulseaudio and probably some additional
5. Be ready that packages installation will take long time. Select macports python as default python executable:
>> sudo port select --set python python27
6. After you will extract Quisk files from archive to separate directory you must enter Quisk directory and compile Quisk for your system running next command:
>> make macports
7. After installation to use Quisk with AFEDRi SDR, you have to download additional packages: afedriusb , afedrinet extract contains of archive to separated folders afedriusb and afedrinet in main Quisk directory.
8. For network connected AFEDRI you must enter afedrinet folder and compile SDR support library running afe_library script, before you can run this script you must modify path to python library and include folders in this script, for example on my MAC it looks like:
###############################################################
gcc -o afedrinet_io.so --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -I"../" -I"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/" -L"/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/" -lpython2.7
###################################################################
9. For USB connection you must download the sdr_commander v1.22
Extract archive to separate folder and compile source code.
After successful compilation you will get executable sdr_commander, you must
copy it to quick/afedriusb folder
10.
10. Edit qusik_conf.py file to define correct sound device (card) name, copy this file as .quisk_conf.py to user's home directory.
For example for portaudio devices it will look like this one:
#################################################################
from afedriusb import quisk_hardware
sample_rate = 185185
name_of_sound_capt = "portaudio#2"
name_of_sound_play = "portaudiodefault"
latency_millisecs = 150
default_rf_gain = 14
default_screen = 'WFall'
playback_rate = 48000
default 48000
##########################################################

40
afedrinet/sdr_control.py Executable file
View File

@ -0,0 +1,40 @@
#!/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 array
#import socket # Import socket module
from afedri import *
#import afedri
class Control:
def __init__(self, sdr_address="192.168.0.8", sdr_port=50000):
self.hw = 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)

16
afedrinet/test.py Executable file
View File

@ -0,0 +1,16 @@
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import os,sys
from ctypes import *
os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH']
from sdr_control import *
#import sdr_control
#class self(control)
cnt = Control()
cnt.OpenHW()
cnt.SetHWLO(28050000)
cnt.SetHWSR(740740)
cnt.CloseHW()

2532
configure.py Executable file

File diff suppressed because it is too large Load Diff

33
defaults.html Executable file
View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>QUISK Configuration Defaults: quisk_conf_defaults.py</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="description" content="The file that describes the default configuration for Quisk.">
<meta name="author" content="James C. Ahlstrom">
<meta name="keywords" content="quisk, sdr, software defined radio, ham radio">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
* {
margin: 0px;
padding: 0px;
border: 0px;
}
iframe {
overflow: scroll;
width: 100%;
height: 100%;
}
div.contents {
position: fixed;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="contents">
<iframe src="quisk_conf_defaults.py"> </iframe>
</div>
</body>
</html>

1482
docs.html Executable file

File diff suppressed because it is too large Load Diff

141
dxcluster.py Executable file
View File

@ -0,0 +1,141 @@
from __future__ import absolute_import
# This code was contributed by Christof, DJ4CM. Many Thanks!!
import threading
import time
import telnetlib
import quisk_conf_defaults as conf
class DxEntry:
def __init__(self):
self.info = []
def getFreq(self):
return self.freq
def getDX(self):
return self.dx
def getSpotter(self, index):
return self.info[index][0]
def getTime(self, index):
return self.info[index][1]
def getLocation(self, index):
return self.info[index][2]
def getComment(self, index):
return self.info[index][3]
def getLen(self):
return len(self.info)
def equal(self, element):
if element.getDX() == self.dx:
return True
else:
return False
def join (self, element):
for i in range (0, len(element.info)):
self.info.insert(0, element.info[i])
length = len(self.info)
# limit to max history
if length > 3:
del (self.info[length-1])
self.timestamp = max (self.timestamp, element.timestamp)
def isExpired(self):
return time.time()-self.timestamp > conf.dxClExpireTime * 60
def parseMessage(self, message):
words = message.split()
sTime = ''
locator = ''
comment = ''
if len(words) > 3 and words[0].lower() == 'dx' and words[1].lower() == 'de':
spotter = words[2].strip(':')
self.freq = int(float(words[3])*1000)
self.dx = words[4]
for index in range (5, len(words)):
word = words[index]
try:
if sTime != '':
locator = word.strip('\07')
#search time
if word[0:3].isdigit() and word[4].isalpha():
sTime = word.strip('\07')
sTime = sTime[0:2]+':'+sTime[2:4]+ ' UTC'
if sTime == '':
if comment != '':
comment += ' '
comment += word
except:
pass
self.info.insert(0, (spotter, sTime, locator, comment))
self.timestamp = time.time()
#print(self.dx, self.freq, spotter, sTime, locator, comment)
return True
return False
class DxCluster(threading.Thread):
def __init__(self):
self.do_init = 1
threading.Thread.__init__(self)
self.doQuit = threading.Event()
self.dxSpots = []
self.doQuit.clear()
def run(self):
self.telnetInit()
self.telnetConnect()
while not self.doQuit.isSet():
try:
self.telnetRead()
except:
self.tn.close()
time.sleep(20)
if not self.doQuit.isSet():
self.telnetConnect()
self.tn.close()
def setListener (self, listener):
self.listener = listener
def telnetInit(self):
self.tn = telnetlib.Telnet()
def telnetConnect(self):
for i in range(10):
try:
self.tn.open(conf.dxClHost, conf.dxClPort, 10)
self.tn.read_until('login:', 10)
self.tn.write(str(conf.user_call_sign) + "\n") # user_call_sign may be Unicode
break
except:
time.sleep(0.5)
if conf.dxClPassword:
self.tn.read_until("Password: ")
self.tn.write(str(conf.dxClPassword) + "\n")
def telnetRead(self):
message = self.tn.read_until('\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
extdemod.c Executable file
View File

@ -0,0 +1,47 @@
#include <Python.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include "quisk.h"
// If you set add_extern_demod in your config file, you will get another
// button that will call this module. Change it to what you want. Save
// a copy because new releases of Quisk will overwrite this file.
//
// NOTE: NEW RELEASES OF QUISK WILL OVERWRITE THIS FILE!
int quisk_extern_demod(complex double * cSamples, int nSamples, double decim)
{ // Filter and demodulate the I/Q samples into audio play samples.
// cSamples: The input I/Q samples, and the output stereo play samples.
// nSamples: The number of input samples; maximum is SAMP_BUFFER_SIZE.
// decim: The decimation needed (1.0 for no decimation).
// The output play samples are stereo, and are placed into cSamples.
// The return value is the number of output samples = nSamples / decim.
// See quisk.h for useful data in quisk_sound_state. For example, the
// sample rate is quisk_sound_state.sample_rate. If you need decimation,
// look at iDecimate() and fDecimate() in quisk.c.
int i;
double d, di;
complex double cx;
static complex double fm_1 = 10; // Sample delayed by one
static complex double fm_2 = 10; // Sample delayed by two
if (fabs (decim - 1.0) > 0.001) // no provision for decimation
return 0;
for (i = 0; i < nSamples; i++) { // narrow FM
cx = cSamples[i];
di = creal(fm_1) * (cimag(cx) - cimag(fm_2)) -
cimag(fm_1) * (creal(cx) - creal(fm_2));
d = creal(fm_1) * creal(fm_1) + cimag(fm_1) * cimag(fm_1);
if (d == 0) // I don't think this can happen
di = 0;
else
di = di / d * quisk_sound_state.sample_rate;
fm_2 = fm_1; // fm_2 is sample cSamples[i - 2]
fm_1 = cx; // fm_1 is sample cSamples[i - 1]
cSamples[i] = di + I * di; // monophonic sound, two channels
}
return nSamples; // Number of play samples
}

466
filter.c Executable file
View File

@ -0,0 +1,466 @@
#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_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;
}

84
filter.h Executable file
View File

@ -0,0 +1,84 @@
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_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];

1297
filters.h Executable file

File diff suppressed because it is too large Load Diff

770
filters.py Executable file
View File

@ -0,0 +1,770 @@
# Rate 24000 sps, ripple 0.2 dB, atten 100 dB, shape 1.2
# Filters key is the bandwidth at 24000 sps.
# key = bw * 24000 / rate / 2
# bw = key * 2 * rate / 24000
Filters = {
8500: # 55 taps
[ -0.001578241201420500, -0.002488309592652162, 0.001185909979889287, 0.000286041612735020, -0.001914552184024928, 0.002760744242385052,
-0.001796589368523962, -0.001056335106849282, 0.004390770806455585, -0.005824325923037834, 0.003465568616258560, 0.002462568821267293,
-0.008988281812658983, 0.011629434198087422, -0.007027876977623796, -0.004272818661253936, 0.016890540812408691, -0.022619963224181760,
0.014949848668575091, 0.006115840936585570, -0.031813780331403710, 0.046915611796058194, -0.036226209362519565, -0.007500120805011563,
0.078216492123247466, -0.156996838729026640, 0.218668091485032380, 0.758014834761081340, 0.218668091485032380, -0.156996838729026640,
0.078216492123247466, -0.007500120805011563, -0.036226209362519565, 0.046915611796058194, -0.031813780331403710, 0.006115840936585570,
0.014949848668575091, -0.022619963224181760, 0.016890540812408691, -0.004272818661253936, -0.007027876977623796, 0.011629434198087422,
-0.008988281812658983, 0.002462568821267293, 0.003465568616258560, -0.005824325923037834, 0.004390770806455585, -0.001056335106849282,
-0.001796589368523962, 0.002760744242385052, -0.001914552184024928, 0.000286041612735020, 0.001185909979889287, -0.002488309592652162,
-0.001578241201420500],
7500: # 62 taps
[ 0.000094561136776712, 0.001408783281963872, 0.003252521294198745, 0.001088470223697677, -0.002238006126669353, 0.000797427337925380,
0.001818354390057951, -0.002971083246008131, 0.000737271087741229, 0.003304967641132078, -0.004678221609224611, 0.000618143813431047,
0.005732278632558854, -0.007264716193352521, 0.000382151456262394, 0.009375552017313197, -0.011107789354244654, 0.000048810931888440,
0.014797494013431127, -0.016988015917755381, -0.000322746345617388, 0.023289857616213484, -0.026716501910571362, -0.000681860486120778,
0.038508535988246995, -0.045999278172093885, -0.000961050429347091, 0.076884527279961243, -0.108006033576806130, -0.001113929105132611,
0.551548401015432340, 0.551548401015432340, -0.001113929105132611, -0.108006033576806130, 0.076884527279961243, -0.000961050429347091,
-0.045999278172093885, 0.038508535988246995, -0.000681860486120778, -0.026716501910571362, 0.023289857616213484, -0.000322746345617388,
-0.016988015917755381, 0.014797494013431127, 0.000048810931888440, -0.011107789354244654, 0.009375552017313197, 0.000382151456262394,
-0.007264716193352521, 0.005732278632558854, 0.000618143813431047, -0.004678221609224611, 0.003304967641132078, 0.000737271087741229,
-0.002971083246008131, 0.001818354390057951, 0.000797427337925380, -0.002238006126669353, 0.001088470223697677, 0.003252521294198745,
0.001408783281963872, 0.000094561136776712],
6000: # 77 taps
[0.000208183216794497, 0.001160220144497454, 0.002498877019501132, 0.002217684703504632, -0.000364985361618348, -0.001823661861823501,
0.000356579314286080, 0.002005946277037424, -0.000685757071824683, -0.002428321330453817, 0.001256816376812629, 0.002943833745660115,
-0.002130949481744204, -0.003446793644558851, 0.003380457505759830, 0.003807055791440830, -0.005074958590792311, -0.003869772827804518,
0.007242784565398008, 0.003418656761533474, -0.009894698971107840, -0.002211451952907241, 0.012977467451293289, -0.000058006701182926,
-0.016390443243429843, 0.003773319671211690, 0.019989716564592076, -0.009449626980637388, -0.023581188491464997, 0.017927337095200193,
0.026945533578336647, -0.030887338584165511, -0.029856365801280219, 0.052657593695444822, 0.032108551641510746, -0.099253908185103573,
-0.033533139787951992, 0.315983886115891840, 0.534019780032233380, 0.315983886115891840, -0.033533139787951992, -0.099253908185103573,
0.032108551641510746, 0.052657593695444822, -0.029856365801280219, -0.030887338584165511, 0.026945533578336647, 0.017927337095200193,
-0.023581188491464997, -0.009449626980637388, 0.019989716564592076, 0.003773319671211690, -0.016390443243429843, -0.000058006701182926,
0.012977467451293289, -0.002211451952907241, -0.009894698971107840, 0.003418656761533474, 0.007242784565398008, -0.003869772827804518,
-0.005074958590792311, 0.003807055791440830, 0.003380457505759830, -0.003446793644558851, -0.002130949481744204, 0.002943833745660115,
0.001256816376812629, -0.002428321330453817, -0.000685757071824683, 0.002005946277037424, 0.000356579314286080, -0.001823661861823501,
-0.000364985361618348, 0.002217684703504632, 0.002498877019501132, 0.001160220144497454, 0.000208183216794497],
5000: # 92 taps
[ 0.000172282027074915, 0.000805722137099302, 0.001789488027434626, 0.002208898476835740, 0.001133555920432830, -0.000798012231727563,
-0.001468876483570138, 0.000053213574826391, 0.001640044205108085, 0.000693959429133929, -0.001663633271521857, -0.001609719846438075,
0.001334915862232435, 0.002601113060240987, -0.000511327252542249, -0.003459777069001452, -0.000863807590293483, 0.003915575549171324,
0.002725579328876624, -0.003678783180717698, -0.004875619345633069, 0.002500119680365451, 0.006978228022424931, -0.000223624762857588,
-0.008586543420189311, -0.003156720729060025, 0.009180028261871051, 0.007460886285077675, -0.008222908809263846, -0.012298402032805727,
0.005219135899689294, 0.017069401572755501, 0.000240426913514277, -0.020971624610692834, -0.008474182212803629, 0.022996901553415793,
0.019787130769856574, -0.021836278313112276, -0.034759648853466663, 0.015468734639474426, 0.055194479055695952, 0.000590985958335975,
-0.088238504731419823, -0.043831925654670714, 0.182996493758220890, 0.409587845444253250, 0.409587845444253250, 0.182996493758220890,
-0.043831925654670714, -0.088238504731419823, 0.000590985958335975, 0.055194479055695952, 0.015468734639474426, -0.034759648853466663,
-0.021836278313112276, 0.019787130769856574, 0.022996901553415793, -0.008474182212803629, -0.020971624610692834, 0.000240426913514277,
0.017069401572755501, 0.005219135899689294, -0.012298402032805727, -0.008222908809263846, 0.007460886285077675, 0.009180028261871051,
-0.003156720729060025, -0.008586543420189311, -0.000223624762857588, 0.006978228022424931, 0.002500119680365451, -0.004875619345633069,
-0.003678783180717698, 0.002725579328876624, 0.003915575549171324, -0.000863807590293483, -0.003459777069001452, -0.000511327252542249,
0.002601113060240987, 0.001334915862232435, -0.001609719846438075, -0.001663633271521857, 0.000693959429133929, 0.001640044205108085,
0.000053213574826391, -0.001468876483570138, -0.000798012231727563, 0.001133555920432830, 0.002208898476835740, 0.001789488027434626,
0.000805722137099302, 0.000172282027074915],
4500: # 102 taps
[ 0.000109626786353929, 0.000462685347095325, 0.000976091511950423, 0.001146819725082014, 0.000393473830731237, -0.001141511548465342,
-0.002282834370334400, -0.001829207175666342, -0.000101528442900756, 0.000987382913028173, 0.000039751448950354, -0.001822373509053446,
-0.001985247021210946, 0.000232895236822806, 0.002153431919758378, 0.000930723811183338, -0.002248491432658168, -0.003017055190706798,
0.000342078561845510, 0.003721285661215643, 0.002085819103613060, -0.003140571194897081, -0.004835117960215140, 0.000288263563014682,
0.005924235485555640, 0.003715592386573005, -0.004558690834977428, -0.007633320493226938, 0.000139066056400393, 0.009165154333957292,
0.006093793617689697, -0.006780247949818048, -0.011926916343123978, -0.000057228900700871, 0.014232549280021243, 0.009771685994328400,
-0.010499186884061604, -0.019033587881754202, -0.000249830231388923, 0.023228171670193941, 0.016360728644062135, -0.017855007053639658,
-0.033458833271228797, -0.000405885453398987, 0.044679917304132288, 0.033455780397647107, -0.040439521683945157, -0.085684078775375178,
-0.000493182012170114, 0.201251835014007290, 0.374516101600616320, 0.374516101600616320, 0.201251835014007290, -0.000493182012170114,
-0.085684078775375178, -0.040439521683945157, 0.033455780397647107, 0.044679917304132288, -0.000405885453398987, -0.033458833271228797,
-0.017855007053639658, 0.016360728644062135, 0.023228171670193941, -0.000249830231388923, -0.019033587881754202, -0.010499186884061604,
0.009771685994328400, 0.014232549280021243, -0.000057228900700871, -0.011926916343123978, -0.006780247949818048, 0.006093793617689697,
0.009165154333957292, 0.000139066056400393, -0.007633320493226938, -0.004558690834977428, 0.003715592386573005, 0.005924235485555640,
0.000288263563014682, -0.004835117960215140, -0.003140571194897081, 0.002085819103613060, 0.003721285661215643, 0.000342078561845510,
-0.003017055190706798, -0.002248491432658168, 0.000930723811183338, 0.002153431919758378, 0.000232895236822806, -0.001985247021210946,
-0.001822373509053446, 0.000039751448950354, 0.000987382913028173, -0.000101528442900756, -0.001829207175666342, -0.002282834370334400,
-0.001141511548465342, 0.000393473830731237, 0.001146819725082014, 0.000976091511950423, 0.000462685347095325, 0.000109626786353929],
4000: # 115 taps
[ 0.000078524780320715, 0.000329316620713670, 0.000756688762644958, 0.001124260123708105, 0.001012255791778806, 0.000167097630541898,
-0.001099970608704988, -0.001940975178324101, -0.001625045960154994, -0.000333634994172134, 0.000774600995458535, 0.000561151578352762,
-0.000854751315214004, -0.001984988321940357, -0.001407739886101419, 0.000563473972774177, 0.001900424989770745, 0.000925528523083090,
-0.001609328373038515, -0.002935359144493341, -0.001199176692593675, 0.002118199425756780, 0.003340065554812114, 0.000621160360920014,
-0.003467567745617676, -0.004224605385298791, -0.000122849763708023, 0.004757557369118008, 0.004664298923525774, -0.001148493552322016,
-0.006629807337962404, -0.005080846185003255, 0.002831233307698604, 0.008609924128977818, 0.004891354675929344, -0.005425659232916345,
-0.010937452229276256, -0.004116901228197529, 0.008877312794209294, 0.013300805492116527, 0.002232134062032589, -0.013614806007146969,
-0.015720726837983701, 0.001140172406302503, 0.019985979932262889, 0.017946437617680795, -0.007018595519087580, -0.029113241143045584,
-0.019901010720465118, 0.017502959326722210, 0.043879416112787749, 0.021395356319346116, -0.039872421015430898, -0.076438935865810770,
-0.022353637195009195, 0.124776719393937450, 0.286085089477746360, 0.356006518780215160, 0.286085089477746360, 0.124776719393937450,
-0.022353637195009195, -0.076438935865810770, -0.039872421015430898, 0.021395356319346116, 0.043879416112787749, 0.017502959326722210,
-0.019901010720465118, -0.029113241143045584, -0.007018595519087580, 0.017946437617680795, 0.019985979932262889, 0.001140172406302503,
-0.015720726837983701, -0.013614806007146969, 0.002232134062032589, 0.013300805492116527, 0.008877312794209294, -0.004116901228197529,
-0.010937452229276256, -0.005425659232916345, 0.004891354675929344, 0.008609924128977818, 0.002831233307698604, -0.005080846185003255,
-0.006629807337962404, -0.001148493552322016, 0.004664298923525774, 0.004757557369118008, -0.000122849763708023, -0.004224605385298791,
-0.003467567745617676, 0.000621160360920014, 0.003340065554812114, 0.002118199425756780, -0.001199176692593675, -0.002935359144493341,
-0.001609328373038515, 0.000925528523083090, 0.001900424989770745, 0.000563473972774177, -0.001407739886101419, -0.001984988321940357,
-0.000854751315214004, 0.000561151578352762, 0.000774600995458535, -0.000333634994172134, -0.001625045960154994, -0.001940975178324101,
-0.001099970608704988, 0.000167097630541898, 0.001012255791778806, 0.001124260123708105, 0.000756688762644958, 0.000329316620713670,
0.000078524780320715],
3000: # 153 taps
[ 0.000035516842253053, 0.000120241356692793, 0.000262655558849812, 0.000421822903927101, 0.000504577556110132, 0.000393748344899348,
0.000013887896365045, -0.000595749610102644, -0.001250516069080062, -0.001672797229233531, -0.001624666112532619, -0.001059802835950466,
-0.000199611602796516, 0.000539126532493226, 0.000756552265297489, 0.000316087930054194, -0.000530090257617794, -0.001244496438584232,
-0.001312107606392913, -0.000583465448463906, 0.000572285419075017, 0.001440987738923421, 0.001399231913182619, 0.000355359654986622,
-0.001108071194955440, -0.002036249585608732, -0.001707924634389252, -0.000169594756520662, 0.001685676765584473, 0.002618141236597755,
0.001855681542593165, -0.000301881949096732, -0.002542398236828585, -0.003317624887398237, -0.001892391320644168, 0.001031582912020721,
0.003613502326574886, 0.004009329165673526, 0.001661239094822322, -0.002143832449766766, -0.004935146076383144, -0.004634572876828250,
-0.001061541395886805, 0.003698344992855291, 0.006465358367957256, 0.005053382115343493, -0.000073525601278193, -0.005791007488487868,
-0.008166064459464053, -0.005109407447857288, 0.001926336767092539, 0.008511258651897639, 0.009958456933858797, 0.004574761558815073,
-0.004759500034603159, -0.012000283350062089, -0.011748002733961208, -0.003120526928071337, 0.008993494047071084, 0.016529385910476514,
0.013426861274101313, 0.000181178754148492, -0.015466003330041472, -0.022748666494653846, -0.014877802849879123, 0.005502737892245722,
0.026345716718994539, 0.032621748472455206, 0.016002483863926359, -0.017887767208878101, -0.049636964594866217, -0.054713319818683277,
-0.016713474537575129, 0.061906959876918494, 0.157995584096340960, 0.236648015289099320, 0.266956938993864910, 0.236648015289099320,
0.157995584096340960, 0.061906959876918494, -0.016713474537575129, -0.054713319818683277, -0.049636964594866217, -0.017887767208878101,
0.016002483863926359, 0.032621748472455206, 0.026345716718994539, 0.005502737892245722, -0.014877802849879123, -0.022748666494653846,
-0.015466003330041472, 0.000181178754148492, 0.013426861274101313, 0.016529385910476514, 0.008993494047071084, -0.003120526928071337,
-0.011748002733961208, -0.012000283350062089, -0.004759500034603159, 0.004574761558815073, 0.009958456933858797, 0.008511258651897639,
0.001926336767092539, -0.005109407447857288, -0.008166064459464053, -0.005791007488487868, -0.000073525601278193, 0.005053382115343493,
0.006465358367957256, 0.003698344992855291, -0.001061541395886805, -0.004634572876828250, -0.004935146076383144, -0.002143832449766766,
0.001661239094822322, 0.004009329165673526, 0.003613502326574886, 0.001031582912020721, -0.001892391320644168, -0.003317624887398237,
-0.002542398236828585, -0.000301881949096732, 0.001855681542593165, 0.002618141236597755, 0.001685676765584473, -0.000169594756520662,
-0.001707924634389252, -0.002036249585608732, -0.001108071194955440, 0.000355359654986622, 0.001399231913182619, 0.001440987738923421,
0.000572285419075017, -0.000583465448463906, -0.001312107606392913, -0.001244496438584232, -0.000530090257617794, 0.000316087930054194,
0.000756552265297489, 0.000539126532493226, -0.000199611602796516, -0.001059802835950466, -0.001624666112532619, -0.001672797229233531,
-0.001250516069080062, -0.000595749610102644, 0.000013887896365045, 0.000393748344899348, 0.000504577556110132, 0.000421822903927101,
0.000262655558849812, 0.000120241356692793, 0.000035516842253053],
2800: # 164 taps
[ 0.000031131729843669, 0.000101363194753254, 0.000221445426297415, 0.000366876574918002, 0.000474767078312489, 0.000455842035531617,
0.000232260566090893, -0.000211274148558287, -0.000789599231892209, -0.001321558408924163, -0.001590694231360794, -0.001445876584766002,
-0.000895790759735873, -0.000140741015448872, 0.000492974178868593, 0.000701994832615354, 0.000371048524479058, -0.000337331657814867,
-0.001034893398244673, -0.001296923457098872, -0.000901505495786621, 0.000013737161230886, 0.000983722390445291, 0.001446949255083495,
0.001067648443145340, -0.000031354774249279, -0.001279007610852184, -0.001938862367504847, -0.001531888866790246, -0.000161657161535566,
0.001470243306104189, 0.002410991575715065, 0.002003292184824320, 0.000324608046693798, -0.001764616004515830, -0.003045092340783782,
-0.002637146414877572, -0.000580772176139985, 0.002073712223135724, 0.003782183003317882, 0.003389875946583850, 0.000885046737439423,
-0.002455184150014141, -0.004690784048082864, -0.004326357662532086, -0.001279167957787954, 0.002902671922490944, 0.005795008254325585,
0.005478283962467929, 0.001772012477148658, -0.003450331904449436, -0.007166940301855534, -0.006922766471023288, -0.002399780547747377,
0.004134972375816635, 0.008909643621809859, 0.008776960071400969, 0.003217389082858829, -0.005021094247026112, -0.011201040385085154,
-0.011246577131734643, -0.004323921802028225, 0.006225285499714047, 0.014372818914766290, 0.014725605621083649, 0.005913959753070873,
-0.007987043798634774, -0.019127577707865114, -0.020080950001885520, -0.008435384367710340, 0.010879472951485192, 0.027244661531034900,
0.029649597250285761, 0.013188790214990415, -0.016693663421477496, -0.044885495280403948, -0.052572421004629472, -0.026098663081897257,
0.035426680640922303, 0.117691081437968410, 0.195575709828410660, 0.242828423889182740, 0.242828423889182740, 0.195575709828410660,
0.117691081437968410, 0.035426680640922303, -0.026098663081897257, -0.052572421004629472, -0.044885495280403948, -0.016693663421477496,
0.013188790214990415, 0.029649597250285761, 0.027244661531034900, 0.010879472951485192, -0.008435384367710340, -0.020080950001885520,
-0.019127577707865114, -0.007987043798634774, 0.005913959753070873, 0.014725605621083649, 0.014372818914766290, 0.006225285499714047,
-0.004323921802028225, -0.011246577131734643, -0.011201040385085154, -0.005021094247026112, 0.003217389082858829, 0.008776960071400969,
0.008909643621809859, 0.004134972375816635, -0.002399780547747377, -0.006922766471023288, -0.007166940301855534, -0.003450331904449436,
0.001772012477148658, 0.005478283962467929, 0.005795008254325585, 0.002902671922490944, -0.001279167957787954, -0.004326357662532086,
-0.004690784048082864, -0.002455184150014141, 0.000885046737439423, 0.003389875946583850, 0.003782183003317882, 0.002073712223135724,
-0.000580772176139985, -0.002637146414877572, -0.003045092340783782, -0.001764616004515830, 0.000324608046693798, 0.002003292184824320,
0.002410991575715065, 0.001470243306104189, -0.000161657161535566, -0.001531888866790246, -0.001938862367504847, -0.001279007610852184,
-0.000031354774249279, 0.001067648443145340, 0.001446949255083495, 0.000983722390445291, 0.000013737161230886, -0.000901505495786621,
-0.001296923457098872, -0.001034893398244673, -0.000337331657814867, 0.000371048524479058, 0.000701994832615354, 0.000492974178868593,
-0.000140741015448872, -0.000895790759735873, -0.001445876584766002, -0.001590694231360794, -0.001321558408924163, -0.000789599231892209,
-0.000211274148558287, 0.000232260566090893, 0.000455842035531617, 0.000474767078312489, 0.000366876574918002, 0.000221445426297415,
0.000101363194753254, 0.000031131729843669],
2500: # 184 taps
[ 0.000025917992771962, 0.000080053245121681, 0.000175970492512707, 0.000306338738553062, 0.000441565642149420, 0.000529323030925432,
0.000506924615738917, 0.000324946604356638, -0.000025129491557388, -0.000491406291157732, -0.000962621820235116, -0.001296070805120129,
-0.001367486032675225, -0.001125819560345427, -0.000629044038382859, -0.000039974702137716, 0.000424508165741649, 0.000577252836600570,
0.000345217289298406, -0.000184075454087644, -0.000781983763019378, -0.001165504636264451, -0.001120965614204760, -0.000611972955861357,
0.000181852749867249, 0.000923465749187075, 0.001259585458205595, 0.000987840637523650, 0.000172679353454093, -0.000852839379782549,
-0.001614970097646428, -0.001711408440970664, -0.001014931729801465, 0.000231871248756494, 0.001490613857222986, 0.002154309898448981,
0.001835132214640792, 0.000579040055238457, -0.001106680482118841, -0.002448432281636389, -0.002754083239929045, -0.001760402090267228,
0.000190461179667856, 0.002251447413131553, 0.003429705849477585, 0.003056701038439272, 0.001150595364468159, -0.001518817798865299,
-0.003726827835919960, -0.004345414044232734, -0.002906143603126905, 0.000100825920300210, 0.003366376342127399, 0.005318377282596896,
0.004864845763651887, 0.001979300066273931, -0.002175246374000726, -0.005692516278767321, -0.006780045002879058, -0.004649909783330805,
-0.000010396809644141, 0.005123662380431661, 0.008280363892442090, 0.007693898397635463, 0.003246147289573304, -0.003296547115364492,
-0.008942285826077026, -0.010803588267982528, -0.007523983582706444, -0.000118131836810526, 0.008247900700950585, 0.013549640285344118,
0.012768247956349992, 0.005492469173933735, -0.005544176969275239, -0.015378525898323159, -0.018928522076093565, -0.013447400254072768,
-0.000204681888673014, 0.015499492179231059, 0.026221846254622386, 0.025562428680330915, 0.011389219733612054, -0.012327402868838598,
-0.036056520568517325, -0.047698955925824099, -0.037076675237560336, -0.000252599132841553, 0.058062325226935912, 0.125125149137395140,
0.183789926776792010, 0.217965757483672360, 0.217965757483672360, 0.183789926776792010, 0.125125149137395140, 0.058062325226935912,
-0.000252599132841553, -0.037076675237560336, -0.047698955925824099, -0.036056520568517325, -0.012327402868838598, 0.011389219733612054,
0.025562428680330915, 0.026221846254622386, 0.015499492179231059, -0.000204681888673014, -0.013447400254072768, -0.018928522076093565,
-0.015378525898323159, -0.005544176969275239, 0.005492469173933735, 0.012768247956349992, 0.013549640285344118, 0.008247900700950585,
-0.000118131836810526, -0.007523983582706444, -0.010803588267982528, -0.008942285826077026, -0.003296547115364492, 0.003246147289573304,
0.007693898397635463, 0.008280363892442090, 0.005123662380431661, -0.000010396809644141, -0.004649909783330805, -0.006780045002879058,
-0.005692516278767321, -0.002175246374000726, 0.001979300066273931, 0.004864845763651887, 0.005318377282596896, 0.003366376342127399,
0.000100825920300210, -0.002906143603126905, -0.004345414044232734, -0.003726827835919960, -0.001518817798865299, 0.001150595364468159,
0.003056701038439272, 0.003429705849477585, 0.002251447413131553, 0.000190461179667856, -0.001760402090267228, -0.002754083239929045,
-0.002448432281636389, -0.001106680482118841, 0.000579040055238457, 0.001835132214640792, 0.002154309898448981, 0.001490613857222986,
0.000231871248756494, -0.001014931729801465, -0.001711408440970664, -0.001614970097646428, -0.000852839379782549, 0.000172679353454093,
0.000987840637523650, 0.001259585458205595, 0.000923465749187075, 0.000181852749867249, -0.000611972955861357, -0.001120965614204760,
-0.001165504636264451, -0.000781983763019378, -0.000184075454087644, 0.000345217289298406, 0.000577252836600570, 0.000424508165741649,
-0.000039974702137716, -0.000629044038382859, -0.001125819560345427, -0.001367486032675225, -0.001296070805120129, -0.000962621820235116,
-0.000491406291157732, -0.000025129491557388, 0.000324946604356638, 0.000506924615738917, 0.000529323030925432, 0.000441565642149420,
0.000306338738553062, 0.000175970492512707, 0.000080053245121681, 0.000025917992771962],
2250: # 204 taps
[ 0.000021235929725912, 0.000058222413723091, 0.000120890002831670, 0.000203732920433964, 0.000289842861102114, 0.000349648687783014,
0.000345987806871638, 0.000244741113281256, 0.000028683359304148, -0.000289767552139220, -0.000662562723513035, -0.001011139906426526,
-0.001244168321321761, -0.001284537444429324, -0.001097853884402359, -0.000712241533727792, -0.000220016931252946, 0.000242881192519691,
0.000536533139642942, 0.000564406769439812, 0.000312158569581760, -0.000137882856713157, -0.000625674547956830, -0.000963565458358823,
-0.001002878434444682, -0.000693839038801721, -0.000115120712337792, 0.000542271936437241, 0.001036221937634474, 0.001161519435408656,
0.000833656052982527, 0.000135090190320360, -0.000698564960603252, -0.001354001229403848, -0.001554644716587147, -0.001173297796072647,
-0.000298257519547670, 0.000778476775888895, 0.001654851153636767, 0.001966028879000262, 0.001532563461927731, 0.000450688659966846,
-0.000917352451094544, -0.002059631204322221, -0.002502298531213019, -0.002001396043186186, -0.000662678470838726, 0.001065622033389926,
0.002537010973466811, 0.003144134768937308, 0.002563969753555681, 0.000913777569993959, -0.001253108006318142, -0.003125545369287028,
-0.003932636886255475, -0.003254230145379617, -0.001221262259301159, 0.001484407116176422, 0.003849603664148359, 0.004902048797404353,
0.004101215157545026, 0.001593866974392046, -0.001780147556263722, -0.004757480195317273, -0.006114639178429671, -0.005158848135225734,
-0.002053747031888304, 0.002164989106816366, 0.005919348898632932, 0.007665501534134797, 0.006511589945553144, 0.002635680468215082,
-0.002680807848145089, -0.007454309167215744, -0.009718217353005822, -0.008306681703332177, -0.003400058938312392, 0.003406734158392871,
0.009587959427131761, 0.012589212182082522, 0.010837597719690596, 0.004476080310267183, -0.004494123792760167, -0.012783165957938696,
-0.016952789434463451, -0.014752125292065949, -0.006159094027769298, 0.006304669250227038, 0.018192572127496920, 0.024568955795679859,
0.021832946865353555, 0.009314262398551900, -0.009958208720588032, -0.029683337395096002, -0.041898689176258344, -0.039357322455930641,
-0.017957925042069021, 0.021558306071190737, 0.073246545073647112, 0.127141924217462950, 0.171715605172332860, 0.196918159377366730,
0.196918159377366730, 0.171715605172332860, 0.127141924217462950, 0.073246545073647112, 0.021558306071190737, -0.017957925042069021,
-0.039357322455930641, -0.041898689176258344, -0.029683337395096002, -0.009958208720588032, 0.009314262398551900, 0.021832946865353555,
0.024568955795679859, 0.018192572127496920, 0.006304669250227038, -0.006159094027769298, -0.014752125292065949, -0.016952789434463451,
-0.012783165957938696, -0.004494123792760167, 0.004476080310267183, 0.010837597719690596, 0.012589212182082522, 0.009587959427131761,
0.003406734158392871, -0.003400058938312392, -0.008306681703332177, -0.009718217353005822, -0.007454309167215744, -0.002680807848145089,
0.002635680468215082, 0.006511589945553144, 0.007665501534134797, 0.005919348898632932, 0.002164989106816366, -0.002053747031888304,
-0.005158848135225734, -0.006114639178429671, -0.004757480195317273, -0.001780147556263722, 0.001593866974392046, 0.004101215157545026,
0.004902048797404353, 0.003849603664148359, 0.001484407116176422, -0.001221262259301159, -0.003254230145379617, -0.003932636886255475,
-0.003125545369287028, -0.001253108006318142, 0.000913777569993959, 0.002563969753555681, 0.003144134768937308, 0.002537010973466811,
0.001065622033389926, -0.000662678470838726, -0.002001396043186186, -0.002502298531213019, -0.002059631204322221, -0.000917352451094544,
0.000450688659966846, 0.001532563461927731, 0.001966028879000262, 0.001654851153636767, 0.000778476775888895, -0.000298257519547670,
-0.001173297796072647, -0.001554644716587147, -0.001354001229403848, -0.000698564960603252, 0.000135090190320360, 0.000833656052982527,
0.001161519435408656, 0.001036221937634474, 0.000542271936437241, -0.000115120712337792, -0.000693839038801721, -0.001002878434444682,
-0.000963565458358823, -0.000625674547956830, -0.000137882856713157, 0.000312158569581760, 0.000564406769439812, 0.000536533139642942,
0.000242881192519691, -0.000220016931252946, -0.000712241533727792, -0.001097853884402359, -0.001284537444429324, -0.001244168321321761,
-0.001011139906426526, -0.000662562723513035, -0.000289767552139220, 0.000028683359304148, 0.000244741113281256, 0.000345987806871638,
0.000349648687783014, 0.000289842861102114, 0.000203732920433964, 0.000120890002831670, 0.000058222413723091, 0.000021235929725912],
2200: #208 taps
[ 0.000019773973500698, 0.000050300702719923, 0.000099108356371034, 0.000158194404233062, 0.000210525538441445, 0.000229815943545636,
0.000185844232898202, 0.000053925802103604, -0.000173777821813056, -0.000478713240844808, -0.000812707500835325, -0.001104087723445511,
-0.001273942998532759, -0.001259278130931017, -0.001036517683714317, -0.000637135855849719, -0.000147905388336950, 0.000307371053002897,
0.000601888175399474, 0.000646481239850203, 0.000423598448328852, 0.000001477795930511, -0.000479511456398659, -0.000847542574860787,
-0.000956181777710176, -0.000738934578391376, -0.000241404176917473, 0.000383518196967566, 0.000920251953818672, 0.001163208934048226,
0.000993921481591226, 0.000434388361798019, -0.000346030182304409, -0.001078079759008518, -0.001482742622315739, -0.001374843351537245,
-0.000742498373847161, 0.000230275873300524, 0.001212510588437732, 0.001835971920260912, 0.001830429895718975, 0.001135824361158899,
-0.000053708874146025, -0.001337763115758486, -0.002240751224958943, -0.002384735614959788, -0.001642386517013215, -0.000209813690831605,
0.001436458922346993, 0.002693390382420342, 0.003048363283850710, 0.002282591868736866, 0.000582131982803615, -0.001494683726987109,
-0.003194641328318874, -0.003837926603637492, -0.003083544121084786, -0.001090420131933939, 0.001496937564140111, 0.003749027796032780,
0.004778960997800515, 0.004083937494744029, 0.001771796479680179, -0.001424175363221383, -0.004367185919670888, -0.005912778052177615,
-0.005343837748934002, -0.002682898015250491, 0.001248211259214371, 0.005067191417250770, 0.007305293729039890, 0.006958279310681562,
0.003912446091368335, -0.000927394507475861, -0.005884363717650123, -0.009072440354829070, -0.009093145110623519, -0.005615743824657722,
0.000389138616762466, 0.006884468473346532, 0.011428522924352827, 0.012058034647345471, 0.008084684337356606, 0.000498409327693868,
-0.008214286092348189, -0.014832606629260737, -0.016526271123526743, -0.011969567139734955, -0.002035744936384088, 0.010229678038446030,
0.020433030409654745, 0.024256700710114647, 0.019055511377788287, 0.005093243716641166, -0.014044229049919777, -0.032091634104820632,
-0.041707387370849711, -0.036619723239870061, -0.013689386302707808, 0.025758635108220899, 0.075797616878918297, 0.127078744612693170,
0.169056056140688250, 0.192658606871644660, 0.192658606871644660, 0.169056056140688250, 0.127078744612693170, 0.075797616878918297,
0.025758635108220899, -0.013689386302707808, -0.036619723239870061, -0.041707387370849711, -0.032091634104820632, -0.014044229049919777,
0.005093243716641166, 0.019055511377788287, 0.024256700710114647, 0.020433030409654745, 0.010229678038446030, -0.002035744936384088,
-0.011969567139734955, -0.016526271123526743, -0.014832606629260737, -0.008214286092348189, 0.000498409327693868, 0.008084684337356606,
0.012058034647345471, 0.011428522924352827, 0.006884468473346532, 0.000389138616762466, -0.005615743824657722, -0.009093145110623519,
-0.009072440354829070, -0.005884363717650123, -0.000927394507475861, 0.003912446091368335, 0.006958279310681562, 0.007305293729039890,
0.005067191417250770, 0.001248211259214371, -0.002682898015250491, -0.005343837748934002, -0.005912778052177615, -0.004367185919670888,
-0.001424175363221383, 0.001771796479680179, 0.004083937494744029, 0.004778960997800515, 0.003749027796032780, 0.001496937564140111,
-0.001090420131933939, -0.003083544121084786, -0.003837926603637492, -0.003194641328318874, -0.001494683726987109, 0.000582131982803615,
0.002282591868736866, 0.003048363283850710, 0.002693390382420342, 0.001436458922346993, -0.000209813690831605, -0.001642386517013215,
-0.002384735614959788, -0.002240751224958943, -0.001337763115758486, -0.000053708874146025, 0.001135824361158899, 0.001830429895718975,
0.001835971920260912, 0.001212510588437732, 0.000230275873300524, -0.000742498373847161, -0.001374843351537245, -0.001482742622315739,
-0.001078079759008518, -0.000346030182304409, 0.000434388361798019, 0.000993921481591226, 0.001163208934048226, 0.000920251953818672,
0.000383518196967566, -0.000241404176917473, -0.000738934578391376, -0.000956181777710176, -0.000847542574860787, -0.000479511456398659,
0.000001477795930511, 0.000423598448328852, 0.000646481239850203, 0.000601888175399474, 0.000307371053002897, -0.000147905388336950,
-0.000637135855849719, -0.001036517683714317, -0.001259278130931017, -0.001273942998532759, -0.001104087723445511, -0.000812707500835325,
-0.000478713240844808, -0.000173777821813056, 0.000053925802103604, 0.000185844232898202, 0.000229815943545636, 0.000210525538441445,
0.000158194404233062, 0.000099108356371034, 0.000050300702719923, 0.000019773973500698],
2000: # 230 taps
[ 0.000018263332334824, 0.000047149978647285, 0.000097406241243338, 0.000168379817473922, 0.000253967146823514, 0.000340023431940833,
0.000404952972379638, 0.000422974737428721, 0.000369902505736477, 0.000230539284736893, 0.000005684130024729, -0.000283317092531425,
-0.000594762485225633, -0.000872546941267996, -0.001057521343918215, -0.001102228302856328, -0.000985222996371773, -0.000720881682050162,
-0.000360902436481765, 0.000014352678995945, 0.000313854754768513, 0.000459361314264358, 0.000408357201159471, 0.000169601665199639,
-0.000194309748856567, -0.000579769259034295, -0.000868450605965827, -0.000960850467769566, -0.000807759775150557, -0.000429923379846805,
0.000081456338895501, 0.000585585033204507, 0.000930280363007829, 0.000997553630123726, 0.000743304377770887, 0.000218142932019430,
-0.000439021999187466, -0.001035464584506922, -0.001379274582853274, -0.001339118806894442, -0.000890898584125663, -0.000135426700809927,
0.000721291534689607, 0.001422586528925900, 0.001736709146196323, 0.001531678957580429, 0.000825809348346554, -0.000204332584606896,
-0.001264953954035041, -0.002026681147110289, -0.002224767812024736, -0.001747989347578871, -0.000687126008900549, 0.000675855233958752,
0.001939810141526326, 0.002700254906709327, 0.002676337670079708, 0.001809809227907344, 0.000302346385971505, -0.001425894059131038,
-0.002850111164443048, -0.003497344177823653, -0.003099785097153715, -0.001696282588251145, 0.000354381431382131, 0.002459975729847483,
0.003962117890550910, 0.004338291351008917, 0.003377760857642002, 0.001273722107127891, -0.001404260811957126, -0.003858729944018619,
-0.005296868477641261, -0.005180794578143301, -0.003418238131210470, -0.000428690403667462, 0.002946095146143898, 0.005671970307130408,
0.006832059383649537, 0.005925580242615656, 0.003061135525037048, -0.001025055462746871, -0.005146493168885704, -0.008000447007066858,
-0.008567643322000875, -0.006458387751298429, -0.002092389293377035, 0.003358433615988978, 0.008267542643372157, 0.011021009169962534,
0.010525039504447272, 0.006600477461859877, 0.000131874225722409, -0.007098295196117369, -0.012870086951713777, -0.015169746940300757,
-0.012839241513492812, -0.006035689378457242, 0.003657448927275838, 0.013537787301098309, 0.020464471653522786, 0.021753420096582588,
0.016052407125049746, 0.003944898543127921, -0.011915101650258511, -0.027222337492958711, -0.036934437155297005, -0.036483323022197825,
-0.022992339899140058, 0.003814121297142671, 0.041261656200314752, 0.084061156150449762, 0.125284573669860120, 0.157774677964436120,
0.175667194419142110, 0.175667194419142110, 0.157774677964436120, 0.125284573669860120, 0.084061156150449762, 0.041261656200314752,
0.003814121297142671, -0.022992339899140058, -0.036483323022197825, -0.036934437155297005, -0.027222337492958711, -0.011915101650258511,
0.003944898543127921, 0.016052407125049746, 0.021753420096582588, 0.020464471653522786, 0.013537787301098309, 0.003657448927275838,
-0.006035689378457242, -0.012839241513492812, -0.015169746940300757, -0.012870086951713777, -0.007098295196117369, 0.000131874225722409,
0.006600477461859877, 0.010525039504447272, 0.011021009169962534, 0.008267542643372157, 0.003358433615988978, -0.002092389293377035,
-0.006458387751298429, -0.008567643322000875, -0.008000447007066858, -0.005146493168885704, -0.001025055462746871, 0.003061135525037048,
0.005925580242615656, 0.006832059383649537, 0.005671970307130408, 0.002946095146143898, -0.000428690403667462, -0.003418238131210470,
-0.005180794578143301, -0.005296868477641261, -0.003858729944018619, -0.001404260811957126, 0.001273722107127891, 0.003377760857642002,
0.004338291351008917, 0.003962117890550910, 0.002459975729847483, 0.000354381431382131, -0.001696282588251145, -0.003099785097153715,
-0.003497344177823653, -0.002850111164443048, -0.001425894059131038, 0.000302346385971505, 0.001809809227907344, 0.002676337670079708,
0.002700254906709327, 0.001939810141526326, 0.000675855233958752, -0.000687126008900549, -0.001747989347578871, -0.002224767812024736,
-0.002026681147110289, -0.001264953954035041, -0.000204332584606896, 0.000825809348346554, 0.001531678957580429, 0.001736709146196323,
0.001422586528925900, 0.000721291534689607, -0.000135426700809927, -0.000890898584125663, -0.001339118806894442, -0.001379274582853274,
-0.001035464584506922, -0.000439021999187466, 0.000218142932019430, 0.000743304377770887, 0.000997553630123726, 0.000930280363007829,
0.000585585033204507, 0.000081456338895501, -0.000429923379846805, -0.000807759775150557, -0.000960850467769566, -0.000868450605965827,
-0.000579769259034295, -0.000194309748856567, 0.000169601665199639, 0.000408357201159471, 0.000459361314264358, 0.000313854754768513,
0.000014352678995945, -0.000360902436481765, -0.000720881682050162, -0.000985222996371773, -0.001102228302856328, -0.001057521343918215,
-0.000872546941267996, -0.000594762485225633, -0.000283317092531425, 0.000005684130024729, 0.000230539284736893, 0.000369902505736477,
0.000422974737428721, 0.000404952972379638, 0.000340023431940833, 0.000253967146823514, 0.000168379817473922, 0.000097406241243338,
0.000047149978647285, 0.000018263332334824],
1200: # 390 taps
[ -0.000004331430434813, 0.000000895178754483, 0.000004819206934844, 0.000012387135981227, 0.000024702874344864, 0.000042904801446151,
0.000067985064859609, 0.000100627030615644, 0.000141025873402660, 0.000188738905083740, 0.000242545741476270, 0.000300399529662751,
0.000359433330154281, 0.000416062388387887, 0.000466131771574125, 0.000505276256945394, 0.000529201721306850, 0.000534142706869689,
0.000517270879752382, 0.000477091889474874, 0.000413779420856055, 0.000329374266276144, 0.000227844055373822, 0.000114956874548630,
-0.000002037964985788, -0.000114915331771079, -0.000215124321927526, -0.000294538502564660, -0.000346261198238618, -0.000365357958551737,
-0.000349492174517184, -0.000299346594446952, -0.000218781210350266, -0.000114696259371792, 0.000003439467944363, 0.000124342943768293,
0.000235923038322712, 0.000326439472488279, 0.000385716174095538, 0.000406270345323467, 0.000384244892423745, 0.000320026589486952,
0.000218451713387220, 0.000088560889198400, -0.000057113100623293, -0.000203679685243764, -0.000335341513050654, -0.000437048860128537,
-0.000496163931220179, -0.000503976472887228, -0.000456879139659229, -0.000357064175200153, -0.000212619491029857, -0.000036988477009561,
0.000152196470731637, 0.000334813551887072, 0.000490329017689163, 0.000600045160004744, 0.000649282295414473, 0.000629236561431697,
0.000538304340843976, 0.000382670778203303, 0.000176071280391891, -0.000061311579103233, -0.000304731538051063, -0.000527388822625111,
-0.000703310824877695, -0.000810309677712974, -0.000832681816046785, -0.000763333939111765, -0.000605059771056946, -0.000370777976208385,
-0.000082637093413831, 0.000229962373724818, 0.000533282556646377, 0.000792807201162301, 0.000977090407652497, 0.001061465563886132,
0.001031195280476629, 0.000883666665500652, 0.000629337435771274, 0.000291251466919408, -0.000096894708513463, -0.000493996123025552,
-0.000855794020410478, -0.001139717153414092, -0.001309782074955667, -0.001341010390430392, -0.001222824367769252, -0.000960990590585683,
-0.000577807284515617, -0.000110428182918688, 0.000392587860975165, 0.000876197024190483, 0.001284780403728217, 0.001568419571371085,
0.001688844037687690, 0.001624356624161237, 0.001373126810656710, 0.000954389812320343, 0.000407316271842430, -0.000212443049446438,
-0.000838254589610985, -0.001399522686014101, -0.001829507546348522, -0.002073092512801241, -0.002093612886275790, -0.001877924713394541,
-0.001439048979367570, -0.000815971265919441, -0.000070480695977658, 0.000718736847294852, 0.001464215661002276, 0.002079218519234449,
0.002487627013869813, 0.002633104169545422, 0.002486455189532861, 0.002050261072858978, 0.001360126142354939, 0.000482234298234895,
-0.000492687418387142, -0.001458485408297803, -0.002304891424791415, -0.002929849705482365, -0.003251510669015779, -0.003218513620108414,
-0.002817308401190046, -0.002075543578763564, -0.001060944493273319, 0.000124409621994965, 0.001353984612741832, 0.002489950284201132,
0.003398266860636230, 0.003964087731928019, 0.004105749968228098, 0.003785703607610913, 0.003016991173328030, 0.001864327522378759,
0.000439388406465501, -0.001109448711112102, -0.002612032761559656, -0.003894589690524808, -0.004799215003224437, -0.005202582868054837,
-0.005031731058566960, -0.004274996752467488, -0.002986628326221043, -0.001284225000364678, 0.000661081413588171, 0.002640990158453115,
0.004431457793095885, 0.005817070614785085, 0.006615805917154524, 0.006701450960612391, 0.006021062146242219, 0.004605255832862724,
0.002569791244804701, 0.000107781839820803, -0.002527142979701336, -0.005045394324250144, -0.007151459446961864, -0.008576455016121923,
-0.009109805876143171, -0.008626557809676316, -0.007107110348255605, -0.004646780506669336, -0.001453545516455117, 0.002166532839260334,
0.005835432981396591, 0.009138830213137229, 0.011668164700843125, 0.013065169962452085, 0.013064440018490608, 0.011529579182106710,
0.008478886219133458, 0.004097368728152922, -0.001266921998658053, -0.007122790428060381, -0.012871021717463436, -0.017852189458389456,
-0.021403262319433757, -0.022918110896739993, -0.021906440359153793, -0.018045605353792606, -0.011220225882222989, -0.001545481108326266,
0.010628660576177286, 0.024733344647961189, 0.040016116357312823, 0.055593053292742431, 0.070513617769391357, 0.083832869405100041,
0.094684919293335654, 0.102351302009811070, 0.106318317517167070, 0.106318317517167070, 0.102351302009811070, 0.094684919293335654,
0.083832869405100041, 0.070513617769391357, 0.055593053292742431, 0.040016116357312823, 0.024733344647961189, 0.010628660576177286,
-0.001545481108326266, -0.011220225882222989, -0.018045605353792606, -0.021906440359153793, -0.022918110896739993, -0.021403262319433757,
-0.017852189458389456, -0.012871021717463436, -0.007122790428060381, -0.001266921998658053, 0.004097368728152922, 0.008478886219133458,
0.011529579182106710, 0.013064440018490608, 0.013065169962452085, 0.011668164700843125, 0.009138830213137229, 0.005835432981396591,
0.002166532839260334, -0.001453545516455117, -0.004646780506669336, -0.007107110348255605, -0.008626557809676316, -0.009109805876143171,
-0.008576455016121923, -0.007151459446961864, -0.005045394324250144, -0.002527142979701336, 0.000107781839820803, 0.002569791244804701,
0.004605255832862724, 0.006021062146242219, 0.006701450960612391, 0.006615805917154524, 0.005817070614785085, 0.004431457793095885,
0.002640990158453115, 0.000661081413588171, -0.001284225000364678, -0.002986628326221043, -0.004274996752467488, -0.005031731058566960,
-0.005202582868054837, -0.004799215003224437, -0.003894589690524808, -0.002612032761559656, -0.001109448711112102, 0.000439388406465501,
0.001864327522378759, 0.003016991173328030, 0.003785703607610913, 0.004105749968228098, 0.003964087731928019, 0.003398266860636230,
0.002489950284201132, 0.001353984612741832, 0.000124409621994965, -0.001060944493273319, -0.002075543578763564, -0.002817308401190046,
-0.003218513620108414, -0.003251510669015779, -0.002929849705482365, -0.002304891424791415, -0.001458485408297803, -0.000492687418387142,
0.000482234298234895, 0.001360126142354939, 0.002050261072858978, 0.002486455189532861, 0.002633104169545422, 0.002487627013869813,
0.002079218519234449, 0.001464215661002276, 0.000718736847294852, -0.000070480695977658, -0.000815971265919441, -0.001439048979367570,
-0.001877924713394541, -0.002093612886275790, -0.002073092512801241, -0.001829507546348522, -0.001399522686014101, -0.000838254589610985,
-0.000212443049446438, 0.000407316271842430, 0.000954389812320343, 0.001373126810656710, 0.001624356624161237, 0.001688844037687690,
0.001568419571371085, 0.001284780403728217, 0.000876197024190483, 0.000392587860975165, -0.000110428182918688, -0.000577807284515617,
-0.000960990590585683, -0.001222824367769252, -0.001341010390430392, -0.001309782074955667, -0.001139717153414092, -0.000855794020410478,
-0.000493996123025552, -0.000096894708513463, 0.000291251466919408, 0.000629337435771274, 0.000883666665500652, 0.001031195280476629,
0.001061465563886132, 0.000977090407652497, 0.000792807201162301, 0.000533282556646377, 0.000229962373724818, -0.000082637093413831,
-0.000370777976208385, -0.000605059771056946, -0.000763333939111765, -0.000832681816046785, -0.000810309677712974, -0.000703310824877695,
-0.000527388822625111, -0.000304731538051063, -0.000061311579103233, 0.000176071280391891, 0.000382670778203303, 0.000538304340843976,
0.000629236561431697, 0.000649282295414473, 0.000600045160004744, 0.000490329017689163, 0.000334813551887072, 0.000152196470731637,
-0.000036988477009561, -0.000212619491029857, -0.000357064175200153, -0.000456879139659229, -0.000503976472887228, -0.000496163931220179,
-0.000437048860128537, -0.000335341513050654, -0.000203679685243764, -0.000057113100623293, 0.000088560889198400, 0.000218451713387220,
0.000320026589486952, 0.000384244892423745, 0.000406270345323467, 0.000385716174095538, 0.000326439472488279, 0.000235923038322712,
0.000124342943768293, 0.000003439467944363, -0.000114696259371792, -0.000218781210350266, -0.000299346594446952, -0.000349492174517184,
-0.000365357958551737, -0.000346261198238618, -0.000294538502564660, -0.000215124321927526, -0.000114915331771079, -0.000002037964985788,
0.000114956874548630, 0.000227844055373822, 0.000329374266276144, 0.000413779420856055, 0.000477091889474874, 0.000517270879752382,
0.000534142706869689, 0.000529201721306850, 0.000505276256945394, 0.000466131771574125, 0.000416062388387887, 0.000359433330154281,
0.000300399529662751, 0.000242545741476270, 0.000188738905083740, 0.000141025873402660, 0.000100627030615644, 0.000067985064859609,
0.000042904801446151, 0.000024702874344864, 0.000012387135981227, 0.000004819206934844, 0.000000895178754483,
-0.000004331430434813],
1050: # 436 taps
[ 0.000009448479205375, 0.000012696465277652, 0.000020370172970180, 0.000030184991625035, 0.000042106693955817, 0.000055852072599971,
0.000070887005271560, 0.000086420158465092, 0.000101355825900618, 0.000114363268643203, 0.000123887834821102, 0.000128274519298202,
0.000125832586450277, 0.000114986590640600, 0.000094403728281619, 0.000063154754823051, 0.000020845988244699, -0.000032265535133044,
-0.000095180544123547, -0.000166116860144892, -0.000242513979646880, -0.000321114777409093, -0.000398098262223401, -0.000469277292459888,
-0.000530343005106164, -0.000577150816605647, -0.000606030552867816, -0.000614088176959215, -0.000599488135042163, -0.000561676165437471,
-0.000501537574188219, -0.000421453273437500, -0.000325256102322617, -0.000218069429252057, -0.000106038676866598, 0.000004038733321948,
0.000105163201801783, 0.000190629395147399, 0.000254537701320533, 0.000292276183024851, 0.000300940504739341, 0.000279647367467038,
0.000229712182001491, 0.000154664133559244, 0.000060087287885032, -0.000046708916188273, -0.000157178278901246, -0.000262150604318790,
-0.000352532643004546, -0.000420038843447295, -0.000457900791044766, -0.000461490220027360, -0.000428802562294159, -0.000360749682491985,
-0.000261227783830262, -0.000136942941254748, 0.000003006863095541, 0.000147769782466689, 0.000285560076794725, 0.000404597750694492,
0.000494097819748234, 0.000545227040891837, 0.000551946387992179, 0.000511661803626807, 0.000425615907406962, 0.000298975923041507,
0.000140591237099924, -0.000037572806623586, -0.000221297040657601, -0.000395204977572457, -0.000544014582709044, -0.000653844664412656,
-0.000713468032359235, -0.000715401015700932, -0.000656726220245329, -0.000539565358551840, -0.000371142263662183, -0.000163412087825843,
0.000067734912716773, 0.000303646478822936, 0.000524376688359045, 0.000710337247559984, 0.000843997687273033, 0.000911491282666008,
0.000903981565989860, 0.000818661581901103, 0.000659279571163340, 0.000436124965687766, 0.000165448090041410, -0.000131662114271814,
-0.000430871860061819, -0.000706551570190628, -0.000933927661174878, -0.001091260873927019, -0.001161862605544963, -0.001135768011733947,
-0.001010903443574116, -0.000793624567958766, -0.000498548786522930, -0.000147664383619282, 0.000231239373696082, 0.000606716500508849,
0.000946221796915500, 0.001218879442233109, 0.001398230278516649, 0.001464719587961910, 0.001407696084540016, 0.001226727670795367,
0.000932088534687613, 0.000544338945220889, 0.000092993997809168, -0.000385643796196990, -0.000851335887551482, -0.001263225987480693,
-0.001583356294077108, -0.001780085545791869, -0.001831106846925676, -0.001725785932705194, -0.001466586997062531, -0.001069422391909016,
-0.000562849829295198, 0.000013863768127086, 0.000613691289313864, 0.001185595579630164, 0.001678788016865224, 0.002047148219684201,
0.002253427125685210, 0.002272859461156746, 0.002095846355480220, 0.001729435227991120, 0.001197417642361670, 0.000538977181181343,
-0.000194056307018250, -0.000941167802354134, -0.001638040755694804, -0.002221979108638823, -0.002637419881293469, -0.002841061961880444,
-0.002806150254550888, -0.002525507242008391, -0.002012996612957378, -0.001303224500472842, -0.000449429868884502, 0.000480331461610398,
0.001408451066145862, 0.002254087235765462, 0.002940037601874124, 0.003399594478686270, 0.003582789834066899, 0.003461463426614760,
0.003032665933951381, 0.002320030370388926, 0.001372905661370613, 0.000263228943665216, -0.000919692494408471, -0.002076132077760251,
-0.003104105713267222, -0.003908095577429846, -0.004407611415610481, -0.004544845892385366, -0.004290726778249251, -0.003648773776410054,
-0.002656330572825240, -0.001382946510100638, 0.000074084090594797, 0.001596775046947397, 0.003055612326596284, 0.004320145989574181,
0.005270175821128589, 0.005806607129742208, 0.005861039774711523, 0.005403216045538973, 0.004445593651759434, 0.003044516829901583,
0.001297719390662469, -0.000661812043684862, -0.002675281726881974, -0.004570210544203085, -0.006174367519919625, -0.007330437981090785,
-0.007910248018541601, -0.007827342889142773, -0.007046794045290511, -0.005591280073358511, -0.003542743024648185, -0.001039245490340182,
0.001732978782646685, 0.004551902468552846, 0.007175761837551073, 0.009361715988035382, 0.010885774625277341, 0.011562497118639900,
0.011262913292836945, 0.009929181902797219, 0.007584685668611571, 0.004338552786979482, 0.000383973096667143, -0.004009880384869925,
-0.008512042746918275, -0.012749310695913854, -0.016329981037081505, -0.018870242377519306, -0.020021437259417553, -0.019496284710171480,
-0.017092162548070899, -0.012709698776261012, -0.006365201717689276, 0.001804145292512615, 0.011542917413936767, 0.022487965990827675,
0.034186326021837028, 0.046119492420907078, 0.057732581500000407, 0.068466529917194638, 0.077791244144850280, 0.085237520364008251,
0.090425612489785534, 0.093088535620340640, 0.093088535620340640, 0.090425612489785534, 0.085237520364008251, 0.077791244144850280,
0.068466529917194638, 0.057732581500000407, 0.046119492420907078, 0.034186326021837028, 0.022487965990827675, 0.011542917413936767,
0.001804145292512615, -0.006365201717689276, -0.012709698776261012, -0.017092162548070899, -0.019496284710171480, -0.020021437259417553,
-0.018870242377519306, -0.016329981037081505, -0.012749310695913854, -0.008512042746918275, -0.004009880384869925, 0.000383973096667143,
0.004338552786979482, 0.007584685668611571, 0.009929181902797219, 0.011262913292836945, 0.011562497118639900, 0.010885774625277341,
0.009361715988035382, 0.007175761837551073, 0.004551902468552846, 0.001732978782646685, -0.001039245490340182, -0.003542743024648185,
-0.005591280073358511, -0.007046794045290511, -0.007827342889142773, -0.007910248018541601, -0.007330437981090785, -0.006174367519919625,
-0.004570210544203085, -0.002675281726881974, -0.000661812043684862, 0.001297719390662469, 0.003044516829901583, 0.004445593651759434,
0.005403216045538973, 0.005861039774711523, 0.005806607129742208, 0.005270175821128589, 0.004320145989574181, 0.003055612326596284,
0.001596775046947397, 0.000074084090594797, -0.001382946510100638, -0.002656330572825240, -0.003648773776410054, -0.004290726778249251,
-0.004544845892385366, -0.004407611415610481, -0.003908095577429846, -0.003104105713267222, -0.002076132077760251, -0.000919692494408471,
0.000263228943665216, 0.001372905661370613, 0.002320030370388926, 0.003032665933951381, 0.003461463426614760, 0.003582789834066899,
0.003399594478686270, 0.002940037601874124, 0.002254087235765462, 0.001408451066145862, 0.000480331461610398, -0.000449429868884502,
-0.001303224500472842, -0.002012996612957378, -0.002525507242008391, -0.002806150254550888, -0.002841061961880444, -0.002637419881293469,
-0.002221979108638823, -0.001638040755694804, -0.000941167802354134, -0.000194056307018250, 0.000538977181181343, 0.001197417642361670,
0.001729435227991120, 0.002095846355480220, 0.002272859461156746, 0.002253427125685210, 0.002047148219684201, 0.001678788016865224,
0.001185595579630164, 0.000613691289313864, 0.000013863768127086, -0.000562849829295198, -0.001069422391909016, -0.001466586997062531,
-0.001725785932705194, -0.001831106846925676, -0.001780085545791869, -0.001583356294077108, -0.001263225987480693, -0.000851335887551482,
-0.000385643796196990, 0.000092993997809168, 0.000544338945220889, 0.000932088534687613, 0.001226727670795367, 0.001407696084540016,
0.001464719587961910, 0.001398230278516649, 0.001218879442233109, 0.000946221796915500, 0.000606716500508849, 0.000231239373696082,
-0.000147664383619282, -0.000498548786522930, -0.000793624567958766, -0.001010903443574116, -0.001135768011733947, -0.001161862605544963,
-0.001091260873927019, -0.000933927661174878, -0.000706551570190628, -0.000430871860061819, -0.000131662114271814, 0.000165448090041410,
0.000436124965687766, 0.000659279571163340, 0.000818661581901103, 0.000903981565989860, 0.000911491282666008, 0.000843997687273033,
0.000710337247559984, 0.000524376688359045, 0.000303646478822936, 0.000067734912716773, -0.000163412087825843, -0.000371142263662183,
-0.000539565358551840, -0.000656726220245329, -0.000715401015700932, -0.000713468032359235, -0.000653844664412656, -0.000544014582709044,
-0.000395204977572457, -0.000221297040657601, -0.000037572806623586, 0.000140591237099924, 0.000298975923041507, 0.000425615907406962,
0.000511661803626807, 0.000551946387992179, 0.000545227040891837, 0.000494097819748234, 0.000404597750694492, 0.000285560076794725,
0.000147769782466689, 0.000003006863095541, -0.000136942941254748, -0.000261227783830262, -0.000360749682491985, -0.000428802562294159,
-0.000461490220027360, -0.000457900791044766, -0.000420038843447295, -0.000352532643004546, -0.000262150604318790, -0.000157178278901246,
-0.000046708916188273, 0.000060087287885032, 0.000154664133559244, 0.000229712182001491, 0.000279647367467038, 0.000300940504739341,
0.000292276183024851, 0.000254537701320533, 0.000190629395147399, 0.000105163201801783, 0.000004038733321948, -0.000106038676866598,
-0.000218069429252057, -0.000325256102322617, -0.000421453273437500, -0.000501537574188219, -0.000561676165437471, -0.000599488135042163,
-0.000614088176959215, -0.000606030552867816, -0.000577150816605647, -0.000530343005106164, -0.000469277292459888, -0.000398098262223401,
-0.000321114777409093, -0.000242513979646880, -0.000166116860144892, -0.000095180544123547, -0.000032265535133044, 0.000020845988244699,
0.000063154754823051, 0.000094403728281619, 0.000114986590640600, 0.000125832586450277, 0.000128274519298202, 0.000123887834821102,
0.000114363268643203, 0.000101355825900618, 0.000086420158465092, 0.000070887005271560, 0.000055852072599971, 0.000042106693955817,
0.000030184991625035, 0.000020370172970180, 0.000012696465277652, 0.000009448479205375 ],
900: # 507 taps
[ 0.000008584643858565, 0.000009418988876053, 0.000014080571079703, 0.000019768330546629, 0.000026407938202815, 0.000033835554436160,
0.000041775447258916, 0.000049840688763229, 0.000057525397104827, 0.000064222904381994, 0.000069229653331724, 0.000071774979399342,
0.000071044828173283, 0.000066239730112684, 0.000056611414853593, 0.000041517869163367, 0.000020462723568921, -0.000006825518135279,
-0.000040362137084292, -0.000079866391614059, -0.000124740944604127, -0.000174014477804677, -0.000226421731946560, -0.000280359514663211,
-0.000333959777524947, -0.000385165398367582, -0.000431779497772114, -0.000471602831913601, -0.000502508714800933, -0.000522587601943708,
-0.000530244985591953, -0.000524326246532992, -0.000504199216087390, -0.000469843487184555, -0.000421891912076977, -0.000361653837423190,
-0.000291090671787090, -0.000212772068148448, -0.000129775708505696, -0.000045562167590561, 0.000036181038541454, 0.000111715248667983,
0.000177457791675865, 0.000230168155042476, 0.000267145694421862, 0.000286408115238973, 0.000286822428035563, 0.000268225243572190,
0.000231462231911356, 0.000178399161339807, 0.000111858949442223, 0.000035506782941005, -0.000046322038661538, -0.000128840476454010,
-0.000207064338646769, -0.000276095967153748, -0.000331410885812673, -0.000369138410006413, -0.000386317254135060, -0.000381111227069818,
-0.000352960919521696, -0.000302673831926362, -0.000232432591548855, -0.000145719497401035, -0.000047160366530124, 0.000057705644340284,
0.000162731306278391, 0.000261502601122459, 0.000347728839468377, 0.000415628506871784, 0.000460307676423158, 0.000478101121808084,
0.000466848645480576, 0.000426095215060785, 0.000357188814988566, 0.000263277817392097, 0.000149189135268233, 0.000021201177134175,
-0.000113285445089856, -0.000246159846025947, -0.000369078758947477, -0.000473974817394057, -0.000553573238187010, -0.000601879769614216,
-0.000614609034335983, -0.000589524295786869, -0.000526664670957786, -0.000428434330673012, -0.000299553787970989, -0.000146857332295172,
0.000021049474537857, 0.000194254912976452, 0.000362113995846136, 0.000513885517380605, 0.000639407293886249, 0.000729757323783321,
0.000777868837348859, 0.000779050717176191, 0.000731379294218185, 0.000635932028877494, 0.000496840283261992, 0.000321150767788200,
0.000118496954373743, -0.000099406083911376, -0.000319420735859412, -0.000527759843251443, -0.000710834560643461, -0.000856122360814998,
-0.000953000983271033, -0.000993496322190293, -0.000972892522850171, -0.000890155314802122, -0.000748144004291994, -0.000553578640569728,
-0.000316765531771492, -0.000051080037307638, 0.000227762682493944, 0.000502615360611366, 0.000755932794801656, 0.000970869777260266,
0.001132378198325256, 0.001228230831826114, 0.001249905234811852, 0.001193265605753869, 0.001058991562763383, 0.000852715731034719,
0.000584852833903113, 0.000270116979072993, -0.000073253806782210, -0.000424524625176341, -0.000761689644626737, -0.001062825395605405,
-0.001307487536115730, -0.001478067477842081, -0.001561017661066179, -0.001547863924535660, -0.001435933022484648, -0.001228735487931008,
-0.000935971757611233, -0.000573140492907433, -0.000160766721791647, 0.000276722077340107, 0.000712407276705890, 0.001118502589392694,
0.001468091403060663, 0.001736873763731978, 0.001904808530758137, 0.001957546402581353, 0.001887551700267456, 0.001694830920708386,
0.001387207697842587, 0.000980110478290227, 0.000495866308669078, -0.000037468525946086, -0.000587687238352686, -0.001120332197278825,
-0.001600807439724522, -0.001996577848597569, -0.002279316251738701, -0.002426862344229953, -0.002424859679572050, -0.002267956637237035,
-0.001960478287612480, -0.001516506229070831, -0.000959342574003313, -0.000320365808444990, 0.000362667830279848, 0.001047791955533175,
0.001691344168593488, 0.002250683068847439, 0.002686938772729870, 0.002967628123966539, 0.003068960407414939, 0.002977678084347548,
0.002692297230438709, 0.002223648489886749, 0.001594655540313720, 0.000839340735107497, 0.000001086828787463, -0.000869758241867305,
-0.001718811255366889, -0.002490949831033119, -0.003133783611782112, -0.003601086428852005, -0.003855971828191669, -0.003873602941247202,
-0.003643247691842578, -0.003169524021369342, -0.002472727031941658, -0.001588180215901802, -0.000564613998354494, 0.000538365561072565,
0.001653594591084052, 0.002710281216575659, 0.003638323695477434, 0.004372765950231630, 0.004858115780200945, 0.005052252604909589,
0.004929664405729861, 0.004483785715272022, 0.003728252839970840, 0.002696955568587015, 0.001442828675796044, 0.000035407607162536,
-0.001442758217956277, -0.002900658243916532, -0.004244107302544382, -0.005381470691504941, -0.006229500241120763, -0.006718928923729580,
-0.006799475746570288, -0.006443928763042613, -0.005651020748503490, -0.004446866818108242, -0.002884813426831026, -0.001043632901391050,
0.000975905251500855, 0.003055961712387741, 0.005067831037893753, 0.006879122081429573, 0.008361541203410547, 0.009398858161408168,
0.009894604643011696, 0.009779054490283380, 0.009015056125786116, 0.007602336538990519, 0.005579966604992522, 0.003026770185465134,
0.000059562008821989, -0.003170779553680069, -0.006485285707743206, -0.009684675209522759, -0.012558594763991068, -0.014895884693277550,
-0.016495367870764655, -0.017176610952606329, -0.016790098400516502, -0.015226268406232360, -0.012422909599680294, -0.008370486626783004,
-0.003115063791803408, 0.003241392133040441, 0.010543411361223271, 0.018587073767192364, 0.027127521275133942, 0.035888613808298719,
0.044574291632219959, 0.052881118780651611, 0.060511410978741485, 0.067186317405564938, 0.072658219055817971, 0.076721836801003668,
0.079223499565701364, 0.080068115845938093, 0.079223499565701364, 0.076721836801003668, 0.072658219055817971, 0.067186317405564938,
0.060511410978741485, 0.052881118780651611, 0.044574291632219959, 0.035888613808298719, 0.027127521275133942, 0.018587073767192364,
0.010543411361223271, 0.003241392133040441, -0.003115063791803408, -0.008370486626783004, -0.012422909599680294, -0.015226268406232360,
-0.016790098400516502, -0.017176610952606329, -0.016495367870764655, -0.014895884693277550, -0.012558594763991068, -0.009684675209522759,
-0.006485285707743206, -0.003170779553680069, 0.000059562008821989, 0.003026770185465134, 0.005579966604992522, 0.007602336538990519,
0.009015056125786116, 0.009779054490283380, 0.009894604643011696, 0.009398858161408168, 0.008361541203410547, 0.006879122081429573,
0.005067831037893753, 0.003055961712387741, 0.000975905251500855, -0.001043632901391050, -0.002884813426831026, -0.004446866818108242,
-0.005651020748503490, -0.006443928763042613, -0.006799475746570288, -0.006718928923729580, -0.006229500241120763, -0.005381470691504941,
-0.004244107302544382, -0.002900658243916532, -0.001442758217956277, 0.000035407607162536, 0.001442828675796044, 0.002696955568587015,
0.003728252839970840, 0.004483785715272022, 0.004929664405729861, 0.005052252604909589, 0.004858115780200945, 0.004372765950231630,
0.003638323695477434, 0.002710281216575659, 0.001653594591084052, 0.000538365561072565, -0.000564613998354494, -0.001588180215901802,
-0.002472727031941658, -0.003169524021369342, -0.003643247691842578, -0.003873602941247202, -0.003855971828191669, -0.003601086428852005,
-0.003133783611782112, -0.002490949831033119, -0.001718811255366889, -0.000869758241867305, 0.000001086828787463, 0.000839340735107497,
0.001594655540313720, 0.002223648489886749, 0.002692297230438709, 0.002977678084347548, 0.003068960407414939, 0.002967628123966539,
0.002686938772729870, 0.002250683068847439, 0.001691344168593488, 0.001047791955533175, 0.000362667830279848, -0.000320365808444990,
-0.000959342574003313, -0.001516506229070831, -0.001960478287612480, -0.002267956637237035, -0.002424859679572050, -0.002426862344229953,
-0.002279316251738701, -0.001996577848597569, -0.001600807439724522, -0.001120332197278825, -0.000587687238352686, -0.000037468525946086,
0.000495866308669078, 0.000980110478290227, 0.001387207697842587, 0.001694830920708386, 0.001887551700267456, 0.001957546402581353,
0.001904808530758137, 0.001736873763731978, 0.001468091403060663, 0.001118502589392694, 0.000712407276705890, 0.000276722077340107,
-0.000160766721791647, -0.000573140492907433, -0.000935971757611233, -0.001228735487931008, -0.001435933022484648, -0.001547863924535660,
-0.001561017661066179, -0.001478067477842081, -0.001307487536115730, -0.001062825395605405, -0.000761689644626737, -0.000424524625176341,
-0.000073253806782210, 0.000270116979072993, 0.000584852833903113, 0.000852715731034719, 0.001058991562763383, 0.001193265605753869,
0.001249905234811852, 0.001228230831826114, 0.001132378198325256, 0.000970869777260266, 0.000755932794801656, 0.000502615360611366,
0.000227762682493944, -0.000051080037307638, -0.000316765531771492, -0.000553578640569728, -0.000748144004291994, -0.000890155314802122,
-0.000972892522850171, -0.000993496322190293, -0.000953000983271033, -0.000856122360814998, -0.000710834560643461, -0.000527759843251443,
-0.000319420735859412, -0.000099406083911376, 0.000118496954373743, 0.000321150767788200, 0.000496840283261992, 0.000635932028877494,
0.000731379294218185, 0.000779050717176191, 0.000777868837348859, 0.000729757323783321, 0.000639407293886249, 0.000513885517380605,
0.000362113995846136, 0.000194254912976452, 0.000021049474537857, -0.000146857332295172, -0.000299553787970989, -0.000428434330673012,
-0.000526664670957786, -0.000589524295786869, -0.000614609034335983, -0.000601879769614216, -0.000553573238187010, -0.000473974817394057,
-0.000369078758947477, -0.000246159846025947, -0.000113285445089856, 0.000021201177134175, 0.000149189135268233, 0.000263277817392097,
0.000357188814988566, 0.000426095215060785, 0.000466848645480576, 0.000478101121808084, 0.000460307676423158, 0.000415628506871784,
0.000347728839468377, 0.000261502601122459, 0.000162731306278391, 0.000057705644340284, -0.000047160366530124, -0.000145719497401035,
-0.000232432591548855, -0.000302673831926362, -0.000352960919521696, -0.000381111227069818, -0.000386317254135060, -0.000369138410006413,
-0.000331410885812673, -0.000276095967153748, -0.000207064338646769, -0.000128840476454010, -0.000046322038661538, 0.000035506782941005,
0.000111858949442223, 0.000178399161339807, 0.000231462231911356, 0.000268225243572190, 0.000286822428035563, 0.000286408115238973,
0.000267145694421862, 0.000230168155042476, 0.000177457791675865, 0.000111715248667983, 0.000036181038541454, -0.000045562167590561,
-0.000129775708505696, -0.000212772068148448, -0.000291090671787090, -0.000361653837423190, -0.000421891912076977, -0.000469843487184555,
-0.000504199216087390, -0.000524326246532992, -0.000530244985591953, -0.000522587601943708, -0.000502508714800933, -0.000471602831913601,
-0.000431779497772114, -0.000385165398367582, -0.000333959777524947, -0.000280359514663211, -0.000226421731946560, -0.000174014477804677,
-0.000124740944604127, -0.000079866391614059, -0.000040362137084292, -0.000006825518135279, 0.000020462723568921, 0.000041517869163367,
0.000056611414853593, 0.000066239730112684, 0.000071044828173283, 0.000071774979399342, 0.000069229653331724, 0.000064222904381994,
0.000057525397104827, 0.000049840688763229, 0.000041775447258916, 0.000033835554436160, 0.000026407938202815, 0.000019768330546629,
0.000014080571079703, 0.000009418988876053, 0.000008584643858565 ],
800: # 576 taps
[ 0.000007812421315026, 0.000008939491599758, 0.000013730623424634, 0.000019922294330328, 0.000027648080357536, 0.000037033583075960,
0.000048143861498476, 0.000060975611534136, 0.000075478577997365, 0.000091486545323856, 0.000108759338791293, 0.000126959389238696,
0.000145652693573148, 0.000164310512299333, 0.000182322037177544, 0.000198999590569024, 0.000213614009898736, 0.000225404773677130,
0.000233610866318186, 0.000237501949870309, 0.000236413452609260, 0.000229779689874953, 0.000217172712546861, 0.000198326327006374,
0.000173169245803356, 0.000141846034715806, 0.000104732554871058, 0.000062442756567725, 0.000015827588463025, -0.000034041343381570,
-0.000085892061029301, -0.000138290085401831, -0.000189681915226441, -0.000238447248218383, -0.000282958234132596, -0.000321645514236948,
-0.000353063703221184, -0.000375960779331289, -0.000389338959490572, -0.000392510765617404, -0.000385145111580360, -0.000367301005108244,
-0.000339445383884735, -0.000302456824898826, -0.000257608154338197, -0.000206533574886596, -0.000151176196094362, -0.000093718946756075,
-0.000036501336346782, 0.000018073847262639, 0.000067643276268719, 0.000109986899426513, 0.000143132709269975, 0.000165454349492432,
0.000175757544777044, 0.000173350787020117, 0.000158097036687462, 0.000130440616590328, 0.000091410233747216, 0.000042593436932362,
-0.000013915345466225, -0.000075592647414163, -0.000139587283955501, -0.000202842130917589, -0.000262229388149273, -0.000314696298480236,
-0.000357412309140960, -0.000387912413334777, -0.000404229097174175, -0.000405005829251745, -0.000389585053939050, -0.000358067160688509,
-0.000311334118688652, -0.000251037234855322, -0.000179546871425107, -0.000099865220473760, -0.000015504805729649, 0.000069662241126261,
0.000151579558482804, 0.000226197017031803, 0.000289669081062027, 0.000338550686637535, 0.000369981142430884, 0.000381846581094768,
0.000372912653196213, 0.000342918280693206, 0.000292625889758754, 0.000223822598023746, 0.000139271063990380, 0.000042610318846710,
-0.000061790478444653, -0.000169020516263524, -0.000273849135305279, -0.000370972701089329, -0.000455274194431906, -0.000522083581325246,
-0.000567425902673365, -0.000588244271105343, -0.000582585214321533, -0.000549736803809912, -0.000490309633189676, -0.000406255267998045,
-0.000300818490773199, -0.000178423294758992, -0.000044495794702522, 0.000094768978920233, 0.000232687846388931, 0.000362404482394644,
0.000477224887882656, 0.000570957019366285, 0.000638237711550962, 0.000674829917199715, 0.000677874702997924, 0.000646082835685789,
0.000579855135491155, 0.000481322426719260, 0.000354300348814663, 0.000204158180969291, 0.000037605243749942, -0.000137597792476766,
-0.000312990053930070, -0.000479809451013147, -0.000629420667419594, -0.000753751787384093, -0.000845717937205157, -0.000899610318070534,
-0.000911429428339170, -0.000879144467901314, -0.000802862554846735, -0.000684896491503848, -0.000529724099269400, -0.000343837417747543,
-0.000135485422990712, 0.000085680046384777, 0.000309041306180360, 0.000523514975880865, 0.000718092538346410, 0.000882395437488790,
0.001007217243930078, 0.001085025011663583, 0.001110393602574907, 0.001080348246990215, 0.000994595533680270, 0.000855627164276610,
0.000668686966519433, 0.000441598292681654, 0.000184456030731509, -0.000090806124488888, -0.000370954941565034, -0.000642080313861205,
-0.000890272488296022, -0.001102321495571061, -0.001266404272056495, -0.001372724600470066, -0.001414071700002426, -0.001386267150663963,
-0.001288473577308051, -0.001123345195626614, -0.000897007516317755, -0.000618861629989936, -0.000301217045862462, 0.000041233610152644,
0.000392077118279518, 0.000733950102097711, 0.001049382185659160, 0.001321671816520764, 0.001535751682495805, 0.001678999613916214,
0.001741952717366617, 0.001718885253761411, 0.001608217075415199, 0.001412726517149060, 0.001139550679396572, 0.000799966281315092,
0.000408955257020119, -0.000015430291445883, -0.000452877901224665, -0.000881764419386712, -0.001280202174740378, -0.001627132056108478,
-0.001903410054728627, -0.002092832630669335, -0.002183047066222210, -0.002166297669634002, -0.002039964832379242, -0.001806863227100527,
-0.001475276496367705, -0.001058718218567234, -0.000575422161543602, -0.000047579005659538, 0.000499650640507829, 0.001039304486976568,
0.001543915361236087, 0.001986875765177565, 0.002343803978935238, 0.002593843111794459, 0.002720826151891805, 0.002714244052422841,
0.002569962094668674, 0.002290640442950059, 0.001885828071941918, 0.001371714610702517, 0.000770541414234860, 0.000109689735095918,
-0.000579518937674694, -0.001263260162418279, -0.001906817708604015, -0.002476294031416823, -0.002940338141748221, -0.003271805587759476,
-0.003449265353836316, -0.003458273739088719, -0.003292343903957135, -0.002953552492773730, -0.002452740833751403, -0.001809286792967657,
-0.001050443545598396, -0.000210263213371804, 0.000671856047428268, 0.001552940847463649, 0.002388454943494185, 0.003134462521134076,
0.003749841173565993, 0.004198437225205720, 0.004451055565195895, 0.004487180085620033, 0.004296330509189801, 0.003878975870600702,
0.003246943568337220, 0.002423285251527749, 0.001441585983790830, 0.000344729182561098, -0.000816843070689627, -0.001987309118898022,
-0.003107997569512837, -0.004120159055022873, -0.004967855525593480, -0.005600832763225632, -0.005977237320460713, -0.006066041703895594,
-0.005849050345318046, -0.005322373876566778, -0.004497280230451145, -0.003400356983323659, -0.002072948915619172, -0.000569867563016818,
0.001042597072372037, 0.002689299061586182, 0.004289398058621653, 0.005759936423629122, 0.007019685172079656, 0.007993094199877881,
0.008614171947670925, 0.008830116409127314, 0.008604524292277297, 0.007920018129346281, 0.006780151368661940, 0.005210478633443022,
0.003258711582163722, 0.000993917994210585, -0.001495237701498300, -0.004103172616953404, -0.006710901704621617, -0.009190272912439044,
-0.011408767543032211, -0.013234683860549021, -0.014542500326924340, -0.015218201853231224, -0.015164348354310610, -0.014304669860257387,
-0.012587987008943695, -0.009991279084291772, -0.006521752749437525, -0.002217802857491734, 0.002851200149507507, 0.008586315129089878,
0.014861597832277761, 0.021527754009268797, 0.028416755733892799, 0.035347257703852740, 0.042130616026199795, 0.048577285227121583,
0.054503351348228368, 0.059736951256086870, 0.064124330411942668, 0.067535303652784234, 0.069867905879939018, 0.071052050289491700,
0.071052050289491700, 0.069867905879939018, 0.067535303652784234, 0.064124330411942668, 0.059736951256086870, 0.054503351348228368,
0.048577285227121583, 0.042130616026199795, 0.035347257703852740, 0.028416755733892799, 0.021527754009268797, 0.014861597832277761,
0.008586315129089878, 0.002851200149507507, -0.002217802857491734, -0.006521752749437525, -0.009991279084291772, -0.012587987008943695,
-0.014304669860257387, -0.015164348354310610, -0.015218201853231224, -0.014542500326924340, -0.013234683860549021, -0.011408767543032211,
-0.009190272912439044, -0.006710901704621617, -0.004103172616953404, -0.001495237701498300, 0.000993917994210585, 0.003258711582163722,
0.005210478633443022, 0.006780151368661940, 0.007920018129346281, 0.008604524292277297, 0.008830116409127314, 0.008614171947670925,
0.007993094199877881, 0.007019685172079656, 0.005759936423629122, 0.004289398058621653, 0.002689299061586182, 0.001042597072372037,
-0.000569867563016818, -0.002072948915619172, -0.003400356983323659, -0.004497280230451145, -0.005322373876566778, -0.005849050345318046,
-0.006066041703895594, -0.005977237320460713, -0.005600832763225632, -0.004967855525593480, -0.004120159055022873, -0.003107997569512837,
-0.001987309118898022, -0.000816843070689627, 0.000344729182561098, 0.001441585983790830, 0.002423285251527749, 0.003246943568337220,
0.003878975870600702, 0.004296330509189801, 0.004487180085620033, 0.004451055565195895, 0.004198437225205720, 0.003749841173565993,
0.003134462521134076, 0.002388454943494185, 0.001552940847463649, 0.000671856047428268, -0.000210263213371804, -0.001050443545598396,
-0.001809286792967657, -0.002452740833751403, -0.002953552492773730, -0.003292343903957135, -0.003458273739088719, -0.003449265353836316,
-0.003271805587759476, -0.002940338141748221, -0.002476294031416823, -0.001906817708604015, -0.001263260162418279, -0.000579518937674694,
0.000109689735095918, 0.000770541414234860, 0.001371714610702517, 0.001885828071941918, 0.002290640442950059, 0.002569962094668674,
0.002714244052422841, 0.002720826151891805, 0.002593843111794459, 0.002343803978935238, 0.001986875765177565, 0.001543915361236087,
0.001039304486976568, 0.000499650640507829, -0.000047579005659538, -0.000575422161543602, -0.001058718218567234, -0.001475276496367705,
-0.001806863227100527, -0.002039964832379242, -0.002166297669634002, -0.002183047066222210, -0.002092832630669335, -0.001903410054728627,
-0.001627132056108478, -0.001280202174740378, -0.000881764419386712, -0.000452877901224665, -0.000015430291445883, 0.000408955257020119,
0.000799966281315092, 0.001139550679396572, 0.001412726517149060, 0.001608217075415199, 0.001718885253761411, 0.001741952717366617,
0.001678999613916214, 0.001535751682495805, 0.001321671816520764, 0.001049382185659160, 0.000733950102097711, 0.000392077118279518,
0.000041233610152644, -0.000301217045862462, -0.000618861629989936, -0.000897007516317755, -0.001123345195626614, -0.001288473577308051,
-0.001386267150663963, -0.001414071700002426, -0.001372724600470066, -0.001266404272056495, -0.001102321495571061, -0.000890272488296022,
-0.000642080313861205, -0.000370954941565034, -0.000090806124488888, 0.000184456030731509, 0.000441598292681654, 0.000668686966519433,
0.000855627164276610, 0.000994595533680270, 0.001080348246990215, 0.001110393602574907, 0.001085025011663583, 0.001007217243930078,
0.000882395437488790, 0.000718092538346410, 0.000523514975880865, 0.000309041306180360, 0.000085680046384777, -0.000135485422990712,
-0.000343837417747543, -0.000529724099269400, -0.000684896491503848, -0.000802862554846735, -0.000879144467901314, -0.000911429428339170,
-0.000899610318070534, -0.000845717937205157, -0.000753751787384093, -0.000629420667419594, -0.000479809451013147, -0.000312990053930070,
-0.000137597792476766, 0.000037605243749942, 0.000204158180969291, 0.000354300348814663, 0.000481322426719260, 0.000579855135491155,
0.000646082835685789, 0.000677874702997924, 0.000674829917199715, 0.000638237711550962, 0.000570957019366285, 0.000477224887882656,
0.000362404482394644, 0.000232687846388931, 0.000094768978920233, -0.000044495794702522, -0.000178423294758992, -0.000300818490773199,
-0.000406255267998045, -0.000490309633189676, -0.000549736803809912, -0.000582585214321533, -0.000588244271105343, -0.000567425902673365,
-0.000522083581325246, -0.000455274194431906, -0.000370972701089329, -0.000273849135305279, -0.000169020516263524, -0.000061790478444653,
0.000042610318846710, 0.000139271063990380, 0.000223822598023746, 0.000292625889758754, 0.000342918280693206, 0.000372912653196213,
0.000381846581094768, 0.000369981142430884, 0.000338550686637535, 0.000289669081062027, 0.000226197017031803, 0.000151579558482804,
0.000069662241126261, -0.000015504805729649, -0.000099865220473760, -0.000179546871425107, -0.000251037234855322, -0.000311334118688652,
-0.000358067160688509, -0.000389585053939050, -0.000405005829251745, -0.000404229097174175, -0.000387912413334777, -0.000357412309140960,
-0.000314696298480236, -0.000262229388149273, -0.000202842130917589, -0.000139587283955501, -0.000075592647414163, -0.000013915345466225,
0.000042593436932362, 0.000091410233747216, 0.000130440616590328, 0.000158097036687462, 0.000173350787020117, 0.000175757544777044,
0.000165454349492432, 0.000143132709269975, 0.000109986899426513, 0.000067643276268719, 0.000018073847262639, -0.000036501336346782,
-0.000093718946756075, -0.000151176196094362, -0.000206533574886596, -0.000257608154338197, -0.000302456824898826, -0.000339445383884735,
-0.000367301005108244, -0.000385145111580360, -0.000392510765617404, -0.000389338959490572, -0.000375960779331289, -0.000353063703221184,
-0.000321645514236948, -0.000282958234132596, -0.000238447248218383, -0.000189681915226441, -0.000138290085401831, -0.000085892061029301,
-0.000034041343381570, 0.000015827588463025, 0.000062442756567725, 0.000104732554871058, 0.000141846034715806, 0.000173169245803356,
0.000198326327006374, 0.000217172712546861, 0.000229779689874953, 0.000236413452609260, 0.000237501949870309, 0.000233610866318186,
0.000225404773677130, 0.000213614009898736, 0.000198999590569024, 0.000182322037177544, 0.000164310512299333, 0.000145652693573148,
0.000126959389238696, 0.000108759338791293, 0.000091486545323856, 0.000075478577997365, 0.000060975611534136, 0.000048143861498476,
0.000037033583075960, 0.000027648080357536, 0.000019922294330328, 0.000013730623424634, 0.000008939491599758, 0.000007812421315026],
400: # Shape 1.3, 770 taps
[ 0.000006365988874769, 0.000003794338487480, 0.000004876278267121, 0.000006131564967683, 0.000007569854655698, 0.000009199819153557,
0.000011019782182642, 0.000013040101237056, 0.000015265995566942, 0.000017696391039940, 0.000020312791765427, 0.000023135950087517,
0.000026132051354026, 0.000029299266022519, 0.000032613010658575, 0.000036050466832082, 0.000039588439426462, 0.000043195256918759,
0.000046833693036583, 0.000050464843382682, 0.000054046630064787, 0.000057526210900478, 0.000060855931076401, 0.000063977534092402,
0.000066834004766232, 0.000069367495918698, 0.000071512066722409, 0.000073206600728293, 0.000074384990732719, 0.000074985154083054,
0.000074941358848070, 0.000074196098488127, 0.000072687036065854, 0.000070364017870395, 0.000067174711830892, 0.000063076338216280,
0.000058031241355323, 0.000052010623717769, 0.000044992453647518, 0.000036966078084084, 0.000027928525061234, 0.000017889642287636,
0.000006870052370296, -0.000005097430373119, -0.000017968467835226, -0.000031683467914261, -0.000046174009079182, -0.000061354520788796,
-0.000077131717460578, -0.000093396057473154, -0.000110030073586572, -0.000126901644954483, -0.000143873358110243, -0.000160793959726774,
-0.000177509258936783, -0.000193855013323854, -0.000209665383215103, -0.000224769517767120, -0.000238997508310048, -0.000252178063434561,
-0.000264145812850823, -0.000274737753978821, -0.000283800555901376, -0.000291188041391623, -0.000296767743750952, -0.000300418295008671,
-0.000302037282504393, -0.000301535929224389, -0.000298849141904324, -0.000293929053180242, -0.000286753991998278, -0.000277322604938755,
-0.000265661916507038, -0.000251820837747345, -0.000235878209412169, -0.000217935329921142, -0.000198122592158803, -0.000176593305324287,
-0.000153527761718214, -0.000129127285942292, -0.000103617728527825, -0.000077242169252941, -0.000050264484742360, -0.000022961141775614,
0.000004376321843836, 0.000031449535456591, 0.000057950921452100, 0.000083573577884305, 0.000108007974814302, 0.000130953051335089,
0.000152112926634605, 0.000171207503556438, 0.000187970143706098, 0.000202157681598790, 0.000213547707697876, 0.000221948537710715,
0.000227196325735486, 0.000229163803316040, 0.000227757838181676, 0.000222926398781727, 0.000214655798042235, 0.000202977177552243,
0.000187962083392261, 0.000169728357656893, 0.000148434669356957, 0.000124285090373991, 0.000097522588191792, 0.000068432637827430,
0.000037334851940437, 0.000004586327504349, -0.000029428168903046, -0.000064294238370152, -0.000099577998322800, -0.000134825630330724,
-0.000169573965195115, -0.000203350785393334, -0.000235686105255552, -0.000266113076153881, -0.000294178911934326, -0.000319446762108148,
-0.000341506152453346, -0.000359974720676106, -0.000374508618833672, -0.000384803219892303, -0.000390603203520662, -0.000391702472886875,
-0.000387952968439801, -0.000379263512998934, -0.000365607622249019, -0.000347020514221526, -0.000323605716996244, -0.000295530392174600,
-0.000263029891857937, -0.000226402015568579, -0.000186009246146028, -0.000142271402525828, -0.000095666125277552, -0.000046719850734115,
0.000003993583197103, 0.000055863130913344, 0.000108243891748510, 0.000160468312893279, 0.000211850586288707, 0.000261699269396572,
0.000309322128759128, 0.000354039461924243, 0.000395189349018748, 0.000432140920160531, 0.000464299583558709, 0.000491120046640221,
0.000512110607671310, 0.000526845446928034, 0.000534967981519684, 0.000536201343488612, 0.000530350597696912, 0.000517311013160068,
0.000497068439035188, 0.000469705445435111, 0.000435399334882175, 0.000394425628337960, 0.000347153886365670, 0.000294048338105669,
0.000235661140309264, 0.000172630559665850, 0.000105671355374271, 0.000035570791815593, -0.000036823410279226, -0.000110609450083053,
-0.000184845354264341, -0.000258557173028134, -0.000330754528114594, -0.000400439970264790, -0.000466625524492906, -0.000528342888003039,
-0.000584659866982658, -0.000634690947746067, -0.000677613119135274, -0.000712675673752122, -0.000739214923907549, -0.000756662495413132,
-0.000764558104223763, -0.000762555872583250, -0.000750434429795932, -0.000728100396603541, -0.000695595696462148, -0.000653097503960073,
-0.000600922257516729, -0.000539522021491309, -0.000469484695604341, -0.000391526816237233, -0.000306489974421006, -0.000215329834691466,
-0.000119109058134576, -0.000018982981184245, 0.000083810915235651, 0.000187970000243908, 0.000292139959571129, 0.000394933986966016,
0.000494948142664746, 0.000590782205985295, 0.000681056090464857, 0.000764431197740454, 0.000839627050452169, 0.000905442225580665,
0.000960769995583447, 0.001004618039369409, 0.001036121906273736, 0.001054562391583931, 0.001059376177251162, 0.001050169688173629,
0.001026726061735900, 0.000989014843850052, 0.000937194466007411, 0.000871617237374737, 0.000792826925935815, 0.000701558421554040,
0.000598730377991698, 0.000485439514972828, 0.000362948081805026, 0.000232673195056634, 0.000096169337651215, -0.000044886850392886,
-0.000188718302856941, -0.000333466707544108, -0.000477217660879214, -0.000618022612530573, -0.000753926550301084, -0.000882991568244702,
-0.001003325671471952, -0.001113106854174517, -0.001210611462788910, -0.001294237402501427, -0.001362530796885569, -0.001414206728098591,
-0.001448172776524704, -0.001463546100508022, -0.001459672417038066, -0.001436138251655395, -0.001392784297634421, -0.001329711615112041,
-0.001247288727402393, -0.001146150989584800, -0.001027200706007495, -0.000891599407168167, -0.000740760728594229, -0.000576335492878354,
-0.000400197621159931, -0.000214422239224392, -0.000021265147290331, 0.000176865113062629, 0.000377433546776307, 0.000577811529416548,
0.000775307530646278, 0.000967203529925253, 0.001150788527481832, 0.001323396897145278, 0.001482443038707630, 0.001625459660097671,
0.001750131670233381, 0.001854332615855901, 0.001936155690016456, 0.001993946486300650, 0.002026329220986863, 0.002032233882826800,
0.002010916100718580, 0.001961977029179007, 0.001885375282542786, 0.001781438385022068, 0.001650865633292049, 0.001494730107495952,
0.001314471906401099, 0.001111890114781271, 0.000889126269781853, 0.000648646430391355, 0.000393215009447102, 0.000125867561738431,
-0.000150124130738416, -0.000431287566747790, -0.000713991806927365, -0.000994489713483095, -0.001268965941124358, -0.001533583756312415,
-0.001784536663195940, -0.002018097583665120, -0.002230671541941067, -0.002418844734804261, -0.002579435657364371, -0.002709541359817244,
-0.002806584464746950, -0.002868353953987823, -0.002893045404115256, -0.002879294007683593, -0.002826205616681453, -0.002733379783299579,
-0.002600929724527880, -0.002429493528198460, -0.002220241603361130, -0.001974874872112559, -0.001695618645272131, -0.001385207220497454,
-0.001046863974406666, -0.000684272287342287, -0.000301542320981976, 0.000096831014522634, 0.000506013584333645, 0.000920885468043068,
0.001336097138497067, 0.001746132300635148, 0.002145372272540894, 0.002528165649885475, 0.002888898066467300, 0.003222065542166644,
0.003522346486582096, 0.003784675282650529, 0.004004312987512708, 0.004176917749158860, 0.004298610545483354, 0.004366038868235595,
0.004376434088990949, 0.004327664995106265, 0.004218283602051056, 0.004047565581181025, 0.003815541611827637, 0.003523022274162837,
0.003171612798693589, 0.002763720451889364, 0.002302551278585364, 0.001792098861036779, 0.001237122342212990, 0.000643116381836866,
0.000016270569630469, -0.000636578845765101, -0.001308006968347628, -0.001990065317450888, -0.002674355367539247, -0.003352107995747014,
-0.004014269925046706, -0.004651594401548673, -0.005254737118938589, -0.005814354519325696, -0.006321205162109337, -0.006766251436254966,
-0.007140762118819473, -0.007436412979114623, -0.007645386018101693, -0.007760464454403432, -0.007775124017275518, -0.007683617984862557,
-0.007481056258606499, -0.007163476264301046, -0.006727906019989577, -0.006172417333684170, -0.005496169589494688, -0.004699442443010971,
-0.003783657807377434, -0.002751390001364686, -0.001606364515685684, -0.000353444465756940, 0.001001394411574224, 0.002451100972703334,
0.003987597007902468, 0.005601837062643716, 0.007283878572786960, 0.009022962344277727, 0.010807601882596756, 0.012625681525580169,
0.014464561665273282, 0.016311190660485246, 0.018152221825464991, 0.019974134681837181, 0.021763358913199277, 0.023506400111173612,
0.025189965611159389, 0.026801089460580949, 0.028327254981021979, 0.029756513765852269, 0.031077599771363505, 0.032280037413131318,
0.033354242302380432, 0.034291613871121174, 0.035084618575605728, 0.035726863022388727, 0.036213156095256299, 0.036539559528566301,
0.036703426247133436, 0.036703426247133436, 0.036539559528566301, 0.036213156095256299, 0.035726863022388727, 0.035084618575605728,
0.034291613871121174, 0.033354242302380432, 0.032280037413131318, 0.031077599771363505, 0.029756513765852269, 0.028327254981021979,
0.026801089460580949, 0.025189965611159389, 0.023506400111173612, 0.021763358913199277, 0.019974134681837181, 0.018152221825464991,
0.016311190660485246, 0.014464561665273282, 0.012625681525580169, 0.010807601882596756, 0.009022962344277727, 0.007283878572786960,
0.005601837062643716, 0.003987597007902468, 0.002451100972703334, 0.001001394411574224, -0.000353444465756940, -0.001606364515685684,
-0.002751390001364686, -0.003783657807377434, -0.004699442443010971, -0.005496169589494688, -0.006172417333684170, -0.006727906019989577,
-0.007163476264301046, -0.007481056258606499, -0.007683617984862557, -0.007775124017275518, -0.007760464454403432, -0.007645386018101693,
-0.007436412979114623, -0.007140762118819473, -0.006766251436254966, -0.006321205162109337, -0.005814354519325696, -0.005254737118938589,
-0.004651594401548673, -0.004014269925046706, -0.003352107995747014, -0.002674355367539247, -0.001990065317450888, -0.001308006968347628,
-0.000636578845765101, 0.000016270569630469, 0.000643116381836866, 0.001237122342212990, 0.001792098861036779, 0.002302551278585364,
0.002763720451889364, 0.003171612798693589, 0.003523022274162837, 0.003815541611827637, 0.004047565581181025, 0.004218283602051056,
0.004327664995106265, 0.004376434088990949, 0.004366038868235595, 0.004298610545483354, 0.004176917749158860, 0.004004312987512708,
0.003784675282650529, 0.003522346486582096, 0.003222065542166644, 0.002888898066467300, 0.002528165649885475, 0.002145372272540894,
0.001746132300635148, 0.001336097138497067, 0.000920885468043068, 0.000506013584333645, 0.000096831014522634, -0.000301542320981976,
-0.000684272287342287, -0.001046863974406666, -0.001385207220497454, -0.001695618645272131, -0.001974874872112559, -0.002220241603361130,
-0.002429493528198460, -0.002600929724527880, -0.002733379783299579, -0.002826205616681453, -0.002879294007683593, -0.002893045404115256,
-0.002868353953987823, -0.002806584464746950, -0.002709541359817244, -0.002579435657364371, -0.002418844734804261, -0.002230671541941067,
-0.002018097583665120, -0.001784536663195940, -0.001533583756312415, -0.001268965941124358, -0.000994489713483095, -0.000713991806927365,
-0.000431287566747790, -0.000150124130738416, 0.000125867561738431, 0.000393215009447102, 0.000648646430391355, 0.000889126269781853,
0.001111890114781271, 0.001314471906401099, 0.001494730107495952, 0.001650865633292049, 0.001781438385022068, 0.001885375282542786,
0.001961977029179007, 0.002010916100718580, 0.002032233882826800, 0.002026329220986863, 0.001993946486300650, 0.001936155690016456,
0.001854332615855901, 0.001750131670233381, 0.001625459660097671, 0.001482443038707630, 0.001323396897145278, 0.001150788527481832,
0.000967203529925253, 0.000775307530646278, 0.000577811529416548, 0.000377433546776307, 0.000176865113062629, -0.000021265147290331,
-0.000214422239224392, -0.000400197621159931, -0.000576335492878354, -0.000740760728594229, -0.000891599407168167, -0.001027200706007495,
-0.001146150989584800, -0.001247288727402393, -0.001329711615112041, -0.001392784297634421, -0.001436138251655395, -0.001459672417038066,
-0.001463546100508022, -0.001448172776524704, -0.001414206728098591, -0.001362530796885569, -0.001294237402501427, -0.001210611462788910,
-0.001113106854174517, -0.001003325671471952, -0.000882991568244702, -0.000753926550301084, -0.000618022612530573, -0.000477217660879214,
-0.000333466707544108, -0.000188718302856941, -0.000044886850392886, 0.000096169337651215, 0.000232673195056634, 0.000362948081805026,
0.000485439514972828, 0.000598730377991698, 0.000701558421554040, 0.000792826925935815, 0.000871617237374737, 0.000937194466007411,
0.000989014843850052, 0.001026726061735900, 0.001050169688173629, 0.001059376177251162, 0.001054562391583931, 0.001036121906273736,
0.001004618039369409, 0.000960769995583447, 0.000905442225580665, 0.000839627050452169, 0.000764431197740454, 0.000681056090464857,
0.000590782205985295, 0.000494948142664746, 0.000394933986966016, 0.000292139959571129, 0.000187970000243908, 0.000083810915235651,
-0.000018982981184245, -0.000119109058134576, -0.000215329834691466, -0.000306489974421006, -0.000391526816237233, -0.000469484695604341,
-0.000539522021491309, -0.000600922257516729, -0.000653097503960073, -0.000695595696462148, -0.000728100396603541, -0.000750434429795932,
-0.000762555872583250, -0.000764558104223763, -0.000756662495413132, -0.000739214923907549, -0.000712675673752122, -0.000677613119135274,
-0.000634690947746067, -0.000584659866982658, -0.000528342888003039, -0.000466625524492906, -0.000400439970264790, -0.000330754528114594,
-0.000258557173028134, -0.000184845354264341, -0.000110609450083053, -0.000036823410279226, 0.000035570791815593, 0.000105671355374271,
0.000172630559665850, 0.000235661140309264, 0.000294048338105669, 0.000347153886365670, 0.000394425628337960, 0.000435399334882175,
0.000469705445435111, 0.000497068439035188, 0.000517311013160068, 0.000530350597696912, 0.000536201343488612, 0.000534967981519684,
0.000526845446928034, 0.000512110607671310, 0.000491120046640221, 0.000464299583558709, 0.000432140920160531, 0.000395189349018748,
0.000354039461924243, 0.000309322128759128, 0.000261699269396572, 0.000211850586288707, 0.000160468312893279, 0.000108243891748510,
0.000055863130913344, 0.000003993583197103, -0.000046719850734115, -0.000095666125277552, -0.000142271402525828, -0.000186009246146028,
-0.000226402015568579, -0.000263029891857937, -0.000295530392174600, -0.000323605716996244, -0.000347020514221526, -0.000365607622249019,
-0.000379263512998934, -0.000387952968439801, -0.000391702472886875, -0.000390603203520662, -0.000384803219892303, -0.000374508618833672,
-0.000359974720676106, -0.000341506152453346, -0.000319446762108148, -0.000294178911934326, -0.000266113076153881, -0.000235686105255552,
-0.000203350785393334, -0.000169573965195115, -0.000134825630330724, -0.000099577998322800, -0.000064294238370152, -0.000029428168903046,
0.000004586327504349, 0.000037334851940437, 0.000068432637827430, 0.000097522588191792, 0.000124285090373991, 0.000148434669356957,
0.000169728357656893, 0.000187962083392261, 0.000202977177552243, 0.000214655798042235, 0.000222926398781727, 0.000227757838181676,
0.000229163803316040, 0.000227196325735486, 0.000221948537710715, 0.000213547707697876, 0.000202157681598790, 0.000187970143706098,
0.000171207503556438, 0.000152112926634605, 0.000130953051335089, 0.000108007974814302, 0.000083573577884305, 0.000057950921452100,
0.000031449535456591, 0.000004376321843836, -0.000022961141775614, -0.000050264484742360, -0.000077242169252941, -0.000103617728527825,
-0.000129127285942292, -0.000153527761718214, -0.000176593305324287, -0.000198122592158803, -0.000217935329921142, -0.000235878209412169,
-0.000251820837747345, -0.000265661916507038, -0.000277322604938755, -0.000286753991998278, -0.000293929053180242, -0.000298849141904324,
-0.000301535929224389, -0.000302037282504393, -0.000300418295008671, -0.000296767743750952, -0.000291188041391623, -0.000283800555901376,
-0.000274737753978821, -0.000264145812850823, -0.000252178063434561, -0.000238997508310048, -0.000224769517767120, -0.000209665383215103,
-0.000193855013323854, -0.000177509258936783, -0.000160793959726774, -0.000143873358110243, -0.000126901644954483, -0.000110030073586572,
-0.000093396057473154, -0.000077131717460578, -0.000061354520788796, -0.000046174009079182, -0.000031683467914261, -0.000017968467835226,
-0.000005097430373119, 0.000006870052370296, 0.000017889642287636, 0.000027928525061234, 0.000036966078084084, 0.000044992453647518,
0.000052010623717769, 0.000058031241355323, 0.000063076338216280, 0.000067174711830892, 0.000070364017870395, 0.000072687036065854,
0.000074196098488127, 0.000074941358848070, 0.000074985154083054, 0.000074384990732719, 0.000073206600728293, 0.000071512066722409,
0.000069367495918698, 0.000066834004766232, 0.000063977534092402, 0.000060855931076401, 0.000057526210900478, 0.000054046630064787,
0.000050464843382682, 0.000046833693036583, 0.000043195256918759, 0.000039588439426462, 0.000036050466832082, 0.000032613010658575,
0.000029299266022519, 0.000026132051354026, 0.000023135950087517, 0.000020312791765427, 0.000017696391039940, 0.000015265995566942,
0.000013040101237056, 0.000011019782182642, 0.000009199819153557, 0.000007569854655698, 0.000006131564967683, 0.000004876278267121,
0.000003794338487480, 0.000006365988874769],
}

550
freedv.c Executable file
View File

@ -0,0 +1,550 @@
#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"
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 WINAPI hLib;
#define GET_HANDLE1 hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll")
#define GET_HANDLE2 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll")
#define GET_HANDLE3 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll")
#define GET_HANDLE4 hLib = LoadLibrary("libcodec2.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("./freedvpkg/libcodec2.so", RTLD_LAZY)
#define GET_HANDLE2 hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY)
#define GET_HANDLE3 hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY)
#define GET_HANDLE4 hLib = dlopen("libcodec2.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;
#define SPEECH_BUF_SIZE 3000 // speech buffer size
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] ;
// 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_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);
/* 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)
{
if (DEBUG) printf("Try handle 1\n");
GET_HANDLE1;
if (hLib) { // check the first library name
freedv_version = -2;
freedv_get_version = GET_ADDR("freedv_get_version");
if (freedv_get_version != NULL)
freedv_version = freedv_get_version();
}
if (freedv_version < 10) { // try the next library
if (hLib)
CLOSE_LIB;
if (DEBUG) printf("Try handle 2\n");
GET_HANDLE2;
if (hLib) {
freedv_version = -2;
freedv_get_version = GET_ADDR("freedv_get_version");
if (freedv_get_version != NULL)
freedv_version = freedv_get_version();
}
}
if (freedv_version < 10) { // try the next library
if (hLib)
CLOSE_LIB;
if (DEBUG) printf("Try handle 3\n");
GET_HANDLE3;
if (hLib) {
freedv_version = -2;
freedv_get_version = GET_ADDR("freedv_get_version");
if (freedv_get_version != NULL)
freedv_version = freedv_get_version();
}
}
if (freedv_version < 10) { // try the next library
if (hLib)
CLOSE_LIB;
if (DEBUG) printf("Try handle 4\n");
GET_HANDLE4;
if (hLib) {
freedv_version = -2;
freedv_get_version = GET_ADDR("freedv_get_version");
if (freedv_get_version != NULL)
freedv_version = freedv_get_version();
}
}
if (DEBUG) printf("freedv_version is %d\n", freedv_version);
if (freedv_version < 10) {
if (hLib)
CLOSE_LIB;
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
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.
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;
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);
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 0
pCh->demod_in[pCh->rxdata_index].real = creal(cx) / scale;
pCh->demod_in[pCh->rxdata_index].imag = cimag(cx) / scale;
#else
pCh->demod_in[pCh->rxdata_index].real = (creal(cx) - cimag(cx)) / scale;
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 < count; i++)
dsamples[i] = 0;
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
return count;
}
}
for (nout = 0; nout < pCh->speech_available && nout < count; 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 < count)
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;
if (dsamples == NULL) { // shutdown
if (mod_out)
free(mod_out);
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);
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;
}
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
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);
mod_index = 0;
speech_index = 0;
}
else { // write out samples slowly
if (mod_index < n_nom_modem_samples) {
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 (rx_channel[i].demod_in) {
free(rx_channel[i].demod_in);
rx_channel[i].demod_in = NULL;
}
}
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) printf("freedv_open: version %d\n", freedv_version);
if (freedv_version < 10) {
CloseFreedv();
requested_mode = -1;
return 0; // failure
}
if (requested_mode == FREEDV_MODE_700D && 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);
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 (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);
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} ;
struct freedv * hFreedv;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch, &interleave_frames, &bpf))
return NULL;
if (ptMsg)
strncpy(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)
;
else if (freedv_current_mode < 0) // not started
requested_mode = mode;
else if (freedv_version == 10 && mode == 0)
requested_mode = mode;
else if (freedv_version == 11 && mode <= 2)
requested_mode = mode;
else {
hFreedv = freedv_open(mode); // test new mode
if (hFreedv != NULL) {
freedv_close(hFreedv);
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);
}

10
freedv.h Executable file
View File

@ -0,0 +1,10 @@
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

118
freedvpkg/README.txt Executable file
View File

@ -0,0 +1,118 @@
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
in 1200 Hz bandwidth suitable for HF 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 mode button unless your config file contains the line
add_freedv_button = 0
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 14.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/libcodec2.so.
Search Order
============
Quisk will search for a valid codec2 library in this order on Windows:
1. freedvpkg/libcodec2.dll. Not included. Copy the codec2 you want to use to this name.
2. freedvpkg/libcodec2_32.dll. The 32-bit codec2 shipped with Quisk.
3. The system codec2 library installed outside of Quisk by another program.
Quisk will search for a valid codec2 library in this order on Linux:
1. freedvpkg/libcodec2.so. Not included. Copy the codec2 you want to use to this name.
2. freedvpkg/libcodec2_32.so. The 32-bit codec2 shipped with Quisk.
3. freedvpkg/libcodec2_64.so. The 64-bit codec2 shipped with Quisk.
4. The system codec2 library installed outside of Quisk by another program.
How to Build a New codec2
=========================
The source for codec2 is in SourceForge in the freetel project. 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 svn:
svn co https://svn.code.sf.net/p/freetel/code/codec2-dev codec2-dev
Note that we are using codec2-dev to get the most recent source. Then build codec2 using the directions
found in README. The directions given below are current as of May 2018, but check for changes.
Then copy the codec2 library to the freedvpkg directory under Quisk.
If the new codec2 contains new modes, add them to freedv_modes in your config file.
The Speex "-dev" packages are not needed by codec2, but are required for the Unit Test modules.
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-dev
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-dev
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 MSYS2 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 msys2 32-bit shell:
cd codec2-dev
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 msys2 64-bit shell:
cd codec2-dev
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
c:/python27/python.exe # (or just "python" on Linux)
import _quisk
_quisk.freedv_get_version() # This should return 10 or higher for a recent codec2

1
freedvpkg/__init__.py Executable file
View File

@ -0,0 +1 @@
#

BIN
freedvpkg/libcodec2_32.dll Executable file

Binary file not shown.

BIN
freedvpkg/libcodec2_32.so Executable file

Binary file not shown.

BIN
freedvpkg/libcodec2_64.dll Executable file

Binary file not shown.

BIN
freedvpkg/libcodec2_64.so Executable file

Binary file not shown.

526
help.html Executable file
View File

@ -0,0 +1,526 @@
<!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><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="docs.html">The documentation is here</a>.
<br>
<br>
<a href="defaults.html">The default configuration is here</a>.
<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 contributers 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.
</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 to replace the
previous one. 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>
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 Sound screen gives the name of
the sound cards detected.
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>
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 wsjtx 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.
On the Quisk radio "Sound" configuration screen, set Digital Input to
pulse:QuiskDigitalInput.monitor, and set Digital Output to pulse:QuiskDigitalOutput.
These names should be on the drop down list. If they are missing, restart Quisk.
In your digital program, connect the digital
input to QuiskDigitalOutput.monitor and the digital output to QuiskDigitalInput.
Quisk will then send a slice of the receive spectrum to the digital program. The
center frequency is 1500 Hertz. Set that frequency in your digital program for transmit.
See the <a href="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. See the Config/radio/Remote screen.
To connect an external program to Quisk using Hamlib, configure your program to use "Hamlib NET rigctl" (rig 2).
Then go to the Quisk "Remote" config screen for your radio and set
"IP port for Hamlib" to 4532. This assumes you are not using the rigctld daemon program. If you are,
set the Quisk port to 4575 and tell rigctld to control quisk on port 4575.
You can also control Quisk from another program by using the XML-RPC method if this is available
in your program. Fldigi can use this method. If your program only uses a serial port (N1MM+) then
use Hamlib with the rig set to "Flex" and connect to the Quisk serial port set on the Remote screen.
For Linux, Quisk can set up these ports itself, and they have names like "/tmp/QuiskTTY0". On Windows
you need a "Virtual Serial Port" that is set up by an external program. This is like the "Virtual Audio Cable"
needed for samples. An Internet search will turn up HDD Software, Eltima Software and many others. Set up a port pair,
and enter one name on the Quisk Remote screen, and the other name in the external program.
</p>
<h3 id="g0.0.13">Multiple Receivers </h3>
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 does not know this
limit, so please do not request more sub-receivers than actually exist.
<h4>Have fun with Quisk.</h4>
</body>
</html>

136
help_conf.html Executable file
View File

@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="description" content="Help with Quisk Configuration">
<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><body>
<h3> Help with Quisk Configuration</h3>
<h5> Quick Start </h5>
<p>
Quisk can now manage its settings (sound devices, sample rates, etc.) internally, and a configuration file is
no longer required. Multiple radios are supported as multiple named blocks of settings. Take a look at the
tabs after this tab. The "Radios" tab shows the available radios. Each radio has its own tab after the
"Radios" tab.
</p>
<p>
When you first start Quisk, it starts with the radio named "ConfigFileRadio". This radio takes its settings
only from your configuration file. If you have no configuration file, Quisk uses defaults. If you select
the ConfigFileRadio tab, you can see your settings but not change them, because Quisk will not change your
config file. To change settings, you must name them; that is, create a name for the radio using that block of settings.
</p>
<p>
When you first install Quisk, you will not have any settings for your radio. Press the Config button and go to the Radios screen.
Then create a radio by specifying the general hardware type and give it a name of your choosing.
For a Hermes-Lite, specify "Hermes" as the hardware type and call it "HL2" (or some other name). Press "Add" and
a new tab for your radio will appear. Look through the various settings on the HL2 tab.
</p>
<p>
Suppose you just purchased a HiQSDR, but have no config file for it. When you start Quisk, ConfigFileRadio
has no use, because it was not designed for a HiQSDR. Instead, go to the radios tab and create a new
radio of general type HiQSDR and give it a name. The Radios tab can create, rename and delete radios.
</p>
<p>
When you change settings for any radio, the changes do not apply until Quisk restarts. To restart Quisk,
press the Restart button on the Radios tab. If PulseAudio is running, you must exit and restart Quisk yourself.
</p>
<h5> History </h5>
<p>
Quisk was written in 2008, and since that time it has used a configuration file to control its operation. Setting
sound devices or changing sample rates required editing the configuration file and restarting. But many users
object to config files. And advanced users now have multiple different hardwares, and so must use multiple
configuration files. So Quisk now can manage its settings internally and no longer needs a configuration file.
And multiple radios are supported with multiple named settings.
</p>
<h5> FAQ </h5>
<dl>
<dt>What is a "Radio"?</dt>
<dd>
A Radio means a named block of settings Quisk uses to control a specific kind of hardware.
</dd><br>
<dt>What settings are used when Quisk starts?</dt>
<dd>
Quisk uses the settings specified on the "Radios" tab. This is a list of all named radios,
plus ConfigFileRadio, plus "Ask me" to cause Quisk to request the radio name at startup. When
making changes to settings, it is wise to use "Ask me". If things go wrong and your new radio
won't start, you can select ConfigFileRadio at startup and continue to make changes to your
new radio.
</dd><br>
<dt>I have multiple custom config files and multiple hardwares.</dt>
<dd>
Specify ConfigFileRadio as the startup radio on the Radios tab. Then start Quisk with each of your
config files. For each config file, rename ConfigFileRadio to a suitable name.
</dd><br>
<dt>Should I delete my old config file?</dt>
<dd>
Maybe. When Quisk starts it still reads your config file. It then overwrites the settings in the
config file with the settings for the named radio. So it doesn't really hurt to have a config file.
See below for continuing uses for config files.
</dd><br>
<dt>I only have one kind of hardware, but I sometimes use it with a transverter.</dt>
<dd>
Just create two radios for your single hardware. Create different settings for the two cases.
</dd><br>
<dt>I made some changes and now Quisk will not start. Since Quisk will not start, I can't change things back.</dt>
<dd>
Start quisk with the -a or --ask command line option to cause it to ask for the startup radio. That is, start quisk
with "python quisk.py --ask" or "C:\python27\python.exe quisk.py --ask". Then specify ConfigFileRadio as the startup radio.
Change the startup radio to "Ask me" until you get things fixed.
</dd><br>
<dt>Why do I have to restart Quisk to make the new settings happen?</dt>
<dd>
Some of the settings could happen immediately, such as CW tone. But many settings control which buttons
Quisk displays or other basic features that are awkward to change if Quisk is running. Thus the
need to restart.
</dd><br>
</dl>
<h5> Uses for Config Files </h5>
<p>
There are still some advantages to config files. If you run Quisk on a single board computer with
an attached 7 inch screen, you may find the screen too small to conveniently make changes to the settings.
And the settings are fixed anyway. So a config file might be a superior solution.
</p>
<p>
There are settings in the config file that are not yet available on the radio settings screens. These
are mainly the colors used by Quisk, the colors used for the band plans and the hot keys. If you want
to change any of these, you still need a config file with just these items. Some of these setting are
lengthy, and apply to multiple radios. It is not clear they belong on radio screens.
</p>
<h5> Dual-Boot Systems </h5>
<p>
Quisk stores the radio settings in the file quisk_settings.json in the default location
of your config file. You can change the settings path by specifying settings_file_path="/my/path"
in your config file.
If you have a computer that can dual boot Windows or Linux, and you don't do anything else, you will
have two settings files. That is fine if the settings are different on Windows and Linux, but
probably most are the same. To use a single settings file, specfify settings_file_path to be the same
file in your Windows and Linux config files. For example:
<pre>
# In Windows quisk_conf.py
settings_file_path = "C:\\pub\\quisk_settings.json"
# In Linux .quisk_conf.py
settings_file_path = "/home/jim/pub/quisk_settings.json"
</pre>
The above assumes that these files are really the same file, perhaps because they are on a shared drive,
or because the Linux file maps the Windows partition, or because the files are subject to a sync.
</p>
<p>
You will also need a hardware file name and widget file name that are the same on Windows
and Linux. For example, "./hermes/quisk_hardware.py" is the same on Windows (except for
the forward slashes and backslashes).
Even though you have a single settings file, Quisk will maintain separate values for the audio
device names (different for Windows and Linux) and for data_poll_usec and latency_millisecs.
</p>
</body>
</html>

144
help_vna.html Executable file
View File

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="description" content="Documentation for Quisk VNA"><meta name="author" content="James C. Ahlstrom"><meta name="keywords" content="quisk, vna, vector network analyzer, ham radio">
<title>QUISK Help File</title>
</head><body>
<h3>
Quisk VNA Help (December 2016)
</h3>
<p>This is the Help file for Quisk VNA, a program that turns the
Quisk2010 and HiQSDR transceiver and the Hermes-Lite transceiver into a Vector Network Analyzer
(VNA). This Help appears when you press the Help button. Quisk is written by Jim
Ahlstrom, N2ADR,
www.james.ahlstrom.name. Mail to jahlstr at gmail.com. To run the
Quisk VNA program, use "python quisk_vna.py" or set up a
shortcut. This program only works with hardware that is based
on UDP. It does not work with SoftRock hardware.
</p>
<p>For HiQSDR you need to update your firmware to Version 1.3 or later.
For Hermes-Lite update to 32 or later. The
new firmware locks the phase of the RF output to the phase of the RF
detector. There is a time delay in the path, but this is removed
by the calibration procedure. The calibration graphs will show a linear phase change with frequency due to this delay.
</p>
<p>There are two ways to use your hardware. You could connect the
RF output through attenuators, through a device under test and then
back to the RF input. This is called transmission mode. It is
used to plot the response of filters and to measure the electrical
length of cables. Or you could connect the RF input and output through
attenuators to
a resistive return loss bridge. This is called reflection mode. It is
used to measure SWR and impedance. You must choose a mode and
calibrate for that mode and hardware before you take any data.
The calibration is saved and will be restored the next time the
program starts. This is convenient when making repeated measurements,
but you should calibrate frequently for best accuracy. Hermes-Lite requires a power
on/off and a calibration before taking data, but the calibration is good until it is powered down.
</p>
<p>
The hardware generates RF output only when one of the "Calibrate"
buttons or the "Run" button is turned on.
The calibration routines save data every 15 kHz from zero
to sixty megahertz (thirty for Hermes-Lite). You can change the measurement frequency span
at any time without recalibration. Although you can set a
frequency span up to sixty (thirty) megahertz, my original hardware has a low
pass filter cutoff of 35 megahertz, so the upper frequency range is
smaller and will appear noisy.
Since the transmit and receive frequencies are equal, the data is at
DC, and is averaged and effectively low pass filtered. This
provides immunity from interference when measuring an antenna.
<h3 id="g0.0.1">Input Protection </h3>
Do not connect the RF output to the RF input without inserting
attenuators. The output will overload the input and result in
clipping in the ADC and possible damage. If your device under
test is an amplifier be especially careful to add additional
attenuation to avoid damage. Add enough attenuation to avoid
clipping, but not so much that you lose dynamic range. The
calibration screens show the ADC level. These attenuators also
help to stabilize the input and output impedance and increase
accuracy. I use the HAT series of attenuators from Mini-Circuits.
<h3 id="g0.0.2">Transmission Mode
</h3>
You must perform a calibration before you can take data. First
set the mode to "Transmission" and leave it there. Connect attenuators
and a cable between the RF input and output. Press the "Calibrate.." button.
With the cable connected press "Short". The "Short" calibration is
required for transmission mode. For the optional "Open" calibration, disconnect
the cable and press "Open". Then press "Calibrate" to save the calibration.
Connect the cable and press "Run" and
you should see a flat line at zero dB level and zero phase. Now
insert your test device in series. The test device could be a
filter, an amplifier (be careful) or an additional length of
cable. Press "Run" to take data.
<h3 id="g0.0.3">Reflection Mode
</h3>
<p>
This mode is used with a resistive return loss bridge.
Connect the RF
output to the generator terminal, and the RF input to the detector
terminal. Use attenuators on both. You must
perform a calibration before you can take data.
First set the
mode to "Reflection" and leave it there. Press the "Calibrate.." button.
Connect an open circuit (or nothing) to
the impedance
terminal of the bridge, and then press "Open". Connect a
short circuit to the bridge, and then press "Short". Connect
a 50 ohm termination to the bridge and then press "Load".
Then press "Calibrate" to save the calibration.
If you do not have a set of Open/Short/Load standards, you can just
use "Open" alone, but it is highly
recommended to use Open, Short and Load.
</p>
<p>
Now connect a 50 ohm termination to the bridge, and
press "Run". The graph will show the magnitude and phase of the
reflection coefficient, and the return loss is the drop in magnitude
below zero dB. Ideally your bridge will have a directivity of 30
dB or more. The phase may be noisy if the magnitude is very
small. Now connect an
unknown impedance to the bridge; for example, an antenna. The
graph will plot the return loss, reflection coefficient and SWR.
The status line will display the frequency, the reflection coefficient and SWR, the impedance, the equivalent
capacitance or inductance for that impedance, and the values for the parallel equivalent circuit.
</p>
<p>
You can attach any impedance, such as an unknown capacitor or inductor,
and read the value directly. The value may seem to vary with
frequency due to stray inductance, variation of permeability with
frequency, and bridge imperfections; so choose a reference frequency
wisely. Remember that the bridge measures the impedance relative
to fifty ohms, so accuracy suffers if the impedance is outside the
range of 5 to 500 ohms or so.
</p>
<h3 id="g0.0.4">Fun</h3>
<p>
In transmission mode, add an extra length of cable and see the phase
change. When the phase change is ninety degrees, that is a
quarter wave. The effect of velocity factor is included, and can
be measured. Use a bare wire (with attenuators) as the test
fixture, and then add a ferrite bead to the wire to measure its
properties. Insert a filter to see its response.
</p>
<p>
In reflection mode, measure your antenna from zero to sixty (thirty)
megahertz. If it is a dipole, you will see the drop in SWR at its fundamental and
third harmonic. Add a cable to the bridge to see its impedance at
a quarter and half wave length. Measure the length of your
transmission line by replacing your antenna with a 100 ohm
resister. The bridge will read 100 ohms at multiples of a half
wavelength. If you short out your antenna at the far end of your
transmission line, the bridge will read zero at half wavelengths, and
infinity at quarter wavelengths.
</p>
</body>
</html>

65
hermes/README.txt Executable file
View File

@ -0,0 +1,65 @@
This directory has files for the Hermes-Lite project versions 1 and 2.
Please perform all configuration with the Config screens in Quisk.
To start, go to Config/Radios and add a new radio with the general type "Hermes".
Quisk fully supports the Hermes-Lite project. But you may need a custom Widget or
Hardware file for special purposes. If you create these custom files, enter their
location (path) on the Config/radio/Hardware screen. Do not modify the files supplied
by Quisk, as these will be replaced with each new release.
# This is an example of a custom Widgets file. It changes the power calculations
# to that required by an external power bridge. Any methods (functions) defined here are used
# instead of the usual versions. See the original quisk_widgets.py to see what is available
# for replacement.
from hermes.quisk_widgets import BottomWidgets as BaseWidgets
class BottomWidgets(BaseWidgets): # Add extra widgets to the bottom of the screen
# This replaces the default version. You must alter the code to calculate watts for your
# external power meter that is connected to the Hermes-Lite power ADC.
def Code2FwdRevWatts(self): # Convert the HermesLite fwd/rev power code to watts forward and reverse
# volts = m * code + b # The N2ADR power circuit is linear in voltage
# power = (m**2 * code**2 + 2 * b * m * code + b**2) / 50
fwd = self.hardware.hermes_fwd_power # forward and reverse binary code
rev = self.hardware.hermes_rev_power
Vfwd = 3.26 * fwd / 4096.0 # forward and reverse volts
Vrev = 3.26 * rev / 4096.0
Pfwd = 2.493 * Vfwd**2 + 0.1165 * Vfwd # conversion from volts to power in watts
Prev = 2.493 * Vrev**2 + 0.1165 * Vrev
return Pfwd, Prev # return forward and reverse power in watts
# This is an example of a custom Hardware file:
from hermes.quisk_hardware import Hardware as BaseHardware
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.usingSpot = False # Use bit C2[7] as the Spot indicator
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
# The call to BaseHardware will set C2 according to the Hermes_BandDict{}
ret = BaseHardware.ChangeBand(self, band)
if self.usingSpot:
byte = self.GetControlByte(0, 2) # C0 index == 0, C2: user output
byte |= 0b10000000
self.SetControlByte(0, 2, byte)
return ret
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
ret = BaseHardware.OnSpot(self, level)
if level >= 0 and not self.usingSpot: # Spot was turned on
byte = self.GetControlByte(0, 2)
byte |= 0b10000000
self.SetControlByte(0, 2, byte)
self.usingSpot = True
elif level < 0 and self.usingSpot: # Spot was turned off
byte = self.GetControlByte(0, 2)
byte &= 0b01111111
self.SetControlByte(0, 2, byte)
self.usingSpot = False
return ret

1
hermes/__init__.py Executable file
View File

@ -0,0 +1 @@
#

1
hermes/quisk_conf.py Executable file
View File

@ -0,0 +1 @@
# Configuration files are no longer used. Please use the radio Config screens instead.

1
hermes/quisk_conf2.py Executable file
View File

@ -0,0 +1 @@
# Configuration files are no longer used. Please use the radio Config screens instead.

922
hermes/quisk_hardware.py Executable file
View File

@ -0,0 +1,922 @@
# This is a sample hardware file for UDP control using the Hermes-Metis protocol. Use this for
# the HermesLite project. It can also be used for the HPSDR, but since I don't have one, I
# can't test it.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import socket, traceback, time, math, os
import wx
import _quisk as QS
import quisk_utils
from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 0
class Hardware(BaseHardware):
var_rates = ['48', '96', '192', '384']
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.var_index = 0
self.hermes_mac = bytearray(6)
self.hermes_ip = ""
self.hermes_code_version = -1
self.hermes_board_id = -1
self.hermes_temperature = 0.0
self.hermes_fwd_power = 0.0
self.hermes_rev_power = 0.0
self.hermes_pa_current = 0.0
self.eeprom_valid = 0
self.alex_hpf_f1 = 0
self.alex_hpf_f2 = 0
self.alex_lpf_f1 = 0
self.alex_lpf_f2 = 0
self.mode = None
self.band = None
self.vfo_frequency = 0
self.tx_frequency = 0
self.vna_count = 0
self.vna_started = False
self.repeater_freq = None # original repeater output frequency
try:
self.repeater_delay = conf.repeater_delay # delay for changing repeater frequency in seconds
except:
self.repeater_delay = 0.25
self.repeater_time0 = 0 # time of repeater change in frequency
# Create the proper broadcast addresses for socket_discover
if conf.udp_rx_ip: # Known IP address of hardware
self.broadcast_addrs = [conf.udp_rx_ip]
else:
self.broadcast_addrs = []
for interf in QS.ip_interfaces():
broadc = interf[3] # broadcast address
if broadc and broadc[0:4] != '127.':
self.broadcast_addrs.append(broadc)
self.broadcast_addrs.append('255.255.255.255')
if DEBUG: print ('broadcast_addrs', self.broadcast_addrs)
# This is the control data to send to the Hermes using the Metis protocol
# Duplex must be on or else the first Rx frequency is locked to the Tx frequency
self.pc2hermes = bytearray(17 * 4) # Control bytes not including C0. Python initializes this to zero.
self.pc2hermeslitewritequeue = bytearray(4 * 5)
self.pc2hermes[3] = 0x04 # C0 index == 0, C4[5:3]: number of receivers 0b000 -> one receiver; C4[2] duplex on
self.pc2hermes[4 * 9] = 63 # C0 index == 0b1001, C1[7:0] Tx level
for c0 in range(1, 9): # Set all frequencies to 7012352, 0x006B0000
self.SetControlByte(c0, 2, 0x6B)
value = conf.keyupDelay
if value > 1023:
value = 1023
self.SetControlByte(0x10, 2, value & 0x3) # cw_hang_time
self.SetControlByte(0x10, 1, (value >> 2) & 0xFF) # cw_hang_time
self.SetLowPwrEnable(conf.hermes_lowpwr_tr_enable)
self.EnablePowerAmp(conf.hermes_power_amp)
self.MakePowerCalibration()
def pre_open(self):
# This socket is used for the Metis Discover protocol
self.discover_request = b"\xEF\xFE\x02" + b"\x00" * 60
self.socket_discover = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket_discover.setblocking(0)
self.socket_discover.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
found = False
st = "No capture device found."
port = self.conf.rx_udp_port
for i in range(5):
if found:
break
if DEBUG: print ('Send discover')
try:
for broadcast_addr in self.broadcast_addrs:
self.socket_discover.sendto(self.discover_request, (broadcast_addr, port))
if DEBUG: print ('discover_request', (broadcast_addr, port))
time.sleep(0.01)
except:
if DEBUG > 1: traceback.print_exc()
for j in range(5):
try:
data, addr = self.socket_discover.recvfrom(1500)
except:
if DEBUG > 1: traceback.print_exc()
time.sleep(0.02)
continue
else:
if DEBUG: print('recvfrom', addr, 'length', len(data), "type", type(data))
data = bytearray(data)
if len(data) > 32 and data[0] == 0xEF and data[1] == 0xFE:
if DEBUG: print('data', data)
ver = self.conf.hermes_code_version
bid = self.conf.hermes_board_id
if ver >= 0 and data[9] != ver:
pass
elif bid >= 0 and data[10] != bid:
pass
else:
st = 'Capture from Hermes device: Mac %2x:%2x:%2x:%2x:%2x:%2x, Code version %d, ID %d' % tuple(data[3:11])
self.hermes_mac = data[3:9]
self.hermes_ip = addr[0]
self.hermes_code_version = data[9]
self.hermes_board_id = data[10]
QS.set_hermes_id(data[9], data[10])
if data[0x16] >> 6 == 0:
QS.set_params(bandscopeScale = 2048)
if DEBUG: print (st)
adr = self.conf.rx_udp_ip
found = True
if adr and adr != addr[0]: # Change the IP address
if DEBUG: print("Change IP address from %s to %s" % (addr[0], adr))
ip = adr.split('.')
ip = list(map(int, ip))
cmd = bytearray(73)
cmd[0] = 0xEF
cmd[1] = 0xFE
cmd[2] = 0x03
cmd[3] = data[3]
cmd[4] = data[4]
cmd[5] = data[5]
cmd[6] = data[6]
cmd[7] = data[7]
cmd[8] = data[8]
cmd[9] = ip[0]
cmd[10] = ip[1]
cmd[11] = ip[2]
cmd[12] = ip[3]
for broadcast_addr in self.broadcast_addrs:
self.socket_discover.sendto(cmd, (broadcast_addr, port))
time.sleep(0.01)
# Note: There is no response, contrary to the documentation
self.hermes_ip = adr
if False:
try:
data, addr = self.socket_discover.recvfrom(1500)
except:
if DEBUG: traceback.print_exc()
else:
print(repr(data), addr)
##self.hermes_ip = adr
time.sleep(1.0)
st += ', IP %s' % self.hermes_ip
break
if not found and self.conf.udp_rx_ip:
self.hermes_ip = self.conf.udp_rx_ip
code = 62
bid = 6
self.hermes_code_version = code
self.hermes_board_id = bid
QS.set_hermes_id(code, bid)
st = 'Capture from Hermes device at specified IP %s' % self.hermes_ip
found = True
if found:
# Open a socket for communication with the hardware
msg = QS.open_rx_udp(self.hermes_ip, port)
if msg[0:8] != "Capture ":
st = msg # Error
self.socket_discover.close()
self.config_text = st
self.ChangeLNA(2) # Initialize the LNA using the correct LNA code from the FPGA code version
def open(self):
return self.config_text
def GetValue(self, name): # return values stored in the hardware
if name == 'Hware_Hl2_EepromIP':
addr1 = self.ReadEEPROM(0x08)
addr2 = self.ReadEEPROM(0x09)
addr3 = self.ReadEEPROM(0x0A)
addr4 = self.ReadEEPROM(0x0B)
if addr1 < 0 or addr2 < 0 or addr3 < 0 or addr4 < 0:
return "Read failed"
else:
return "%d.%d.%d.%d" % (addr1, addr2, addr3, addr4)
elif name == 'Hware_Hl2_EepromIPUse':
use = self.ReadEEPROM(0x06)
if use < 0:
return "Read failed"
if not use & 0b10000000:
return 'Ignore'
elif use & 0b100000:
return 'Use DHCP first'
else:
return 'Set address'
elif name == 'Hware_Hl2_EepromMAC':
addr1 = self.ReadEEPROM(0x0C)
addr2 = self.ReadEEPROM(0x0D)
if addr1 < 0 or addr2 < 0:
return "Read failed"
else:
return "0x%X 0x%X" % (addr1, addr2)
elif name == 'Hware_Hl2_EepromMACUse':
use = self.ReadEEPROM(0x06)
if use < 0:
return "Read failed"
if use & 0b1000000:
return 'Set address'
else:
return 'Ignore'
return "Name failed"
def SetValue(self, ctrl):
name = ctrl.quisk_data_name
value = ctrl.GetValue()
if name == 'Hware_Hl2_EepromIP':
try:
addr1, addr2, addr3, addr4 = value.split('.')
addr1 = int(addr1)
addr2 = int(addr2)
addr3 = int(addr3)
addr4 = int(addr4)
except:
pass
else:
self.WriteEEPROM(0x08, addr1)
self.WriteEEPROM(0x09, addr2)
self.WriteEEPROM(0x0A, addr3)
self.WriteEEPROM(0x0B, addr4)
elif name == 'Hware_Hl2_EepromIPUse':
use = self.ReadEEPROM(0x06)
if use >= 0:
self.eeprom_valid = use
if value == 'Ignore':
self.eeprom_valid &= ~0b0010000000
elif value == 'Use DHCP first':
self.eeprom_valid |= 0b0010100000
elif value == 'Set address':
self.eeprom_valid |= 0b0010000000
self.eeprom_valid &= ~0b0000100000
self.WriteEEPROM(0x06, self.eeprom_valid)
elif name == 'Hware_Hl2_EepromMAC':
try:
addr1, addr2 = value.split()
addr1 = int(addr1, base=0)
addr2 = int(addr2, base=0)
except:
pass
else:
self.WriteEEPROM(0x0C, addr1)
self.WriteEEPROM(0x0D, addr2)
elif name == 'Hware_Hl2_EepromMACUse':
use = self.ReadEEPROM(0x06)
if use >= 0:
self.eeprom_valid = use
if value == 'Ignore':
self.eeprom_valid &= ~0b0001000000
elif value == 'Set address':
self.eeprom_valid |= 0b0001000000
self.WriteEEPROM(0x06, self.eeprom_valid)
def GetControlByte(self, C0_index, byte_index):
# Get the control byte at C0 index and byte index. The bytes are C0, C1, C2, C3, C4.
# The C0 index is 0 to 16 inclusive. The byte index is 1 to 4. The byte index of C2 is 2.
return self.pc2hermes[C0_index * 4 + byte_index - 1]
def SetControlByte(self, C0_index, byte_index, value): # Set the control byte as above.
self.pc2hermes[C0_index * 4 + byte_index - 1] = value
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("SetControlByte C0_index %d byte_index %d to 0x%X" % (C0_index, byte_index, value))
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
if tx_freq and tx_freq > 0:
if source == 'BtnBand' or abs(tx_freq - self.tx_frequency) > 1000000:
self.ChangeAlexFilters(tx_freq=tx_freq)
self.tx_frequency = tx_freq
tx = int(tx_freq - self.transverter_offset)
self.pc2hermes[ 4] = tx >> 24 & 0xff # C0 index == 1, C1, C2, C3, C4: Tx freq, MSB in C1
self.pc2hermes[ 5] = tx >> 16 & 0xff
self.pc2hermes[ 6] = tx >> 8 & 0xff
self.pc2hermes[ 7] = tx & 0xff
if self.vfo_frequency != vfo_freq:
self.vfo_frequency = vfo_freq
vfo = int(vfo_freq - self.transverter_offset)
self.pc2hermes[ 8] = vfo >> 24 & 0xff # C0 index == 2, C1, C2, C3, C4: Rx freq, MSB in C1
self.pc2hermes[ 9] = vfo >> 16 & 0xff
self.pc2hermes[10] = vfo >> 8 & 0xff
self.pc2hermes[11] = vfo & 0xff
if DEBUG > 1: print("Change freq Tx", tx_freq, "Rx", vfo_freq)
QS.pc_to_hermes(self.pc2hermes)
return tx_freq, vfo_freq
def ChangeAlexFilters(self, tx_freq=None, edit=False):
if tx_freq is None:
tx_freq = self.tx_frequency
fmin = tx_freq
fmax = tx_freq
for pane in self.application.multi_rx_screen.receiver_list:
freq = pane.VFO + pane.txFreq
if freq < fmin:
fmin = freq
if freq > fmax:
fmax = freq
if DEBUG: print ('fmin', fmin, 'fmax', fmax)
if edit or not (self.alex_hpf_f1 <= fmin < self.alex_hpf_f2): # Within same HP filter?
self.alex_hpf_f1, self.alex_hpf_f2, rx, tx = self.FreqAlexFilters(fmin, self.conf.AlexHPF, self.conf.AlexHPF_TxEn)
if DEBUG: print ("Change HP filter fmin, f1, f2", fmin, self.alex_hpf_f1, self.alex_hpf_f2, "rx, tx", rx, tx)
QS.set_alex_hpf(rx, tx)
if edit or not (self.alex_lpf_f1 <= fmax < self.alex_lpf_f2): # Within same LP filter?
self.alex_lpf_f1, self.alex_lpf_f2, rx, tx = self.FreqAlexFilters(fmax, self.conf.AlexLPF, self.conf.AlexLPF_TxEn)
if DEBUG: print ("Change LP filter fmax, f1, f2", fmax, self.alex_lpf_f1, self.alex_lpf_f2, "rx, tx", rx, tx)
QS.set_alex_lpf(rx, tx)
def FreqAlexFilters(self, tx_freq, filt, enabl):
# Find the new frequency band
gap1 = 0.0 # If we are in a gap in the filter frequencies, this is f1 and f2 for the gap.
gap2 = 1E20
for f1, f2, rx, tx in filt: # f1 and f2 are strings in MHz
try:
f1 = float(f1) * 1E6
f2 = float(f2) * 1E6
except:
continue
if f1 >= f2:
continue
if f1 <= tx_freq < f2:
if not enabl:
tx = rx
return f1, f2, rx, tx
if tx_freq >= f2 and f2 > gap1:
gap1 = f2
if tx_freq <= f1 and f1 < gap2:
gap2 = f1
return gap1, gap2, 0, 0
def Freq2Phase(self, freq=None): # Return the phase increment as calculated by the FPGA
# This code attempts to duplicate the calculation of phase increment in the FPGA code.
clock = ((int(self.conf.rx_udp_clock) + 24000) // 48000) * 48000 # this assumes the nominal clock is a multiple of 48kHz
M2 = 2 ** 57 // clock
M3 = 2 ** 24
if freq is None:
freqcomp = int(self.vfo_frequency - self.transverter_offset) * M2 + M3
else:
freqcomp = int(freq) * M2 + M3
phase = (freqcomp // 2 ** 25) & 0xFFFFFFFF
return phase
def ReturnVfoFloat(self, freq=None): # Return the accurate VFO as a float
phase = self.Freq2Phase(freq)
freq = float(phase) * self.conf.rx_udp_clock / 2.0**32
return freq
def ReturnFrequency(self): # Return the current tuning and VFO frequency
return None, None # frequencies have not changed
def HeartBeat(self):
self.hermes_temperature, self.hermes_fwd_power, self.hermes_rev_power, self.hermes_pa_current = QS.get_hermes_TFRC()
if self.application.bottom_widgets:
self.application.bottom_widgets.UpdateText()
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
if offset is None: # Return True if frequency change is complete
if time.time() > self.repeater_time0 + self.repeater_delay:
return True
elif offset == 0: # Change back to the original frequency
if self.repeater_freq is not None:
self.repeater_time0 = time.time()
self.ChangeFrequency(self.repeater_freq, self.vfo_frequency, 'repeater')
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.repeater_time0 = time.time()
self.ChangeFrequency(self.tx_frequency + offset, self.vfo_frequency, 'repeater')
return False
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
BaseHardware.ChangeBand(self, band)
self.band = band
self.ChangeBandFilters()
self.SetTxLevel()
def ChangeBandFilters(self):
highest = self.band
freq = self.conf.BandEdge.get(highest, (0, 0))[0]
for pane in self.application.multi_rx_screen.receiver_list:
f = self.conf.BandEdge.get(pane.band, (0, 0))[0]
if freq < f:
freq = f
highest = pane.band
Rx = self.conf.Hermes_BandDict.get(highest, 0)
self.SetControlByte(0, 2, Rx << 1) # C0 index == 0, C2[7:1]: user output
if self.conf.Hermes_BandDictEnTx:
Tx = self.conf.Hermes_BandDictTx.get(self.band, 0) # Use Tx filter
else:
Tx = self.conf.Hermes_BandDict.get(self.band, 0) # Use Rx filter
QS.set_hermes_filters(Rx, Tx)
self.ChangeAlexFilters()
def ChangeMode(self, mode):
# mode is a string: "USB", "AM", etc.
BaseHardware.ChangeMode(self, mode)
self.mode = mode
self.SetTxLevel()
def OnButtonPTT(self, event):
btn = event.GetEventObject()
if btn.GetValue():
QS.set_PTT(1)
else:
QS.set_PTT(0)
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
pass
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.application.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
self.pc2hermes[0] = self.var_index # C0 index == 0, C1[1:0]: rate
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change sample rate to", rate)
return rate
def VarDecimRange(self):
return (48000, 384000)
## Hardware AGC is no longer supported in HL2 identifying as version >=40
def ChangeAGC(self, value):
if value:
self.pc2hermes[2] |= 0x10 # C0 index == 0, C3[4]: AGC enable
else:
self.pc2hermes[2] &= ~0x10
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change AGC to", value)
## Simpler LNA setting for HL2 identifying as version >=40, see HL2 wiki for details
def ChangeLNA(self, value):
# value is -12 to +48
if self.hermes_code_version < 40:
if value < 20:
self.pc2hermes[2] |= 0x08 # C0 index == 0, C3[3]: LNA +32 dB disable == 1
value = 19 - value
else:
self.pc2hermes[2] &= ~0x08 # C0 index == 0, C3[3]: LNA +32 dB enable == 0
value = 51 - value
else:
value = ((value+12) & 0x3f) | 0x40
self.pc2hermes[4 * 10 + 3] = value # C0 index == 0x1010, C4[4:0] LNA 0-32 dB gain
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change LNA to", value)
def SetTxLevel(self):
try:
tx_level = self.conf.tx_level[self.band]
except KeyError:
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
tx_level = int(tx_level *reduc/100.0)
if tx_level < 0:
tx_level = 0
elif tx_level > 255:
tx_level = 255
self.pc2hermes[4 * 9] = tx_level # C0 index == 0x1001, C1[7:0] Tx level
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print("Change tx_level to", tx_level)
def MultiRxCount(self, count): # count == number of additional receivers besides the Tx/Rx receiver: 1, 2, 3
# C0 index == 0, C4[5:3]: number of receivers 0b000 -> one receiver; C4[2] duplex on
self.pc2hermes[3] = 0x04 | count << 3
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print("Change MultiRx count to", count)
def MultiRxFrequency(self, index, vfo): # index of multi rx receiver: 0, 1, 2, ...
if DEBUG: print("Change MultiRx %d frequency to %d" % (index, vfo))
index = index * 4 + 12 # index does not include first Tx/Rx receiver in C0 index == 1, 2
self.pc2hermes[index ] = vfo >> 24 & 0xff
self.pc2hermes[index + 1] = vfo >> 16 & 0xff # C1, C2, C3, C4: Rx freq, MSB in C1
self.pc2hermes[index + 2] = vfo >> 8 & 0xff
self.pc2hermes[index + 3] = vfo & 0xff
QS.pc_to_hermes(self.pc2hermes)
def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False):
if vna_count is not None: # must be called first
self.vna_count = vna_count
if vna_start is None:
start = 0
stop = 0
else: # Set the start and stop frequencies and the frequency change for each point
# vna_start and vna_stop must be specified together
self.pc2hermes[ 4] = vna_start >> 24 & 0xff # C0 index == 1, C1, C2, C3, C4: Tx freq, MSB in C1
self.pc2hermes[ 5] = vna_start >> 16 & 0xff # used for vna starting frequency
self.pc2hermes[ 6] = vna_start >> 8 & 0xff
self.pc2hermes[ 7] = vna_start & 0xff
N = self.vna_count - 1
ph_start = self.Freq2Phase(vna_start) # Calculate using phases
ph_stop = self.Freq2Phase(vna_stop)
delta = (ph_stop - ph_start + N // 2) // N
delta = int(float(delta) * self.conf.rx_udp_clock / 2.0**32 + 0.5)
self.pc2hermes[ 8] = delta >> 24 & 0xff # C0 index == 2, C1, C2, C3, C4: Rx freq, MSB in C1
self.pc2hermes[ 9] = delta >> 16 & 0xff # used for the frequency to add for each point
self.pc2hermes[10] = delta >> 8 & 0xff
self.pc2hermes[11] = delta & 0xff
self.pc2hermes[4 * 9 + 2] = (self.vna_count >> 8) & 0xff # C0 index == 0b1001, C3
self.pc2hermes[4 * 9 + 3] = self.vna_count & 0xff # C0 index == 0b1001, C4
QS.pc_to_hermes(self.pc2hermes)
start = self.ReturnVfoFloat(vna_start)
phase = ph_start + self.Freq2Phase(delta) * N
stop = float(phase) * self.conf.rx_udp_clock / 2.0**32
start = int(start + 0.5)
stop = int(stop + 0.5)
if DEBUG: print ("Change VNA start", vna_start, start, "stop", vna_stop, stop, 'count', self.vna_count)
if key_down is None:
pass
elif key_down:
if not self.vna_started:
self.vna_started = True
self.SetControlByte(9, 2, 0x80) # turn on VNA mode
QS.set_PTT(1)
else:
QS.set_PTT(0)
return start, stop # Return actual frequencies after all phase rounding
def EnablePowerAmp(self, enable):
if enable:
self.pc2hermes[4 * 9 + 1] |= 0x08 # C0 index == 9, C2
else:
self.pc2hermes[4 * 9 + 1] &= ~0x08
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change PwrAmp 0x%X" % (self.pc2hermes[4*9 + 1]))
def SetLowPwrEnable(self, enable):
if enable:
self.pc2hermes[4 * 9 + 1] |= 0x04 # C0 index == 9, C2
else:
self.pc2hermes[4 * 9 + 1] &= ~0x04
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change LpPwrEnable 0x%X" % (self.pc2hermes[4*9 + 1]))
def DisableSyncFreq(self, value): # Thanks to Steve, KF7O
if value:
self.pc2hermes[4 * 0 + 2] |= 0x10 # C0 index == 0, C3
else:
self.pc2hermes[4 * 0 + 2] &= ~0x10
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change SyncFreq 0x%X" % (self.pc2hermes[4*0 + 2]))
def EnableBiasChange(self, enable):
# Bias settings are in location 12, 13, 14, 15, and are not sent unless C1 == 0x06
if enable:
for base in (12, 13, 14, 15):
self.pc2hermes[4 * base] = 0x06 # C1
self.pc2hermes[4 * base + 1] = 0xA8 # C2
self.pc2hermes[4 * 12 + 2] = 0x00 # C3 bias 1, volitile
self.pc2hermes[4 * 13 + 2] = 0x20 # C3 bias 1, non-volitile
self.pc2hermes[4 * 14 + 2] = 0x10 # C3 bias 2, volitile
self.pc2hermes[4 * 15 + 2] = 0x30 # C3 bias 2, non-volitile
else:
for base in (12, 13, 14, 15):
self.pc2hermes[4 * base] = 0x00 # C1
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Enable bias change", enable)
## Bias is 0 indexed to match schematic
## Changes for HermesLite v2 thanks to Steve, KF7O
def ChangeBias0(self, value):
if self.hermes_code_version >= 60:
i2caddr,value = 0xac,(value%256)
else:
i2caddr,value = 0xa8,(255-(value%256))
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x06,i2caddr,0x00,value
self.WriteQueue(1)
if DEBUG: print ("Change bias 0", value)
def ChangeBias1(self, value):
if self.hermes_code_version >= 60:
i2caddr,value = 0xac,(value%256)
else:
i2caddr,value = 0xa8,(255-(value%256))
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x06,i2caddr,0x10,value
self.WriteQueue(1)
if DEBUG: print ("Change bias 1", value)
def WriteBias(self, value0, value1):
if self.hermes_code_version >= 60:
i2caddr,value0 = 0xac,(value0%256)
else:
i2caddr,value0 = 0xa8,(255-(value0%256))
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x06,i2caddr,0x20,value0
self.WriteQueue(1)
## Wait >10ms as that is the longest EEPROM write cycle time
time.sleep(0.015)
value1 = (value1%256) if self.hermes_code_version >= 60 else (255-(value1%256))
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x06,i2caddr,0x30,value1
self.WriteQueue(1)
## Double write bias to EEPROM
time.sleep(0.030)
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x06,i2caddr,0x30,value1
self.WriteQueue(1)
time.sleep(0.015)
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x06,i2caddr,0x20,value0
self.WriteQueue(1)
if DEBUG: print ("Write bias", value0, value1)
def WriteQueue(self,qlen):
## Make sure last write(s) went through
dt = 0.005 ## 5 ms initial delay
while QS.get_hermeslite_writepointer() > 0:
time.sleep(dt)
dt *= 2
if dt > 0.300:
print("ERROR: Hermes-Lite write queue timeout")
return
## Send next write(s)
QS.pc_to_hermeslite_writequeue(self.pc2hermeslitewritequeue)
QS.set_hermeslite_writepointer(qlen)
if DEBUG:
if dt > 0.005: print("Final dt in Hermes-Lite write queue was",dt)
## In HL2 firmware identifying as version >=40, AD9866 access is available
## See AD9866 datasheet for details, some examples:
## self.writeAD9866(0x08,0xff) ## Set LPF target frequency
## self.WriteAD9866(0x07,0x01) ## Enable RX LPF, RX to high power usage
## self.WriteAD9866(0x07,0x00) ## Disable RX LPF, RX to high power usage
## self.WriteAD9866(0x0e,0x81) ## Low digital drive strength
## self.WriteAD9866(0x0e,0x01) ## High digital drive strength
## Set RX bias to default levels
## cpga = 0
## spga = 0
## adcb = 0
## self.WriteAD9866(0x13,((cpga & 0x07) << 5) | ((spga & 0x03) << 3) | (adcb & 0x07))
def WriteAD9866(self,addr,data):
addr = addr & 0x01f
data = data & 0x0ff
self.pc2hermeslitewritequeue[0:5] = 0x7b,0x06,addr,0x00,data
self.WriteQueue(1)
if DEBUG: print ("Write AD9866 addr={0:06x} data={1:06x}".format(addr,data))
def MakePowerCalibration(self):
# Use spline interpolation to convert the ADC power sensor value to power in watts
name = self.conf.power_meter_calib_name
try: # look in config file
table = self.conf.power_meter_std_calibrations[name]
except:
try: # look in local name space
table = self.application.local_conf.GetRadioDict().get('power_meter_local_calibrations', {})[name]
except: # not found
self.power_interpolator = None
return
if len(table) < 3:
self.power_interpolator = None
return
table.sort()
if table[0][0] > 0: # Add zero code at zero power
table.insert(0, [0, 0.0])
# fill out the table to the maximum code 4095
l = len(table) - 1
x = table[l][0] * 1.1 # voltage increase
y = table[l][1] * 1.1**2 # square law power increase
while 1:
table.append([x, y])
if x > 4095:
break
x *= 1.1
y *= 1.1**2
self.power_interpolator = quisk_utils.SplineInterpolator(table)
def InterpolatePower(self, x):
if not self.power_interpolator:
return 0.0
y = self.power_interpolator.Interpolate(x)
if y < 0.0:
y = 0.0
return y
def VersaOut2(self, divisor): # Use the VersaClock output 2 with a floating point divisor
div = int(divisor * 2**24 + 0.1)
intgr = div >> 24
frac = (div & 0xFFFFFF) << 2
self.WriteVersa5(0x62,0x3b) # Clock2 CMOS1 output, 3.3V
self.WriteVersa5(0x2c,0x00) # Disable aux output on clock 1
self.WriteVersa5(0x31,0x81) # Use divider for clock2
# Integer portion
self.WriteVersa5(0x3d, intgr >> 4)
self.WriteVersa5(0x3e, intgr << 4)
# Fractional portion
self.WriteVersa5(0x32,frac >> 24) # [29:22]
self.WriteVersa5(0x33,frac >> 16) # [21:14]
self.WriteVersa5(0x34,frac >> 8) # [13:6]
self.WriteVersa5(0x35,(frac & 0xFF)<<2) # [5:0] and disable ss
self.WriteVersa5(0x63,0x01) # Enable clock2
# Thanks to Steve Haynal for VersaClock code:
def WriteVersa5(self,addr,data):
data = data & 0x0ff
addr = addr & 0x0ff
## i2caddr is 7 bits, no read write
## Bit 8 is set to indicate stop to HL2
## i2caddr = 0x80 | (0xd4 >> 1) ## ea
self.pc2hermeslitewritequeue[0:5] = 0x7c,0x06,0xea,addr,data
self.WriteQueue(1)
def EnableCL2_sync76p8MHz(self):
self.WriteVersa5(0x62,0x3b) ## Clock2 CMOS1 output, 3.3V
self.WriteVersa5(0x2c,0x01) ## Enable aux output on clock 1
self.WriteVersa5(0x31,0x0c) ## Use clock1 aux output as input for clock2
self.WriteVersa5(0x63,0x01) ## Enable clock2
def EnableCL2_61p44MHz(self):
self.WriteVersa5(0x62,0x3b) ## Clock2 CMOS1 output, 3.3V
self.WriteVersa5(0x2c,0x00) ## Disable aux output on clock 1
self.WriteVersa5(0x31,0x81) ## Use divider for clock2
## VCO multiplier is shared for all outputs, set to 68 by firmware
## VCO = 38.4*68 = 2611.2 MHz
## There is a hardwired divide by 2 in the Versa 5 at the VCO output
## VCO to Dividers = 2611.2 MHZ/2 = 1305.6
## Target frequency of 61.44 requires dividers of 1305.6/61.44 = 21.25
## Frational dividers are supported
## Set integer portion of divider 21 = 0x15, 12 bits split across 2 registers
self.WriteVersa5(0x3d,0x01)
self.WriteVersa5(0x3e,0x50)
## Set fractional portion, 30 bits, 2**24 * .25 = 0x400000
self.WriteVersa5(0x32,0x01) ## [29:22]
self.WriteVersa5(0x33,0x00) ## [21:14]
self.WriteVersa5(0x34,0x00) ## [13:6]
self.WriteVersa5(0x35,0x00) ## [5:0] and disable ss
self.WriteVersa5(0x63,0x01) ## Enable clock2
def WriteEEPROM(self, addr, value):
## Write values into the MCP4662 EEPROM registers
## For example, to set a fixed IP of 192.168.33.20
## hw.WriteEEPROM(8,192)
## hw.WriteEEPROM(9,168)
## hw.WriteEEPROM(10,33)
## hw.WriteEEPROM(11,20)
## To set the last two values of the MAC to 55:66
## hw.WriteEEPROM(12,55)
## hw.WriteEEPROM(13,66)
## To enable the fixed IP and alternate MAC, and favor DHCP
## hw.WriteEEPROM(6, 0x80 | 0x40 | 0x20)
## See https://github.com/softerhardware/Hermes-Lite2/wiki/Protocol
if self.hermes_code_version >= 60:
i2caddr,value = 0xac,(value%256)
else:
i2caddr,value = 0xa8,(255-(value%256))
addr = (addr << 4)%256
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x06,i2caddr,addr,value
self.WriteQueue(1)
if DEBUG: print ("Write EEPROM", addr, value)
def ReadEEPROM(self, addr):
## To read the bias settings for bias0 and bias1
## hw.ReadEEPROM(2)
## hw.ReadEEPROM(3)
if self.hermes_code_version >= 60:
i2caddr = 0xac
else:
i2caddr = 0xa8
faddr = ((addr << 4)%256) | 0xc
QS.clear_hermeslite_response()
self.pc2hermeslitewritequeue[0:5] = 0x7d,0x07,i2caddr,faddr,0
self.WriteQueue(1)
for j in range(50):
time.sleep(0.001)
resp = QS.get_hermeslite_response()
##print("RESP:",j,resp[0],resp[1],resp[2],resp[3],resp[4])
if resp[0] != 0: break
if resp[0] == 0:
if DEBUG: print("EEPROM read did not return a value")
return -1
else:
## MCP4662 does not autoincrement when reading 8 bytes
## MCP4662 stores 9 bit values, msb came first and is in lower order byte
v0 = (resp[4] << 8) | resp[3]
v1 = (resp[2] << 8) | resp[1]
if (resp[0] >> 1) != 0x7d:
## Response mismatch
if DEBUG: print("EEPROM read response mismatch",resp[0] >> 1)
return -1
elif v0 != v1:
if DEBUG: print("EEPROM read values do not agree",v0,v1)
return -1
else:
if DEBUG: print("EEPROM read {0:#x} from address {1:#x}".format(v0,addr))
return v0
def ProgramGateware(self, event): # Program the Gateware (FPGA firmware) over Ethernet
title = "Program the Gateware"
main_frame = self.application.main_frame
dlg = wx.FileDialog(main_frame, message='Choose an RBF file for programming the Gateware',
style=wx.FD_OPEN, wildcard="RBF files (*.rbf)|*.rbf")
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
else:
dlg.Destroy()
return
timeout = 0.2 # socket timeout in seconds
erase_time = 50 # in units of timeout
hermes_ip = self.hermes_ip
hermes_mac = self.hermes_mac
if not hermes_ip:
msg = wx.MessageDialog(main_frame, "No Hermes hardware was found.", title, wx.OK|wx.ICON_ERROR)
msg.ShowModal()
msg.Destroy()
return
try:
fp = open(path, "rb")
size = os.stat(path).st_size
except:
msg = wx.MessageDialog(main_frame, "Can not read the RBF file specified.", title, wx.OK|wx.ICON_ERROR)
msg.ShowModal()
msg.Destroy()
return
for i in range(10):
state = QS.set_params(hermes_pause=1)
#print ("state", state)
if state == 23:
break
else:
time.sleep(0.05)
else:
msg = wx.MessageDialog(main_frame, "Failure to find a running Hermes and stop the samples.", title, wx.OK|wx.ICON_ERROR)
msg.ShowModal()
msg.Destroy()
fp.close()
return
blocks = (size + 255) // 256
dlg = wx.ProgressDialog(title, "Erase old program...", blocks + 1, main_frame, wx.PD_APP_MODAL)
program_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
program_socket.settimeout(timeout)
port = self.conf.rx_udp_port
program_socket.connect((hermes_ip, port))
cmd = bytearray(64) # Erase command
cmd[0] = 0xEF
cmd[1] = 0xFE
cmd[2] = 0x03
cmd[3] = 0x02
program_socket.send(cmd)
success = False
for i in range(erase_time):
dlg.Update(i * blocks // erase_time)
try:
reply = program_socket.recv(1500)
except socket.timeout:
pass
else:
reply = bytearray(reply)
if reply[0:3] == bytearray(b"\xEF\xFE\03") and reply[3:9] == hermes_mac:
success = True
break
if not success:
dlg.Destroy()
self.application.Yield()
fp.close()
msg = wx.MessageDialog(main_frame, "Failure to erase the old program. Please push the Program button again.", title, wx.OK|wx.ICON_ERROR)
msg.ShowModal()
msg.Destroy()
program_socket.close()
return
dlg.Update(0, "Programming...")
cmd = bytearray(8)
cmd[0] = 0xEF
cmd[1] = 0xFE
cmd[2] = 0x03
cmd[3] = 0x01
cmd[4] = (blocks >> 24) & 0xFF
cmd[5] = (blocks >> 16) & 0xFF
cmd[6] = (blocks >> 8) & 0xFF
cmd[7] = (blocks ) & 0xFF
for block in range(blocks):
dlg.Update(block)
prog = fp.read(256)
if block == blocks - 1: # last block may have an odd number of bytes
prog = prog + bytearray(b"\xFF" * (256 - len(prog)))
if len(prog) != 256:
print ("read wrong number of bytes for block", block)
success = False
break
try:
program_socket.send(cmd + prog)
reply = program_socket.recv(1500)
except socket.timeout:
print ("Socket timeout while programming block", block)
success = False
break
else:
reply = bytearray(reply)
if reply[0:3] != bytearray(b"\xEF\xFE\04") or reply[3:9] != hermes_mac:
print ("Program failed at block", block)
success = False
break
fp.close()
for i in range(10): # throw away extra packets
try:
program_socket.recv(1500)
except socket.timeout:
break
if success:
dlg.Update(0, "Waiting for Hermes to start...")
wait_secs = 15 # number of seconds to wait for restart
cmd = bytearray(63) # Discover
cmd[0] = 0xEF
cmd[1] = 0xFE
cmd[2] = 0x02
program_socket.settimeout(1.0)
for i in range(wait_secs):
dlg.Update(i * blocks // wait_secs)
if i < 5:
time.sleep(1.0)
continue
program_socket.send(cmd)
try:
reply = program_socket.recv(1500)
except socket.timeout:
pass
else:
reply = bytearray(reply)
#print ("0x%X 0x%X %d 0x%X 0x%X 0x%X 0x%X 0x%X 0x%X %d %d" % tuple(reply[0:11]))
if reply[0] == 0xEF and reply[1] == 0xFE and reply[10] == 6:
self.hermes_mac = reply[3:9]
self.hermes_code_version = reply[9]
st = 'Capture from Hermes device: Mac %2x:%2x:%2x:%2x:%2x:%2x, Code version %d, ID %d' % tuple(reply[3:11])
st += ', IP %s' % self.hermes_ip
self.config_text = st
#print (st)
self.application.config_text = st
self.application.main_frame.SetConfigText(st)
QS.set_params(hermes_pause=0)
break
dlg.Destroy()
self.application.Yield()
else:
dlg.Destroy()
self.application.Yield()
msg = wx.MessageDialog(main_frame, "Programming failed. Please push the Program button again.", title, wx.OK|wx.ICON_ERROR)
msg.ShowModal()
msg.Destroy()
program_socket.close()

157
hermes/quisk_widgets.py Executable file
View File

@ -0,0 +1,157 @@
# 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
b = app.QuiskCheckbutton(frame, self.OnAGC, 'RfAgc')
if hardware.hermes_code_version >= 40:
b.Enable(False)
gbs.Add(b, (start_row, self.start_col), (1, 2), flag=wx.EXPAND)
bw, bh = b.GetMinSize()
init = self.config.hermes_LNA_dB
sl = app.SliderBoxHH(frame, 'RfLna %d dB', init, -12, 48, self.OnLNA, True)
hardware.ChangeLNA(init)
gbs.Add(sl, (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_LEFT|wx.ALIGN_CENTER_VERTICAL)
szr.Add(b, 0, flag=wx.ALIGN_RIGHT|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:
self.text_temperature = app.QuiskText(frame, '', bh)
self.text_pa_current = app.QuiskText(frame, '', bh)
self.text_fwd_power = app.QuiskText(frame, '', bh)
self.text_swr = app.QuiskText(frame, '', bh)
gbs.Add(self.text_temperature, (start_row, self.start_col + 10), (1, 2), flag=wx.EXPAND)
gbs.Add(self.text_pa_current, (start_row, self.start_col + 12), (1, 2), flag=wx.EXPAND)
gbs.Add(self.text_fwd_power, (start_row, self.start_col + 15), (1, 2), flag=wx.EXPAND)
gbs.Add(self.text_swr, (start_row, self.start_col + 17), (1, 2), flag=wx.EXPAND)
def OnAGC(self, event):
btn = event.GetEventObject()
value = btn.GetValue()
self.hardware.ChangeAGC(value)
def OnLNA(self, event):
sl = event.GetEventObject()
value = sl.GetValue()
self.hardware.ChangeLNA(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): # 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.hermes_fwd_power
fwd = self.hardware.InterpolatePower(fwd)
rev = self.hardware.hermes_rev_power
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 power
fwd, rev = self.Code2FwdRevWatts()
# forward less reverse power
power = fwd - rev
if power < 0.0:
power = 0.0
text = " PA %3.1f watts" % power
self.text_fwd_power.SetLabel(text)
# SWR
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)
def OnTextDataMenu(self, event):
btn = event.GetEventObject()
btn.PopupMenu(self.text_data_menu, (0,0))
def OnDataTemperature(self, event):
self.data_sizer.Replace(self.text_data, self.text_temperature)
self.text_data.Hide()
self.text_data = self.text_temperature
self.text_data.Show()
self.data_sizer.Layout()
def OnDataPaCurrent(self, event):
self.data_sizer.Replace(self.text_data, self.text_pa_current)
self.text_data.Hide()
self.text_data = self.text_pa_current
self.text_data.Show()
self.data_sizer.Layout()
def OnDataFwdPower(self, event):
self.data_sizer.Replace(self.text_data, self.text_fwd_power)
self.text_data.Hide()
self.text_data = self.text_fwd_power
self.text_data.Show()
self.data_sizer.Layout()
def OnDataSwr(self, event):
self.data_sizer.Replace(self.text_data, self.text_swr)
self.text_data.Hide()
self.text_data = self.text_swr
self.text_data.Show()
self.data_sizer.Layout()

1
hiqsdr/__init__.py Executable file
View File

@ -0,0 +1 @@
#

33
hiqsdr/quisk_conf.py Executable file
View File

@ -0,0 +1,33 @@
# This is a sample config file for the N2ADR 2010 transceiver hardware and for the
# improved version HiQSDR. If you use the HiQSDR you should upgrade your firmware
# to version 1.1.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from hiqsdr import quisk_hardware
add_imd_button = 1
add_fdx_button = 1
latency_millisecs = 50
# tx_level = {None:120, '60':52} # Use this to change your transmit level.
#use_rx_udp = 1 # Use this for the N2ADR 2010 hardware
use_rx_udp = 2 # Use this for the HiQSDR
rx_udp_ip = "192.168.2.196" # Sample source IP address
rx_udp_port = 0xBC77 # Sample source UDP port
rx_udp_clock = 122880000 # ADC sample rate in Hertz
rx_udp_decimation = 8 * 8 * 8 # Decimation from clock to UDP sample rate
sample_rate = int(float(rx_udp_clock) / rx_udp_decimation + 0.5) # Don't change this
name_of_sound_capt = "" # We do not capture from the soundcard
name_of_sound_play = "hw:0"
data_poll_usec = 10000
playback_rate = 48000
microphone_name = "hw:1"
tx_ip = rx_udp_ip
key_method = "" # Use internal method
tx_audio_port = 0xBC79
mic_out_volume = 1.0

426
hiqsdr/quisk_hardware.py Executable file
View File

@ -0,0 +1,426 @@
# This is a sample hardware file for UDP control. Use this file for my 2010 transceiver
# described in QEX and for the improved version HiQSDR. To turn on the extended
# features in HiQSDR, update your FPGA firmware to version 1.1 or later and use use_rx_udp = 2.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import struct, socket, math, traceback
import _quisk as QS
from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 0
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.got_udp_status = '' # status from UDP receiver
# want_udp_status is a 14-byte string with numbers in little-endian order:
# [0:2] 'St'
# [2:6] Rx tune phase
# [6:10] Tx tune phase
# [10] Tx output level 0 to 255
# [11] Tx control bits:
# 0x01 Enable CW transmit
# 0x02 Enable all other transmit
# 0x04 Use the HiQSDR extended IO pins not present in the 2010 QEX ver 1.0
# 0x08 The key is down (software key)
# bits 5 and 4: Transmit sample rate
# 0b00 48k
# 0b01 192k
# 0b10 480k
# 0b11 8k
# 0x40 odyssey: Spot button is in use
# 0x80 odyssey: Mic Boost 20dB
# [12] Rx control bits
# bits 5 through 0
# Second stage decimation less one, 1-39, six bits
# bits 7, 6
# 0b00 Prescaler 8, 3-byte samples I and Q; 1440 / 6 = 240 samples per UDP packet
# 0b01 Prescaler 2, 2-byte samples
# 0b10 Prescaler 40, 3-byte samples
# 0b11 Prescaler 2, 1-byte samples
# [13] zero or firmware version number
# The above is used for firmware version 1.0.
# Version 1.1 adds eight more bytes for the HiQSDR conntrol ports:
# [14] X1 connector: Preselect pins 69, 68, 65, 64; Preamp pin 63, Tx LED pin 57
# [15] Attenuator pins 84, 83, 82, 81, 80
# [16] More bits: AntSwitch pin 41 is 0x01
# [17:22] The remaining five bytes are sent as zero.
# Version 1.2 uses the same format as 1.1, but adds the "Qs" command (see below).
# Version 1.3 adds features needed by the new quisk_vna.py program:
# [17] The sidetone volume 0 to 255
# [18:20] This is vna_count, the number of VNA data points; or zero for normal operation
# [20] The CW delay as specified in the config file
# [21] Control bits:
# 0x01 Switch on tx mirror on rx for adaptive predistortion
# [22:24] Noise blanker level
# The "Qs" command is a two-byte UDP packet sent to the control port. It returns the hardware status
# as the above string, except that the string starts with "Qs" instead of "St". Do not send the "Qs" command
# from Quisk, as it interferes with the "St" command. The "Qs" command is meant to be used from an
# external program, such as HamLib or a logging program.
# When vna_count != 0, we are in VNA mode. The start frequency is rx_phase, and for each point tx_phase is added
# to advance the frequency. A zero sample is added to mark the blocks. The samples are I and Q averaged at DC.
self.rx_phase = 0
self.tx_phase = 0
self.tx_level = 0
self.tx_control = 0
self.rx_control = 0
QS.set_sample_bytes(3)
self.vna_count = 0 # VNA scan count; MUST be zero for non-VNA operation
self.cw_delay = conf.cw_delay
self.index = 0
self.mode = None
self.usingSpot = False
self.band = None
self.rf_gain = 0
self.sidetone_volume = 0 # sidetone volume 0 to 255
self.repeater_freq = None # original repeater output frequency
self.HiQSDR_Connector_X1 = 0
self.HiQSDR_Attenuator = 0
self.HiQSDR_Bits = 0
try:
if conf.radio_sound_mic_boost:
self.tx_control = 0x80
except:
pass
if conf.use_rx_udp == 2: # Set to 2 for the HiQSDR
self.rf_gain_labels = ('RF 0 dB', 'RF +10', 'RF -10', 'RF -20', 'RF -30')
self.antenna_labels = ('Ant 1', 'Ant 2')
self.firmware_version = None # firmware version is initially unknown
self.rx_udp_socket = None
self.vfo_frequency = 0 # current vfo frequency
self.tx_frequency = 0
self.decimations = [] # supported decimation rates
for dec in (40, 20, 10, 8, 5, 4, 2):
self.decimations.append(dec * 64)
self.decimations.append(80)
self.decimations.append(64)
if self.conf.fft_size_multiplier == 0:
self.conf.fft_size_multiplier = 6 # Set size needed by VarDecim
def open(self):
# Create the proper broadcast address for rx_udp_ip.
nm = self.conf.rx_udp_ip_netmask.split('.')
ip = self.conf.rx_udp_ip.split('.')
nm = list(map(int, nm))
ip = list(map(int, ip))
bc = ''
for i in range(4):
x = (ip[i] | ~ nm[i]) & 0xFF
bc = bc + str(x) + '.'
self.broadcast_addr = bc[:-1]
# This socket is used for the Simple Network Discovery Protocol by AE4JY
self.socket_sndp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket_sndp.setblocking(0)
self.socket_sndp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.sndp_request = bytearray(56)
self.sndp_request[0] = 56
self.sndp_request[1] = 0
self.sndp_request[2] = 0x5A
self.sndp_request[3] = 0xA5
self.sndp_active = self.conf.sndp_active
# conf.rx_udp_port is used for returning ADC samples
# conf.rx_udp_port + 1 is used for control
self.rx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rx_udp_socket.setblocking(0)
self.rx_udp_socket.connect((self.conf.rx_udp_ip, self.conf.rx_udp_port + 1))
return QS.open_rx_udp(self.conf.rx_udp_ip, self.conf.rx_udp_port)
def close(self):
if self.rx_udp_socket:
self.rx_udp_socket.close()
self.rx_udp_socket = None
def ReturnFrequency(self): # Return the current tuning and VFO frequency
return None, None # frequencies have not changed
def ReturnVfoFloat(self, freq=None): # Return the accurate VFO as a float
if freq is None:
rx_phase = self.rx_phase
else:
rx_phase = int(float(freq) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
return float(rx_phase) * self.conf.rx_udp_clock / 2.0**32
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
if vfo_freq != self.vfo_frequency:
self.vfo_frequency = vfo_freq
self.rx_phase = int(float(vfo_freq - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
if tx_freq and tx_freq > 0:
self.tx_frequency = tx_freq
self.tx_phase = int(float(tx_freq - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.NewUdpStatus()
return tx_freq, vfo_freq
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
if offset is None: # Return True if frequency change is complete
self.HeartBeat()
return self.want_udp_status == self.got_udp_status
if offset == 0: # Change back to the original frequency
if self.repeater_freq is None: # Frequency was already reset
return self.want_udp_status == self.got_udp_status
self.tx_frequency = self.repeater_freq
self.repeater_freq = None
else: # Shift to repeater input frequency
self.repeater_freq = self.tx_frequency
offset = int(offset * 1000) # Convert kHz to Hz
self.tx_frequency += offset
self.tx_phase = int(float(self.tx_frequency - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.NewUdpStatus(True)
return False
def ChangeMode(self, mode):
# mode is a string: "USB", "AM", etc.
self.mode = mode
self.tx_control &= ~0x03 # Erase last two bits
if self.vna_count:
pass
elif self.usingSpot:
self.tx_control |= 0x02
elif mode in ("CWL", "CWU"):
self.tx_control |= 0x01
else:
self.tx_control |= 0x02
self.SetTxLevel()
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
BaseHardware.ChangeBand(self, band)
self.band = band
self.HiQSDR_Connector_X1 &= ~0x0F # Mask in the last four bits
self.HiQSDR_Connector_X1 |= self.conf.HiQSDR_BandDict.get(band, 0) & 0x0F
self.SetTxLevel()
def SetTxLevel(self):
# As tx_level varies from 50 to 200, the output level changes from 263 to 752 mV
# So 0 to 255 is 100 to 931, or 1.0 to 9.31; v = 1.0 + 0.0326 * level
if not self.vna_count:
try:
self.tx_level = self.conf.tx_level[self.band]
except KeyError:
self.tx_level = self.conf.tx_level.get(None, 127) # The default
if self.mode[0:3] in ('DGT', 'FDV'): # Digital modes; change power by a percentage
reduc = self.application.digital_tx_level
else:
reduc = self.application.tx_level
level = 1.0 + self.tx_level * 0.0326
level *= math.sqrt(reduc / 100.0) # Convert from a power to an amplitude
self.tx_level = int((level - 1.0) / 0.0326 + 0.5)
if self.tx_level < 0:
self.tx_level = 0
elif self.tx_level > 255:
self.tx_level = 255
self.NewUdpStatus()
def OnButtonRfGain(self, event):
# The HiQSDR attenuator is five bits: 2, 4, 8, 10, 20 dB
btn = event.GetEventObject()
n = btn.index
self.HiQSDR_Connector_X1 &= ~0x10 # Mask in the preamp bit
if n == 0: # 0dB
self.HiQSDR_Attenuator = 0
self.rf_gain = 0
elif n == 1: # +10
self.HiQSDR_Attenuator = 0
self.HiQSDR_Connector_X1 |= 0x10
self.rf_gain = 10
elif n == 2: # -10
self.HiQSDR_Attenuator = 0x08
self.rf_gain = -10
elif n == 3: # -20
self.HiQSDR_Attenuator = 0x10
self.rf_gain = -20
elif n == 4: # -30
self.HiQSDR_Attenuator = 0x18
self.rf_gain = -30
else:
self.HiQSDR_Attenuator = 0
self.rf_gain = 0
print ('Unknown RfGain')
self.NewUdpStatus()
def OnButtonPTT(self, event):
# This feature requires firmware version 1.1 or higher
if self.firmware_version:
btn = event.GetEventObject()
if btn.GetValue(): # Turn the software key bit on or off
self.tx_control |= 0x08
else:
self.tx_control &= ~0x08
self.NewUdpStatus(True) # Prompt update for PTT
def OnButtonAntenna(self, event):
# This feature requires extended IO
btn = event.GetEventObject()
if btn.index:
self.HiQSDR_Bits |= 0x01
else:
self.HiQSDR_Bits &= ~0x01
self.NewUdpStatus()
def ChangeSidetone(self, value): # The sidetone volume changed
self.sidetone_volume = int(value * 255.1) # Change 0.0-1.0 to 0-255
self.NewUdpStatus()
def HeartBeat(self):
if self.sndp_active: # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address
try:
if DEBUG: print("Sndp send")
self.socket_sndp.sendto(self.sndp_request, (self.broadcast_addr, 48321))
data, ffrom = self.socket_sndp.recvfrom(1024)
if DEBUG: print("Sndp From", ffrom, "Data", repr(data))
except:
# traceback.print_exc()
pass
else:
data = bytearray(data)
if len(data) == 56 and data[5:14] == bytearray(b'HiQSDR-v1'):
ip = self.conf.rx_udp_ip.split('.')
t = data[0:4]
t.append(2)
t += data[5:37]
t.append(int(ip[3]))
t.append(int(ip[2]))
t.append(int(ip[1]))
t.append(int(ip[0]))
t += bytearray(12)
t.append(self.conf.rx_udp_port & 0xFF)
t.append(self.conf.rx_udp_port >> 8)
t.append(0)
if DEBUG: print("Sndp reply", repr(t))
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
try: # receive the old status if any
data = self.rx_udp_socket.recv(1024)
if DEBUG:
self.PrintStatus(' got ', data)
except:
pass
else:
data = bytearray(data)
if data[0:2] == b'St':
self.got_udp_status = data
if self.firmware_version is None: # get the firmware version
if self.want_udp_status[0:13] != self.got_udp_status[0:13]:
try:
self.rx_udp_socket.send(self.want_udp_status)
if DEBUG:
self.PrintStatus('Start', self.want_udp_status)
except:
pass
else: # We got a correct response.
self.firmware_version = self.got_udp_status[13] # Firmware version is returned here
if DEBUG:
print ('Got version', self.firmware_version)
if self.firmware_version > 0 and self.conf.use_rx_udp == 2:
self.tx_control |= 0x04 # Use extra control bytes
self.sndp_active = False
self.NewUdpStatus()
else:
if self.want_udp_status != self.got_udp_status:
if DEBUG:
self.PrintStatus('Have ', self.got_udp_status)
self.PrintStatus(' send', self.want_udp_status)
try:
self.rx_udp_socket.send(self.want_udp_status)
except:
pass
elif DEBUG:
self.rx_udp_socket.send(b'Qs')
def PrintStatus(self, msg, data):
print (msg, ' ', end=' ')
print (data[0:2], end=' ')
for c in data[2:]:
print ("%2X" % c, end=' ')
print ()
def GetFirmwareVersion(self):
return self.firmware_version
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
# The Spot button sets the mode to SSB-equivalent for CW so that the Spot level works.
if level >= 0 and not self.usingSpot: # Spot was turned on
self.usingSpot = True
self.tx_control |= 0x40
self.ChangeMode(self.mode)
elif level < 0 and self.usingSpot: # Spot was turned off
self.usingSpot = False
self.tx_control &= ~0x40
self.ChangeMode(self.mode)
def OnBtnFDX(self, is_fdx): # Status of FDX button, 0 or 1
if is_fdx:
self.HiQSDR_Connector_X1 |= 0x20 # Mask in the FDX bit
else:
self.HiQSDR_Connector_X1 &= ~0x20
self.NewUdpStatus()
def VarDecimGetChoices(self): # return text labels for the control
clock = self.conf.rx_udp_clock
l = [] # a list of sample rates
for dec in self.decimations:
l.append(str(int(float(clock) / dec / 1e3 + 0.5)))
return l
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate ksps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimSet(self, index=None): # set decimation, return sample rate
if index is None: # initial call to set decimation before the call to open()
rate = self.application.vardecim_set # May be None or from different hardware
try:
dec = int(float(self.conf.rx_udp_clock // rate + 0.5))
self.index = self.decimations.index(dec)
except:
try:
self.index = self.decimations.index(self.conf.rx_udp_decimation)
except:
self.index = 0
else:
self.index = index
dec = self.decimations[self.index]
if dec >= 128:
self.rx_control = dec // 64 - 1 # Second stage decimation less one
QS.set_sample_bytes(3)
else:
self.rx_control = dec // 16 - 1 # Second stage decimation less one
self.rx_control |= 0b01000000 # Change prescaler to 2 (instead of 8)
QS.set_sample_bytes(2)
self.NewUdpStatus()
return int(float(self.conf.rx_udp_clock) / dec + 0.5)
def VarDecimRange(self):
return (48000, 960000)
def NewUdpStatus(self, do_tx=False):
s = bytearray(b'St')
s = s + struct.pack("<L", self.rx_phase)
s = s + struct.pack("<L", self.tx_phase)
s.append(self.tx_level & 0xFF)
s.append(self.tx_control & 0xFF)
s.append(self.rx_control & 0xFF)
if self.firmware_version: # Add the version
s.append(self.firmware_version & 0xFF) # The firmware version will be returned
if self.tx_control & 0x04: # Use extra HiQSDR control bytes
s.append(self.HiQSDR_Connector_X1 & 0xFF)
s.append(self.HiQSDR_Attenuator & 0xFF)
s.append(self.HiQSDR_Bits & 0xFF)
else:
s += bytearray(3)
s.append(self.sidetone_volume & 0xFF)
s = s + struct.pack("<H", self.vna_count)
s.append(self.cw_delay & 0xFF)
s.append(0)
else: # firmware version 0 or None
s.append(0) # assume version 0
self.want_udp_status = s
if do_tx:
try:
self.rx_udp_socket.send(s)
except:
pass
def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False):
if key_down is None:
pass
elif key_down:
self.tx_control |= 0x08
else:
self.tx_control &= ~0x08
if vna_count is not None:
self.vna_count = vna_count # Number of scan points
if vna_start is not None: # Set the start and stop frequencies. The tx_phase is the frequency delta.
self.rx_phase = int(float(vna_start) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.tx_phase = int(float(vna_stop - vna_start) / (self.vna_count - 1) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF
self.tx_control &= ~0x03 # Erase last two bits
self.rx_control = 40 - 1
self.tx_level = 255
self.NewUdpStatus(do_tx)
start = int(float(self.rx_phase) * self.conf.rx_udp_clock / 2.0**32 + 0.5)
phase = self.rx_phase + self.tx_phase * (self.vna_count - 1)
stop = int(float(phase) * self.conf.rx_udp_clock / 2.0**32 + 0.5)
return start, stop # return the start and stop frequencies after integer rounding

83
import_quisk_api.c Executable file
View File

@ -0,0 +1,83 @@
/*
This module uses the Python CObject or Capsule interface to import pointers to
functions and variables defined in the _quisk Python extension module. These functions and
variables can then be used in another extension module. This is an alternative to linking
the other extension module to _quisk with the C linker. This interface is used by the SDR-IQ
extension module, and you can use that as a model.
This feature exists because of Maitland Bottoms, AA4HS, who requested the feature and provided patches.
To use this interface in your own extension module, first modify your setup.py or makefile so that
you are not linking in symbols from the _quisk module. Add import_quisk_api.c to your source files.
Then add this define before including quisk.h:
#define IMPORT_QUISK_API
#include "quisk.h"
Add this code after Py_InitModule() in your module init function (PyMODINIT_FUNC):
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
printf("Failure to import pointers from _quisk\n");
return; //Error
}
Use this new function to set your Start/Stop/Read functions (if used):
quisk_sample_source(&quisk_start_sdriq, &quisk_stop_sdriq, &quisk_read_sdriq);
Change references to quisk_sound_state to use the pointer pt_quisk_sound_state everywhere. For
example, replace this:
quisk_sound_state.read_error++;
with this:
pt_quisk_sound_state->read_error++;
*/
#include <Python.h>
void ** Quisk_API; // array of pointers to functions and variables from module _quisk
struct sound_conf * pt_quisk_sound_state; // pointer to quisk_sound_state
#if ( (PY_VERSION_HEX < 0x02070000) || ((PY_VERSION_HEX >= 0x03000000) && (PY_VERSION_HEX < 0x03010000)) )
// Old Python interface using CObject
int import_quisk_api(void)
{
PyObject *c_api_object;
PyObject *module;
module = PyImport_ImportModule("_quisk");
if (module == NULL) {
printf("Failure 1 to import Quisk_API\n");
return -1;
}
c_api_object = PyObject_GetAttrString(module, "QUISK_C_API");
if (c_api_object == NULL) {
Py_DECREF(module);
printf("Failure 2 to import Quisk_API\n");
return -1;
}
if (PyCObject_Check(c_api_object)) {
Quisk_API = (void **)PyCObject_AsVoidPtr(c_api_object);
}
else {
printf("Failure 3 to import Quisk_API\n");
Py_DECREF(c_api_object);
Py_DECREF(module);
return -1;
}
Py_DECREF(c_api_object);
Py_DECREF(module);
pt_quisk_sound_state = (struct sound_conf *)Quisk_API[0];
return 0;
}
#else
// New Python interface using Capsule
int import_quisk_api(void)
{
Quisk_API = (void **)PyCapsule_Import("_quisk.QUISK_C_API", 0);
if (Quisk_API == NULL) {
printf("Failure to import Quisk_API\n");
return -1;
}
pt_quisk_sound_state = (struct sound_conf *)Quisk_API[0];
return 0;
}
#endif

323
is_key_down.c Executable file
View File

@ -0,0 +1,323 @@
#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 key_is_down = 0; // internal key state
// Changes for MacOS support thanks to Mario, DL3LSM.
#if defined(MS_WINDOWS) || defined(__MACH__)
int quisk_open_key(const char * name)
{ // Open the hardware key; return 0 for success, else an error code.
return 0;
}
void quisk_close_key(void)
{
}
int quisk_is_key_down(void)
{
return key_is_down;
}
void quisk_set_key_down(int state)
{ // Set the key state internally
if (state)
key_is_down = 1;
else
key_is_down = 0;
}
#else
// Not MS Windows:
#include <stdio.h>
#include <fcntl.h>
#include <netinet/in.h>
#ifdef __linux__
#include <linux/ppdev.h>
#endif
#ifdef __FreeBSD__
#include <dev/ppbus/ppi.h>
#include <dev/ppbus/ppbconf.h>
#endif
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
static int open_key_pport(const char * name);
static int open_key_serport(const char * name);
static int open_key_enet(const char * name);
static void close_key_pport(void);
static void close_key_serport(void);
static void close_key_enet(void);
static int is_key_down_pport(void);
static int is_key_down_serport(void);
static int is_key_down_enet(void);
static enum { // The key access method
None, // Return the internal state; default key is always up
ParPort, // Use the parallel port
SerPort, // Use the serial port
Udp // Use UDP Ethernet
} key_method = None;
static int fd = -1; // File descriptor to read the parallel or serial port
static int KEY_PORT = 0x553C; // Ethernet UDP port
static int key_socket = -1; // Ethernet socket
int quisk_open_key(const char * name)
{ // Open the hardware key; return 0 for success, else an error code.
int ret;
if (!name[0]){ // null string means internal key state
key_method = None;
ret = 0;
}
else if (!strncmp(name, "/dev/tty", 8)){ // serial port
key_method = SerPort;
ret = open_key_serport(name);
}
else if (name[0] == '/'){ // starting '/' means parallel port name
key_method = ParPort;
ret = open_key_pport(name);
}
else if (isdigit(name[0])){ // IP address
key_method = Udp;
ret = open_key_enet(name);
}
else {
ret = 5;
}
return ret;
}
void quisk_close_key(void)
{
switch(key_method) {
case None:
break;
case ParPort:
close_key_pport();
break;
case SerPort:
close_key_serport();
break;
case Udp:
close_key_enet();
break;
}
return;
}
int quisk_is_key_down(void)
{
switch(key_method) {
case None:
return key_is_down;
case SerPort:
return is_key_down_serport();
case ParPort:
return is_key_down_pport();
case Udp:
return is_key_down_enet();
}
return 0;
}
void quisk_set_key_down(int state)
{ // Set the key state internally
if (state)
key_is_down = 1;
else
key_is_down = 0;
}
// ***************************************************
// Access the parallel port
static int open_key_pport(const char * name)
{
int byte;
if (fd >= 0)
close(fd);
fd = open(name, O_RDONLY);
if (fd == -1) {
printf("Open %s failed, try modprobe ppdev.\n", name);
}
#ifdef __linux__
else if (ioctl (fd, PPCLAIM)) {
perror ("PPCLAIM");
close (fd);
fd = -1;
}
#endif
else {
byte = 0x0;
#if defined(__linux__)
ioctl(fd, PPWCONTROL, &byte);
#endif
return 0; // Success
}
return -1;
}
static void close_key_pport(void)
{
int byte;
if (fd >= 0) {
#ifdef __linux__
byte = 0x0;
ioctl(fd, PPWCONTROL, &byte);
#endif
close(fd);
}
fd = -1;
}
// This code writes to the control register so the PC can send a signal.
// Currently unused.
// ioctl(fd, PPRCONTROL, &byte);
// byte |= 0x02;
// ioctl(fd, PPWCONTROL, &byte);
static int is_key_down_pport(void)
{
#if defined(__linux__)
int byte;
if (fd < 0) // port not open
return 0; // Key is up
byte = 0;
ioctl(fd, PPRSTATUS, &byte);
if (byte & 0x10)
return 1; // Key is down
#elif defined(__FreeBSD__)
uint8_t byte;
if (fd < 0) // port not open
return 0; // Key is up
byte = 0;
ioctl(fd, PPIGSTATUS, &byte);
if (byte & 0x10)
return 1; // Key is down
#endif
return 0; // Key is up
}
// ***************************************************
// Access using Ethernet
// Check for a UDP packet from the network to determine key status
static int open_key_enet(const char * ip)
{
struct sockaddr_in Addr;
close_key_enet();
key_socket = socket(PF_INET, SOCK_DGRAM, 0);
if (key_socket < 0)
return -1;
memset(&Addr, 0, sizeof(Addr)); // Assign an address to our socket
Addr.sin_family = AF_INET;
Addr.sin_addr.s_addr = htonl(INADDR_ANY);
Addr.sin_port = htons(KEY_PORT);
if (bind(key_socket, (struct sockaddr *)&Addr, sizeof(Addr)) != 0) {
close_key_enet();
return -1;
}
memset(&Addr, 0, sizeof(Addr)); // Only accept UDP from this host
Addr.sin_family = AF_INET;
inet_aton(ip, &Addr.sin_addr);
Addr.sin_port = htons(KEY_PORT);
if (connect(key_socket, (struct sockaddr *)&Addr, sizeof(Addr)) != 0) {
close_key_enet();
return -1;
}
return 0; // Success
}
static void close_key_enet(void)
{
if (key_socket != -1) {
shutdown(key_socket, SHUT_RDWR);
close(key_socket);
key_socket = -1;
}
}
static int is_key_down_enet(void)
{
static int keyed = 0;
unsigned char buf[4];
if (key_socket >= 0 && recv(key_socket, buf, 2, MSG_DONTWAIT) == 2)
keyed = buf[0]; // new key state is available
return keyed; // return current key state
}
// ***************************************************
// Access the serial port. This code sets DTR high, and monitors DSR.
// When DSR is high the key is down (else up).
// Set the RTS signal high when the key is down; else low after a delay.
static int open_key_serport(const char * name)
{
int bits;
if (fd >= 0)
close(fd);
fd = open(name, O_RDWR | O_NOCTTY);
if (fd == -1) {
printf("Open serial port %s failed.\n", name);
return -1;
}
ioctl(fd, TIOCMGET, &bits); // read modem bits
bits |= TIOCM_DTR; // Set DTR
bits &= ~TIOCM_RTS; // Clear RTS at first
ioctl(fd, TIOCMSET, &bits);
return 0; // Success
}
static void close_key_serport(void)
{
if (fd >= 0)
close(fd);
fd = -1;
}
// Delay clearing RTS when the key goes up (semi breakin)
#define KEY_UP_DELAY_SECS 1.5
static int is_key_down_serport(void)
{
int bits;
struct timeval tv;
double time;
static double time0=0; // time when the key was last down
if (fd < 0) // Port not open
return 0; // Key is up
gettimeofday(&tv, NULL);
time = tv.tv_sec + tv.tv_usec / 1.0E6; // time is in seconds
ioctl(fd, TIOCMGET, &bits); // read modem bits
if (bits & TIOCM_DSR) { // Key is down
bits |= TIOCM_RTS; // Set RTS
ioctl(fd, TIOCMSET, &bits);
time0 = time;
return 1;
}
else { // Key is up
if (time - time0 > KEY_UP_DELAY_SECS) {
bits &= ~TIOCM_RTS; // Clear RTS after a delay
ioctl(fd, TIOCMSET, &bits);
}
return 0;
}
}
#endif

BIN
libfftw3-3.dll Executable file

Binary file not shown.

BIN
libgcc_s_dw2-1.dll Executable file

Binary file not shown.

30
libusb.txt Executable file
View File

@ -0,0 +1,30 @@
Notes on libusb and pyusb
=========================
Libusb provides access to the USB bus from user space. It uses the
files in /dev/bus/usb/*/*. The following commands are useful on Linux:
List devices on the USB bus:
lsusb
Add -d 16c0:05dc for a specific device, -v for more information.
List file permissions for bus 001 device 005:
ls -l /dev/bus/usb/001/005
These permissions default to 660 group root.
List udev information for bus 001 device 005:
udevadm info --query=all --name=/dev/bus/usb/001/005 --attribute-walk
These items can be used in udev rules.
Default USB permissions do not allow a non-root user to write to the bus.
To change permissions, add a rule to /etc/udev/rules.d/local.rules like this:
SUBSYSTEM=="usb", ATTR{idVendor}=="16c0" , ATTR{idProduct}=="05dc", MODE="0666", GROUP="dialout"
Then notify udev with "udevadm control --reload_rules", or /etc/init.d/udev/restart. But
on my system, I need to plug in the SoftRock and reboot.
To install Libusb on Windows, follow the instructions in
http://sourceforge.net/apps/trac/libusb-win32/wiki.
Run the program libusb-win32*/bin/inf-wizard.exe.
On Windows, when libusb-win32 is properly installed, Device Manager reports a
top-level device "libusb-win32 devices" and a sub-device "DG8SAQ-I2C". Otherwise
it reports "Unknown Device" under "Universal Serial Bus controllers".

BIN
libwinpthread-1.dll Executable file

Binary file not shown.

129
license.txt Executable file
View File

@ -0,0 +1,129 @@
This software is Copyright (C) 2007-2018 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.

23
makefile Executable file
View File

@ -0,0 +1,23 @@
.PHONY: quisk
quisk:
@echo 'Please specify either quisk2 or quisk3 for Python 2 or 3'
quisk2:
python2 setup.py build_ext --force --inplace
@echo
@echo 'Use "make soapy2" to make the Python2 soapy module'
quisk3:
python3 setup.py build_ext --force --inplace
@echo
@echo 'Use "make soapy3" to make the Python3 soapy module'
soapy2:
(cd soapypkg; make soapy2)
soapy3:
(cd soapypkg; make soapy3)
macports:
env ARCHFLAGS="-arch x86_64" python setup.py build_ext --force --inplace -D USE_MACPORTS

1567
microphone.c Executable file

File diff suppressed because it is too large Load Diff

1
microphone.h Executable file
View File

@ -0,0 +1 @@
// Filters were moved to filter.c.

246
n2adr/.quisk_init.pkl Executable file
View File

@ -0,0 +1,246 @@
(dp0
S'VFO'
p1
I146000000
sS'levelSquelch'
p2
I500
sS'txAudioClipFm'
p3
I0
sS'txAudioPreemphAm'
p4
I0
sS'txAudioPreemphUsb'
p5
I70
sS'levelSquelchSSB'
p6
I200
sS'bandState'
p7
(dp8
S'23cm'
p9
(I1270000000
I10000
S'USB'
p10
tp11
sS'60'
p12
(I5370000
I1500
g10
tp13
sS'80'
p14
(I3660000
I-10000
S'LSB'
p15
tp16
sS'20'
p17
(I14180000
I10000
g10
tp18
sS'40'
p19
(I7180000
I-5000
g15
tp20
sS'2'
p21
(I146540000
I-114461
VUSB
p22
tp23
sS'4'
p24
(I70250000
I10000
g10
tp25
sS'6'
p26
(I52000000
I10000
g10
tp27
sS'33cm'
p28
(I915000000
I10000
g10
tp29
sS'3cm'
p30
(I10250000000
I10000
g10
tp31
sS'WWV'
p32
(I20000000
I334523
S'AM'
p33
tp34
sS'Time'
p35
(I5000000
I0
S'AM'
p36
tp37
sS'5cm'
p38
(I5787500000
I10000
g10
tp39
sS'Audio'
p40
(I0
I0
g15
tp41
sS'160'
p42
(I1890000
I-10000
g15
tp43
sS'13cm'
p44
(I2375000000
I10000
g10
tp45
sS'10'
p46
(I28850000
I10000
g10
tp47
sS'12'
p48
(I24940000
I10000
g10
tp49
sS'15'
p50
(I21230000
I10000
g10
tp51
sS'17'
p52
(I18120000
I10000
g10
tp53
sS'30'
p54
(I10120000
I-10000
S'CWL'
p55
tp56
sS'9cm'
p57
(I3400000000
I10000
g10
tp58
sS'70cm'
p59
(I435000000
I-167000
g10
tp60
sS'1.25'
p61
(I223500000
I10000
g10
tp62
ssS'levelOffAGC'
p63
I100
sS'graphScaleZ'
p64
(dp65
V2
p66
(I88
I89
tp67
sV33cm
p68
(I100
I92
tp69
ssS'txAudioPreemphFdv'
p70
I0
sS'split_rxtx_play'
p71
I2
sS'volumeAudio'
p72
I577
sS'txAudioClipFdv'
p73
I0
sS'filterAdjBw1'
p74
I1000
sS'txAudioPreemphFm'
p75
I0
sS'txAudioClipAm'
p76
I0
sS'levelVOX'
p77
I-20
sS'sidetone_volume'
p78
I0
sS'wfallScaleZ'
p79
(dp80
sS'bandAmplPhase'
p81
(dp82
sS'levelSpot'
p83
I500
sS'levelAGC'
p84
I500
sS'txAudioClipUsb'
p85
I5
sS'lastBand'
p86
g21
sS'txFreq'
p87
I0
sS'mode'
p88
g22
sS'timeVOX'
p89
I500
sS'vardecim_set'
p90
I1152000
s.

1
n2adr/__init__.py Executable file
View File

@ -0,0 +1 @@
#

13
n2adr/conf1.py Executable file
View File

@ -0,0 +1,13 @@
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path
settings_file_path = "../quisk_settings.json"
name_of_sound_play = n2adr_sound_usb_play
microphone_name = n2adr_sound_usb_mic
digital_input_name = ""
digital_output_name = ""
use_rx_udp = 1 # Get ADC samples from UDP
rx_udp_ip = "192.168.1.196" # Sample source IP address
rx_udp_port = 0xBC77 # Sample source UDP port
graph_width = 0.80

20
n2adr/conf2.py Executable file
View File

@ -0,0 +1,20 @@
# This is a second config file that I use to test various hardware configurations.
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic
from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path
settings_file_path = "../quisk_settings.json"
name_of_sound_play = n2adr_sound_usb_play
name_of_sound_capt = n2adr_sound_pc_capt
sdriq_name = "/dev/ttyUSB0" # Name of the SDR-IQ device to open
default_screen = 'WFall'
waterfall_y_scale = 80
waterfall_y_zero = 40
waterfall_graph_y_scale = 40
waterfall_graph_y_zero = 90
waterfall_graph_size = 160
display_fraction = 1.00 # The edges of the full bandwidth are not valid

13
n2adr/conf3.py Executable file
View File

@ -0,0 +1,13 @@
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path
name_of_sound_play = n2adr_sound_usb_play
microphone_name = n2adr_sound_usb_mic
settings_file_path = "../quisk_settings.json"
rx_udp_clock = 73728000 - 102
rx_udp_ip = "192.168.1.213" # Sample source IP address "" for DHCP
do_repeater_offset = True
#bandTransverterOffset = {'10' : 300000}
spot_button_keys_tx = True

5
n2adr/conf3A.py Executable file
View File

@ -0,0 +1,5 @@
from n2adr import conf3
settings_file_path = "../quisk_settings.json"
microphone_name = ""

41
n2adr/conf4.py Executable file
View File

@ -0,0 +1,41 @@
# 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)
self.use_sidetone = 1
def OnButtonPTT(self, event):
if event.GetEventObject().GetValue():
QS.set_key_down(1)
else:
QS.set_key_down(0)

15
n2adr/conf5.py Executable file
View File

@ -0,0 +1,15 @@
import sys
settings_file_path = "../quisk_settings.json"
#hamlib_port = 4575 # Standard port for Quisk control. Set the port in Hamlib to 4575 too.
hamlib_port = 4532 # Default port for rig 2. Use this if you can not set the Hamlib port.
if sys.platform == "win32":
pass
elif 0:
digital_input_name = 'pulse'
digital_output_name =''
else:
digital_input_name = 'hw:Loopback,0'
digital_output_name = digital_input_name

31
n2adr/conf6.py Executable file
View File

@ -0,0 +1,31 @@
# 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
key_poll_msec = 5
do_repeater_offset = True
#bandTransverterOffset = {'40' : 300000}
# Microphone capture and playback:
microphone_name = n2adr_sound_usb_mic
name_of_mic_play = n2adr_sound_pc_play
mic_playback_rate = sample_rate
mic_out_volume = 0.6

14
n2adr/conf7.py Executable file
View File

@ -0,0 +1,14 @@
import sys
settings_file_path = "../quisk_settings.json"
if sys.platform == "win32":
digital_output_name = 'CABLE-A Input'
elif 0:
digital_input_name = 'pulse'
digital_output_name =''
else:
digital_output_name = 'hw:Loopback,0'
name_of_sound_play = ''
microphone_name = ''

63
n2adr/hl2_hardware.py Executable file
View File

@ -0,0 +1,63 @@
# 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 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
self.v2filter = None
# 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
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)
else:
self.anttuner.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.CorrectSmeter()
return ret
def HeartBeat(self): # Called at about 10 Hz by the main
self.anttuner.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.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)

47
n2adr/quisk_conf.py Executable file
View File

@ -0,0 +1,47 @@
# This is the config file from my shack, which controls various hardware.
# The files to control my 2010 transceiver and for the improved version HiQSDR
# are in the package directory HiQSDR.
import sys
sample_rate = 48000
if sys.platform == 'win32':
favorites_file_path = "C:/pub/quisk_favorites.txt"
settings_file_path = "C:/pub/quisk_settings.json"
name_of_sound_play = ""
name_of_sound_capt = "Primary"
microphone_name = ""
name_of_mic_play = ""
else:
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
settings_file_path = "/home/jim/pub/quisk_settings.json"
name_of_sound_play = ""
name_of_sound_capt = "hw:0"
microphone_name = ""
name_of_mic_play = ""
#lin_microphone_name = "ALC1150 Analog"
#lin_name_of_mic_play = "USB Sound Device"
# These are for CM106 like sound device
#n2adr_sound_usb_mic = 'alsa:USB Sound Device'
#microphone_name = n2adr_sound_usb_mic
#mixer_settings = [
# (microphone_name, 16, 1), # PCM capture from line
# (microphone_name, 14, 0), # PCM capture switch
# (microphone_name, 11, 1), # line capture switch
# (microphone_name, 12, 0.70), # line capture volume
# (microphone_name, 3, 0), # mic playback switch
# (microphone_name, 9, 0), # mic capture switch
# ]
# These are for Asus internal sound
n2adr_sound_usb_mic = 'alsa:ALC1150 Analog'
microphone_name = n2adr_sound_usb_mic
mixer_settings = [
(microphone_name, 19, 2), # PCM capture from line
(microphone_name, 24, 0), # PCM capture switch
(microphone_name, 22, 1), # line capture switch
(microphone_name, 27, 0), # line capture volume boost
(microphone_name, 21, 0.70), # line capture volume
]

202
n2adr/quisk_conf_8600.py Executable file
View File

@ -0,0 +1,202 @@
# These are the configuration parameters for receiving the
# 10.7 MHz IF output of the AOR AR8600 receiver with my
# transceiver. This results in a 100 kHz to 3 GHz
# wide range receiver with pan adapter.
#
# Due to noise starting at 11.18 MHz when tuned to 449.0 MHz, we tune to 10.5 MHz center.
#
# Note: The AR8600 IF output in WFM mode seems to tune in 10kHz increments
# no matter what the step size, even though the display reads a
# different frequency.
# The AR8600 inverts the spectrum of these bands: 10, 2, 220, 440, 900
# The AR8600 does not invert these bands: 1240
# The change from inverted to non-inverted is about 1040 MHz.
# Please do not change this sample file.
# Instead copy it to your own .quisk_conf.py and make changes there.
# See quisk_conf_defaults.py for more information.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import time, traceback, os
import _quisk as QS
import serial # From the pyserial package
from n2adr.quisk_conf import *
from n2adr import scanner_widgets as quisk_widgets
settings_file_path = "../quisk_settings.json"
bandLabels = [ ('60',) * 5, '40', '20',
'15', '12', '10', '2', '220', '440', '900', '1240', ('Time',) * len(bandTime)]
# Define the Hardware class in this config file instead of a separate file.
from hiqsdr.quisk_hardware import Hardware as BaseHardware
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.ar8600_frequency = 0 # current AR8600 tuning frequency
self.hware_frequency = 0 # current hardware VFO frequency
self.vfo_frequency = 0 # current Quisk VFO frequency
self.invert = 1 # The frequency spectrum is backwards
self.serial = None # the open serial port
self.timer = 0.02 # time between AR8600 commands in seconds
self.time0 = 0 # time of last AR8600 command
self.serial_out = [] # send commands slowly
self.offset = 10700000 # frequency offset from AR8600 tuning freq to IF output
self.tx_freq = 0 # current frequency
conf.BandEdge['220'] = (222000000, 225000000)
conf.BandEdge['440'] = (420000000, 450000000)
conf.BandEdge['900'] = (902000000, 928000000)
conf.BandEdge['1240'] = (1240000000, 1300000000)
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
rpt_file = os.path.join(rpt_file, 'MetroCor.txt')
fp = open(rpt_file, 'r')
self.repeaters = {}
for line in fp:
line = line.strip()
if line and line[0] != '#':
line = line.split('\t')
fout = int(float(line[0]) * 1000000 + 0.1)
text = "%s %s, %s" % (line[2], line[3], line[5])
if fout in self.repeaters:
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
else:
self.repeaters[fout] = text
fp.close()
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
rpt_file = os.path.join(rpt_file, 'ARCC.csv')
fp = open(rpt_file, 'r')
for line in fp:
line = line.strip()
if line and line[0] != '#':
line = line.split(',')
fout = float(line[3])
if fout >= 2000.0:
continue
fout = int(fout * 1000000 + 0.1)
text = "%s %s, %s" % (line[5], line[2], line[0])
if fout in self.repeaters:
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
else:
self.repeaters[fout] = text
fp.close()
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
rpt_file = os.path.join(rpt_file, 'Repeaters.csv')
fp = open(rpt_file, 'r')
for line in fp:
line = line.strip()
if line and line[0] != '#':
line = line.split(',')
fout = float(line[3])
if fout >= 2000.0:
continue
fout = int(fout * 1000000 + 0.1)
if line[0]:
text = "%s %s, %s" % (line[5], line[2], line[0])
else:
text = line[5]
if fout in self.repeaters:
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
else:
self.repeaters[fout] = text
fp.close()
for freq, text in list(self.repeaters.items()):
if len(text) > 80:
t =''
stations = text.split(';')
for s in stations:
s = s.strip()
t = t + s.split()[0] + ' ' + s.split(',')[1] + '; '
self.repeaters[freq] = t
self.rpt_freq_list = list(self.repeaters)
self.rpt_freq_list.sort()
def OpenPort(self):
if sys.platform == "win32":
tty_list = ("COM7", "COM8")
else:
tty_list = ("/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2")
for tty_name in tty_list:
try:
port = serial.Serial(port=tty_name, baudrate=9600,
stopbits=serial.STOPBITS_TWO, xonxoff=1, timeout=0)
except:
#traceback.print_exc()
pass
else:
time.sleep(0.1)
for i in range(3):
port.write('VR\r')
time.sleep(0.1)
chars = port.read(1024)
if "VR0101" in chars:
self.serial = port
port.write('MD0\r') # set WFM mode so the IF output is available
break
if self.serial:
break
else:
port.close()
def open(self):
self.OpenPort()
QS.invert_spectrum(self.invert)
t = BaseHardware.open(self) # save the message
return t
def close(self):
BaseHardware.close(self)
if self.serial:
self.serial.write('EX\r')
time.sleep(1) # wait for output to drain, but don't block
self.serial.close()
self.serial = None
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.tx_freq = tx_freq
try:
rpt = self.repeaters[tx_freq]
except KeyError:
self.application.bottom_widgets.UpdateText('')
else:
self.application.bottom_widgets.UpdateText(rpt)
if vfo_freq != self.vfo_frequency and vfo_freq >= 10000:
self.vfo_frequency = vfo_freq
# Calculate new AR8600 and hardware frequencies
ar8600 = (vfo_freq + 50000) // 100000 * 100000 - 200000
if self.ar8600_frequency != ar8600:
self.ar8600_frequency = ar8600
self.SendAR8600('RF%010d\r' % ar8600)
if ar8600 < 1040000000:
self.invert = 1
else:
self.invert = 0
QS.invert_spectrum(self.invert)
if self.invert:
hware = self.offset - vfo_freq + self.ar8600_frequency
else:
hware = self.offset + vfo_freq - self.ar8600_frequency
if self.hware_frequency != hware:
self.hware_frequency = hware
BaseHardware.ChangeFrequency(self, 0, hware)
#print 'AR8600 Hware', self.ar8600_frequency, self.hware_frequency
return tx_freq, vfo_freq
def SendAR8600(self, msg): # Send commands to the AR8600, but not too fast
if self.serial:
if time.time() - self.time0 > self.timer:
self.serial.write(msg) # send message now
self.time0 = time.time()
else:
self.serial_out.append(msg) # send message later
def HeartBeat(self): # Called at about 10 Hz by the main
BaseHardware.HeartBeat(self)
if self.serial:
chars = self.serial.read(1024)
#if chars:
# print chars
if self.serial_out and time.time() - self.time0 > self.timer:
self.serial.write(self.serial_out[0])
self.time0 = time.time()
del self.serial_out[0]

79
n2adr/quisk_hardware.py Executable file
View File

@ -0,0 +1,79 @@
# 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)
else:
self.anttuner.SetTxFreq(tx_freq)
self.v2filter.SetTxFreq(tx_freq)
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.ChangeFilterFrequency(tx_freq)
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
ret = BaseHw.ChangeBand(self, band)
self.anttuner.ChangeBand(band)
#self.lpfilter.ChangeBand(band)
#self.hpfilter.ChangeBand(band)
self.v2filter.ChangeBand(band)
self.CorrectSmeter()
return ret
def HeartBeat(self): # Called at about 10 Hz by the main
self.anttuner.HeartBeat()
#self.lpfilter.HeartBeat()
#self.hpfilter.HeartBeat()
self.v2filter.HeartBeat()
self.controlbox.HeartBeat()
return BaseHw.HeartBeat(self)
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
self.anttuner.OnSpot(level)
return BaseHw.OnSpot(self, level)
def OnButtonRfGain(self, event):
#self.hpfilter.OnButtonRfGain(event)
self.v2filter.OnButtonRfGain(event)
self.CorrectSmeter()
def CorrectSmeter(self): # S-meter correction can change with band or RF gain
if self.band == '40': # Basic S-meter correction by band
self.correct_smeter = 20.5
else:
self.correct_smeter = 20.5
self.correct_smeter -= self.rf_gain / 6.0 # Correct S-meter for RF gain
self.application.waterfall.ChangeRfGain(self.rf_gain) # Waterfall colors are constant
def OnButtonPTT(self, event):
self.controlbox.OnButtonPTT(event)
return BaseHw.OnButtonPTT(self, event)

56
n2adr/quisk_widgets.py Executable file
View File

@ -0,0 +1,56 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import wx, time
import _quisk as QS
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
#self.config = conf
#self.hardware = hardware
#self.application = app
#start_row = app.widget_row # The first available row
#start_col = app.button_start_col # The start of the button columns
#b = app.QuiskCycleCheckbutton(frame, self.OnAntTuner, ('Antenna', 'Ant 0', 'Ant 1'))
#bw, bh = b.GetMinSize()
#gbs.Add(b, (start_row, start_col), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L+')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 2), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L-')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 4), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C+')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 6), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C-')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 8), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'Save')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 10), (1, 2), flag=wx.EXPAND)
#self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
#gbs.Add(self.swr_label, (start_row, start_col + 2), (1, 10), flag=wx.EXPAND)
#b = app.QuiskCheckbutton(frame, None, text='')
#gbs.Add(b, (start_row, start_col + 25), (1, 2), flag=wx.EXPAND)
# Example of a horizontal slider:
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
# def OnPreamp(self, event):
# print event.GetPosition()
self.num_rows_added = 0
#def OnAntTuner(self, event):
# btn = event.GetEventObject()
# text = btn.GetLabel()
# self.hardware.OnAntTuner(text)
#def UpdateText(self, text):
# self.swr_label.SetLabel(text)

140
n2adr/scanner_widgets.py Executable file
View File

@ -0,0 +1,140 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import wx, time
import _quisk as QS
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
row = 4 # The next available row
b = app.QuiskPushbutton(frame, None, 'Tune')
bw, bh = b.GetMinSize()
b.Enable(0)
gbs.Add(b, (row, 0), (1, 2), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, None, '')
gbs.Add(b, (row, 2), (1, 2), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, None, '')
gbs.Add(b, (row, 4), (1, 2), flag=wx.EXPAND)
b = self.btnScanner = app.QuiskCheckbutton(frame, self.OnBtnScanner, text='Scanner', use_right=True)
self.scan_timer = wx.Timer(b) # timed events for the scanner
b.Bind(wx.EVT_TIMER, self.OnTimerEvent)
gbs.Add(b, (row, 6), (1, 2), flag=wx.EXPAND)
b = self.btnNext = app.QuiskPushbutton(frame, self.OnBtnNext, 'Next', True)
gbs.Add(b, (row, 8), (1, 2), flag=wx.EXPAND)
b = app.QuiskCheckbutton(frame, self.OnBtnRptr, text='Rptr')
b.SetValue(True, True)
gbs.Add(b, (row, 10), (1, 2), flag=wx.EXPAND)
self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
gbs.Add(self.swr_label, (row, 15), (1, 12), flag=wx.EXPAND)
# Example of a horizontal slider:
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
# def OnPreamp(self, event):
# print event.GetPosition()
def UpdateText(self, text):
self.swr_label.SetLabel(text)
def OnBtnRptr(self, event):
btn = event.GetEventObject()
if btn.GetValue():
self.config.freq_spacing = 5000
else:
self.config.freq_spacing = 0
def OnBtnNext(self, event):
self.direction = self.btnNext.direction # +1 for left -> go up; -1 for down
self.keep_going = wx.GetKeyState(wx.WXK_SHIFT) # if Shift is down, move to next band
self.scanner = False
if self.keep_going:
if not self.ScanScreen(event):
self.MoveVfo(event)
self.scan_timer.Start(500)
else:
self.ScanScreen(event)
def ScanScreen(self, event): # Look for signals on the current screen
lst = self.hardware.rpt_freq_list
app = self.application
vfo = app.VFO
tx_freq = vfo + app.txFreq
sample_rate = app.sample_rate
limit = int(sample_rate / 2.0 * self.config.display_fraction * 0.95) # edge of screen
self.scan_n1 = None
self.scan_n = None
for n in range(len(lst)):
if lst[n] > vfo - limit and self.scan_n1 is None:
self.scan_n1 = n # inclusive
if lst[n] >= tx_freq and self.scan_n is None:
self.scan_n = n
if lst[n] > vfo + limit:
break
self.scan_n2 = n # inclusive
if self.scan_n is None:
self.scan_n = self.scan_n1
if self.direction > 0: # left click; go up
seq = list(range(self.scan_n + 1, self.scan_n2 + 1))
if not self.keep_going:
seq += list(range(self.scan_n1, self.scan_n))
else: # right click; go down
seq = list(range(self.scan_n - 1, self.scan_n1 - 1, -1))
if not self.keep_going:
seq += list(range(self.scan_n2, self.scan_n, -1))
for n in seq:
freq = lst[n]
if not QS.get_squelch(freq - vfo):
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
return True # frequency was changed
return False # frequency was not changed
def MoveVfo(self, event): # Move the VFO to look for further signals
lst = self.hardware.rpt_freq_list
app = self.application
vfo = app.VFO
tx_freq = vfo + app.txFreq
sample_rate = app.sample_rate
if self.direction > 0: # left click; go up
n = self.scan_n2 + 1
if n >= len(lst):
n = 0
freq = lst[n]
vfo = freq + sample_rate * 4 // 10
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
else: # right click; go down
n = self.scan_n1 - 1
if n < 0:
n = len(lst) - 1
freq = lst[n]
vfo = freq - sample_rate * 4 // 10
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
def OnBtnScanner(self, event):
self.direction = self.btnScanner.direction # +1 for left -> go up; -1 for down
self.keep_going = wx.GetKeyState(wx.WXK_SHIFT) # if Shift is down, move to next band
self.scanner = True
if self.btnScanner.GetValue():
self.btnNext.Enable(0)
if self.keep_going:
if not self.ScanScreen(event):
self.MoveVfo(event)
else:
self.ScanScreen(event)
self.scan_timer.Start(500)
else:
self.btnNext.Enable(1)
self.scan_timer.Stop()
def OnTimerEvent(self, event):
if QS.get_squelch(self.application.txFreq):
if self.keep_going:
if not self.ScanScreen(event):
self.MoveVfo(event)
else:
self.ScanScreen(event)
elif not self.scanner:
self.scan_timer.Stop()

92
n2adr/startup.py Executable file
View File

@ -0,0 +1,92 @@
#! /usr/bin/python
# All QUISK software is Copyright (C) 2006-2011 by James C. Ahlstrom.
# This free software is licensed for use under the GNU General Public
# License (GPL), see http://www.opensource.org.
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
"Select the desired hardware, and start Quisk"
import sys, wx, subprocess, os
Choices = [
(' My Transceiver', 'n2adr/quisk_conf.py', ''),
(' VHF/UHF Receiver', 'n2adr/uhfrx_conf.py', ''),
(' Softrock Rx Ensemble', 'softrock/conf_rx_ensemble2.py', 'n2adr/conf2.py'),
(' Softrock Rx/Tx Ensemble', 'softrock/conf_rx_tx_ensemble.py', 'n2adr/conf6.py'),
(' Plain Sound Card, Rx only', 'n2adr/conf2.py', ''),
(' Test microphone sound', 'n2adr/conf4.py', ''),
(' SDR-IQ, receive only, antenna to RF input', 'quisk_conf_sdriq.py', 'n2adr/conf2.py'),
(' AOR AR8600 with IF to my hardware', 'n2adr/quisk_conf_8600.py', ''),
(' AOR AR8600 with IF to SDR-IQ', 'quisk_conf_sdr8600.py', 'n2adr/conf2.py'),
(' Fldigi with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf5.py'),
(' Freedv.org Rx with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf7.py'),
(' Hermes-Lite', 'hermes/quisk_conf.py', 'n2adr/conf3.py'),
(' Odyssey', 'odyssey/quisk_conf.py', 'n2adr/conf1.py'),
(' My Transceiver to Hermes-Lite', 'Quisk2Hermes', ''),
]
if sys.platform == 'win32':
os.chdir('C:\\pub\\quisk')
exe = "C:\\python27\\pythonw.exe"
else:
os.chdir('/home/jim/pub/quisk')
exe = "/usr/bin/python"
class ListBoxFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Select Hardware')
font = wx.Font(14, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL)
self.SetFont(font)
charx = self.GetCharWidth()
chary = self.GetCharHeight()
width = 0
height = chary * 2
tlist = []
for txt, conf1, conf2 in Choices:
text = "%s, %s" % (txt, conf1)
if conf2:
text = "%s, %s" % (text, conf2)
tlist.append(text)
w, h = self.GetTextExtent(text)
width = max(width, w)
height += h
width += 3 * chary
lb = wx.ListBox(self, -1, (0, 0), (width, height), tlist, wx.LB_SINGLE)
lb.SetSelection(0)
lb.SetFont(font)
lb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnDClick, lb)
lb.Bind(wx.EVT_KEY_DOWN, self.OnChar)
self.SetClientSize((width, height))
def OnDClick(self, event):
lb = event.GetEventObject()
index = lb.GetSelection()
text, conf1, conf2 = Choices[index]
if conf1 == "Quisk2Hermes":
subprocess.Popen([exe, 'quisk.py', '-c', 'n2adr/quisk_conf.py', '--local', 'Q2H'])
subprocess.Popen([exe, 'quisk.py', '-c', 'hermes/quisk_conf.py', '--config2', 'n2adr/conf3A.py', '--local', 'Q2H'])
else:
cmd = [exe, 'quisk.py', '-c', conf1]
if conf2:
cmd = cmd + ['--config2', conf2]
subprocess.Popen(cmd)
self.Destroy()
def OnChar(self, event):
if event.GetKeyCode() == 13:
self.OnDClick(event)
else:
event.Skip()
class App(wx.App):
def __init__(self):
if sys.stdout.isatty():
wx.App.__init__(self, redirect=False)
else:
wx.App.__init__(self, redirect=True)
def OnInit(self):
frame = ListBoxFrame()
frame.Show()
return True
app = App()
app.MainLoop()

1025
n2adr/station_hardware.py Executable file

File diff suppressed because it is too large Load Diff

65
n2adr/uhf_conf.py Executable file
View File

@ -0,0 +1,65 @@
# This is the config file for the VHF/UHF receiver and transmitter.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, struct, socket, traceback
settings_file_path = "../quisk_settings.json"
DEBUG = 0
if sys.platform == "win32":
n2adr_sound_pc_capt = 'Line In (Realtek High Definition Audio)'
n2adr_sound_pc_play = 'Speakers (Realtek High Definition Audio)'
n2adr_sound_usb_play = 'Primary'
n2adr_sound_usb_mic = 'Primary'
latency_millisecs = 150
data_poll_usec = 20000
favorites_file_path = "C:/pub/quisk_favorites.txt"
elif 0: # portaudio devices
name_of_sound_play = 'portaudio:CODEC USB'
microphone_name = "portaudio:AK5370"
latency_millisecs = 150
data_poll_usec = 5000
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
else: # alsa devices
n2adr_sound_pc_capt = 'alsa:ALC1150 Analog'
n2adr_sound_pc_play = 'alsa:ALC1150 Analog'
n2adr_sound_usb_play = 'alsa:USB Sound Device'
n2adr_sound_usb_mic = 'alsa:USB Sound Device'
latency_millisecs = 150
data_poll_usec = 5000
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
name_of_sound_capt = ""
name_of_sound_play = n2adr_sound_pc_play
microphone_name = n2adr_sound_pc_capt
playback_rate = 48000
agc_off_gain = 80
do_repeater_offset = True
station_display_lines = 1
# DX cluster telent login data, thanks to DJ4CM.
dxClHost = ''
#dxClHost = 'dxc.w8wts.net'
dxClPort = 7373
user_call_sign = 'n2adr'
bandLabels = ['6', '2', '1.25', '70cm', '33cm', '23cm', 'WWV']
bandState['WWV'] = (19990000, 10000, 'AM')
BandEdge['WWV'] = (19500000, 20500000)
use_rx_udp = 17 # Get ADC samples from UDP
rx_udp_ip = "192.168.1.199" # Sample source IP address
rx_udp_port = 0xAA53 # Sample source UDP port
#rx_clk38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal
#rx_udp_clock = rx_clk38 * 32 // 2 // 9 # ADC sample rate in Hertz
sample_rate = 96000 # 96, 192, 384, 768, 1152 (for 69120/3/10)
display_fraction = 1.00
fft_size_multiplier = 16
tx_ip = "192.168.1.201"
tx_audio_port = 0xBC79
add_imd_button = 1
add_fdx_button = 1

465
n2adr/uhf_hardware.old Executable file
View File

@ -0,0 +1,465 @@
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 Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.use_sidetone = 0
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.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)
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')
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)

499
n2adr/uhf_hardware.py Executable file
View File

@ -0,0 +1,499 @@
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.use_sidetone = 0
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)

145
n2adr/uhf_widgets.py Executable file
View File

@ -0,0 +1,145 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import wx, time
import _quisk as QS
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
self.correct_screen = None
self.num_rows_added = 1
start_row = app.widget_row # The first available row
start_col = app.button_start_col # The start of the button columns
b = app.QuiskPushbutton(frame, hardware.TestVfoPlus, 'Adf+')
bw, bh = b.GetMinSize()
gbs.Add(b, (start_row, start_col), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, hardware.TestVfoMinus, 'Adf-')
gbs.Add(b, (start_row, start_col + 1), flag=wx.EXPAND)
b = app.QuiskPushbutton(frame, self.OnBtnCorrect, 'Corr')
gbs.Add(b, (start_row, start_col + 2), flag=wx.EXPAND)
self.status_label = app.QuiskText(frame, 'Ready', bh)
gbs.Add(self.status_label, (start_row, start_col + 3), (1, 24), flag=wx.EXPAND)
def UpdateText(self, text):
self.status_label.SetLabel(text)
def OnBtnCorrect(self, event):
if self.correct_screen:
self.correct_screen.Raise()
else:
self.correct_screen = QCorrect(self, self.application.width)
class QCorrect(wx.Frame):
"""Create a window with DC adjustment controls"""
f_DcI = "DC adjustment for In-Phase %.6f"
f_DcQ = "DC adjustment for Quadrature %.6f"
def __init__(self, parent, width):
self.parent = parent
self.application = parent.application
self.config = parent.config
self.hardware = parent.hardware
t = "DC Null Adjustment"
wx.Frame.__init__(self, self.application.main_frame, -1, t, pos=(50, 100), style=wx.CAPTION)
panel = wx.Panel(self)
self.MakeControls(panel, width)
self.Show()
def MakeControls(self, panel, width): # Make controls for DC adjustment
self.old_DcI = self.DcI = self.hardware.DcI
self.old_DcQ = self.DcQ = self.hardware.DcQ
self.hardware.NewUdpCorrect(self.DcI, self.DcQ)
sl_max = width * 4 // 10 # maximum +/- value for slider
self.dc_scale = float(0.3) / sl_max
font = wx.Font(self.config.default_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
wx.FONTWEIGHT_NORMAL, False, self.config.quisk_typeface)
chary = self.GetCharHeight()
y = chary * 3 // 10
self.t_DcI = wx.StaticText(panel, -1, self.f_DcI % self.old_DcI, pos=(0, y))
self.t_DcI.SetFont(font)
y += self.t_DcI.GetSize().GetHeight()
self.DcI1 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcI1.GetSize().GetHeight()
self.DcI2 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcI2.GetSize().GetHeight()
self.PositionDcI(self.old_DcI)
self.t_DcQ = wx.StaticText(panel, -1, self.f_DcQ % self.old_DcQ, pos=(0, y))
self.t_DcQ.SetFont(font)
y += self.t_DcQ.GetSize().GetHeight()
self.DcQ1 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcQ1.GetSize().GetHeight()
self.DcQ2 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
pos=(0, y), size=(width, -1))
y += self.DcQ2.GetSize().GetHeight()
sv = self.application.QuiskPushbutton(panel, self.OnBtnSave, 'Save')
cn = self.application.QuiskPushbutton(panel, self.OnBtnCancel, 'Cancel')
w, h = cn.GetSize().Get()
sv.SetSize((w, h))
y += h // 4
x = (width - w * 3) // 4
sv.SetPosition((x, y))
cn.SetPosition((x*3 + w*2, y))
sv.SetBackgroundColour('light blue')
cn.SetBackgroundColour('light blue')
y += h
y += h // 4
self.DcI1.SetBackgroundColour('aquamarine')
self.DcI2.SetBackgroundColour('orange')
self.DcQ1.SetBackgroundColour('aquamarine')
self.DcQ2.SetBackgroundColour('orange')
self.PositionDcQ(self.old_DcQ)
self.SetClientSize(wx.Size(width, y))
self.DcI1.Bind(wx.EVT_SCROLL, self.OnChange)
self.DcI2.Bind(wx.EVT_SCROLL, self.OnFineDcI)
self.DcQ1.Bind(wx.EVT_SCROLL, self.OnChange)
self.DcQ2.Bind(wx.EVT_SCROLL, self.OnFineDcQ)
def PositionDcI(self, dc): # set pos1, pos2 for I
pos2 = round(dc / self.dc_scale)
remain = dc - pos2 * self.dc_scale
pos1 = round(remain / self.dc_scale * 50.0)
self.DcI1.SetValue(pos1)
self.DcI2.SetValue(pos2)
def PositionDcQ(self, dc): # set pos1, pos2 for Q
pos2 = round(dc / self.dc_scale)
remain = dc - pos2 * self.dc_scale
pos1 = round(remain / self.dc_scale * 50.0)
self.DcQ1.SetValue(pos1)
self.DcQ2.SetValue(pos2)
def OnChange(self, event):
dc = self.dc_scale * self.DcI1.GetValue() / 50.0 + self.dc_scale * self.DcI2.GetValue()
if abs(dc) < self.dc_scale * 3.0 / 50.0:
dc = 0.0
self.t_DcI.SetLabel(self.f_DcI % dc)
self.DcI = dc
dc = self.dc_scale * self.DcQ1.GetValue() / 50.0 + self.dc_scale * self.DcQ2.GetValue()
if abs(dc) < self.dc_scale * 3.0 / 50.0:
dc = 0.0
self.t_DcQ.SetLabel(self.f_DcQ % dc)
self.DcQ = dc
self.hardware.NewUdpCorrect(self.DcI, self.DcQ)
def OnFineDcI(self, event): # re-center the fine slider when the coarse slider is adjusted
dc = self.dc_scale * self.DcI1.GetValue() / 50.0 + self.dc_scale * self.DcI2.GetValue()
self.PositionDcI(dc)
self.OnChange(event)
def OnFineDcQ(self, event): # re-center the fine slider when the coarse slider is adjusted
dc = self.dc_scale * self.DcQ1.GetValue() / 50.0 + self.dc_scale * self.DcQ2.GetValue()
self.PositionDcQ(dc)
self.OnChange(event)
def OnBtnSave(self, event):
self.hardware.CorrectTxDc[self.hardware.band] = (self.hardware.tx_frequency * 1E-6, self.DcI, self.DcQ)
self.hardware.PrintUdpCorrect()
self.parent.correct_screen = None
self.Destroy()
def OnBtnCancel(self, event=None):
self.hardware.NewUdpCorrect(self.old_DcI, self.old_DcQ)
self.parent.correct_screen = None
self.Destroy()

1
plot_wxmplot.py Executable file
View File

@ -0,0 +1 @@
# plot using the wxmplot package

85
portaudio.py Executable file
View File

@ -0,0 +1,85 @@
#! /usr/bin/python
# Test for PortAudio devices using ctypes
from __future__ import print_function
import ctypes, ctypes.util
class PaDeviceInfo (ctypes.Structure):
_fields_ = [
('structVersion', ctypes.c_int),
('name', ctypes.c_char_p),
('hostApi', ctypes.c_int), # PaHostApiIndex
('maxInputChannels', ctypes.c_int),
('maxOutputChannels', ctypes.c_int),
('defaultLowInputLatency', ctypes.c_double), # PaTime
('defaultLowOutputLatency', ctypes.c_double), # PaTime
('defaultHighInputLatency', ctypes.c_double), # PaTime
('defaultHighOutputLatency', ctypes.c_double), # PaTime
('defaultSampleRate', ctypes.c_double),
]
class PaHostApiInfo (ctypes.Structure):
_fields_ = [
('structVersion', ctypes.c_int),
('type', ctypes.c_int), # enum PaHostApiTypeId
('name', ctypes.c_char_p),
('deviceCount', ctypes.c_int),
('defaultInputDevice', ctypes.c_int),
('defaultOutputDevice', ctypes.c_int),
]
class PaStreamParameters (ctypes.Structure):
_fields_ = [
('device', ctypes.c_int), #PaDeviceIndex
('channelCount', ctypes.c_int),
('sampleFormat', ctypes.c_ulong), #PaSampleFormat
('suggestedLatency', ctypes.c_double), # PaTime
('hostApiSpecificStreamInfo', ctypes.c_void_p),
]
pa_name = ctypes.util.find_library("portaudio")
pa = ctypes.CDLL(pa_name)
pa.Pa_GetDeviceInfo.restype = ctypes.POINTER(PaDeviceInfo)
pa.Pa_GetHostApiInfo.restype = ctypes.POINTER(PaHostApiInfo)
pa.Pa_GetVersionText.restype = ctypes.c_char_p
inputParameters = PaStreamParameters (device=0, channelCount=2,
sampleFormat=2, suggestedLatency=0, # format 2 is paInt32
hostApiSpecificStreamInfo=ctypes.c_void_p() )
outputParameters = PaStreamParameters (device=0, channelCount=2,
sampleFormat=2, suggestedLatency=0, # format 2 is paInt32
hostApiSpecificStreamInfo=ctypes.c_void_p() )
print('Open', pa.Pa_Initialize())
try:
print('Version', pa.Pa_GetVersion())
print('Version Text', pa.Pa_GetVersionText())
count = pa.Pa_GetDeviceCount()
print('NumDev', count)
for i in range(count):
pt_info = pa.Pa_GetDeviceInfo(i)
info = pt_info.contents
print("Device %2d, host api %s" % (i, pa.Pa_GetHostApiInfo(info.hostApi).contents.name))
print(" Name %s" % info.name)
print(" Max inputs %d, Max outputs %d" % (info.maxInputChannels, info.maxOutputChannels))
inputParameters.device = i
outputParameters.device = i
if info.maxInputChannels >= 2:
ptIn = ctypes.pointer(inputParameters)
else:
ptIn = ctypes.c_void_p()
if info.maxOutputChannels >= 2:
ptOut = ctypes.pointer(outputParameters)
else:
ptOut = ctypes.c_void_p()
print(" Speeds for 2-channel paInt32:", end=' ')
for speed in (44100, 48000, 96000, 192000):
if pa.Pa_IsFormatSupported(ptIn, ptOut, ctypes.c_double(speed)) == 0:
print(" %d" % speed, end=' ')
print()
finally:
print('Close', pa.Pa_Terminate())

8
quisk Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/python
import quisk # May be quisk.py or package quisk
if quisk.__file__.find('__init__') >= 0: # quisk is the package
import quisk.quisk as quisk
quisk.main()

5339
quisk.c Executable file

File diff suppressed because it is too large Load Diff

32
quisk.egg-info/PKG-INFO Executable file
View File

@ -0,0 +1,32 @@
Metadata-Version: 1.1
Name: quisk
Version: 4.1.52
Summary: QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.
Home-page: http://james.ahlstrom.name/quisk/
Author: James C. Ahlstrom
Author-email: jahlstr@gmail.com
License: UNKNOWN
Description: QUISK is a Software Defined Radio (SDR) transceiver.
You supply radio hardware that converts signals at the antenna to complex (I/Q) data at an
intermediate frequency (IF). Data can come from a sound card, Ethernet or USB. Quisk then filters and
demodulates the data and sends the audio to your speakers or headphones. For transmit, Quisk takes
the microphone signal, converts it to I/Q data and sends it to the hardware.
Quisk can be used with SoftRock, Hermes Lite 2, HiQSDR, Odyssey and many radios that use the Hermes protocol.
Quisk can connect to digital programs like Fldigi and WSJT-X. Quisk can be connected to other software like
N1MM+ and software that uses Hamlib.
Platform: UNKNOWN
Classifier: Development Status :: 6 - Mature
Classifier: Environment :: X11 Applications
Classifier: Environment :: Win32 (MS Windows)
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Natural Language :: English
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C
Classifier: Topic :: Communications :: Ham Radio
Provides: quisk

244
quisk.egg-info/SOURCES.txt Executable file
View File

@ -0,0 +1,244 @@
CHANGELOG.txt
Extensions.txt
MANIFEST.in
README.txt
WinEdit.pyw
WinQuisk.pyw
WinQuiskVna.pyw
__init__.py
__main__.py
_quisk.pyd
configure.py
defaults.html
docs.html
dxcluster.py
extdemod.c
filter.c
filter.h
filters.h
filters.py
freedv.c
freedv.h
help.html
help_conf.html
help_vna.html
import_quisk_api.c
is_key_down.c
libfftw3-3.dll
libgcc_s_dw2-1.dll
libusb.txt
libwinpthread-1.dll
license.txt
makefile
microphone.c
microphone.h
plot_wxmplot.py
portaudio.py
quisk
quisk.c
quisk.h
quisk.py
quisk_conf_defaults.py
quisk_conf_kx3.py
quisk_conf_model.py
quisk_conf_peaberry.py
quisk_conf_sdr8600.py
quisk_conf_sdriq.py
quisk_conf_win.py
quisk_hardware_fixed.py
quisk_hardware_hamlib.py
quisk_hardware_model.py
quisk_hardware_sdr8600.py
quisk_hardware_sdriq.py
quisk_utils.py
quisk_vna.py
quisk_widgets.py
setup.py
sound.c
sound_alsa.c
sound_directx.c
sound_portaudio.c
sound_pulseaudio.c
utility.c
windows.txt
winsound.txt
./CHANGELOG.txt
./Extensions.txt
./README.txt
./__init__.py
./__main__.py
./configure.py
./defaults.html
./docs.html
./dxcluster.py
./filters.py
./help.html
./help_conf.html
./help_vna.html
./libfftw3-3.dll
./libgcc_s_dw2-1.dll
./libusb.txt
./libwinpthread-1.dll
./license.txt
./plot_wxmplot.py
./portaudio.py
./quisk.py
./quisk_conf_defaults.py
./quisk_conf_kx3.py
./quisk_conf_model.py
./quisk_conf_peaberry.py
./quisk_conf_sdr8600.py
./quisk_conf_sdriq.py
./quisk_conf_win.py
./quisk_hardware_fixed.py
./quisk_hardware_hamlib.py
./quisk_hardware_model.py
./quisk_hardware_sdr8600.py
./quisk_hardware_sdriq.py
./quisk_utils.py
./quisk_vna.py
./quisk_widgets.py
./windows.txt
./winsound.txt
./afedrinet/SOURCE.txt
./afedrinet/__init__.py
./afedrinet/afedri.py
./afedrinet/quisk_conf.py
./afedrinet/quisk_conf_linux.py
./afedrinet/quisk_conf_mac.py
./afedrinet/quisk_hardware.py
./afedrinet/readme.txt
./afedrinet/sdr_control.py
./afedrinet/test.py
./freedvpkg/README.txt
./freedvpkg/__init__.py
./freedvpkg/libcodec2_32.dll
./freedvpkg/libcodec2_32.so
./freedvpkg/libcodec2_64.dll
./freedvpkg/libcodec2_64.so
./hermes/README.txt
./hermes/__init__.py
./hermes/quisk_conf.py
./hermes/quisk_conf2.py
./hermes/quisk_hardware.py
./hermes/quisk_widgets.py
./hiqsdr/__init__.py
./hiqsdr/quisk_conf.py
./hiqsdr/quisk_hardware.py
./n2adr/__init__.py
./n2adr/conf1.py
./n2adr/conf2.py
./n2adr/conf3.py
./n2adr/conf3A.py
./n2adr/conf4.py
./n2adr/conf5.py
./n2adr/conf6.py
./n2adr/conf7.py
./n2adr/hl2_hardware.py
./n2adr/quisk_conf.py
./n2adr/quisk_conf_8600.py
./n2adr/quisk_hardware.py
./n2adr/quisk_widgets.py
./n2adr/scanner_widgets.py
./n2adr/startup.py
./n2adr/station_hardware.py
./n2adr/uhf_conf.py
./n2adr/uhf_hardware.py
./n2adr/uhf_widgets.py
./sdriqpkg/README.txt
./sdriqpkg/__init__.py
./sdriqpkg/quisk_hardware.py
./soapypkg/__init__.py
./soapypkg/quisk_hardware.py
./soapypkg/setup.py
./softrock/README.txt
./softrock/__init__.py
./softrock/conf_fixed.py
./softrock/conf_rx_ensemble2.py
./softrock/conf_rx_tx_ensemble.py
./softrock/hardware_net.py
./softrock/hardware_usb.py
./softrock/hardware_usb_new.py
./softrock/widgets_tx.py
afedrinet/SOURCE.txt
afedrinet/__init__.py
afedrinet/af_comp.bat
afedrinet/afe_library
afedrinet/afe_library.mac
afedrinet/afedri.py
afedrinet/afedrinet_io.c
afedrinet/afedrinet_io.pyd
afedrinet/quisk_conf.py
afedrinet/quisk_conf_linux.py
afedrinet/quisk_conf_mac.py
afedrinet/quisk_hardware.py
afedrinet/readme.txt
afedrinet/sdr_control.py
afedrinet/test.py
freedvpkg/README.txt
freedvpkg/__init__.py
freedvpkg/libcodec2_32.dll
freedvpkg/libcodec2_32.so
freedvpkg/libcodec2_64.dll
freedvpkg/libcodec2_64.so
hermes/README.txt
hermes/__init__.py
hermes/quisk_conf.py
hermes/quisk_conf2.py
hermes/quisk_hardware.py
hermes/quisk_widgets.py
hiqsdr/__init__.py
hiqsdr/quisk_conf.py
hiqsdr/quisk_hardware.py
n2adr/.quisk_init.pkl
n2adr/__init__.py
n2adr/conf1.py
n2adr/conf2.py
n2adr/conf3.py
n2adr/conf3A.py
n2adr/conf4.py
n2adr/conf5.py
n2adr/conf6.py
n2adr/conf7.py
n2adr/hl2_hardware.py
n2adr/quisk_conf.py
n2adr/quisk_conf_8600.py
n2adr/quisk_hardware.py
n2adr/quisk_widgets.py
n2adr/scanner_widgets.py
n2adr/startup.py
n2adr/station_hardware.py
n2adr/uhf_conf.py
n2adr/uhf_hardware.old
n2adr/uhf_hardware.py
n2adr/uhf_widgets.py
quisk.egg-info/PKG-INFO
quisk.egg-info/SOURCES.txt
quisk.egg-info/dependency_links.txt
quisk.egg-info/entry_points.txt
quisk.egg-info/top_level.txt
sdriqpkg/README.txt
sdriqpkg/__init__.py
sdriqpkg/quisk_hardware.py
sdriqpkg/sdriq.c
sdriqpkg/sdriq.h
sdriqpkg/sdriq.pyd
soapypkg/__init__.py
soapypkg/makefile
soapypkg/quisk_hardware.py
soapypkg/setup.py
soapypkg/soapy.c
soapypkg/soapy.pyd
soapypkg/build/import_quisk_api.o
soapypkg/build/temp.linux-x86_64-2.7/soapy.o
soapypkg/build/temp.linux-x86_64-3.6/soapy.o
soapypkg/build/temp.win32-2.7/import_quisk_api.o
softrock/README.txt
softrock/__init__.py
softrock/conf_fixed.py
softrock/conf_rx_ensemble2.py
softrock/conf_rx_tx_ensemble.py
softrock/hardware_net.py
softrock/hardware_usb.py
softrock/hardware_usb_new.py
softrock/widgets_tx.py

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,4 @@
[gui_scripts]
quisk = quisk.quisk:main
quisk_vna = quisk.quisk_vna:main

1
quisk.egg-info/top_level.txt Executable file
View File

@ -0,0 +1 @@
quisk

358
quisk.h Executable file
View File

@ -0,0 +1,358 @@
#define DEBUG_IO 0
#define DEBUG_MIC 0
// Sound parameters
//
#define QUISK_SC_SIZE 128
#define QUISK_PATH_SIZE 256 // max file path length
#define IP_SIZE 32
#define MAX_FILTER_SIZE 10001
#define BIG_VOLUME 2.2e9
#define CLOSED_TEXT "The sound device is closed."
#define CLIP32 2147483647
#define CLIP16 32767
#define SAMP_BUFFER_SIZE 66000 // size of arrays used to capture samples
#define IMD_TONE_1 1200 // frequency of IMD test tones
#define IMD_TONE_2 1600
#define INTERP_FILTER_TAPS 85 // interpolation filter
#define MIC_OUT_RATE 48000 // mic post-processing sample rate
#define PA_LIST_SIZE 16 // max number of pulseaudio devices
#define QUISK_MAX_RECEIVERS 9 // max number of receiver channels
// Test the audio: 0 == No test; normal operation;
// 1 == Copy real data to the output; 2 == copy imaginary data to the output;
// 3 == Copy transmit audio to the output.
#define TEST_AUDIO 0
#ifdef MS_WINDOWS
#define QUISK_SHUT_RD SD_RECEIVE
#define QUISK_SHUT_BOTH SD_BOTH
#else
#define SOCKET int
#define INVALID_SOCKET -1
#define QUISK_SHUT_RD SHUT_RD
#define QUISK_SHUT_BOTH SHUT_RDWR
#endif
#if PY_MAJOR_VERSION >= 3
#define PyInt_FromLong PyLong_FromLong
#define PyInt_Check PyLong_Check
#define PyInt_AsLong PyLong_AsLong
#define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask
#endif
#define PyString_FromString PyUnicode_FromString
typedef enum _rx_mode {
CWL = 0,
CWU = 1,
LSB = 2,
USB = 3,
AM = 4,
FM = 5,
EXT = 6,
DGT_U = 7,
DGT_L = 8,
DGT_IQ = 9,
IMD = 10,
FDV_U = 11,
FDV_L = 12,
DGT_FM = 13
} rx_mode_type;
// Pulseaudio support added by Philip G. Lee. Many thanks!
/*!
* \brief Specifies which driver a \c sound_dev is opened with
*/
typedef enum dev_driver{
DEV_DRIVER_NONE = 0,
DEV_DRIVER_PORTAUDIO,
DEV_DRIVER_ALSA,
DEV_DRIVER_PULSEAUDIO
} dev_driver_t;
struct sound_dev { // data for sound capture or playback device
char name[QUISK_SC_SIZE]; // string name of device
char stream_description[QUISK_SC_SIZE]; // Short description of device/stream
void * handle; // Handle of open device, or NULL
dev_driver_t driver; // Which audio driver the device is using
void * buffer; // Handle of buffer for device
int portaudio_index; // index of portaudio device, or -1
int doAmplPhase; // Amplitude and Phase corrections
double AmPhAAAA;
double AmPhCCCC;
double AmPhDDDD;
double portaudio_latency; // Suggested latency for portaudio device
int sample_rate; // Sample rate such as 48000, 96000, 192000
int sample_bytes; // Size of one channel sample in bytes, either 2 or 3 or 4
int num_channels; // number of channels per frame: 1, 2, 3, ...
int channel_I; // Index of I and Q channels: 0, 1, ...
int channel_Q;
int channel_Delay; // Delay this channel by one sample; -1 for no delay, else channel_I or _Q
int overrange; // Count for ADC overrange (clip) for device
// Number of frames for a read request.
// If 0, the read should be non-blocking and read all available
// frames.
int read_frames;
int latency_frames; // desired latency in audio play samples
int play_buf_size; // size of playback buffer in samples
int use_float; // DirectX: Use IEEE floating point
int dataPos; // DirectX: data position
int oldPlayPos; // DirectX: previous value of playPos
int play_delay; // DirectX: bytes of sound available to play
int started; // DirectX: started flag or state
int dev_error; // read or write error
int dev_underrun; // lack of samples to play
int dev_latency; // latency frames
unsigned int rate_min; // min and max available sample rates
unsigned int rate_max;
unsigned int chan_min; // min and max available number of channels
unsigned int chan_max;
complex double dc_remove; // filter to remove DC from samples
double save_sample; // Used to delay the I or Q sample
char msg1[QUISK_SC_SIZE]; // string for information message
int stream_dir_record; // 1 for recording, 0 for playback
char server[IP_SIZE]; // server string for remote pulseaudio
int stream_format; // format of pulseaudio device
int pulse_stream_state; // state of the pulseaudio stream
volatile int cork_status; // 1 for corked, 0 for uncorked
double average_square; // average of squared sample magnitude
} ;
struct sound_conf {
char dev_capt_name[QUISK_SC_SIZE];
char dev_play_name[QUISK_SC_SIZE];
int sample_rate; // Input sample rate from the ADC
int playback_rate; // Output play rate to sound card
int data_poll_usec;
int latency_millisecs;
unsigned int rate_min;
unsigned int rate_max;
unsigned int chan_min;
unsigned int chan_max;
int read_error;
int write_error;
int underrun_error;
int overrange; // count of ADC overrange (clip) for non-soundcard device
int latencyCapt;
int latencyPlay;
int interupts;
char msg1[QUISK_SC_SIZE];
char err_msg[QUISK_SC_SIZE];
// These parameters are for the microphone:
char mic_dev_name[QUISK_SC_SIZE]; // capture device
char name_of_mic_play[QUISK_SC_SIZE]; // playback device
char mic_ip[IP_SIZE];
int mic_sample_rate; // capture sample rate
int mic_playback_rate; // playback sample rate
int tx_audio_port;
int mic_read_error;
int mic_channel_I; // channel number for microphone: 0, 1, ...
int mic_channel_Q;
double mic_out_volume;
char IQ_server[IP_SIZE]; //IP address of optional streaming IQ server (pulseaudio)
int verbose_pulse; //verbose output for pulse audio
} ;
enum quisk_rec_state {
IDLE,
RECORD_RADIO,
RECORD_MIC,
PLAYBACK,
PLAY_FILE,
PLAY_SAMPLES } ;
extern enum quisk_rec_state quisk_record_state;
struct QuiskWav { // data to create a WAV or RAW audio file
double scale;
int sample_rate;
short format; // RAW is 0; PCM integer is 1; IEEE float is 3.
short nChan;
short bytes_per_sample;
FILE * fp;
unsigned int samples;
int fpStart;
int fpEnd;
int fpPos;
} ;
void QuiskWavClose(struct QuiskWav *);
int QuiskWavWriteOpen(struct QuiskWav *, char *, short, short, short, int, double);
void QuiskWavWriteC(struct QuiskWav *, complex double *, int);
void QuiskWavWriteD(struct QuiskWav *, double *, int);
int QuiskWavReadOpen(struct QuiskWav *, char *, short, short, short, int, double);
void QuiskWavReadC(struct QuiskWav *, complex double *, int);
void QuiskWavReadD(struct QuiskWav *, double *, int);
void QuiskMeasureRate(const char *, int);
extern struct sound_conf quisk_sound_state, * pt_quisk_sound_state;
extern int mic_max_display; // display value of maximum microphone signal level
extern int quiskSpotLevel; // 0 for no spotting; else the level 10 to 1000
extern int data_width;
extern int quisk_using_udp; // is a UDP port used for capture (0 or 1)?
extern int quisk_rx_udp_started; // have we received any data?
extern rx_mode_type rxMode; // mode CWL, USB, etc.
extern int quisk_tx_tune_freq; // Transmit tuning frequency as +/- sample_rate / 2
extern PyObject * quisk_pyConfig; // Configuration module instance
extern double quisk_mic_preemphasis; // Mic preemphasis 0.0 to 1.0; or -1.0
extern double quisk_mic_clip; // Mic clipping; try 3.0 or 4.0
extern int quisk_noise_blanker; // Noise blanker level, 0 for off
extern int quisk_sidetoneCtrl; // sidetone control value 0 to 1000
extern int quiskKeyupDelay; // key-up delay from the config file
extern double quisk_audioVolume; // volume control for radio sound playback, 0.0 to 1.0
extern int quiskImdLevel; // level for rxMode IMD
extern int quiskTxHoldState; // state machine for Tx wait for repeater frequency shift
extern double quisk_ctcss_freq; // frequency in Hertz
extern unsigned char quisk_pc_to_hermes[17 * 4]; // Data to send from the PC to the Hermes hardware
extern unsigned char quisk_hermeslite_writequeue[4 * 5]; // One-time writes to Hermes-Lite
extern unsigned int quisk_hermeslite_writepointer; // write pointer into write queue, nonzero value triggers writes,
extern unsigned int quisk_hermeslite_writeattempts; // counter for write retries
extern unsigned int quisk_hermes_code_version; // Hermes code version from Hermes to PC
extern unsigned int quisk_hermes_board_id; // Hermes board ID from Hermes to PC
extern int quisk_use_rx_udp; // Method of access to UDP hardware
extern complex double cRxFilterOut(complex double, int, int);
extern int quisk_multirx_count; // number of additional receivers zero or 1, 2, 3, ..
extern struct sound_dev quisk_DigitalRx1Output; // Output sound device for sub-receiver 1
extern int quisk_is_vna; // is this the VNA program?
extern PyObject * quisk_set_spot_level(PyObject * , PyObject *);
extern PyObject * quisk_get_tx_filter(PyObject * , PyObject *);
extern PyObject * quisk_set_ampl_phase(PyObject * , PyObject *);
extern PyObject * quisk_capt_channels(PyObject * , PyObject *);
extern PyObject * quisk_play_channels(PyObject * , PyObject *);
extern PyObject * quisk_micplay_channels(PyObject * , PyObject *);
extern PyObject * quisk_sound_devices(PyObject * , PyObject *);
extern PyObject * quisk_pa_sound_devices(PyObject * , PyObject *);
extern PyObject * quisk_sound_errors(PyObject *, PyObject *);
extern PyObject * quisk_set_file_record(PyObject *, PyObject *);
extern PyObject * quisk_set_file_name(PyObject *, PyObject *, PyObject *);
extern PyObject * quisk_set_tx_audio(PyObject *, PyObject *, PyObject *);
extern PyObject * quisk_is_vox(PyObject *, PyObject *);
extern PyObject * quisk_set_udp_tx_correct(PyObject *, PyObject *);
extern PyObject * quisk_set_hermes_filter(PyObject *, PyObject *);
extern PyObject * quisk_set_alex_hpf(PyObject *, PyObject *);
extern PyObject * quisk_set_alex_lpf(PyObject *, PyObject *);
extern PyObject * quisk_freedv_open(PyObject *, PyObject *);
extern PyObject * quisk_freedv_close(PyObject *, PyObject *);
extern PyObject * quisk_freedv_get_snr(PyObject *, PyObject *);
extern PyObject * quisk_freedv_get_version(PyObject *, PyObject *);
extern PyObject * quisk_freedv_get_rx_char(PyObject *, PyObject *);
extern PyObject * quisk_freedv_set_options(PyObject *, PyObject *, PyObject *);
extern PyObject * quisk_set_sparams(PyObject *, PyObject *, PyObject *);
// These function pointers are the Start/Stop/Read interface for
// the SDR-IQ and any other C-language extension modules that return
// radio data samples.
typedef void (* ty_sample_start)(void);
typedef void (* ty_sample_stop)(void);
typedef int (* ty_sample_read)(complex double *);
typedef int (* ty_sample_write)(complex double *, int);
extern ty_sample_write quisk_pt_sample_write;
void quisk_open_sound(void);
void quisk_close_sound(void);
int quisk_process_samples(complex double *, int);
void quisk_play_samples(complex double *, int);
void quisk_play_zeros(int);
void quisk_start_sound(void);
int quisk_get_overrange(void);
void quisk_mixer_set(char *, int, PyObject *, char *, int);
int quisk_read_sound(void);
int quisk_process_microphone(int, complex double *, int);
void quisk_open_mic(void);
void quisk_close_mic(void);
int quisk_open_key(const char *);
void quisk_close_key(void);
void quisk_set_key_down(int);
void quisk_set_tx_mode(void);
void ptimer(int);
int quisk_extern_demod(complex double *, int, double);
void quisk_tmp_microphone(complex double *, int);
void quisk_tmp_record(complex double * , int, double);
void quisk_file_microphone(complex double *, int);
void quisk_file_playback(complex double *, int, double);
void quisk_tmp_playback(complex double *, int, double);
void quisk_hermes_tx_send(int, int *);
void quisk_udp_mic_error(char *);
void quisk_check_freedv_mode(void);
void quisk_calc_audio_graph(double, complex double *, double *, int, int);
int QuiskDeltaMsec(int);
// Functions supporting digital voice codecs
typedef int (* ty_dvoice_codec_rx)(complex double *, double *, int, int);
typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int);
extern ty_dvoice_codec_rx pt_quisk_freedv_rx;
extern ty_dvoice_codec_tx pt_quisk_freedv_tx;
// Driver function definitions=================================================
int quisk_read_alsa(struct sound_dev *, complex double *);
void quisk_play_alsa(struct sound_dev *, int, complex double *, int, double);
void quisk_start_sound_alsa(struct sound_dev **, struct sound_dev **);
void quisk_close_sound_alsa(struct sound_dev **, struct sound_dev **);
int quisk_read_portaudio(struct sound_dev *, complex double *);
void quisk_play_portaudio(struct sound_dev *, int, complex double *, int, double);
void quisk_start_sound_portaudio(struct sound_dev **, struct sound_dev **);
void quisk_close_sound_portaudio(void);
void play_sound_interface(struct sound_dev * , int, complex double * , int, double);
int quisk_read_pulseaudio(struct sound_dev *, complex double *);
void quisk_play_pulseaudio(struct sound_dev *, int, complex double *, int, double);
void quisk_start_sound_pulseaudio(struct sound_dev **, struct sound_dev **);
void quisk_close_sound_pulseaudio(void);
void quisk_cork_pulseaudio(struct sound_dev *, int);
void quisk_flush_pulseaudio(struct sound_dev *);
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/*
Functions defined below this point are available for export to other extension modules using the
standard Python CObject or Capsule interface. See the documentation in import_quisk_api.c. Note
that index zero is used for a structure pointer, not a function pointer.
To add a function, declare it twice, use the next array index, and add it to QUISK_API_INIT.
Be very careful; here be dragons!
*/
#ifdef IMPORT_QUISK_API
// For use by modules that import the _quisk symbols
extern void ** Quisk_API; // array of pointers to functions and variables from module _quisk
int import_quisk_api(void); // used to initialize Quisk_API
#define QuiskGetConfigInt (*( int (*) (const char *, int) )Quisk_API[1])
#define QuiskGetConfigDouble (*( double (*) (const char *, double) )Quisk_API[2])
#define QuiskGetConfigString (*( char * (*) (const char *, char *) )Quisk_API[3])
#define QuiskTimeSec (*( double (*) (void) )Quisk_API[4])
#define QuiskSleepMicrosec (*( void (*) (int) )Quisk_API[5])
#define QuiskPrintTime (*( void (*) (const char *, int) )Quisk_API[6])
#define quisk_sample_source (*( void (*) (ty_sample_start, ty_sample_stop, ty_sample_read) )Quisk_API[7])
#define quisk_dvoice_freedv (*( void (*) (ty_dvoice_codec_rx, ty_dvoice_codec_tx) )Quisk_API[8])
#define quisk_is_key_down (*( int (*) (void) )Quisk_API[9])
#define quisk_sample_source4 (*( void (*) (ty_sample_start, ty_sample_stop, ty_sample_read, ty_sample_write) )Quisk_API[10])
#else
// Used to export symbols from _quisk in quisk.c
int QuiskGetConfigInt(const char *, int);
double QuiskGetConfigDouble(const char *, double);
char * QuiskGetConfigString(const char *, char *);
double QuiskTimeSec(void);
void QuiskSleepMicrosec(int);
void QuiskPrintTime(const char *, int);
void quisk_sample_source(ty_sample_start, ty_sample_stop, ty_sample_read);
void quisk_dvoice_freedv(ty_dvoice_codec_rx, ty_dvoice_codec_tx);
int quisk_is_key_down(void);
void quisk_sample_source4(ty_sample_start, ty_sample_stop, ty_sample_read, ty_sample_write);
#define QUISK_API_INIT { \
&quisk_sound_state, &QuiskGetConfigInt, &QuiskGetConfigDouble, &QuiskGetConfigString, &QuiskTimeSec, \
&QuiskSleepMicrosec, &QuiskPrintTime, &quisk_sample_source, &quisk_dvoice_freedv, &quisk_is_key_down, \
&quisk_sample_source4 \
}
#endif

5956
quisk.py Executable file

File diff suppressed because it is too large Load Diff

2292
quisk_conf_defaults.py Executable file

File diff suppressed because it is too large Load Diff

37
quisk_conf_kx3.py Executable file
View File

@ -0,0 +1,37 @@
from __future__ import absolute_import
# Please do not change this configuration file for Quisk. Copy it to
# your own config file and make changes there.
#
# This config file is for hamlib control of a KX3 through hamlib, with Quisk
# acting as a panadapter. The daemon rigctld must be running. The open() method
# below tries to start it, or you can start it by hand.
import sys, os
if sys.platform == "win32":
name_of_sound_capt = 'Primary'
name_of_sound_play = 'Primary'
latency_millisecs = 150
data_poll_usec = 20000
else:
name_of_sound_capt = 'hw:0'
name_of_sound_play = 'hw:0'
latency_millisecs = 150
data_poll_usec = 5000
# Use the hamlib hardware module to talk to the KX3
from quisk_hardware_hamlib import Hardware as BaseHardware
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
# Change the port and timing parameters here:
# self.hamlib_rigctld_port = 4532 # Standard rigctld control port
# self.hamlib_poll_seconds = 0.2 # Time interval to poll for changes
def open(self):
ret = BaseHardware.open(self)
if not self.hamlib_connected: # rigctld is not started. Try to start it.
os.system("rigctld -m 229 -r /dev/ttyUSB0 -s 4800 & ") # Check the baud rate menu setting
# If this fails, start rigctld by hand.
return ret

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