22 Commits
Author SHA1 Message Date
James C. Ahlstrom e219a5b975 Add PTT to TX0 2026-09-15 15:54:57 -04:00
James C. Ahlstrom 2a530e5d11 Add TX0 samples 2026-09-15 15:32:53 -04:00
James C. Ahlstrom ceab3bed26 Set up the MicronTRX project by David Fainitski, N7DDC 2026-08-20 15:05:13 -04:00
James C. Ahlstrom 674bbad1ab fix non-ascii char in setup.py 2026-08-20 14:08:21 -04:00
James C. Ahlstrom 67c6fbcad9 Quisk version 4.2.54 2026-08-04 12:01:45 -04:00
James C. Ahlstrom 84e194c644 Quisk version 4.2.53 2026-06-30 17:06:52 -04:00
James C. Ahlstrom b45a71b017 Quisk version 4.2.52 2026-05-30 11:18:52 -04:00
James C. Ahlstrom e6b9d9dd05 Quisk version 4.2.51 2026-05-10 15:22:33 -04:00
James C. Ahlstrom 1f68d05cd5 Quisk version 4.2.50 2025-12-29 14:01:36 -05:00
James C. Ahlstrom 018fc0e871 Quisk version 4.2.49 2025-12-22 15:12:47 -05:00
James C. Ahlstrom 1111d24333 Quisk version 4.2.48 2025-11-24 12:28:01 -05:00
James C. Ahlstrom d8aee9284d Quisk version 4.2.47 2025-11-22 13:39:10 -05:00
James C. Ahlstrom 7121dacbbd Quisk version 4.2.46 2025-10-12 12:43:21 -04:00
James C. Ahlstrom 0528e8bbbe Quisk version 4.2.45 2025-10-08 16:29:31 -04:00
James C. Ahlstrom 458e43252d Quisk version 4.2.44 2025-09-22 16:24:45 -04:00
James C. Ahlstrom fd0a8a3d0e Correct comment in Makefile 2025-09-09 17:00:29 -04:00
James C. Ahlstrom 6c2e9012c4 Quisk version 4.2.43 2025-09-08 12:06:26 -04:00
James C. Ahlstrom f5c4732eba Quisk version 4.2.42 2025-04-11 15:33:20 -04:00
James C. Ahlstrom 1c209457dc Quisk version 4.2.41 2025-01-30 13:48:38 -05:00
James C. Ahlstrom 7e8a0846f7 Quisk version 4.2.40 2024-11-15 16:39:47 -05:00
James C. Ahlstrom fb888a2e08 Quisk version 4.2.39 2024-10-01 14:22:23 -04:00
James C. Ahlstrom 78a1ca3d60 Quisk version 4.2.38 2024-09-01 13:57:53 -04:00
213 changed files with 73014 additions and 2071 deletions
+136
View File
@@ -1,3 +1,139 @@
Quisk Version 4.2.54 August 2026
=================================
Quisk can now accept audio samples for Tx from TCI at rates 48000, 24000, 12000 and 8000. Please test.
Quisk now includes program icons in the directory "quisk/icons". The Windows icon is quisk/icons/quisk.ico.
On Windows, right-click the desktop icon and change the icon to <path-to-quisk>/quisk/icons/quisk.ico.
The Linux icon is quisk/icons/quisk.svg. On Linux, change the quisk.desktop file to
Icon=<path-to-quisk>/quisk/icons/quisk.svg.
Quisk Version 4.2.53 June 2026
===============================
I added TCI support for Software Defined Connectors CW skimmer. Please test.
This version of Quisk now works on Python 3.14 and higher. I changed to a new build system because
the old one has been deprecated by Python. They no longer support running setup.py.
The Linux version has a new makefile. The Windows version is installed by pip as usual.
Please test.
Quisk Version 4.2.52 May 2026
==============================
This is a fix for various TCI bugs. Please test with WSJT-X and let me know if all problems are solved.
It is unlikely that other digital software will connect without problems, but please test and let me know.
Quisk Version 4.2.51 May 2026
==============================
This version can use the TCI protocol to connect to WSJT-X. Please see Digital Modes in the documentation.
Quisk Version 4.2.50 December 2025
===================================
This is a quick fix for a bug in 4.2.49 that results in the repeated message
AttributeError: 'Hardware' object has no attribute 'is_HermesLite2'
Quisk Version 4.2.49 December 2025
===================================
This version adds more features for the Anan 10E and other OpenHPSDR radios. Please test.
I added the forward power, SWR, temperature and PA current display to the OpenHPSDR radio. But
I don't have that radio so I need some help. I think the forward and reverse power connections
are correct, but it is still necessary to create a table to convert analog input codes to power.
Go to the Hardware screen and check the "Power meter calibration" setting. You will need to enter
"New" and create a suitable table. To see the ADC values, choose table "Show ADC".
There is no protocol standard to specify which analog input is used for temperature and PA current.
And AFAIK the Anan 10E has no such sensors. I am hoping to use the board ID to find the right input.
The ADC conversion is linear and the two points are specified on the Hardware screen.
I currently assigned the analog inputs as follows:
AIN1 Forward power
AIN2 Reverse power
AIN3 PA current
AIN5 Temperature
If you need to see the ADC codes 0-4095 for each input, go to hermes/quisk_hardware.py and
change DEBUG_SENSORS to one.
Quisk Version 4.2.48 November 2025
===================================
This is a quisk fix for a bug in the new OpenHPSDR radios. It fixes use_rx_udp that was reset to zero.
Quisk Version 4.2.47 November 2025
===================================
To improve the performance of Quisk with the Anan 10-E and other OpenHPSDR radios, I added
a new type of radio "OpenHPSDR". Use this type for the Anan 10-E and similar radios. It removes
all the protocol extensions added by the Hermes-Lite2 project. I removed the configuration
option "Hermes Lite2 protocol enable". This is now automatic.
I added buttons for antennas 0, 1, 2 and 3 for OpenHPSDR radios that support more antennas.
I added controls for the OpenHPSDR preamplifier and attenuator.
I do not have any Apache Labs radios, so I must depend on my users to report problems and solutions.
Quisk Version 4.2.46 October 2025
==================================
I fixed a bug in Hamlib rig 2 which happens with the new Pure Signal code. The Hamlib "f" command
now always returns the main receiver frequency, not one of the added receiver frequencies. This is
consistent with the Flex ZZ commands.
Quisk Version 4.2.45 October 2025
==================================
The new FreeDV including RADE mode is now a built-in mode of Quisk. Just press "FDV" to talk FreeDV.
In brief, install FreeDV-GUI from the web site. Then configure the usual Quisk digital input and
output that you use for digital modes. In FreeDV-GUI use the digital input and output. For the
speakers and microphone use the same ones as Quisk. Don't forget to configure your call sign in
FreeDV so it works with FreeDV Reporter. For detailed instructions see http://james.ahlstrom.name/rade.html.
The Quisk digital output device is now always active; a DGT mode is not required.
Quisk Version 4.2.44 September 2025
====================================
Quisk now has Pure Signal for the Hermes Lite2. This is baseband predistortion to reduce IMD products.
To use it, press "PsCal" for calibration, and "PS" to enable Pure Signal. There are no adjustments needed.
More details are in the documentation.
Quisk Version 4.2.43 September 2025
====================================
I fixed a bug in the WDSP library NR2 noise reduction feature. I added the "Trained" method of NR2.
Quisk uses the WDSP library for the NR2 noise reduction and the SNB noise blanker. The WDSP library must be available
for these features to work. For windows, the library is included. For Linux you must build it by changing to the
quisk/wdsp directory and entering "make". The library is used in-place and no installation is necessary.
The new "trained" method of NR2 is included. Press the NR2 button twice to select "NR2t".
I added documentation for the new FreeDV mode RADE. See http://james.ahlstrom.name/rade.html.
The default IMD magnitude is now 100%.
Quisk Version 4.2.42 April 2025
================================
I fixed a bug in the Kenwood IF CAT command. Thanks Nick! I moved the Pulse server IP address to the Sound screen.
I added a CAT method to imitate the Elecraft K4 command set over a TCP port. Quisk now has four CAT controls. To
make this clearer I added more documentation to the Remote screen.
Quisk Version 4.2.41 January 2025
==================================
I made some improvements to the top-level Config/Radios screen. There is now an option to
make a new radio by copying settings from an existing radio. I worked around a bug in wxPython
and gtk3 that caused drawing problems on the config screens. This problem is not present on Windows.
Quisk Version 4.2.40 November 2024
===================================
Python 3.13 eliminated the telnetlib module, so I wrote a replacement. I fixed a bug in window sizing.
If you use the Dx Cluster feature, please report any problems. Dx Cluster used to use the telnetlib module
but is now new code.
I updated the install to Python 3.13. Please report any problems with Python 3.13.
Quisk Version 4.2.39 October 2024
==================================
I added an optional Hardware method PollGuiControl(self). If you define this in your Hardware file,
the GUI thread will call it frequently. It can be used to set PTT from your hardware.
I am trying to work around a wxPython quirk that causes mouse wheel tuning to fail. Please test.
Quisk Version 4.2.38 September 2024
====================================
This is a bugfix release. I fixed a bug in the Hamlib serial port when using a physical TTY instead
of a pseudo-tty. The Hamlib ZZFA command now changes the receive frequency instead of the transmit
frequency. I fixed some window sizing issues.
Quisk Version 4.2.37 August 2024
=================================
Quisk now has a new option "Fixed tune offset" on the Config/radio/Options screen. This is meant for
-21
View File
@@ -1,21 +0,0 @@
# Quisk Software Defined Radio by N2ADR
**December 27, 2023**
This is Quisk, a Software Defined Radio (SDR).
You supply radio hardware such as Hermes Lite2 or SoftRock to convert
the antenna voltage to I/Q samples.
Then send the samples to a computer running Quisk.
The Quisk software will read the I/Q data, tune it, filter it,
demodulate it, and send the audio to headphones or speakers.
Quisk has a microphone input and a key input so it can operate as a
complete transceiver.
The web page for this project is [https://groups.io/g/n2adr-sdr](https://groups.io/g/n2adr-sdr).
The change log is [here](CHANGELOG.txt).
Installation instructions are [here](http://james.ahlstrom.name/quisk/docs.html#Installation).
The documentation is [here](http://james.ahlstrom.name/quisk/docs.html).
The help file is [here](http://james.ahlstrom.name/quisk/help.html).
+2 -2
View File
@@ -1,2 +1,2 @@
#Quisk version 4.2.37
from .quisk import main
#Quisk version 4.2.54
from .quisk import main
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -395,7 +395,7 @@ class ControlCommon(BaseHardware): # This is the Hardware class for the control
for idName in ("Mute", "NR2", "AGC", "Sqlch", "NB 1", "Notch", "Test 1", "Spot", "FDX", "PTT", "VOX"):
self.RemoteCtlSend("%s;%d\n" % (idName, app.idName2Button[idName].GetIndex()))
# Menus
for menu in (app.NB_menu, app.split_menu, app.freedv_menu, app.smeter_menu):
for menu in (app.NB_menu, app.split_menu, app.smeter_menu):
if menu:
for nid in menu.id2data:
menu_item = menu.FindItemById(nid)
+1
View File
@@ -9,6 +9,7 @@
#include <Python.h>
#include <complex.h>
#include <math.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
+4 -3
View File
@@ -3,6 +3,7 @@
#ifdef MS_WINDOWS
#include <Winsock2.h>
#include <windows.h>
#include <string.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
@@ -105,7 +106,7 @@ static PyObject * open_rx_udp(const char * ip, int port)
wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
sprintf(buf, "Failed to initialize Winsock (WSAStartup)");
return PyString_FromString(buf);
return PyUnicode_FromString(buf);
}
#endif
// quisk_using_udp = 1;
@@ -145,7 +146,7 @@ static PyObject * open_rx_udp(const char * ip, int port)
else {
sprintf(buf, "Failed to open socket");
}
return PyString_FromString(buf);
return PyUnicode_FromString(buf);
}
static PyObject * close_rx_udp(PyObject * self, PyObject * args)
@@ -343,7 +344,7 @@ static PyObject * open_samples(PyObject * self, PyObject * args)
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
// return PyUnicode_FromString(buf); // return a string message
}
// Miscellaneous functions needed by the SDR-IQ; called from the GUI thread as
+21 -4
View File
@@ -1,7 +1,24 @@
MODULE_NAME := afedrinet_io
OBJDIR := ../build/local
afedrinet2:
python2 setup.py build_ext --force --inplace
CFLAGS := $(shell python3 ../makefil.py --cflags)
LDFLAGS := $(shell python3 ../makefil.py --ldflags)
EXTENSION_SUFFIX := $(shell python3 ../makefil.py --suffix)
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
afedrinet3:
# Default target: Link the shared library object
$(TARGET): afedrinet_io.o $(OBJDIR)/import_quisk_api.o $(OBJDIR)/is_key_down.o
$(CC) -o $@ afedrinet_io.o $(LDFLAGS) $(OBJDIR)/import_quisk_api.o $(OBJDIR)/is_key_down.o
# Compile the C source files into object files
afedrinet_io.o: afedrinet_io.c ../quisk.h
$(CC) -o $@ -c $< $(CFLAGS) -I..
.PHONY: clean
clean:
-rm -f *.o
-rm -f $(MODULE_NAME)*.so
.PHONY: afedrinet-old
afedrinet-old:
python3 setup.py build_ext --force --inplace
+155
View File
@@ -0,0 +1,155 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "base64.h"
static const unsigned char base64_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* base64_encode - Base64 encode
* @src: Data to be encoded
* @len: Length of the data to be encoded
* @out_len: Pointer to output length variable, or %NULL if not used
* Returns: Allocated buffer of out_len bytes of encoded data,
* or %NULL on failure
*
* Caller is responsible for freeing the returned buffer. Returned buffer is
* nul terminated to make it easier to use as a C string. The nul terminator is
* not included in out_len.
*/
unsigned char * base64_encode(const unsigned char *src, size_t len,
size_t *out_len)
{
unsigned char *out, *pos;
const unsigned char *end, *in;
size_t olen;
int line_len;
olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
olen += olen / 72; /* line feeds */
olen++; /* nul termination */
if (olen < len)
return NULL; /* integer overflow */
out = malloc(olen);
if (out == NULL)
return NULL;
end = src + len;
in = src;
pos = out;
line_len = 0;
while (end - in >= 3) {
*pos++ = base64_table[in[0] >> 2];
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
*pos++ = base64_table[in[2] & 0x3f];
in += 3;
line_len += 4;
if (line_len >= 72) {
*pos++ = '\n';
line_len = 0;
}
}
if (end - in) {
*pos++ = base64_table[in[0] >> 2];
if (end - in == 1) {
*pos++ = base64_table[(in[0] & 0x03) << 4];
*pos++ = '=';
} else {
*pos++ = base64_table[((in[0] & 0x03) << 4) |
(in[1] >> 4)];
*pos++ = base64_table[(in[1] & 0x0f) << 2];
}
*pos++ = '=';
line_len += 4;
}
if (line_len)
*pos++ = '\n';
*pos = '\0';
if (out_len)
*out_len = pos - out;
return out;
}
/**
* base64_decode - Base64 decode
* @src: Data to be decoded
* @len: Length of the data to be decoded
* @out_len: Pointer to output length variable
* Returns: Allocated buffer of out_len bytes of decoded data,
* or %NULL on failure
*
* Caller is responsible for freeing the returned buffer.
*/
unsigned char * base64_decode(const unsigned char *src, size_t len,
size_t *out_len)
{
unsigned char dtable[256], *out, *pos, block[4], tmp;
size_t i, count, olen;
int pad = 0;
memset(dtable, 0x80, 256);
for (i = 0; i < sizeof(base64_table) - 1; i++)
dtable[base64_table[i]] = (unsigned char) i;
dtable['='] = 0;
count = 0;
for (i = 0; i < len; i++) {
if (dtable[src[i]] != 0x80)
count++;
}
if (count == 0 || count % 4)
return NULL;
olen = count / 4 * 3;
pos = out = malloc(olen);
if (out == NULL)
return NULL;
count = 0;
for (i = 0; i < len; i++) {
tmp = dtable[src[i]];
if (tmp == 0x80)
continue;
if (src[i] == '=')
pad++;
block[count] = tmp;
count++;
if (count == 4) {
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
*pos++ = (block[2] << 6) | block[3];
count = 0;
if (pad) {
if (pad == 1)
pos--;
else if (pad == 2)
pos -= 2;
else {
/* Invalid padding */
free(out);
return NULL;
}
break;
}
}
}
*out_len = pos - out;
return out;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#ifndef BASE64_H
#define BASE64_H
#include <sys/types.h>
unsigned char * base64_encode(const unsigned char *src, size_t len,
size_t *out_len);
unsigned char * base64_decode(const unsigned char *src, size_t len,
size_t *out_len);
#endif /* BASE64_H */
+261 -90
View File
@@ -48,6 +48,7 @@ name2format = {
# Increasing the software version will display a message to re-read the soapy device.
soapy_software_version = 3
wxpython_gtk3_bug = 0
def FormatKhz(dnum): # Round to 3 decimal places; remove ending ".000"
t = "%.3f" % dnum
@@ -87,6 +88,7 @@ class Configuration:
platform_ignore = 'win_'
self.sections = []
self.receiver_data = []
self.receiver_explain = []
self.StatePath = conf.settings_file_path
if not self.StatePath:
self.StatePath = os.path.join(app.QuiskFilesDir, "quisk_settings.json")
@@ -157,9 +159,9 @@ class Configuration:
dlg.Destroy()
else:
radio_dict["use_soapy"] = '0'
if radio_type not in ("HiQSDR", "Hermes", "Red Pitaya", "Odyssey", "Odyssey2"):
if radio_type not in ("HiQSDR", "Hermes", "Red Pitaya", "Odyssey", "Odyssey2", "OpenHPSDR"):
radio_dict["use_rx_udp"] = '0'
if radio_type in ("Hermes", "Red Pitaya", "Odyssey2"):
if radio_type in ("Hermes", "Red Pitaya", "Odyssey2", "OpenHPSDR"):
if "Hermes_BandDict" not in radio_dict:
radio_dict["Hermes_BandDict"] = {}
if "Hermes_BandDictTx" not in radio_dict:
@@ -384,10 +386,20 @@ class Configuration:
app.bottom_widgets = dct['BottomWidgets'](app, hardware, conf, frame, gbs, vertBox)
return True
def OnPageChanging(self, event): # Called when the top level page changes (not RadioNotebook pages)
global wxpython_gtk3_bug
event.Skip()
notebook = event.GetEventObject()
index = event.GetSelection()
page = notebook.GetPage(index)
if sys.platform != 'win32': # Work around a bug in wxPython 4.2.1 and gtk3
w, h = application.main_frame.GetSize() # Change main window size by +/- one pixel to recalculate sizes
if wxpython_gtk3_bug:
wxpython_gtk3_bug = 0
h += 1
else:
wxpython_gtk3_bug = 1
h -= 1
application.main_frame.SetSize((w, h))
if isinstance(page, RadioNotebook):
if not page.pages:
page.MakePages()
@@ -419,6 +431,14 @@ class Configuration:
elif udp > 0:
return 'HiQSDR'
return 'SoftRock USB'
def FindNotebookPage(self, name):
for index in range(self.radios_page_start, self.notebk.GetPageCount()):
tab_text = self.notebk.GetPageText(index)
if tab_text[0] == '*':
tab_text = tab_text[1:-1]
if tab_text == name:
return index
return None
def AddRadio(self, radio_name, typ):
radio_dict = {}
radio_dict['hardware_file_type'] = typ
@@ -435,13 +455,24 @@ class Configuration:
page = RadioNotebook(self.notebk, radio_name)
self.notebk.AddPage(page, radio_name)
return True
def CopyRadio(self, old_name, new_name):
radio_dict = {}
index = Settings[2].index(old_name)
radio_dict.update(Settings[3][index])
Settings[2].append(new_name)
Settings[3].append(radio_dict)
page = RadioNotebook(self.notebk, new_name)
self.notebk.AddPage(page, new_name)
return True
def RenameRadio(self, old, new):
index = Settings[2].index(old)
n = self.radios_page_start + index
n = self.FindNotebookPage(old)
if n is None:
return
if old == Settings[1]:
self.notebk.SetPageText(n, "*%s*" % new)
else:
self.notebk.SetPageText(n, new)
index = Settings[2].index(old)
Settings[2][index] = new
self.notebk.GetPage(n).NewName(new)
if old == "ConfigFileRadio":
@@ -452,12 +483,10 @@ class Configuration:
index = Settings[2].index(name)
del Settings[2][index]
del Settings[3][index]
try:
n = self.radios_page_start + index
n = self.FindNotebookPage(name)
if n is not None:
self.notebk.DeletePage(n)
except:
pass
return True
return True
def GetRadioDict(self, radio_name=None): # None radio_name means the current radio
if radio_name:
index = Settings[2].index(radio_name)
@@ -573,6 +602,7 @@ class Configuration:
# Receiver sections start with 16 #, "Receivers ", receiver name, explain
# self.receiver_data is a list of [receiver_name, receiver_data]
# receiver_data is a list of [data_name, text, fmt, help_text, values]
# self.receiver_explain is a list of the text in Receivers: the name, a comma and the explanation.
# Variable names start with ## variable_name variable_text, format
# The format is integer, number, text, boolean, integer choice, text choice, rfile
@@ -610,6 +640,7 @@ class Configuration:
continue
if line[0:27] == '################ Receivers ':
section = 'Receivers'
self.receiver_explain.append(line[27:].strip())
args = line[27:].split(',', 1)
rxname = args[0].strip()
section_data = []
@@ -1295,6 +1326,7 @@ class RadioNotebook(wx.Notebook): # The second-level notebook for each radio nam
self.SetBackgroundColour(parent.bg_color)
self.radio_name = radio_name
self.pages = []
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanging)
def MakePages(self):
radio_name = self.radio_name
radio_dict = local_conf.GetRadioDict(radio_name)
@@ -1309,7 +1341,16 @@ class RadioNotebook(wx.Notebook): # The second-level notebook for each radio nam
self.AddPage(page, "Sound")
self.pages.append(page)
for section, names in local_conf.sections:
if section in ('Sound', 'Bands', 'Filters'): # There is a special page for these sections
if section == 'Sound': # There is a special page for these sections
continue
if section == 'Bands':
continue
if section == 'Filters':
continue
if section == 'Remote':
page = RadioRemote(self, radio_name, section, names)
self.AddPage(page, section)
self.pages.append(page)
continue
page = RadioSection(self, radio_name, section, names)
self.AddPage(page, section)
@@ -1325,6 +1366,20 @@ class RadioNotebook(wx.Notebook): # The second-level notebook for each radio nam
self.radio_name = new_name
for page in self.pages:
page.radio_name = new_name
def OnPageChanging(self, event):
global wxpython_gtk3_bug
event.Skip()
index = event.GetSelection()
page = self.GetPage(index)
if sys.platform != 'win32': # Work around a bug in wxPython 4.2.1 and gtk3
w, h = application.main_frame.GetSize() # Change main window size by +/- one pixel to recalculate sizes
if wxpython_gtk3_bug:
wxpython_gtk3_bug = 0
h += 1
else:
wxpython_gtk3_bug = 1
h -= 1
application.main_frame.SetSize((w, h))
class ChoiceCombo(wx.Choice):
text_for_blank = "-blank-"
@@ -1439,7 +1494,13 @@ class ComboCtrl(wxcombo.ComboCtrl):
wm = w
wm += charx * 5
self.SetSizeHints(wm, self.height, 9999, self.height)
def SetValue(self, value):
wxcombo.ComboCtrl.SetValue(self, value)
self.SetBackgroundColour(self.bgColor)
self.Refresh()
def SetSelection(self, n): # Set text to item in list box. Name conflict with wxcombo.ComboCtrl.
self.SetBackgroundColour(self.bgColor)
self.Refresh()
try:
text = self.choices[n]
except IndexError:
@@ -1930,6 +1991,16 @@ class ControlMixin:
btn.SetSizeHints(h, h, h, h)
self.gbs.Add(btn, (self.row, col), flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, border=self.charx*border)
return sld, btn
def AddTextHeading(self, key, text, help_text):
if key in text: # Start of heading
self.NextRow()
self.SetFont(self.heading_font)
self.AddTextL(1, help_text, span=7)
self.SetFont(self.font)
self.NextRow()
self.col = 2
return key
return None
def AddPopupMenuHelp(self, Qclass, col, text, btn_text, help_text, span1=1, span2=1, border=1):
if text:
txt = wx.StaticText(self, -1, text)
@@ -2230,8 +2301,8 @@ class ControlMixin:
if self.radio_name == Settings[1]: # changed for current radio
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec', 'hermes_lite2_enable',
'fixed_tune_offset'):
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec',
'fixed_tune_offset', 'calibrate_temp_20', 'calibrate_temp_40', 'calibrate_current_0', 'calibrate_current_1'):
setattr(conf, name, x)
application.ImmediateChange(name)
elif name[0:4] in ('lin_', 'win_'):
@@ -2328,10 +2399,11 @@ class ControlMixin:
else:
return value
class BaseWindow(wx.ScrolledCanvas, ControlMixin):
class BaseWindow(wx.ScrolledWindow, ControlMixin):
def __init__(self, parent):
wx.ScrolledCanvas.__init__(self, parent)
wx.ScrolledWindow.__init__(self, parent)
ControlMixin.__init__(self, parent)
self.SetScrollRate(8, 8)
class ConfigConfig(BaseWindow):
def __init__(self, parent, width):
@@ -2442,7 +2514,6 @@ The CW message will then repeat. A time of zero means no repeat.'
sl, btn = self.AddTextSliderHelp(4, " Repeat secs %.1f ", 0, 0, 100, self.OnPlayFileRepeat, help_text, span=2, scale=0.1)
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
def MakeFileButton(self, text, path, index, name, help_text):
if index < 10: # record buttons
cb = self.AddCheckBox(4, text, self.OnCheckRecPlay)
@@ -2620,7 +2691,6 @@ class ConfigTxAudio(BaseWindow):
application.CtrlTxAudioPreemph = sld
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
def OnTimer(self, event):
if not self.tmp_playing:
self.timer.Stop()
@@ -2653,48 +2723,81 @@ class Radios(BaseWindow): # The "Radios" first-level page
def __init__(self, parent):
BaseWindow.__init__(self, parent)
self.SetBackgroundColour(parent.bg_color)
self.num_cols = 8
self.num_cols = 5
self.radio_name = None
self.cur_radio_text = self.AddTextL(1, 'xx', self.num_cols - 1)
self.SetCurrentRadioText()
self.index = 0
txt = 'Each "radio" is a block of settings that describes a radio to use with Quisk.'
self.AddTextL(1, txt, self.num_cols - 1)
self.NextRow()
self.NextRow()
item = self.AddTextL(1, "When Quisk starts, use the radio")
self.radio_buttons = []
rb = self.AddRadioButton(1, "When Quisk starts, use the radio", self.NewTop, start=True)
rb.SetValue(True)
self.start_radio = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
self.start_radio.handler = self.OnChoiceStartup
self.radio_buttons.append((rb, self.start_radio))
self.NextRow()
item = self.AddTextL(1, "Add a new radio with the general type")
rb = self.AddRadioButton(1, "Add a new radio with the general type", self.NewTop)
choices = []
for name, data in local_conf.receiver_data:
choices.append(name)
for explain in local_conf.receiver_explain:
choices.append(explain)
choices.sort()
self.add_type = self.AddComboCtrl(2, '', choices=choices, no_edit=True)
self.add_type.SetSelection(0)
item = self.AddTextL(3, "and name the new radio")
self.add_name = self.AddComboCtrl(4, '', choices=["My Radio", "SR with XVtr", "SoftRock"])
item = self.AddPushButton(5, "Add", self.OnBtnAdd)
self.add_name = self.AddComboCtrl(4, '', choices=["My Radio", "Transverter", "SoftRock", "HL2"])
self.radio_buttons.append((rb, self.add_type, self.add_name))
self.NextRow()
item = self.AddTextL(1, "Rename the radio named")
rb = self.AddRadioButton(1, "Add a new radio copied from radio", self.NewTop)
self.add_from = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
item = self.AddTextL(3, "and name the new radio")
self.add_name2 = self.AddComboCtrl(4, '', choices=["My Radio", "Transverter", "SoftRock", "HL2"])
self.radio_buttons.append((rb, self.add_from, self.add_name2))
self.NextRow()
rb = self.AddRadioButton(1, "Rename the radio named", self.NewTop)
self.rename_old = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
item = self.AddTextL(3, "to the new name")
self.rename_new = self.AddComboCtrl(4, '', choices=["My Radio", "SR with XVtr", "SoftRock"])
item = self.AddPushButton(5, "Rename", self.OnBtnRename)
self.rename_new = self.AddComboCtrl(4, '', choices=["My Radio", "Transverter", "SoftRock", "HL2"])
self.radio_buttons.append((rb, self.rename_old, self.rename_new))
self.NextRow()
item = self.AddTextL(1, "Delete the radio named")
rb = self.AddRadioButton(1, "Delete the radio named", self.NewTop)
self.delete_name = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
item = self.AddPushButton(3, "Delete", self.OnBtnDelete)
self.radio_buttons.append((rb, self.delete_name))
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
self.NextRow()
self.apply_btn = self.AddPushButton(3, "Apply", self.OnBtnApply)
self.NewRadioNames()
def SetCurrentRadioText(self):
radio_dict = local_conf.GetRadioDict(self.radio_name)
radio_type = radio_dict['hardware_file_type']
if Settings[1] == "ConfigFileRadio":
text = 'The current radio is ConfigFileRadio, so all settings come from the config file. The hardware type is %s.' % radio_type
self.NewTop()
def NewTop(self, event=None):
if event:
btn = event.GetEventObject()
else: # set first button
btn = self.radio_buttons[0][0]
for i in range(len(self.radio_buttons)):
data = self.radio_buttons[i]
rb = data[0]
if rb == btn:
self.index = i
for c in data[1:]:
c.Enable(rb == btn)
if self.index == 0:
self.apply_btn.Enable(False)
else:
text = "Quisk is running with settings from the radio %s. The hardware type is %s." % (Settings[1], radio_type)
self.cur_radio_text.SetLabel(text)
self.apply_btn.Enable(True)
def OnBtnApply(self, event):
index = self.index
ok = False
if index == 1: # add from hardware type
ok = self.OnBtnAdd()
elif index == 2: # add from existing radio
ok = self.OnBtnCopy()
elif index == 3: # rename
ok = self.OnBtnRename()
elif index == 4: # delete
ok = self.OnBtnDelete()
if ok:
self.radio_buttons[0][0].SetValue(True)
self.NewTop()
def DuplicateName(self, name):
if name in Settings[2] or name == "ConfigFileRadio":
dlg = wx.MessageDialog(self, "The name already exists. Please choose a different name.",
@@ -2703,34 +2806,39 @@ class Radios(BaseWindow): # The "Radios" first-level page
dlg.Destroy()
return True
return False
def OnBtnAdd(self, event):
def OnBtnAdd(self):
name = self.add_name.GetValue().strip()
if not name or self.DuplicateName(name):
return
self.add_name.SetValue('')
typ = self.add_type.GetValue().strip()
typ = self.add_type.GetValue().split(',')[0].strip()
if local_conf.AddRadio(name, typ):
if Settings[0] != "Ask me":
Settings[0] = name
self.NewRadioNames()
local_conf.settings_changed = True
def OnBtnRename(self, event):
return True
def OnBtnCopy(self):
new_name = self.add_name2.GetValue().strip()
if not new_name or self.DuplicateName(new_name):
return
self.add_name2.SetValue('')
old_name = self.add_from.GetValue()
if local_conf.CopyRadio(old_name, new_name):
self.NewRadioNames()
local_conf.settings_changed = True
return True
def OnBtnRename(self):
old = self.rename_old.GetValue()
new = self.rename_new.GetValue().strip()
if not old or not new or self.DuplicateName(new):
return
self.rename_new.SetValue('')
if local_conf.RenameRadio(old, new):
if old == 'ConfigFileRadio' and Settings[1] == "ConfigFileRadio":
if Settings[1] == old:
Settings[1] = new
elif Settings[1] == old:
Settings[1] = new
self.SetCurrentRadioText()
if Settings[0] != "Ask me":
Settings[0] = new
self.NewRadioNames()
local_conf.settings_changed = True
def OnBtnDelete(self, event):
return True
def OnBtnDelete(self):
name = self.delete_name.GetValue()
if not name:
return
@@ -2742,6 +2850,7 @@ class Radios(BaseWindow): # The "Radios" first-level page
if ret == wx.ID_OK and local_conf.DeleteRadio(name):
self.NewRadioNames()
local_conf.settings_changed = True
return True
def OnChoiceStartup(self, ctrl):
choice = self.start_radio.GetValue()
if Settings[0] != choice:
@@ -2751,6 +2860,8 @@ class Radios(BaseWindow): # The "Radios" first-level page
choices = Settings[2][:] # can rename any available radio
self.rename_old.SetItems(choices)
self.rename_old.SetSelection(0)
self.add_from.SetItems(choices)
self.add_from.SetSelection(0)
if "ConfigFileRadio" in choices: # can not delete ConfigFileRadio
choices.remove("ConfigFileRadio")
self.delete_name.SetItems(choices)
@@ -2762,13 +2873,13 @@ class Radios(BaseWindow): # The "Radios" first-level page
try: # Set text in control
index = choices.index(Settings[0]) # last used radio, or new or renamed radio
except:
num = len(Settings[2])
if len == 0:
index = 1
elif num == 1:
index = 0
num = len(Settings[2]) # number of radios
if num == 0: # no radios
index = 1 # use ConfigFileRadio
elif num == 1: # one radio
index = 0 # use the one radio
else:
index = len(choices) - 2
index = len(choices) - 2 # use the last radio
Settings[0] = choices[index]
self.start_radio.SetSelection(index)
@@ -2799,16 +2910,7 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
radio_dict = local_conf.GetRadioDict(self.radio_name)
radio_type = radio_dict['hardware_file_type']
for name, text, fmt, help_text, values in self.names:
if name == 'remote_radio_password':
self.AddTextButtonHelp(col, text, "Change", self.OnChangePassword, help_text)
if col == 1:
col = 4
else:
col = 1
self.NextRow()
elif name == 'remote_radio_ip' and radio_type != "Control Head":
continue
elif name == 'favorites_file_path':
if name == 'favorites_file_path':
self.favorites_path = radio_dict.get('favorites_file_path', '')
row = self.row
self.row = 1
@@ -2902,7 +3004,6 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
self.AddColSpacer(2, 20)
self.AddColSpacer(5, 20)
self.FitInside()
self.SetScrollRate(1, 1)
def OnButtonChangeFavorites(self, event):
if isinstance(event, (ChoiceCombo, ComboCtrl)):
path = event.GetValue()
@@ -2996,14 +3097,6 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
index = self.list_ctrl.GetFirstSelected()
if index >= 0:
self.list_ctrl.Select(index, 0)
def OnChangePassword(self, event):
text = local_conf.globals.get('remote_radio_password', '')
dlg = wx.TextEntryDialog(self, "Please enter a password for remote access",
"Password Entry", text)
if dlg.ShowModal() == wx.ID_OK:
local_conf.globals["remote_radio_password"] = dlg.GetValue()
local_conf.settings_changed = True
local_conf.SaveState()
class RadioHardwareBase(BaseWindow): # The Hardware page in the second-level notebook for each radio
def __init__(self, parent, radio_name):
@@ -3035,7 +3128,6 @@ class RadioHardwareBase(BaseWindow): # The Hardware page in the second-level no
self.NextRow(7)
self.AddColSpacer(2, 20)
self.AddColSpacer(5, 20)
self.SetScrollRate(1, 1)
def OnButtonChangeHardware(self, event):
if isinstance(event, (ChoiceCombo, ComboCtrl)):
path = event.GetValue()
@@ -3082,25 +3174,24 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
data_names = local_conf.GetReceiverData(radio_type)
col = 1
border = 2
hermes_board_id = 0
if radio_type == "Hermes":
try:
hermes_board_id = application.Hardware.hermes_board_id
except:
pass
try:
is_HermesLite2 = application.Hardware.is_HermesLite2
except:
is_HermesLite2 = False
if radio_name == Settings[1] and hasattr(application.Hardware, "ProgramGateware"):
help_text = "Choose an RBF file and program the Gateware (FPGA software) over Ethernet."
self.AddTextButtonHelp(1, "Gateware Update", "Program from RBF file..", application.Hardware.ProgramGateware, help_text)
col = 1
self.NextRow(self.row + 2)
for name, text, fmt, help_text, values in data_names:
text = text.replace("\\u2103", "\u2103")
if name in ('hardware_file_name', 'widgets_file_name'):
pass
elif name[0:4] == platform_ignore:
pass
elif name in ('Hermes_BandDictEnTx', ):
pass
elif 'Hl2_' in name and hermes_board_id != 6:
elif 'Hl2_' in name and not is_HermesLite2:
pass
elif fmt[0:4] in ('dict', 'list'):
pass
@@ -3129,7 +3220,7 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
col = 1
border = 2
self.NextRow()
if hermes_board_id == 6:
if is_HermesLite2:
if col == 4:
self.NextRow()
help_text = ('This controls the bias level for transistors in the final power amplifier. Enter a level from 0 to 255.'
@@ -3145,7 +3236,6 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
self.HermesWriteBiasButton = self.AddPushButton(7, "Write", self.OnButtonHermesWriteBias, border=0)
self.HermesWriteBiasButton.Enable(enbl)
self.FitInside()
self.SetScrollRate(1, 1)
def OnHermesChangeBias0(self, event):
value = self.HermesBias0.GetValue()
application.Hardware.ChangeBias0(value)
@@ -3656,7 +3746,7 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
self.radio_dict = local_conf.GetRadioDict(self.radio_name)
self.num_cols = 8
for name, text, fmt, help_text, values in local_conf.GetSectionData('Sound'):
if name in ('digital_output_level', 'file_play_level'):
if name in ('digital_output_level', 'file_play_level', 'IQ_Server_IP'):
value = self.GetValue(name, self.radio_dict)
no_edit = "choice" in fmt or fmt == 'boolean'
txt, cb, btn = self.AddTextComboHelp(1, text, value, values, help_text, no_edit)
@@ -3759,7 +3849,6 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
break
self.gbs = gbs
self.FitInside()
self.SetScrollRate(1, 1)
def ItemValue(self, row, col):
data_name = self.sound_names[row][col]
if col == 4: # Device names
@@ -3987,7 +4076,6 @@ The Tx bits are sent as the "Alex" filters and are sent to the J16 interface.'
self.AddBitField(col + 1, 7, 'Hermes_BandDictTx', band, bus, self.ChangeIO)
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
def SortCmp(self, item1):
# Numerical conversion of band name to megahertz
try:
@@ -4091,6 +4179,90 @@ The Tx bits are sent as the "Alex" filters and are sent to the J16 interface.'
if hasattr(application.Hardware, "ImmediateChange"):
application.Hardware.ImmediateChange(name)
class RadioRemote(BaseWindow): # The Remote page in the second-level notebook for each radio
def __init__(self, parent, radio_name, section, names):
BaseWindow.__init__(self, parent)
self.radio_name = radio_name
self.section = section
self.names = names
self.heading_font = wx.Font(conf.config_font_size, wx.FONTFAMILY_SWISS, wx.ITALIC,
wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface)
self.MakeControls()
def MakeControls(self):
self.num_cols = 8
#self.MarkCols()
self.AddColSpacer(1, 3)
self.NextRow()
self.col = 2
radio_dict = local_conf.GetRadioDict(self.radio_name)
radio_type = radio_dict['hardware_file_type']
heading = None
for name, text, fmt, help_text, values in self.names:
if heading:
if heading not in text: # End of heading
heading = None
self.NextRow()
self.col = 2
if not heading:
heading = self.AddTextHeading("TCI", text,
'Rig control commands and digital sample streams are sent to multiple clients using the TCI protocol.')
if not heading:
heading = self.AddTextHeading("Hamlib Rig 2", text,
'Rig control commands using "Hamlib Rig 2" are sent to a Quisk TCP port from multiple clients.')
if not heading:
heading = self.AddTextHeading("XML-RPC", text,
'XML-RPC is a protocol that allows clients to call methods on a remote server using XML passed via HTTP')
if not heading:
heading = self.AddTextHeading("CAT serial", text,
'"CAT serial" supports the FlexRadio PowerSDR 2.x command set sent over a serial port. Some Kenwood commands are included.')
if not heading:
heading = self.AddTextHeading("remote radio", text,
'Quisk can be used as a control head to control a real radio located remotely. This feature is due to AC2YD.')
if not heading:
heading = self.AddTextHeading("K4 TCP", text,
'Rig control commands using "K4 TCP" use the Elecraft K4 command set, and are sent to a Quisk TCP port from multiple clients.')
if not heading:
heading = self.AddTextHeading("Dx cluster", text,
'"Dx Cluster" is a telnet service used to announce Dx spots in real time.')
if name == 'remote_radio_password':
self.AddTextButtonHelp(self.col, text, "Change", self.OnChangePassword, help_text)
if self.col == 2:
self.col = 5
else:
self.col = 2
self.NextRow()
elif name == 'remote_radio_ip' and radio_type != "Control Head":
continue
else:
if fmt[0:4] in ('dict', 'list'):
continue
if name[0:4] == platform_ignore:
continue
value = self.GetValue(name, radio_dict)
no_edit = "choice" in fmt or fmt == 'boolean'
txt, cb, btn = self.AddTextComboHelp(self.col, text, value, values, help_text, no_edit)
cb.handler = self.OnChange
cb.quisk_data_name = name
if self.col == 2:
self.col = 5
else:
self.col = 2
self.NextRow()
self.NextRow()
self.AddTextL(1, "End of list", span=7)
self.AddColSpacer(3, 20)
self.AddColSpacer(6, 20)
self.FitInside()
def OnChangePassword(self, event):
text = local_conf.globals.get('remote_radio_password', '')
dlg = wx.TextEntryDialog(self, "Please enter a password for remote access",
"Password Entry", text)
if dlg.ShowModal() == wx.ID_OK:
local_conf.globals["remote_radio_password"] = dlg.GetValue()
local_conf.settings_changed = True
local_conf.SaveState()
class xxRadioFilters(BaseWindow): # The Filters page in the second-level notebook for each radio
def __init__(self, parent, radio_name):
BaseWindow.__init__(self, parent)
@@ -4162,7 +4334,6 @@ class xxRadioFilters(BaseWindow): # The Filters page in the second-level notebo
self.NextRow()
index += 1
self.FitInside()
self.SetScrollRate(1, 1)
def OnChangeFreq(self, event):
freq = event.GetString()
radio_dict = local_conf.GetRadioDict(self.radio_name)
+93 -8
View File
@@ -73,6 +73,9 @@ h3 {font-size:1.6rem;}
<li><a href="#Digital">Digital Modes</a></li>
<li><a href="#Hamlib">Rig Control and Logging</a></li>
<li><a href="#wsjtx">WSJT-X</a></li>
<li><a href="#freedv">FreeDV</a></li>
<li><a href="#wdsp">NR2 and WDSP</a></li>
<li><a href="#pure">Pure Signal</a></li>
<li><a href="#VNA">VNA</a></li>
<li><a href="#Remote">Remote Operation</a></li>
</ul>
@@ -81,7 +84,7 @@ h3 {font-size:1.6rem;}
<div class="contents">
<h1>
<a id="Welcome"></a>
Welcome to QUISK (December 2023)
Welcome to QUISK (June 2026)
</h1>
<p>
@@ -194,18 +197,18 @@ Quisk is free open source software written in Python and C. It is hosted on the
<br>
<p>
If you have Python 3 installed on your computer, just continue to use that version.
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</b>
On Windows Quisk requires Python 3.8 or higher.
If you want to install a more recent version of Python, uninstall the old version first.
Running multiple versions of Python is possible but can be confusing and is not necessary.
A Quisk installation is needed for each version of Python you have.
<b>A Quisk installation is needed for each version of Python you have. If you upgrade to a newer Python
you must re-install Quisk. But your Quisk settings will be preserved.</b>
</p>
<br>
<h3 id="g0.2.2">Windows Initial Installation</h3>
<p>
Windows does not include Python, so you must first install a recent version of 64-bit Python 3.
If you already have 64-bit Python 3 installed, just keep using it.
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</b>
There may be newer versions of Python, but until support is added, use the versions listed above.
On Windows Quisk requires Python 3.8 or higher.
Python is available from <a href="http://www.python.org/">http://www.python.org</a>.
There is an option to <b>Add Python to PATH</b>. You MUST select
this option so that you can start python by just typing "python" instead of the whole path
@@ -252,10 +255,13 @@ and then name the shortcut, perhaps "Quisk".
If you check the properties you will see that Windows expanded the location and start directory to the full path.
If you are curious, Quisk and its Python source files are installed in "<b>your_install_directory</b>\Lib\site-packages\quisk".
If you change to that directory you can run Quisk with "pythonw quisk.py".
Quisk has its icon file in quisk\icons. Right-click your desktop shortcut and change the Icon to
"<b>your_install_directory</b>\Lib\site-packages\quisk\icons\quisk.ico".
</p>
<br>
<p>
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
Add a radio from the list of hardware radios available. Or add the best match.
Then set your sound devices for that radio; the device for the radio speakers, the microphone and so forth. All configuration
is (mostly) from the Config button. Ignore old directions and don't bother with a config file.
</p>
@@ -372,19 +378,23 @@ python3 quisk.py
<br>
<p>
If "make" fails, you probably have missing packages or missing "-dev" packages. Try to figure out what is missing from the error messages.
You only clone github for the initial installation. To update Quisk, use pull:
You only clone github for the initial installation. To update Quisk, use pull followed by make:
</p>
<br>
<pre>
cd ~/quisk
git pull
make clean
make
</pre>
<br>
<p>
You run Quisk by running the file quisk.py with python3. For example, "python3 /home/jim/quisk/quisk.py".
You can create a desktop icon to do that, but the method depends on your version of Linux.
The quisk icon is the file quisk/icons/quisk.svg.
<b>Never run Quisk with root privelege by using sudo. Fix the permission problem instead.</b>
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
Add a radio from the list of hardware radios available. Or add the best match.
Then set your sound devices for that radio; the device for the radio speakers, the microphone and so forth. All configuration
is (mostly) from the Config button. Ignore old directions and don't bother with a config file.
To uninstall Quisk, just delete the quisk directory.
@@ -532,7 +542,7 @@ 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.
Press "Apply" and a new tab for your radio will appear. Look through the various settings on the HL2 tab.
The parameters for the radios are stored in the file quisk_settings.json.
<br>
<br>
@@ -1427,9 +1437,35 @@ is no longer needed
<br>
</p>
<h2 id="Digital">Digital Modes </h2>
<br>
<h3>Digital Modes with the TCI Protocol</h3>
<p>
Using the sound devices in a PC to transfer digital sample streams has always caused problems.
Several new methods are now becoming available. These are the <a href="https://github.com/ExpertSDR3/TCI">TCI protocol</a>
used by <a href="https://eesdr.com/en/">Expert Electronics</a> for
their SunSDR hardware
and <a href="https://www.vita.com/page-1855484">Vita49</a> used by <a href="https://www.flexradio.com">FlexRadio</a>.
And Hamlib is planning to add digital stream support.
</p>
<br>
<p>
Quisk can now connect to WSJT-X version 3.0.0 with the TCI protocol.
On the Quisk Config/radio/Remote screen enter IP address 127.0.0.1 and port 40001.
Use USB for digital modes. The DGT-* modes are for sound cards.
In WSJT-X choose the radio "TCI Client RX1", and for audio choose "TCI audio".
And enter the same IP and port. Everything then just works.
If your WSJT-X does not have "TCI Client RX1" you need to update to a more recent version.
This feature is new. To see the TCI conversation, use the "Debug level" on the Config/radio/Options screen.
</p>
<br>
<h3>Digital Modes with Sound Cards</h3>
<p>
Digital modes need to receive the audio from the radio to decode it.
They need to send audio to the radio to transmit.
These audio streams have traditionally been sent using PC sound cards or
an artificial sound card known as a Virtual Audio Cable.
Quisk has a number of modes "DGT-" to receive and transmit digital
signals. The modes "DGT-U" and "DGT-L" decode the signal as upper
signals on sound devices. The modes "DGT-U" and "DGT-L" decode the signal as upper
or lower sideband, and send the stereo audio to the digital sound
device. The left and right channel are the same.
The bandwidth is set with the filter buttons as
@@ -1493,16 +1529,24 @@ at his site <a href="https://dk1mi.radio/quisk-fldigi/">https://dk1mi.radio/quis
If your digital program only uses Alsa devices or you don't want to use PulseAudio,
you can use the ALSA loopback device.
It works the same way as the Windows VAC. First create the loopback device with this command:
<br>
<br>
<pre>
sudo modprobe snd-aloop index=19
</pre>
<br>
<p>
The "index" is the card number, and it is best to use a number higher than your regular cards.
The loopback device will be gone on the next restart.
You can create the loopback device every time the system starts, but the way to do that depends on your version of Linux.
You could add this line to /etc/modprobe.d/alsa-base.conf:
</p>
<br>
<pre>
options snd-aloop index=19
</pre>
<br>
<p>
Or you could add snd-aloop to /etc/modules or put the modprobe command in /etc/rc.local instead.
Restart Linux. Now you can enter "cat /proc/asound/cards" to
print out your sound cards, and you should see a "Loopback" card listed.
@@ -1517,6 +1561,7 @@ Your audio is now connected and you
should be able to receive digital signals. Be sure to test your
transmit signal off the air. You may need to reduce power to
improve linearity.
</p>
<br>
<br>
@@ -1592,6 +1637,46 @@ For Linux, use the pulseaudio devices:
</tr>
</table>
<br><br>
<h2 id="freedv">FreeDV Digital Voice</h2>
<p>
The <a href="https://freedv.org/">FreeDV project</a> has produced a new and exciting digital voice mode, the
<a href="https://github.com/drowe67/radae/blob/main/doc/rade_intro_waveform.pdf">Radio Autoencoder RADE</a>.
The RADE mode is a real advance. It has a small peak to average power radio. And its digital constellation is not
discrete dots but rather a continuous analog pattern. It works great and sounds great!
You can make digital voice QSO's by using the FreeDV-GUI program. It runs in its own window and connects to your
HF radio using the same virtual audio connections used by other digital programs such as WSJT-X.
I wrote a <a href="http://james.ahlstrom.name/rade.html">guide to using FreeDV-GUI</a> to make it easier to install and use it.
</p>
<br><br>
<h2 id="wdsp">NR2 and WDSP</h2>
<p>
Quisk uses the WDSP library for the NR2 noise reduction and the SNB noise blanker. The WDSP library must be available
for these features to work. For windows, the library is included. For Linux you must build it by changing to the
quisk/wdsp directory and entering "make". The library is used in-place and no installation is necessary.
</p>
<br>
<p>
The new "trained" method of NR2 is included. Press the NR2 button twice to select "NR2t".
</p>
<br><br>
<h2 id="pure">Pure Signal</h2>
<p>
Pure Signal is a term used to describe baseband predistortion, a technique used to reduce IMD products
caused by a non-linear power amp. Pure Signal is available for the Hermes Lite2.
To use it first press the PsCal button to create a calibration. This will key a two second CW carrier.
Then press PS to enable corrections. You can view the result by using the "FDX" button to see the Tx output.
The feature is automatic and no settings are required. You can re-calibrate if you change bands, but this is not
really necessary.
</p>
<br>
<p>
You need to supply a sample of the amplifier output so Pure Signal can correct it. First set the "LNA during Tx dB" to
the minimum gain of -12 on the Hermes Hardware screen. This minimizes the preamp gain and the noise picked up from the HL2 board.
Then provide a Tx sample much above the remaining board noise but below clipping. Use the FDX button to see the Tx output
with and without the Tx sample connected. Twenty or thirty dB over board noise should be enough. To learn how to provide a Tx
signal sample, see <a href="https://github.com/softerhardware/Hermes-Lite2/wiki/PureSignal">the HL2 Pure Signal wiki</a>.
</p>
<br><br>
<h2 id="VNA">Vector Network Analyzer</h2>
<p>
If you have my transceiver hardware from 2010 QEX, or the newer HiQSDR
+87 -39
View File
@@ -1,9 +1,9 @@
from __future__ import absolute_import
# This code was contributed by Christof, DJ4CM. Many Thanks!!
# Modified November 2024 by N2ADR to remove deprecated telnetlib module.
import threading
import socket
import time
import telnetlib
import quisk_conf_defaults as conf
class DxEntry:
@@ -81,61 +81,109 @@ class DxEntry:
class DxCluster(threading.Thread):
def __init__(self):
self.do_init = 1
self.error = 'Starting'
self.dxSpots = []
threading.Thread.__init__(self)
self.doQuit = threading.Event()
self.dxSpots = []
self.doQuit.clear()
self.dxLock = threading.Lock()
self.addr = conf.dxClHost + ':' + str(conf.dxClPort)
self.msg_no_spots = "No DX Cluster data from " + self.addr
self.msg_one_spot = '1 DX spot received from ' + self.addr
self.msg_spots = ' DX spots received from ' + self.addr
def run(self):
self.telnetInit()
self.telnetConnect()
if self.error:
self.sock.close()
return
while not self.doQuit.isSet():
try:
self.telnetRead()
by = self.sock.recv(1024)
except TimeoutError:
continue
except:
self.tn.close()
time.sleep(20)
by = b''
if by:
with self.dxLock:
self.bytes += by
else:
self.sock.close()
self.error = "Restarting " + self.addr
time.sleep(2)
if not self.doQuit.isSet():
self.telnetConnect()
self.tn.close()
def setListener (self, listener):
self.listener = listener
if self.error:
self.sock.close()
return
self.sock.close()
def telnetInit(self):
self.tn = telnetlib.Telnet()
def telnetConnect(self):
self.bytes = bytearray(0)
self.error = 'Starting'
self.sock = socket.socket()
self.sock.settimeout(20)
try:
self.sock.connect( (conf.dxClHost, conf.dxClPort) )
except:
self.error = "Failed to connect to " + self.addr
return
self.sock.settimeout(1)
for i in range(10):
try:
self.tn.open(conf.dxClHost, conf.dxClPort, 10)
self.tn.read_until(b"login:", 10)
self.tn.write(conf.user_call_sign.encode('utf-8', errors='ignore') + b"\n") # user_call_sign may be Unicode
break
self.bytes += self.sock.recv(1024)
except:
time.sleep(0.5)
pass
if b"login:" in self.bytes:
break
else:
self.error = 'No "login:" prompt from ' + self.addr
return
self.sock.sendall(conf.user_call_sign.encode('utf-8', errors='ignore') + b"\r\n")
if conf.dxClPassword:
self.tn.read_until(b"Password: ")
self.tn.write(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\n")
for i in range(10):
try:
self.bytes += self.sock.recv(1024)
except:
pass
if b"Password:" in self.bytes:
break
else:
self.error = 'No "Password:" prompt from ' + self.addr
return
self.sock.sendall(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\r\n")
self.error = ''
self.bytes = bytearray(0)
def telnetRead(self):
message = self.tn.read_until(b'\n', 60).decode(encoding='utf-8', errors='replace')
if self.doQuit.isSet() == False:
dxEntry = DxEntry();
if dxEntry.parseMessage(message):
for i, listElement in enumerate(self.dxSpots):
if (listElement.equal(dxEntry)):
listElement.join (dxEntry)
return
if listElement.isExpired():
del (self.dxSpots[i])
self.dxSpots.append(dxEntry)
if self.listener:
self.listener()
def getHost(self):
return self.tn.host + ':' + str(self.tn.port)
def Poll(self):
with self.dxLock:
index = self.bytes.find(b"\n")
if index >= 0:
message = self.bytes[0:index + 1]
self.bytes = self.bytes[index + 1:]
else:
return
message = message.decode(encoding='utf-8', errors='replace')
dxEntry = DxEntry()
if dxEntry.parseMessage(message):
for i, listElement in enumerate(self.dxSpots):
if (listElement.equal(dxEntry)):
listElement.join (dxEntry)
return True
if listElement.isExpired():
del (self.dxSpots[i])
self.dxSpots.append(dxEntry)
return True
def stop(self):
self.doQuit.set()
def dxStatus(self):
if self.error:
return self.error
nSpots = len(self.dxSpots)
if nSpots == 0:
return self.msg_no_spots
elif nSpots == 1:
return self.msg_one_spot
return str(nSpots) + self.msg_spots
+1
View File
@@ -1,5 +1,6 @@
#include <Python.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <complex.h>
#include "quisk.h"
+1
View File
@@ -1,6 +1,7 @@
#include <Python.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <complex.h> // Use native C99 complex type for fftw3
#include "quisk.h"
#include "filter.h"
+4 -4
View File
@@ -414,7 +414,7 @@ double quiskFilt111D4Coefs[196] = { 0.000022860383861326, 0.000070265173417164,
0.000878636067621920, 0.000694724116305488, 0.000487289901409396, 0.000301317969124407, 0.000160689233204503,
0.000070265173417164, 0.000022860383861326} ;
// Sample 53333 Hz, pass 20000, stop 24000, ripple 0.1 dB, atten 100 dB. For SDR-IQ. Stop 0.45000.
// Sample 53333 Hz, pass 20000, stop 24000, ripple 0.1 dB, atten 100 dB. For SDR-IQ. Pass 0.375. Stop 0.45000.
double quiskFilt53D1Coefs[55] = { 0.000318858391362187, 0.001662116369387873, -0.000065237353639775, -0.000854885424783294,
0.001693126335254083, -0.002034310867663472, 0.001354648789538254, 0.000509693525155950, -0.003079059747940221, 0.005242349925004462,
-0.005626507739484119, 0.003235398612833476, 0.001883583294529978, -0.008273775245721798, 0.013282431836060808, -0.013924391581141530,
@@ -426,7 +426,7 @@ double quiskFilt53D1Coefs[55] = { 0.000318858391362187, 0.001662116369387873, -0
-0.003079059747940221, 0.000509693525155950, 0.001354648789538254, -0.002034310867663472, 0.001693126335254083, -0.000854885424783294,
-0.000065237353639775, 0.001662116369387873, 0.000318858391362187 } ;
// Sample 53333 Hz, pass 10000, stop 12000, ripple 0.5 dB, atten 100 dB. For SDR-IQ. Stop 0.22500.
// Sample 53333 Hz, pass 10000, stop 12000, ripple 0.5 dB, atten 100 dB. For SDR-IQ. Pass 0.1875. Stop 0.22500.
double quiskFilt53D2Coefs[93] = { 0.000107966318729964, 0.000421475598772498, 0.000731343669484184, 0.000328611865414609,
-0.001457272471508304, -0.004143803290279123, -0.005736802939367581, -0.004303861070721504, -0.000415676176666607,
0.002561521351129964, 0.001705074918770091, -0.001794491466202784, -0.003398011312785532, -0.000595401366072839,
@@ -603,7 +603,7 @@ double quiskAudio48p6Coefs[71] = { 0.000324677779792651, 0.001038192405843851, 0
0.001132278163758895, 0.002811674671240099, 0.002938055116822665, 0.002082337464742640, 0.001038192405843851,
0.000324677779792651} ;
// Sample 96 kHz, pass 6, stop 40, ripple 0.1 dB, atten 100 dB. Stop 0.41667.
// Sample 96 kHz, pass 6, stop 40, ripple 0.1 dB, atten 100 dB. Pass 0.0625. Stop 0.41667.
double quiskAudio96Coefs[11] = { -0.004756607210954994, -0.022893760473075838, -0.023609616122334920,
0.078161168368381481, 0.277880235815005590, 0.388489826271597950, 0.277880235815005590,
0.078161168368381481, -0.023609616122334920, -0.022893760473075838, -0.004756607210954994} ;
@@ -663,7 +663,7 @@ double quiskAudioFmHpCoefs[309] = { 0.004847574947800705, -0.001211970386842814,
-0.000365311474947399, -0.000434052387522716, -0.000504808183463150, -0.000579117971409598, -0.000658731927598885, -0.000745515693376396,
-0.000841696354699768, -0.000949749961336003, -0.001072277374239886, -0.001211970386842814, 0.004847574947800705} ;
// Sample 24 kHz, pass 3, stop 4, ripple 0.1 dB, atten 100 dB. Stop 0.166667.
// Sample 24 kHz, pass 3, stop 4, ripple 0.1 dB, atten 100 dB. Pass 0.125. Stop 0.166667.
double quiskAudio24p3Coefs[100] = { 0.000040101217607380, 0.000144142945632828, 0.000313272623844654, 0.000472482653992188,
0.000478145332060253, 0.000196098255702949, -0.000370577202968848, -0.000996638165520275, -0.001297816111521122,
-0.000964536466628102, -0.000042557313824444, 0.000962997869327471, 0.001323534221298773, 0.000594056697947640,
+1 -681
View File
@@ -1,681 +1 @@
#include <Python.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h> // Use native C99 complex type for fftw3
#include <sys/types.h>
#include "quisk.h"
#include "freedv.h"
#include <stdint.h>
int DEBUG;
#define MAX_RECEIVERS 2
typedef struct { // from comp.h
float real;
float imag;
} COMP;
struct freedv; // from freedv_api.h
typedef void (*freedv_callback_rx)(void *, char);
typedef char (*freedv_callback_tx)(void *);
/* Protocol bits are packed MSB-first */
/* Called when a frame containing protocol data is decoded */
typedef void (*freedv_callback_protorx)(void *, char *);
/* Called when a frame containing protocol data is to be sent */
typedef void (*freedv_callback_prototx)(void *, char *);
/* Data packet callbacks */
/* Called when a packet has been received */
typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t size);
/* Called when a new packet can be send */
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size);
/* advanced freedv open options required by some modes */
struct freedv_advanced {
int interleave_frames; // now unused but remains to prevent breaking API for
// legacy apps
// parameters for FREEDV_MODE_FSK_LDPC
int M; // 2 or 4 FSK
int Rs; // Symbol rate Hz
int Fs; // Sample rate Hz
int first_tone; // Freq of first tone Hz
int tone_spacing; // Spacing between tones Hz
char *codename; // LDPC codename, from codes listed in ldpc_codes.c
};
#ifdef MS_WINDOWS
#include <windows.h>
HMODULE hLib;
#define GET_HANDLE1 hLib = LoadLibrary("libcodec2.dll")
#define GET_HANDLE2 hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll")
#define GET_HANDLE3 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll")
#define GET_HANDLE4 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll")
#define GET_ADDR(name) (void *)GetProcAddress(hLib, name)
#define CLOSE_LIB FreeLibrary(hLib)
#else
#include <dlfcn.h>
void * hLib;
#define GET_HANDLE1 hLib = dlopen("libcodec2.so", RTLD_LAZY)
#define GET_HANDLE2 hLib = dlopen("./freedvpkg/libcodec2.so", RTLD_LAZY)
#define GET_HANDLE3 hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY)
#define GET_HANDLE4 hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY)
#define GET_ADDR(name) dlsym(hLib, name)
#define CLOSE_LIB dlclose(hLib)
#endif
static int requested_mode = -1; // requested mode
int freedv_current_mode = -1; // the current running mode
static int quisk_freedv_squelch;
static int interleave_frames = 1;
static int freedv_version = -1;
static int quisk_set_tx_bpf = 1;
static int handle_index = -1;
// need access to these here and in quisk.c
int n_speech_sample_rate = 8000;
int n_modem_sample_rate = 8000;
int n_max_modem_samples = 1000;
#define SPEECH_BUF_SIZE 12000 // speech buffer size increased from 3000
#define MAX_MODEM_SAMPLES 8192 // maximum of n_max_modem_samples for any mode
#define MAX_SPEECH_SAMPLES 8192 // maximum of n_speech_samples for any mode
static struct _rx_channel{
struct freedv * hFreedv;
short * demod_in;
int rxdata_index;
short speech_out[SPEECH_BUF_SIZE]; // output buffer
int speech_available; // number of samples in output buffer
int playing; // are we currently returning speech samples?
} rx_channel[MAX_RECEIVERS] = { 0 }; // safe initialisation to 0
// freedv_version is the library version number, or
// -1 no library was found
// -2 a library was found, but freedv_get_version is missing
// FreeDV API functions:
// open, close
struct freedv * (*freedv_open)(int mode);
struct freedv * (*freedv_open_advanced)(int mode, struct freedv_advanced *adv);
void (*freedv_close)(struct freedv *freedv);
// Transmit
void (*freedv_tx)(struct freedv *freedv, short *, short *);
void (*freedv_comptx)(struct freedv *freedv, COMP *, short *);
// Receive
int (*freedv_nin)(struct freedv *freedv);
int (*freedv_rx)(struct freedv *freedv, short *, short demod_in[]);
int (*freedv_floatrx)(struct freedv *freedv, short *, float demod_in[]);
int (*freedv_comprx)(struct freedv *freedv, short *, COMP demod_in[]);
// Set parameters
void (*freedv_set_callback_txt) (struct freedv *freedv, freedv_callback_rx rx, freedv_callback_tx tx, void *callback_state);
void (*freedv_set_test_frames) (struct freedv *freedv, int test_frames);
void (*freedv_set_smooth_symbols) (struct freedv *freedv, int smooth_symbols);
void (*freedv_set_squelch_en) (struct freedv *freedv, int squelch_en);
void (*freedv_set_snr_squelch_thresh) (struct freedv *freedv, float snr_squelch_thresh);
void (*freedv_set_tx_bpf) (struct freedv *freedv, int val);
// Get parameters
int (*freedv_get_version)(void);
void (*freedv_get_modem_stats)(struct freedv *freedv, int *sync, float *snr_est);
int (*freedv_get_test_frames) (struct freedv *freedv);
int (*freedv_get_n_speech_samples) (struct freedv *freedv);
int (*freedv_get_n_max_modem_samples) (struct freedv *freedv);
int (*freedv_get_n_nom_modem_samples) (struct freedv *freedv);
int (*freedv_get_total_bits) (struct freedv *freedv);
int (*freedv_get_total_bit_errors) (struct freedv *freedv);
int (*freedv_get_modem_sample_rate) (struct freedv *freedv);
int (*freedv_get_speech_sample_rate) (struct freedv *freedv);
//
int (*freedv_get_sync) (struct freedv *freedv);
void (*freedv_set_callback_protocol) (struct freedv *freedv, freedv_callback_protorx rx, freedv_callback_prototx tx, void *callback_state);
void (*freedv_set_callback_data) (struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state);
//
// checkAvxSupport
//
// This function was copied from freedv, file fdmdv2_main.cpp and tweaked for use here
//
// Tests the underlying platform for AVX support. 2020 needs AVX support to run
// in real-time, and old processors do not offer AVX support
//
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
#include <cpuid.h>
static int checkAvxSupport(void)
{
int isAvxPresent = 0;
uint32_t eax, ebx, ecx, edx;
eax = ebx = ecx = edx = 0;
__cpuid(1, eax, ebx, ecx, edx);
if (ecx & (1<<27) && ecx & (1<<28)) {
// CPU supports XSAVE and AVX
uint32_t xcr0, xcr0_high;
asm("xgetbv" : "=a" (xcr0), "=d" (xcr0_high) : "c" (0));
isAvxPresent = (xcr0 & 6) == 6; // AVX state saving enabled?
}
return isAvxPresent;
}
#else
static int checkAvxSupport(void)
{
return 0;
}
#endif
/* Called when a new packet can be sent */
void my_datatx(void *callback_state, unsigned char *packet, size_t *size) {
*size = 0;
}
static void GetAddrs(void)
{
handle_index = 1;
GET_HANDLE1;
if ( ! hLib) {
handle_index = 2;
GET_HANDLE2;
if ( ! hLib) {
handle_index = 4;
GET_HANDLE4;
}
}
if (hLib) {
freedv_get_version = GET_ADDR("freedv_get_version");
if (freedv_get_version != NULL) {
freedv_version = freedv_get_version();
if (DEBUG)
printf("FreeDV codec2 library %d found, version %d\n", handle_index, freedv_version);
}
else {
freedv_version = -2;
if (DEBUG)
printf("FreeDV codec2 library %d found, but no freedv_get_version() !!!\n", handle_index);
CLOSE_LIB;
return;
}
}
else {
handle_index = -1;
if (DEBUG)
printf("Could not find the FreeDV codec2 shared library\n");
return;
}
// open, close
freedv_open = GET_ADDR("freedv_open");
freedv_open_advanced = GET_ADDR("freedv_open_advanced");
freedv_close = GET_ADDR("freedv_close");
// Transmit
freedv_tx = GET_ADDR("freedv_tx");
freedv_comptx = GET_ADDR("freedv_comptx");
// Receive
freedv_nin = GET_ADDR("freedv_nin");
freedv_rx = GET_ADDR("freedv_rx");
freedv_floatrx = GET_ADDR("freedv_floatrx");
freedv_comprx = GET_ADDR("freedv_comprx");
// Set parameters
freedv_set_callback_txt = GET_ADDR("freedv_set_callback_txt");
freedv_set_callback_protocol = GET_ADDR("freedv_set_callback_protocol");
freedv_set_callback_data = GET_ADDR("freedv_set_callback_data");
freedv_set_test_frames = GET_ADDR("freedv_set_test_frames");
freedv_set_smooth_symbols = GET_ADDR("freedv_set_smooth_symbols");
freedv_set_squelch_en = GET_ADDR("freedv_set_squelch_en");
freedv_set_snr_squelch_thresh = GET_ADDR("freedv_set_snr_squelch_thresh");
freedv_set_tx_bpf = GET_ADDR("freedv_set_tx_bpf");
// Get parameters
freedv_get_modem_stats = GET_ADDR("freedv_get_modem_stats");
freedv_get_test_frames = GET_ADDR("freedv_get_test_frames");
freedv_get_n_speech_samples = GET_ADDR("freedv_get_n_speech_samples");
freedv_get_n_max_modem_samples = GET_ADDR("freedv_get_n_max_modem_samples");
freedv_get_n_nom_modem_samples = GET_ADDR("freedv_get_n_nom_modem_samples");
freedv_get_total_bits = GET_ADDR("freedv_get_total_bits");
freedv_get_total_bit_errors = GET_ADDR("freedv_get_total_bit_errors");
freedv_get_sync = GET_ADDR("freedv_get_sync"); // requires version 11
if( freedv_version >= 11)
freedv_get_modem_sample_rate = GET_ADDR("freedv_get_modem_sample_rate"); // requires version 11 or later
if (freedv_version >= 12)
freedv_get_speech_sample_rate = GET_ADDR("freedv_get_speech_sample_rate"); // requires version 12
return;
}
static int quisk_freedv_rx(short * sSamples, double * dsamples, int count, int bank) // Called from the sound thread.
{ // Input digital modulation is sSamples; decoded voice is dsamples. Each "bank" is a stream of audio.
// caller will have decimated input audio down to n_modem_sample_rate and reduced block size to match reduction
// output data will be returned at n_speech_sample_rate with corresponding block sizes
int i, nout, need, have, sync;
int n_speech_samples;
struct freedv * hF;
struct _rx_channel * pCh;
int nCountOut = 1; // In case we have a higher (or lower) value for o/p count
if (sSamples == NULL) { // shutdown
for (i = 0; i < MAX_RECEIVERS; i++) {
if (rx_channel[i].demod_in) {
free(rx_channel[i].demod_in);
rx_channel[i].demod_in = NULL;
}
}
return 0;
}
if (bank < 0 || bank >= MAX_RECEIVERS)
return 0;
hF = rx_channel[bank].hFreedv;
if ( ! hF)
return 0;
pCh = rx_channel + bank;
n_speech_samples = freedv_get_n_speech_samples(hF);
// if (say) input modem rate is the 2020 mode rate of 8000 sa/sec
// and output rate is the corresponding 16000 sa/sec then for an input
// block size of N, outbut 2N samples block size.
// Apply a multiplier/divisor to 'count' for the output samples count
if(n_speech_sample_rate >= n_modem_sample_rate ) {
int nRatio = n_speech_sample_rate / n_modem_sample_rate;
if( nRatio >= 1 && nRatio <= 6)
nCountOut = nRatio * count;
}
else {
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
if( nRatio >= 1 && nRatio <= 6)
nCountOut = count / nRatio;
}
nout = 0;
need = freedv_nin(hF);
for (i = 0; i < count; i++) {
pCh->demod_in[pCh->rxdata_index] = sSamples[i];
pCh->rxdata_index++;
if (pCh->rxdata_index >= need) {
if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space
have = freedv_rx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in);
if (freedv_version > 10)
sync = freedv_get_sync(hF);
else
freedv_get_modem_stats(hF, &sync, NULL);
if (freedv_current_mode == 0) { // mode 1600
if (sync) // throw away speech if not in sync
pCh->speech_available += have;
}
else if (pCh->speech_available < SPEECH_BUF_SIZE * 2 / 3) {
pCh->speech_available += have; // keep speech if there is space
}
else {
if (DEBUG) printf("Close to maximum in speech output buffer\n");
}
}
else { // no space in buffer
if (DEBUG) printf("Overflow in speech output buffer\n");
}
pCh->rxdata_index = 0;
need = freedv_nin(hF);
}
}
if ( ! pCh->playing) {
if (pCh->speech_available >= 2 * n_speech_samples) {
pCh->playing = 1;
}
else { // return zero samples
for (i = 0; i < nCountOut; i++)
dsamples[i] = 0;
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
return nCountOut;
}
}
for (nout = 0; nout < pCh->speech_available && nout < nCountOut; nout++)
dsamples[nout] = pCh->speech_out[nout] * ((double)CLIP32 / CLIP16 * 0.99);
if (nout) {
pCh->speech_available -= nout;
memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short));
}
if ( ! pCh->speech_available) {
pCh->playing = 0;
while (nout < nCountOut)
dsamples[nout++] = 0;
}
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
return nout;
}
static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count, int is_real) // Called from the sound thread.
{ // Input voice samples are dsamples; output digital modulation is cSamples.
int i, nout;
int n_speech_samples;
int n_nom_modem_samples;
static COMP * mod_out = NULL;
static short * speech_in = NULL;
static int speech_index=0, mod_index=0;
// g8kbb - add for 800XA
static short *real_mod_out = NULL;
if (dsamples == NULL) { // shutdown
return 0; // nothing to do
}
if ( ! rx_channel[0].hFreedv)
return 0;
n_speech_samples = freedv_get_n_speech_samples(rx_channel[0].hFreedv);
n_nom_modem_samples = freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv);
// find if need to output at faster rate
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
if( nRatio < 1 || nRatio > 6 )
nRatio = 1;
if (mod_out == NULL) { // initialize
mod_out = (COMP *)malloc(sizeof(COMP) * MAX_MODEM_SAMPLES);
memset(mod_out, 0, sizeof(COMP) * MAX_MODEM_SAMPLES);
speech_in = (short*)malloc(sizeof(short) * MAX_SPEECH_SAMPLES);
speech_index=0;
mod_index=0;
real_mod_out = (short *)malloc(sizeof(short) * MAX_MODEM_SAMPLES);
memset(real_mod_out, 0, sizeof(short) * MAX_MODEM_SAMPLES);
//if( DEBUG ) printf("Initialise tx buffer size = %d, i/p sample size = %d\n", n_nom_modem_samples, n_speech_samples );
}
nout = 0;
for (i = 0; i < count; i++) {
speech_in[speech_index++] = (short)dsamples[i];
if (speech_index >= n_speech_samples) {
// Calculate a new block, but first write out the rest of the old block
// 800xa uses real not complex
if( is_real ) {
for ( ; mod_index < n_nom_modem_samples; mod_index++)
cSamples[nout++] = real_mod_out[mod_index];
freedv_tx(rx_channel[0].hFreedv, real_mod_out, speech_in);
}
else {
for ( ; mod_index < n_nom_modem_samples; mod_index++)
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
freedv_comptx(rx_channel[0].hFreedv, mod_out, speech_in);
}
//if( DEBUG ) printf("Block processed, speech index=%d, n_speech_samples=%d, count=%d\n", speech_index, n_speech_samples, count );
mod_index = 0;
speech_index = 0;
}
else { // write out samples
for( int j=0; j<nRatio; j++) {
if (mod_index < n_nom_modem_samples ) {
if( is_real ) {
cSamples[nout++] = real_mod_out[mod_index];
}
else {
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
}
mod_index++;
}
}
}
}
if (rxMode == FDV_L)
for (i = 0; i < nout; i++)
cSamples[i] = conj(cSamples[i]);
return nout;
}
#define TX_MSG_SIZE 80
static char quisk_tx_msg[TX_MSG_SIZE];
static char get_next_tx_char(void * callback_state)
{
char c;
static int index = 0;
c = quisk_tx_msg[index++];
if (index >= TX_MSG_SIZE)
index = 0;
if ( ! c) {
index = 0;
c = quisk_tx_msg[index++];
}
return c;
}
#define RX_MSG_SIZE 80
static char quisk_rx_msg[RX_MSG_SIZE + 1];
static void put_next_rx_char(void * callback_state, char ch)
{
if (ch == '\n' || ch == '\r')
ch = ' ';
if (ch < 32 || ch > 126) // printable characters
return;
if (strlen(quisk_rx_msg) < RX_MSG_SIZE)
strncat(quisk_rx_msg, &ch, 1);
}
PyObject * quisk_freedv_get_rx_char(PyObject * self, PyObject * args) // Called from the GUI thread.
{
PyObject * txt;
if (!PyArg_ParseTuple (args, ""))
return NULL;
txt = PyString_FromString(quisk_rx_msg);
quisk_rx_msg[0] = 0;
return txt;
}
static void CloseFreedv(void) // Called from the GUI thread or sound thread
{
int i;
for (i = 0; i < MAX_RECEIVERS; i++) {
if (rx_channel[i].hFreedv) {
freedv_close(rx_channel[i].hFreedv);
rx_channel[i].hFreedv = NULL;
}
}
quisk_freedv_rx(NULL, NULL, 0, 0);
quisk_freedv_tx(NULL, NULL, 0, 0);
freedv_current_mode = -1;
}
static int OpenFreedv(void) // Called from the GUI thread or sound thread
{
int i;
struct freedv * hF;
if ( ! hLib)
GetAddrs(); // Get the entry points for funtions in the codec2 library
if (DEBUG) {
switch (handle_index) {
case 1:
printf("freedv_open: system codec2 library found, version %d\n", freedv_version);
break;
case 2:
printf("freedv_open: codec2 library freedvpkg/libcodec2.dll|so found, version %d\n", freedv_version);
break;
case 3:
case 4:
printf("freedv_open: codec2 library freedvpkg/libcodec2_64 found, version %d\n", freedv_version);
break;
default:
printf("freedv_open: Could not find the FreeDV codec2 shared library\n");
break;
}
}
if (freedv_version < 10) {
CloseFreedv();
requested_mode = -1;
if (DEBUG)
printf("freedv_open: Failure because version is less than 10\n");
return 0; // failure
}
if (requested_mode == FREEDV_MODE_2020) {
if (checkAvxSupport() == 0) {
CloseFreedv();
requested_mode = -1;
if (DEBUG)
printf("freedv_open: Failure because mode 2020 requires Avx support\n");
return 0; // failure
}
if (handle_index >= 3) { // Quisk provided codec2
CloseFreedv();
requested_mode = -1;
if (DEBUG)
printf("freedv_open: Failure because mode 2020 requires a system installation of codec2\n");
return 0; // failure
}
}
if ((requested_mode == FREEDV_MODE_700D || requested_mode == FREEDV_MODE_700E) && freedv_open_advanced) {
struct freedv_advanced adv;
memset(&adv, 0, sizeof(adv));
adv.interleave_frames = interleave_frames;
hF = freedv_open_advanced(requested_mode, &adv);
}
else {
hF = freedv_open(requested_mode);
}
if (hF == NULL) {
CloseFreedv();
requested_mode = -1;
return 0; // failure
}
rx_channel[0].hFreedv = hF;
quisk_dvoice_freedv(&quisk_freedv_rx, &quisk_freedv_tx);
if (quisk_tx_msg[0])
freedv_set_callback_txt(hF, &put_next_rx_char, &get_next_tx_char, NULL);
else
freedv_set_callback_txt(hF, &put_next_rx_char, NULL, NULL);
if (freedv_set_callback_protocol)
freedv_set_callback_protocol(hF, NULL, NULL, NULL);
if (freedv_set_callback_data)
freedv_set_callback_data(hF, NULL, my_datatx, NULL);
freedv_set_squelch_en(hF, quisk_freedv_squelch);
if (freedv_set_tx_bpf)
freedv_set_tx_bpf(hF, quisk_set_tx_bpf);
n_max_modem_samples = freedv_get_n_max_modem_samples(hF);
n_speech_sample_rate = 8000;
if (freedv_version >= 12 && freedv_get_speech_sample_rate)
n_speech_sample_rate = freedv_get_speech_sample_rate(hF);
n_modem_sample_rate = 8000;
if (freedv_version >= 11 && freedv_get_modem_sample_rate)
n_modem_sample_rate = freedv_get_modem_sample_rate(hF);
for (i = 0; i < MAX_RECEIVERS; i++) {
rx_channel[i].rxdata_index = 0;
rx_channel[i].speech_available = 0;
rx_channel[i].playing = 0;
if (rx_channel[i].demod_in)
free(rx_channel[i].demod_in);
rx_channel[i].demod_in = (short *)malloc(sizeof(short) * n_max_modem_samples);
if( rx_channel[i].demod_in == NULL) {
CloseFreedv();
requested_mode = -1;
return 0; // failure
}
if (i > 0) {
rx_channel[i].hFreedv = freedv_open(requested_mode);
if (rx_channel[i].hFreedv)
freedv_set_squelch_en(rx_channel[i].hFreedv, quisk_freedv_squelch);
}
}
if (DEBUG) printf("n_nom_modem_samples %d\n", freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv));
if (DEBUG) printf("n_speech_samples %d\n", freedv_get_n_speech_samples(rx_channel[0].hFreedv));
if (DEBUG) printf("n_max_modem_samples %d\n", n_max_modem_samples);
if (DEBUG) {
if (freedv_version >= 11) printf("modem_sample_rate %d\n", n_modem_sample_rate);
if (freedv_version >= 12) printf("speech_sample_rate %d\n", n_speech_sample_rate);
}
freedv_current_mode = requested_mode;
return 1; // success
}
void quisk_check_freedv_mode(void)
{ // see if we need to change the mode
if (requested_mode == freedv_current_mode)
return;
if (DEBUG) printf("Change in mode to %d\n", requested_mode);
CloseFreedv();
if (requested_mode >= 0)
OpenFreedv();
else
requested_mode = -1;
}
PyObject * quisk_freedv_open(PyObject * self, PyObject * args) // Called from the GUI thread before freedv is open
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
return PyInt_FromLong(OpenFreedv());
}
PyObject * quisk_freedv_close(PyObject * self, PyObject * args) // Called from the GUI thread.
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
requested_mode = -1; // request close
Py_INCREF (Py_None);
return Py_None;
}
PyObject * quisk_freedv_set_options(PyObject * self, PyObject * args, PyObject * keywds) // Called from the GUI thread.
{ // Call with keyword arguments ONLY to change parameters. Call before quisk_freedv_open() to set an initial mode.
int mode=-1; // Call again to change the mode.
int bpf=-1;
char * ptMsg=NULL;
static char * kwlist[] = {"mode", "tx_msg", "DEBUG", "squelch", "interleave_frames", "set_tx_bpf", NULL} ;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch, &interleave_frames, &bpf))
return NULL;
if (ptMsg)
strMcpy(quisk_tx_msg, ptMsg, TX_MSG_SIZE);
if (bpf != -1) {
quisk_set_tx_bpf = bpf;
if (freedv_set_tx_bpf && rx_channel[0].hFreedv)
freedv_set_tx_bpf(rx_channel[0].hFreedv, quisk_set_tx_bpf);
}
if (mode == -1) { // mode was not entered
;
}
else if (mode == FREEDV_MODE_700E && freedv_version >= 14) {
requested_mode = mode;
}
else if (mode == FREEDV_MODE_2020) {
if (checkAvxSupport() && handle_index <= 2 && freedv_version >= 13)
requested_mode = mode;
}
else if (freedv_version == 10) {
if (mode == 0)
requested_mode = mode;
}
else if (freedv_version == 11) {
if (mode <= 2)
requested_mode = mode;
}
else {
requested_mode = mode;
}
return PyInt_FromLong(requested_mode); // Return the mode
}
PyObject * quisk_freedv_get_snr(PyObject * self, PyObject * args) // Called from the GUI thread.
{
float snr_est = 0.0;
if (!PyArg_ParseTuple (args, ""))
return NULL;
if (rx_channel[0].hFreedv)
freedv_get_modem_stats(rx_channel[0].hFreedv, NULL, &snr_est);
return PyFloat_FromDouble(snr_est);
}
PyObject * quisk_freedv_get_version(PyObject * self, PyObject * args) // Called from the GUI thread.
{
if (!PyArg_ParseTuple (args, ""))
return NULL;
if ( ! hLib)
GetAddrs(); // Get the entry points for funtions in the codec2 library
return PyInt_FromLong(freedv_version);
}
PyObject * quisk_freedv_set_squelch_en(PyObject * self, PyObject * args) // Called from the GUI thread.
{
int i,state;
if (!PyArg_ParseTuple (args, "i", &state))
return NULL;
quisk_freedv_squelch = state;
for (i = 0; i < MAX_RECEIVERS; i++) {
if( rx_channel[i].hFreedv )
freedv_set_squelch_en(rx_channel[i].hFreedv, state);
}
return PyInt_FromLong(state);
}
// This file is no longer used.
+1 -24
View File
@@ -1,24 +1 @@
extern int freedv_current_mode; // the current FreeDV running mode
// available speech modes
#define FREEDV_MODE_1600 0
#define FREEDV_MODE_2400A 3
#define FREEDV_MODE_2400B 4
#define FREEDV_MODE_800XA 5
#define FREEDV_MODE_700C 6
#define FREEDV_MODE_700D 7
#define FREEDV_MODE_700E 13
#define FREEDV_MODE_2020 8
#define FREEDV_MODE_2020B 16
// available data modes
#define FREEDV_MODE_FSK_LDPC 9
#define FREEDV_MODE_DATAC1 10
#define FREEDV_MODE_DATAC3 12
#define FREEDV_MODE_DATAC0 14
#define FREEDV_MODE_DATAC4 18
#define FREEDV_MODE_DATAC13 19
// Sample rates used
#define FREEDV_FS_8000 8000
#define FREEDV_FS_16000 16000
// This header file is no longer used.
+1 -129
View File
@@ -1,129 +1 @@
Note: The directory freedvpkg no longer contains source files. It only contains copies of
the codec2 libraries for use by Quisk. If any other files are present, delete them.
FreeDV and Directory freedvpkg
==============================
FreeDV is the combination of the codec2 codec and the fdmdv modem. It provides digital voice
suitable for HF and VHF transmission. Quisk has native (built-in) support for
FreeDV. Just push the FDV mode and talk. This freedvpkg directory contains copies of the
codec2 libraries for use by Quisk.
You can also use the separate FreeDV program available at freedv.org, and the Quisk DGT-U mode
which attaches to external digital programs. The setup is identical to fldigi and other external
digital programs.
Quisk will add the FDV button if specified on the Config/radio/Controls tab.
If there is a problem with the freedv module, the button will be grayed out.
The freedv module requires the codec2 library. This library is included for Windows and for
Ubuntu 18.04 LTS 32-bit and 64-bit. For other systems (such as ARM) you will need to build another
codec2. Just try the FDV mode and see if it works. It should always work on Windows, and may work
on Linux. If the FDV button is grayed out, you need a different codec2 than the one included. Make
a new codec2 library, and copy it to freedvpkg or to the correct system directory.
2020 mode
=========
You can now use 2020 mode with Quisk. Note however that 2020 mode requires a processor with AVX.
Also note a Raspberry PI (model 4B) does not have enough processing power even if neon is used
and it is overclocked.
There are two parts to this:
1. build LPCNet
2. rebuild codec2
The details are found here: https://github.com/drowe67/codec2#freedv-2020-support-building-with-lpcnet
Both liblpcnetfreedv and libcodec2 are needed. If you are running under windows, the resultant
liblpcnetfreedv.dll file needs to be placed in the quisk folder. Libcodec2 may be placed in the
quisk freedvpkg folder. Or install the DLL's in the correct system folders.
Search Order
============
Quisk will search for a valid codec2 library in this order on Windows:
1. The system codec2 library installed outside of Quisk by another program.
2. freedvpkg/libcodec2.dll. Not included. You can copy your own codec2 here but a system-wide installation is better.
3. freedvpkg/libcodec2_32.dll. The 32-bit codec2 shipped with Quisk.
4. freedvpkg/libcodec2_64.dll. The 64-bit codec2 shipped with Quisk.
Quisk will search for a valid codec2 library in this order on Linux:
1. The system codec2 library installed outside of Quisk by another program.
2. freedvpkg/libcodec2.so. Not included. You can copy your own codec2 here but a system-wide installation is better.
3. freedvpkg/libcodec2_32.so. The 32-bit codec2 shipped with Quisk.
4. freedvpkg/libcodec2_64.so. The 64-bit codec2 shipped with Quisk.
How to Build a New codec2
=========================
The source for codec2 is available on github. Or google for other sources or perhaps a pre-built library.
If you need to compile codec2 from source, first change to a suitable directory (not the Quisk directory)
and download the source with git:
git clone https://github.com/drowe67/codec2.git
Then build codec2 using the directions found at https://github.com/drowe67/codec2/blob/master/INSTALL.
The directions given below are current as of June 2020, but check for changes.
Then copy the codec2 library to the freedvpkg directory under Quisk.
Build a New codec2 on Linux
===========================
Create the codec2 shared library. This assumes a 64-bit linux. Change the directory
name from build_linux64 to build_linux32 for 32-bit linux. Note the "../".
cd codec2
mkdir build_linux64
cd build_linux64
cmake -DCMAKE_BUILD_TYPE=Release ../
make
cd src
cp libcodec2.so my-quisk-directory/freedvpkg
Build a New 32-bit codec2 on 64-bit Linux
=========================================
Make sure package libc6-dev-i386 is installed.
Create the codec2 shared library. Note the "../".
cd codec2
mkdir build_linux32
cd build_linux32
export CFLAGS=-m32
cmake -DCMAKE_BUILD_TYPE=Release ../
make
cd src
cp libcodec2.so my-quisk-directory/freedvpkg
Build a New codec2 on Windows
=============================
For Windows you need to install MinGW-w64, MSYS2, and g++. Use the mingw shell. The Speex libraries
are not needed by codec2, but are required for the Unit Test modules. To build the Unit Test modules,
you need to install Speex and add -DSPEEXDSP_INCLUDE_DIR=../speex/include/speex -DSPEEXDSP_LIBRARY=../speex/bin.
# Use mingw32.exe 32-bit shell:
cd codec2
mkdir build_win32
cd build_win32
cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF ../
make codec2
cd src
cp libcodec2.dll my-quisk-directory/freedvpkg
# Use mingw64.exe 64-bit shell:
cd codec2
mkdir build_win64
cd build_win64
cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF -DCMAKE_SYSTEM_PROCESSOR=x86_64 ../
make codec2
cd src
cp libcodec2.dll my-quisk-directory/freedvpkg
Testing
=======
You can just start Quisk and see if the FDV button is not grayed out, and FDV works. Or you can
test the import of freedv and look for error messages.
cd my-quisk-directory
python # Use the correct Python 2 or 3 in 32 or 64 bit
import _quisk
_quisk.freedv_get_version() # This should return 13 or higher for a recent codec2
This directory is no longer used by FreeDV. Please see the documentation.
Binary file not shown.
Binary file not shown.
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright (C) 2016-2024 Davidson Francis <davidsondfgl@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#define _POSIX_C_SOURCE 200809L
#include "base64.h"
#include "sha1.h"
#include "ws.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
/**
* @dir src/
* @brief Handshake routines directory
*
* @file handshake.c
* @brief Handshake routines.
*/
/**
* @brief Gets the field Sec-WebSocket-Accept on response, by
* an previously informed key.
*
* @param wsKey Sec-WebSocket-Key
* @param dest source to be stored the value.
*
* @return Returns 0 if success and a negative number
* otherwise.
*
* @attention This is part of the internal API and is documented just
* for completeness.
*/
int get_handshake_accept(char *wsKey, unsigned char **dest)
{
unsigned char hash[SHA1HashSize]; /* SHA-1 Hash. */
SHA1Context ctx; /* SHA-1 Context. */
char *str; /* WebSocket key + magic string. */
/* Invalid key. */
if (!wsKey)
return (-1);
str = calloc(1, sizeof(char) * (WS_KEY_LEN + WS_MS_LEN + 1));
if (!str)
return (-1);
strncpy(str, wsKey, WS_KEY_LEN);
strcat(str, MAGIC_STRING);
SHA1Reset(&ctx);
SHA1Input(&ctx, (const uint8_t *)str, WS_KEYMS_LEN);
SHA1Result(&ctx, hash);
*dest = base64_encode(hash, SHA1HashSize, NULL);
*(*dest + strlen((const char *)*dest) - 1) = '\0';
free(str);
return (0);
}
/**
* @brief Finds the ocorrence of @p needle in @p haystack, case
* insensitive.
*
* @param haystack Target string to be searched.
* @param needle Substring to search for.
*
* @returns If found, returns a pointer at the beginning of the
* found substring. Otherwise, returns NULL.
*/
static const char *strstricase(const char *haystack, const char *needle)
{
size_t length;
for (length = strlen(needle); *haystack; haystack++)
if (!strncasecmp(haystack, needle, length))
return haystack;
return (NULL);
}
/**
* @brief Gets the complete response to accomplish a succesfully
* handshake.
*
* @param hsrequest Client request.
* @param hsresponse Server response.
*
* @return Returns 0 if success and a negative number
* otherwise.
*
* @attention This is part of the internal API and is documented just
* for completeness.
*/
int get_handshake_response(char *hsrequest, char **hsresponse)
{
unsigned char *accept; /* Accept message. */
char *saveptr; /* strtok_r() pointer. */
char *s; /* Current string. */
int ret; /* Return value. */
saveptr = NULL;
for (s = strtok_r(hsrequest, "\r\n", &saveptr); s != NULL;
s = strtok_r(NULL, "\r\n", &saveptr))
{
if (strstricase(s, WS_HS_REQ) != NULL)
break;
}
/* Ensure that we have a valid pointer. */
if (s == NULL)
return (-1);
saveptr = NULL;
s = strtok_r(s, " ", &saveptr);
s = strtok_r(NULL, " ", &saveptr);
ret = get_handshake_accept(s, &accept);
if (ret < 0)
return (ret);
*hsresponse = malloc(sizeof(char) * WS_HS_ACCLEN);
if (*hsresponse == NULL)
return (-1);
strcpy(*hsresponse, WS_HS_ACCEPT);
strcat(*hsresponse, (const char *)accept);
strcat(*hsresponse, "\r\n\r\n");
free(accept);
return (0);
}
+157
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.
import wx, math
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.num_rows_added = 1
self.btnPreamp = app.QuiskCheckbutton(frame, self.OnBtnPreamp, text='Preamp')
gbs.Add(self.btnPreamp, (self.start_row, self.start_col), flag=wx.EXPAND)
bw, bh = self.btnPreamp.GetMinSize()
init = app.hermes_atten_dB
self.sliderAtten = app.SliderBoxHH(frame, 'Atten %d dB', init, 0, 31, self.OnAtten, True)
self.sliderAtten.idName = "RfAtten"
app.midiControls["RfAtten"] = (self.sliderAtten, self.OnAtten)
hardware.ChangeAtten(init)
gbs.Add(self.sliderAtten, (self.start_row, self.start_col + 2), (1, 6), flag=wx.EXPAND)
if conf.button_layout == "Small screen":
# Display four data items in a single window
self.text_temperature = app.QuiskText1(frame, '', bh)
self.text_pa_current = app.QuiskText1(frame, '', bh)
self.text_fwd_power = app.QuiskText1(frame, '', bh)
self.text_swr = app.QuiskText1(frame, '', bh)
self.text_data = self.text_temperature
self.text_pa_current.Hide()
self.text_fwd_power.Hide()
self.text_swr.Hide()
b = app.QuiskPushbutton(frame, self.OnTextDataMenu, '..')
szr = self.data_sizer = wx.BoxSizer(wx.HORIZONTAL)
szr.Add(self.text_data, 1, flag=wx.ALIGN_CENTER_VERTICAL)
szr.Add(b, 0, flag=wx.ALIGN_CENTER_VERTICAL)
gbs.Add(szr, (self.start_row, self.start_col + 10), (1, 2), flag=wx.EXPAND)
# Make a popup menu for the data window
self.text_data_menu = wx.Menu()
item = self.text_data_menu.Append(-1, 'Temperature')
app.Bind(wx.EVT_MENU, self.OnDataTemperature, item)
item = self.text_data_menu.Append(-1, 'PA Current')
app.Bind(wx.EVT_MENU, self.OnDataPaCurrent, item)
item = self.text_data_menu.Append(-1, 'Fwd Power')
app.Bind(wx.EVT_MENU, self.OnDataFwdPower, item)
item = self.text_data_menu.Append(-1, 'SWR')
app.Bind(wx.EVT_MENU, self.OnDataSwr, item)
else:
szr = wx.BoxSizer(wx.HORIZONTAL)
gbs.Add(szr, (self.start_row, self.start_col + 10), (1, 18), flag=wx.EXPAND)
text_temperature = wx.StaticText(frame, -1, ' Temp 100DC XX', style=wx.ST_NO_AUTORESIZE)
size = text_temperature.GetBestSize()
text_temperature.Destroy()
self.text_temperature = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_pa_current = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_fwd_power = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_swr = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL
szr.Add(self.text_temperature, 0, flag=flag)
szr.Add(self.text_pa_current, 0, flag=flag)
szr.Add(self.text_fwd_power, 0, flag=flag)
szr.Add(self.text_swr, 0, flag=flag)
def OnBtnPreamp(self, event):
self.hardware.ChangePreamp(self.btnPreamp.GetValue())
def OnAtten(self, event):
value = self.sliderAtten.GetValue()
self.hardware.ChangeAtten(value)
self.application.hermes_atten_dB = value
def Code2FwdRevWatts(self, fwd, rev): # Convert the fwd/rev power code to watts forward and reverse
#print (self.hardware.hermes_rev_power, self.hardware.hermes_fwd_power)
fwd = self.hardware.InterpolatePower(fwd)
rev = self.hardware.InterpolatePower(rev)
# Which voltage is forward and reverse depends on the polarity of the current sense transformer
if fwd >= rev:
return fwd, rev
else:
return rev, fwd
def UpdateText(self): # Called at intervals
conf = self.config
# Temperature
temp = self.hardware.hermes_temperature
if conf.calibrate_temp_20 == conf.calibrate_temp_40:
temp = " Temp ADC %4d" % temp
else:
m = 20.0 / (conf.calibrate_temp_40 - conf.calibrate_temp_20)
b = 20.0 - m * conf.calibrate_temp_20
temp = m * temp + b
temp = (" Temp %3.0f" % temp) + u'\u2103'
self.text_temperature.SetLabel(temp)
# power amp current
current = self.hardware.hermes_pa_current
if conf.calibrate_current_0 == conf.calibrate_current_1:
current = " Amps ADC %4d" % current
else:
m = 1.0 / (conf.calibrate_current_1 - conf.calibrate_current_0)
b = - m * conf.calibrate_current_0
current = m * current + b
current = " PA %4.0f ma" % (1000*current)
self.text_pa_current.SetLabel(current)
# forward and reverse peak power
if not self.hardware.power_interpolator: # For no interpolator, show ADC
fwd = self.hardware.hermes_fwd_power
rev = self.hardware.hermes_rev_power
self.text_fwd_power.SetLabel("Fwd ADC %4d" % fwd)
self.text_swr.SetLabel("Rev ADC %4d" % rev)
return
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_peak, self.hardware.hermes_rev_peak)
# forward less reverse power
power = fwd - rev
if power < 0.0:
power = 0.0
text = " PEP %3.1f watts" % power
self.text_fwd_power.SetLabel(text)
# SWR based on average power
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_power, self.hardware.hermes_rev_power)
if fwd >= 0.05:
gamma = math.sqrt(rev / fwd)
if gamma < 0.98:
swr = (1.0 + gamma) / (1.0 - gamma)
else:
swr = 99.0
if swr < 9.95:
text = " SWR %4.2f" % swr
else:
text = " SWR %4.0f" % swr
else:
text = " SWR ---"
self.text_swr.SetLabel(text)
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()
+46 -16
View File
@@ -15,6 +15,7 @@ from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 0
DEBUG_I2C = 0
DEBUG_SENSORS = 0
class IOBoard:
"This class controls the N2ADR IO Board for the HermesLite 2"
@@ -46,6 +47,8 @@ class IOBoard:
self.old_receive = None
self.slow = 0
def HeartBeat(self): # Called at 10 Hz for housekeeping tasks
if not self.hardware.is_HermesLite2:
return
if not QS.get_params('rx_udp_started'):
return
if self.have_IO_Board is None:
@@ -154,6 +157,7 @@ class Hardware(BaseHardware):
self.hermes_ip = ""
self.hermes_code_version = -1
self.hermes_board_id = -1
self.is_HermesLite2 = False
self.hermes_temperature = 0.0
self.hermes_fwd_power = 0.0
self.hermes_rev_power = 0.0
@@ -166,7 +170,10 @@ class Hardware(BaseHardware):
self.vna_count = 0
self.vna_started = False
self.repeater_freq = None # original repeater output frequency
self.antenna_labels = ('Ant 0', 'Ant 1') # labels for the antenna button
if conf.hardware_file_type == "Red Pitaya":
self.antenna_labels = ('Ant 0', 'Ant 1') # labels for the antenna button
else:
self.antenna_labels = ('Ant 0', 'Ant 1', 'Ant 2', 'Ant 3')
self.antenna_index = 0 # index of antenna in use
self.delay_config = True # Delay sending message to HL2 until after sound starts
self.TFRC_counter = 0 # Call for power etc. at intervals
@@ -271,6 +278,7 @@ class Hardware(BaseHardware):
self.hermes_ip = addr[0]
self.hermes_code_version = data[9]
self.hermes_board_id = data[10]
self.is_HermesLite2 = self.hermes_board_id == 6 and self.conf.hardware_file_type == "Hermes"
QS.set_hermes_id(data[9], data[10])
if data[0x16] >> 6 == 0:
QS.set_params(bandscopeScale = 2048)
@@ -317,6 +325,7 @@ class Hardware(BaseHardware):
bid = 6
self.hermes_code_version = code
self.hermes_board_id = bid
self.is_HermesLite2 = self.hermes_board_id == 6 and self.conf.hardware_file_type == "Hermes"
QS.set_hermes_id(code, bid)
st = 'Capture from Hermes device at specified IP %s' % self.hermes_ip
found = True
@@ -331,9 +340,13 @@ class Hardware(BaseHardware):
def open(self):
self.delay_config = True # Delay sending message to HL2 until after sound starts
# This list only changes control bits; no use of WriteQueue()
for name in ('keyupDelay',
'hermes_lowpwr_tr_enable', 'hermes_PWM', 'hermes_disable_sync', 'hermes_power_amp', 'Hermes_BandDictEnTx'):
self.ImmediateChange(name)
self.ImmediateChange('keyupDelay')
self.ImmediateChange('Hermes_BandDictEnTx')
if self.is_HermesLite2:
for name in ('hermes_lowpwr_tr_enable', 'hermes_PWM', 'hermes_disable_sync', 'hermes_power_amp'):
self.ImmediateChange(name)
#else: # DEBUG: Turn on power amp for testing OpenHPSDR radio with my Hermes Lite2
# self.SetControlBit(0x09, 19, 1)
return self.config_text
def GetValue(self, name): # return values stored in the hardware
if name == 'Hware_Hl2_EepromIP':
@@ -492,7 +505,15 @@ class Hardware(BaseHardware):
self.key_was_down = key_down
if self.TFRC_counter >= 3:
self.TFRC_counter = 0
self.hermes_temperature, self.hermes_fwd_power, self.hermes_rev_power, self.hermes_pa_current, self.hermes_fwd_peak, self.hermes_rev_peak = QS.get_hermes_TFRC()
AIN1_avg, AIN1_peak, AIN2_avg, AIN2_peak, AIN3, AIN4, AIN5, AIN6 = QS.get_hermes_TFRC()
if self.hermes_board_id == 6:
(self.hermes_fwd_power, self.hermes_fwd_peak, self.hermes_rev_power, self.hermes_rev_peak,
self.hermes_pa_current, self.hermes_temperature) = AIN1_avg, AIN1_peak, AIN2_avg, AIN2_peak, AIN3, AIN5
else:
(self.hermes_fwd_power, self.hermes_fwd_peak, self.hermes_rev_power, self.hermes_rev_peak,
self.hermes_pa_current, self.hermes_temperature) = AIN1_avg, AIN1_peak, AIN2_avg, AIN2_peak, AIN3, AIN5
if DEBUG_SENSORS:
print("AIN 1-6 %4d %4d %4d %4d %4d %4d" % (AIN1_avg, AIN2_avg, AIN3, AIN4, AIN5, AIN6))
if self.application.bottom_widgets:
self.application.bottom_widgets.UpdateText()
if self.delay_config and QS.get_params('rx_udp_started'):
@@ -529,7 +550,9 @@ class Hardware(BaseHardware):
def ChangeBandFilters(self):
if not hasattr(self.application, "multi_rx_screen"):
return # Needed for the VNA program
self.SetControlBit(0x00, 13, self.antenna_index)
self.SetControlBit(0x00, 13, self.antenna_index & 0x01)
self.SetControlBit(0x00, 14, self.antenna_index & 0x02)
self.SetControlBit(0x00, 15, self.antenna_index > 0)
highest = self.band
freq = self.conf.BandEdge.get(highest, (0, 0))[0]
for pane in self.application.multi_rx_screen.receiver_list:
@@ -606,12 +629,7 @@ class Hardware(BaseHardware):
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)
pass
## Simpler LNA setting for HL2 identifying as version >=40, see HL2 wiki for details
def ChangeLNA(self, value): # LNA for Rx
# value is -12 to +48
@@ -624,7 +642,7 @@ class Hardware(BaseHardware):
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
self.pc2hermes[4 * 10 + 3] = value # C0 index == 0b1010, C4[4:0] LNA 0-32 dB gain
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change LNA to", value)
def ChangeTxLNA(self, value): # LNA for Tx
@@ -634,15 +652,25 @@ class Hardware(BaseHardware):
elif value > 48:
value = 48
value = ((value+12) & 0x3f) | 0x40 | 0x80
self.SetControlByte(0x0e, 3, value, False) # C0 index == 0x0e, C3
self.SetControlByte(0x0e, 3, value, False) # C0 index == 0b1110 == 0x0e, C3
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change Tx LNA to", value)
def ChangeAtten(self, value):
# value is 0 to 31
self.pc2hermes[4 * 10 + 3] = value & 0x1F # C0 index == 0b1010, C4[4:0] attenuator, 0 to 31 dB
self.SetControlBit(0b1010, 5, value) # enable attenuator
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change attenuator to", value)
def ChangePreamp(self, value):
self.SetControlBit(0b0000, 10, value) # enable preamp
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
if not self.mode:
reduc = self.application.tx_level
elif 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
@@ -651,7 +679,7 @@ class Hardware(BaseHardware):
tx_level = 0
elif tx_level > 255:
tx_level = 255
self.pc2hermes[4 * 9] = tx_level # C0 index == 0x1001, C1[7:0] Tx level
self.pc2hermes[4 * 9] = tx_level # C0 index == 0b1001, 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
@@ -874,6 +902,8 @@ class Hardware(BaseHardware):
QS.set_hermeslite_writepointer(0)
return False
def WriteQueue(self, wait=False):
if not self.is_HermesLite2:
return False
self._wait_queue()
# Send next write
self.old_writequeue = self.pc2hermeslitewritequeue[:]
+36 -3
View File
@@ -8,6 +8,7 @@ from __future__ import absolute_import
from __future__ import division
import math, wx, time
import _quisk as QS
from configure import ComboCtrl
@@ -24,8 +25,14 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
def Widgets_0x06(self, app, hardware, conf, frame, gbs, vertBox):
self.num_rows_added = 1
start_row = self.start_row
self.btnPS = app.QuiskCheckbutton(frame, self.OnBtnPS, text='PS')
self.btnPS.Enable(False)
gbs.Add(self.btnPS, (start_row, self.start_col), flag=wx.EXPAND)
self.btnPsCal = app.QuiskPushbutton(frame, self.OnBtnPsCal, 'PS Cal')
self.btnPsCal.active = False
gbs.Add(self.btnPsCal, (start_row, self.start_col + 1), flag=wx.EXPAND)
self.atu_ctrl = ComboCtrl(frame, "ATU", ["Tune", "Bypass"], True)
gbs.Add(self.atu_ctrl, (start_row, self.start_col), (1, 2), flag=wx.EXPAND)
gbs.Add(self.atu_ctrl, (start_row, self.start_col + 2), (1, 2), flag=wx.EXPAND)
bw, bh = self.atu_ctrl.GetMinSize()
frame.Bind(wx.EVT_COMBOBOX_CLOSEUP, self.OnAtu)
init = app.hermes_LNA_dB
@@ -33,7 +40,7 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
self.sliderLNA.idName = "RfLna"
app.midiControls["RfLna"] = (self.sliderLNA, self.OnLNA)
hardware.ChangeLNA(init)
gbs.Add(self.sliderLNA, (start_row, self.start_col + 2), (1, 8), flag=wx.EXPAND)
gbs.Add(self.sliderLNA, (start_row, self.start_col + 4), (1, 6), flag=wx.EXPAND)
if conf.button_layout == "Small screen":
# Display four data items in a single window
self.text_temperature = app.QuiskText1(frame, '', bh)
@@ -93,6 +100,20 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
value = self.sliderLNA.GetValue()
self.hardware.ChangeLNA(value)
self.application.hermes_LNA_dB = value
def OnBtnPS(self, event):
if self.btnPS.GetValue():
QS.set_tx_audio(PsEnable=1)
self.btnPsCal.Enable(False)
else:
QS.set_tx_audio(PsEnable=0)
self.btnPsCal.Enable(True)
def OnBtnPsCal(self, event):
if len(self.application.multi_rx_screen.receiver_list) == 0:
self.application.multi_rx_screen.OnAddReceiver(event=None, hide_it=True)
self.btnPS.Enable(False)
self.btnPsCal.active = True
self.btnPsCal.SetColor(self.config.color_check_btn)
QS.set_tx_audio(PsCal=1)
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.
@@ -117,7 +138,13 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
return fwd, rev
else:
return rev, fwd
def UpdateText(self):
def UpdateText(self): # Called at intervals
# Button state
if self.btnPsCal.active:
if not QS.get_tx_audio("PsCal"):
self.btnPsCal.active = False
self.btnPsCal.SetColorNormal()
self.btnPS.Enable(True)
# Temperature
temp = self.Code2Temp()
temp = (" Temp %3.0f" % temp) + u'\u2103'
@@ -127,6 +154,12 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
current = " PA %4.0f ma" % (1000*current)
self.text_pa_current.SetLabel(current)
# forward and reverse peak power
if not self.hardware.power_interpolator: # For no interpolator, show ADC
fwd = self.hardware.hermes_fwd_power
rev = self.hardware.hermes_rev_power
self.text_fwd_power.SetLabel("Fwd ADC %4d" % fwd)
self.text_swr.SetLabel("Rev ADC %4d" % rev)
return
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_peak, self.hardware.hermes_rev_peak)
# forward less reverse power
power = fwd - rev
+2
View File
@@ -402,6 +402,8 @@ class Hardware(BaseHardware):
if do_tx:
try:
self.rx_udp_socket.send(s)
if DEBUG:
self.PrintStatus(' Send', self.want_udp_status)
except:
pass
def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False):
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

+221
View File
@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg5"
sodipodi:docname="quisk.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="1"
inkscape:cx="36.5"
inkscape:cy="265"
inkscape:window-width="1854"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#c0c276;fill-opacity:1;stroke:#7180ff;stroke-width:48.671;stroke-dasharray:none;stroke-opacity:1"
id="rect556"
width="341.84088"
height="349.28082"
x="62.823105"
y="39.435188"
ry="63.709782" />
<path
style="fill:none;stroke:#7180ff;stroke-width:43.2582;stroke-dasharray:none;stroke-opacity:1"
id="path3352"
d="m 193.20147,396.22476 c 6.34642,4.65885 12.2534,9.94281 18.02477,15.29408 7.66447,7.60025 15.04758,15.4729 22.82647,22.96045 6.77582,6.4671 13.60289,12.87487 20.7907,18.88349 12.48185,9.03783 26.6187,15.49278 41.24908,20.07891 13.25326,2.83808 26.93926,1.98796 40.38664,1.44647 9.20443,-0.51765 18.4186,-0.74315 27.63591,-0.83235 5.72468,-0.0231 11.44941,-0.0285 17.17409,-0.009 6.5518,0.45377 13.10409,0.8185 19.62685,1.59571 0,0 -12.83677,-9.54278 -12.83677,-9.54278 v 0 c -6.57664,-0.66042 -13.17806,-1.05429 -19.77266,-1.4853 -5.6652,0.0193 -11.33079,0.0283 -16.99467,0.16838 -9.18212,0.18217 -18.37151,0.31632 -27.53553,0.96497 -13.36116,0.68668 -27.00081,1.48715 -40.0815,-1.91133 -5.00213,-1.62118 -9.82704,-3.39549 -14.63078,-5.53945 -1.63493,-0.7297 -6.26911,-3.36104 -4.89255,-2.21625 2.66168,2.21356 5.85064,3.70365 8.82036,5.48275 1.18824,0.71182 -2.31609,-1.51997 -3.47415,-2.27995 -7.27169,-6.00427 -14.22221,-12.36521 -21.05126,-18.86768 -7.76318,-7.43952 -15.14957,-15.24359 -22.67391,-22.91764 -5.64713,-5.35952 -11.55857,-10.4455 -17.53983,-15.42662 0,0 -15.05126,-5.84659 -15.05126,-5.84659 z" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:17.3033;stroke-dasharray:none;stroke-opacity:1"
d="m 91.391053,213.41065 14.719477,-17.25897 11.13289,28.30763 12.1198,-17.78253 21.1946,-4.54944 18.44448,16.54887 12.64335,-45.07916 6.78807,-41.91379 11.53004,-41.685155 12.62529,26.532375 11.28934,37.31624 2.38907,28.97559 7.74488,35.33638 30.5342,-4.30873 22.19957,-13.63027 4.61563,-22.8555 13.28124,-96.170083 17.96907,92.980663 3.0811,45.22358 24.94369,-9.63446 10.832,-20.40026 14.95416,42.66002"
id="path15623"
sodipodi:nodetypes="cccccccccccccccccccccc" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="rect16404"
width="17.303272"
height="117.87855"
x="86.571724"
y="246.9482"
inkscape:tile-cx="107.539"
inkscape:tile-cy="302.5"
inkscape:tile-w="16"
inkscape:tile-h="109"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18731"
width="17.303272"
height="117.87855"
x="103.87501"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18733"
width="17.303272"
height="117.87855"
x="121.17827"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18735"
width="17.303272"
height="117.87855"
x="138.48155"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18737"
width="17.303272"
height="117.87855"
x="155.78482"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#dc34c6;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18739"
width="17.303272"
height="117.87855"
x="173.08809"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#fffa41;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18741"
width="17.303272"
height="117.87855"
x="190.39134"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#d20ac7;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18743"
width="17.303272"
height="117.87855"
x="207.69463"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18745"
width="17.303272"
height="117.87855"
x="224.99789"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<use
x="0"
y="0"
inkscape:tiled-clone-of="#rect16404"
xlink:href="#rect16404"
transform="translate(155.72944)"
id="use18747"
style="fill:#3e4fa1;fill-opacity:1" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18749"
width="17.303272"
height="117.87855"
x="259.60446"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#b931b2;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18751"
width="17.303272"
height="117.87855"
x="276.90775"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#e7ec2e;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18753"
width="17.303272"
height="117.87855"
x="294.21097"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#a735a6;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18755"
width="17.303272"
height="117.87855"
x="311.51425"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18757"
width="17.303272"
height="117.87855"
x="328.81754"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18759"
width="17.303272"
height="117.87855"
x="346.12079"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<rect
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
id="use18761"
width="17.303272"
height="117.87855"
x="363.42407"
y="246.9482"
inkscape:tile-x0="99.539001"
inkscape:tile-y0="248" />
<path
id="path12957"
style="fill:none;fill-opacity:1;stroke:#7180ff;stroke-width:48.671;stroke-dasharray:none;stroke-opacity:1"
d="m 404.54932,300.57496 v 23.76192 c 0,35.29519 -28.4146,63.70978 -63.70977,63.70978 H 126.41823 c -35.295185,0 -63.709785,-28.41459 -63.709785,-63.70978 v -23.19942" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

+2
View File
@@ -32,6 +32,8 @@ with this:
*/
#include <Python.h>
#include <string.h>
#include <stdio.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
+1
View File
@@ -1,5 +1,6 @@
#include <Python.h> // used by quisk.h
#include <complex.h> // Used by quisk.h
#include <string.h>
#include "quisk.h"
// This module provides methods to access the state of the key.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+70
View File
@@ -0,0 +1,70 @@
# This provides helper functions for our makefile.
# Changes for MacOS support thanks to Mario, DL3LSM.
# Changes for building from macports provided by Eric, KM4DSJ
# Updated code for a Mac build contributed by Christoph, DL1YCF, December 2020.
# Converted from setup.py to makefile by Jim Ahlstrom, June 2026
import sys, os, subprocess, sysconfig
if len(sys.argv) < 2:
sys.exit(2)
job = sys.argv[1]
def flags ():
# Fetch compiler flags and link flags from Python
result = subprocess.run(["python3-config", "--cflags"], capture_output=True, text=True, check=True)
cflags = result.stdout.strip()
cflags += " -fPIC"
result = subprocess.run(["python3-config", "--ldflags"], capture_output=True, text=True, check=True)
ldflags = result.stdout.strip()
ldflags += " -shared -lm -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -lfftw3"
if sys.platform == "darwin": # Build for Macintosh
cflags += " -DQUISK_HAVE_PORTAUDIO "
ldflags += " -lportaudio "
if os.path.isdir('/opt/local/include'): # MacPorts
base_dir = '/opt/local'
elif os.path.isdir('/usr/local/include'): # HomeBrew on macOS Intel
base_dir = '/usr/local'
elif os.path.isdir('/opt/homebrew/include'): # HomeBrew on Apple Silicon
base_dir = '/opt/homebrew'
else: # Regular build?
base_dir = '/usr'
if os.path.isfile(base_dir + "/include/pulse/pulseaudio.h"):
cflags += " -DQUISK_HAVE_PULSEAUDIO "
ldflags += " -lpulse "
cflags += " -I. -I%s/include " % base_dir
ldflags += " -L. -L%s/lib " % base_dir
elif "freebsd" in sys.platform: #Build for FreeBSD
cflags += " -DQUISK_HAVE_PULSEAUDIO -I. -I/usr/local/include"
ldflags += " -lpulse -L. -L/usr/local/lib"
else: # Linux
cflags += " -DQUISK_HAVE_ALSA -DQUISK_HAVE_PULSEAUDIO"
ldflags += " -lasound -lpulse"
if os.path.isfile("/usr/include/portaudio.h"):
cflags += " -DQUISK_HAVE_PORTAUDIO"
ldflags += " -lportaudio"
return cflags, ldflags
if job == "--soapy":
# See if soapy is available
if os.path.isdir("/usr/include/SoapySDR") or os.path.isdir("/usr/local/include/SoapySDR"):
print('1')
else:
print('0')
elif job == "--suffix":
# Determine the platform-specific extension suffix (e.g., .cpython-311-x86_64-linux-gnu.so)
print(sysconfig.get_config_var('EXT_SUFFIX'))
elif job == "--cflags":
cc, ld = flags()
print(cc)
elif job == "--ldflags":
cc, ld = flags()
print(ld)
else:
sys.exit(1)
+91 -16
View File
@@ -1,18 +1,93 @@
.PHONY: quisk
# This makefile replaces the old Python "setup.py build_ext" which has been deprecated.
# See "quisk-old" below to make Quisk using the old setup.py.
quisk:
MODULE_NAME := _quisk
OBJDIR := build/local
SUBDIRS := afedrinet
ifeq ($(shell python3 makefil.py --soapy), 1)
SUBDIRS := $(SUBDIRS) soapypkg
endif
SRCS := \
base64.c \
extdemod.c \
filter.c \
freedv.c \
handshake.c \
is_key_down.c \
microphone.c \
quisk.c \
quisk_wdsp.c \
sha1.c \
sound.c\
sound_alsa.c \
sound_directx.c \
sound_portaudio.c \
sound_pulseaudio.c \
sound_wasapi.c \
tci.c \
utf8.c \
utility.c \
ws.c
CFLAGS := $(shell python3 makefil.py --cflags)
LDFLAGS := $(shell python3 makefil.py --ldflags)
EXTENSION_SUFFIX := $(shell python3 makefil.py --suffix)
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
#$(info Info CFLAGS $(CFLAGS))
#$(info Info LDFLAGS $(LDFLAGS))
#$(info Info TARGET $(TARGET))
#$(info Info SUBDIRS $(SUBDIRS))
OBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(SRCS))
.PHONY: all clean test
.PHONY: $(SUBDIRS)
# Default target
all: $(OBJDIR) $(OBJDIR)/import_quisk_api.o ac2yd/remote.o $(TARGET) $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
$(OBJDIR):
@mkdir -p $(OBJDIR)
# Link the shared library object
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS) ac2yd/remote.o $(LDFLAGS)
# Define the location for dependency files
DEPS = $(patsubst %.o,$(OBJDIR)/%.d,$(notdir $(OBJS)))
DEPFLAGS = -MMD -MP -MF $(OBJDIR)/$(notdir $*).d
-include $(DEPS)
# Compile the C source files into object files
ac2yd/remote.o: ac2yd/remote.c quisk.h filter.h
$(CC) -o $@ -c $< $(CFLAGS) $(DEPFLAGS)
$(OBJDIR)/import_quisk_api.o: import_quisk_api.c
$(CC) -o $@ -c $< $(CFLAGS) $(DEPFLAGS)
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) -o $@ -c $< $(CFLAGS) $(DEPFLAGS)
clean:
-rm -f $(OBJDIR)/*.o $(OBJDIR)/*.d
-rm -f ac2yd/*.o
-rm -f $(MODULE_NAME)*.so
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir clean; \
done
# Run a test script to verify that the module can be imported
test: $(TARGET)
@python3 -c "import $(MODULE_NAME); print('Successfully imported:', $(MODULE_NAME).__file__)"
.PHONY: quisk-old
quisk-old:
python3 setup.py build_ext --force --inplace
@echo
quisk3:
python3 setup.py build_ext --force --inplace
@echo
soapy3:
(cd soapypkg; make soapy3)
afedrinet3:
(cd afedrinet; make afedrinet3)
perseus3:
(cd perseuspkg; make perseus3)
+1
View File
@@ -0,0 +1 @@
#
+418
View File
@@ -0,0 +1,418 @@
# This provides support for the SDR Micron TRX by David Fainitski, N7DDC
# On Windows this uses Python module ftd2xx. Install with pip.
# On Linux it uses ftdi1. Install python3-ftdi1 with your package manager.
import sys, wx, traceback, time
from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 1 # print debug messages during operation
WRITE_LOG = 1 # write the data received from the MicronTRX during self.log_time to the file self.log_name
#WRITE_LOG = 2 # receive data from the file self.log_name instead of from USB
# https://github.com/Dfinitski/SDR-Micron
# Short Control Packet type "SCP" from PC to Micron TRX, 16 bytes, MSB first (big endian)
#
# (0 - 7) Preamble, seven 0x55 and 0xD5
# (8, 9, 10) "SCP"
#(11) RX0 enable, 0 - 1
#(12) RX1 enable, 0 - 1
#(13) BS0 enable, 0 - 1
#(14) TX0 enable, 0 - 1
#(15) CDC enable, 0 - 1
# Universal control packet type "UCP" from PC to TRX or from TRX to PC, 64 bytes, MSB first (big-endian)
#
# (0 - 7) Preamble, seven 0x55 and 0xD5
# (8, 9, 10) "UCP"
# (11) read only FW prefix MSB
# (12) read only FW prefix
# (13) read only FW prefix LSB
# (14) read only char '-'
# (15) Not used
# (16) Not used
# (17) Not used
# (18, 19, 20, 21) - 4 bytes RX0 frequency
# (22, 23, 24, 25) - 4 bytes RX1 frequency
# (26, 27, 28, 29) - 4 bytes BS0 frequency
# (30, 31, 32, 33) - 4 bytes TX0 frequency
# (34) - Attenuator, 0 - 31 (dB value)
# (35) - BS0 Rate
# 0 - 48 kHz,
# 1 - 96 kHz,
# 2 - 192 kHz,
# 3 - 384 kHz,
# 4 - 768 kHz,
# 5 - 1536 kHz
# (36) LNA enable
# (37) TX level 0 - 255
# (38) RX BPF bypass 0 - 1
# (39) BS0 mode
# 0 - 4096 buffered samples
# 1 - 8192 buffered samples
# 2 - 16384 buffered samples
# (40) Not used
# (41) Not used
# (42) read-only PTT state from device 0-1
# (43) Mic Boost, 0 - 1
# (44) TX LPF Bypass 0 - 1
# (45) PTT to device 1 - 0
# (46) CW mode on 0 - 1
# (47) CW key manipulation from PC to device 1 - 0
# (48) Read only PWR, Output power in Watts
# (49) Read only SWR x.x, from 1.0 to 9.9 as a natural number without decimal point
# (50) Key Speed 10 - 63 ppm (20 by default)
# (51) Paddle swap 0, 1
# (52) Key mode, 0 = straight, 01 = Mode A, 10 = Mode B
# (53) Key weight, 33 to 66, (50 by default)
# (54) Key auto spacing, 0, 1
# (55) ST Level, 0 - 255 sight tone level
# (56) ST Freq in tens Hertz, 40 - 100 (400 - 1000Hz), 70 by default
# (57) Not used
# (58) Delay ON, 8 - 255 ms (25 by default) - time for TX carrier delay is needed for transmitter preparing to TX
# (59) Hang out time in tens ms 0 - 255 (0 to 2550 msec) (250 ms by default) needed to keep transmitter ready after PTT
# (60) Read only CW key manipulation from device to PC
# (61) Read only FW2 - firmware version MSB
# (62) Read only FW1
# (63) Read only FW0 - firmware version LSB
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
################ Receivers SdrMicronTRX, The SDR Micron TRX project by David Fainitski.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'microntrxpkg/quisk_hardware.py'
class Hardware(BaseHardware):
sample_rates = [48] #[48, 96, 192, 384, 768, 1536]
BS0_samples = {0:4096, 1:8192, 2:16384}
BS0_bytes = {0:4096 * 2, 1:8192 * 2, 2:16384 * 2} # Two bytes per sample
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.usb_windows = None
self.usb_linux = None
self.prefix = b'\x55\x55\x55\x55\x55\x55\x55\xD5'
self.read_data = bytearray()
self.tx_samples = b''
self.rf_gain_labels = ('RF 0', 'RF -10', 'RF -20', 'RF -30')
self.index = 1
self.old_vfo = 0
self.sdrmicron_clock = 76800000
self.sdrmicron_decim = 1600
self.bscope_data = bytearray()
self.have_version = False
self.frame_msg = ''
self.remaining_BS0 = -1
self.log_name = "microntrxpkg/microntrx_log.dat"
self.log_time = 5.0 # seconds of log data
self.log_time0 = None # starting time.time() to start log file
self.log_fp = None
self.pc_control = bytearray(64)
self.pc_control[0:8] = self.prefix
self.pc_control[8:11] = b'UCP'
self.got_defaults = 0
self.pc_enables = bytearray(16)
self.pc_enables[0:8] = self.prefix
self.pc_enables[8:11] = b'SCP'
self.fw_version = bytearray
if conf.fft_size_multiplier == 0:
conf.fft_size_multiplier = 3 # Set size needed by VarDecim
def open(self): # This method must return a string showing whether the open succeeded or failed.
if WRITE_LOG == 1:
self.log_fp = open(self.log_name, "wb")
elif WRITE_LOG == 2:
self.log_fp = open(self.log_name, "rb")
rx_bytes = 3 # rx_bytes is the number of bytes in each I or Q sample: 1, 2, 3, or 4
rx_endian = 1 # rx_endian is the order of bytes in the sample array: 0 == little endian; 1 == big endian
self.InitSamples(rx_bytes, rx_endian) # Initialize: read samples from this hardware file and send them to Quisk
self.InitTxSamples(2, 1, 48000) # Initialize Tx samples to send to the hardware
bs_bytes = 2
bs_endian = 1
#self.InitBscope(bs_bytes, bs_endian, self.sdrmicron_clock, self.BS1_samples[self.pc_control[40]]) # Initialize bandscope
if WRITE_LOG == 2:
return "Read data from %s" % self.log_name
elif sys.platform == 'win32':
return self.open_win()
else:
return self.open_linux()
def open_win(self):
try:
import ftd2xx as d2xx
except:
return 'The ftd2xx module is missing'
enum = d2xx.createDeviceInfoList() # quantity of FTDI devices
if enum == 0:
return 'There are no FTDI devices available'
for i in range(enum): # Searching and opening needed device
a = d2xx.getDeviceInfoDetail(i)
if DEBUG:
print ("%d: description %s, serial %s" % (i, a['description'], a['serial']))
if a['description']==b'SDR-Micron':
try:
usb = d2xx.openEx(a['serial'])
except:
return 'Device SDR-Micron could not be opened'
usb.setBitMode(255, 0) # reset
time.sleep(0.1)
usb.setBitMode(255, 64) # Configure FT2232H into Sync FIFO mode
usb.setTimeouts(100, 100) # read, write
usb.setLatencyTimer(2)
usb.setUSBParameters(32, 32) # in_tx_size, out_tx_size
time.sleep(1.5) # waiting for device initialisation
usb.read(usb.getQueueStatus()) # clean the usb data buffer
self.frame_msg = a['description'].decode('utf-8') + ' S/N - ' + a['serial'].decode('utf-8')
self.usb_windows = usb
return self.frame_msg
return 'Device SDR-Micron was not found'
def open_linux(self): # Not finished
try:
import ftdi1
except:
return 'The ftdi1 module is missing'
usb = ftdi1.new()
# Open Channel A (interface 1 in libftdi)
ftdi1.set_interface(usb, ftdi1.INTERFACE_A)
rc = ftdi1.usb_open(usb, 0x0403, 0x6014) # 0x6014 is the standard FT232H PID
if rc == 0:
ftdi1.usb_reset(usb)
ftdi1.set_usb_read_timout(usb, 1)
ftdi1.set_usb_write_timout(usb, 5)
ftdi1.set_latency_timer(usb, 2)
# Enable Sync FIFO mode
# 0xFF = pin mask, 0x40 = BITMODE_SYNCFF
ftdi1.set_bitmode(usb, 0xFF, ftdi1.BITMODE_SYNCFF)
if DEBUG:
print("Sync FIFO active via ftdi1.")
self.usb_linux = usb
return "Device SDR-Micron opened"
return 'Device SDR-Micron was not found'
def usb_read(self):
if WRITE_LOG == 2:
data = self.log_fp.read(120)
return data
elif self.usb_windows:
count = self.usb_windows.getQueueStatus() # Read all available bytes
if count > 0:
return self.usb_windows.read(count) # return Python bytes
elif self.usb_linux:
count, data = ftdi1.read_data(self.usb_linux, 1024)
if count > 0:
return data # data is Python bytes
elif count < 0 and DEBUG:
print("Error %d in usb_read", count)
return None
def usb_write(self, data):
data = bytes(data) # Convert bytearray to bytes
if self.usb_windows:
try:
self.usb_windows.write(data)
except:
if DEBUG:
print('Error while usb_write')
elif self.usb_linux:
count = ftdi1.write_data(self.usb_linux, data)
if count != len(data) and DEBUG:
print("Failure in usb_write_data %d %d", count, len(data))
def close(self):
if self.usb_windows:
self.usb_windows.setBitMode(255, 0) # reset
time.sleep(0.5)
self.usb_windows.close()
self.usb_windows = None
elif self.usb_linux:
ftdi1.usb_close(self.usb_linux)
ftdi1.free(self.usb_linux)
self.usb_linux = None
def OnButtonRfGain(self, event):
btn = event.GetEventObject()
n = btn.index
if n == 0:
self.pc_control[34] = 0
elif n == 1:
self.pc_control[34] = 10
elif n == 2:
self.pc_control[34] = 20
elif n == 3:
self.pc_control[34] = 30
if DEBUG:
print ('pc_control[34]', self.pc_control[34])
self.rx_control_upd()
def ChangeFrequency(self, tx_freq, vfo, source='', band='', event=None):
if DEBUG:
print('ChangeFrequency tx_freq', tx_freq, 'vfo', vfo)
if tx_freq and tx_freq > 0:
tx_freq = int(tx_freq - self.transverter_offset)
self.pc_control[30] = self.pc_control[18] = (tx_freq >> 24) & 0xFF # MSB
self.pc_control[31] = self.pc_control[19] = (tx_freq >> 16) & 0xFF
self.pc_control[32] = self.pc_control[20] = (tx_freq >> 8) & 0xFF
self.pc_control[33] = self.pc_control[21] = (tx_freq ) & 0xFF #LSB
self.rx_control_upd()
if vfo:
vfo = int(vfo - self.transverter_offset)
if vfo != self.old_vfo:
self.old_vfo = vfo
self.pc_control[18] = (vfo >> 24) & 0xFF # MSB
self.pc_control[19] = (vfo >> 16) & 0xFF
self.pc_control[20] = (vfo >> 8) & 0xFF
self.pc_control[21] = (vfo ) & 0xFF #LSB
self.rx_control_upd()
return tx_freq, vfo
def OnSpot(self, level):
pass
def OnChangeRxTx(self, is_rx):
self.pc_control[45] = is_rx
self.rx_control_upd()
def VarDecimGetChoices(self): # Return a list/tuple of strings for the decimation control.
return list(map(str, self.sample_rates)) # convert integer to string
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): # return sample rate
if index is None: # initial call to set the sample rate before the call to open()
rate = self.application.vardecim_set
try:
self.index = self.sample_rates.index(rate // 1000)
except:
self.index = 0
else:
self.index = index
self.pc_control[35] = self.index
rate = self.sample_rates[self.index] * 1000
if DEBUG:
print("varDecimSet index", self.index, "rate", rate)
self.rx_control_upd()
return rate
def VarDecimRange(self): # Return the lowest and highest sample rate.
#return (48000, 1536000)
return (48000, 48000)
def StartSamples(self): # called by the sound thread
self.pc_enables[11] = 1 # RX0 enable
#self.pc_enables[13] = 1 # BS0 enable
self.usb_write(self.pc_enables)
if DEBUG:
print ("Start samples")
def StopSamples(self): # called by the sound thread
self.pc_enables[11] = 0
self.pc_enables[13] = 0
self.usb_write(self.pc_enables)
if DEBUG:
print ("Stop Samples")
def rx_control_upd(self):
if self.got_defaults:
self.usb_write(self.pc_control)
def GetRxSamples(self): # Read from the MicronTRX. Called frequently from the sound thread.
# Send Tx samples to the hardware
self.tx_samples += self.GetTxSamples()
while len(self.tx_samples) >= 492:
data = self.prefix + b'TX0\x00\x00\x00\x00\x00' + self.tx_samples[0:492]
self.usb_write(data)
#print(data[0:24], len(data))
#i = data[16] << 8 | data[17]
#print ("%8d" % i)
self.tx_samples = self.tx_samples[492:]
# Read messages from USB
data = self.usb_read()
if data:
self.read_data += data
if DEBUG > 1:
print("Read %s bytes, buffer length %d" % (len(data), len(self.read_data)))
if WRITE_LOG == 1:
if self.log_fp:
if self.log_time0 is None:
self.log_time0 = time.time()
if time.time() - self.log_time0 < self.log_time:
self.log_fp.write(data)
else:
self.log_fp.close()
self.log_fp = None
while True:
start = self.read_data.find(self.prefix)
if start < 0: # No prefix
return
if start > 0:
del self.read_data[0:start]
if DEBUG:
print ("USB blocks lost sync")
if len(self.read_data) < 11:
return
block_type = self.read_data[8:11]
if block_type == b'BS0':
if len(self.read_data) < 508:
return
packet_number = self.read_data[11]
if packet_number == 0: # start of a block of data
self.remaining_BS0 = self.BS0_bytes[self.pc_control[39]]
self.bscope_data = self.read_data[16:508] # 492 bytes
self.remaining_BS0 -= 492
elif self.remaining_BS0 >= 492:
self.bscope_data += self.read_data[16:508] # 492 bytes
self.remaining_BS0 -= 492
elif self.remaining_BS0 <= 0:
pass
else:
self.bscope_data += self.read_data[16:self.remaining_BS0 + 16]
self.remaining_BS0 = -1
# self.AddBscopeSamples(self.bscope_data)
if DEBUG > 1:
print("BS0 buffer length %d" % len(self.bscope_data))
del self.read_data[0:508]
elif block_type == b'CDM':
if len(self.read_data) < 180:
return
if DEBUG > 1:
print("CDM")
del self.read_data[0:180]
elif block_type == b'RX0':
if len(self.read_data) < 508:
return
if self.read_data[12]:
self.GotClip()
self.AddRxSamples(self.read_data[16:508])
if DEBUG > 1:
print("RX0")
del self.read_data[0:508]
elif block_type == b'RX1':
if len(self.read_data) < 508:
return
if DEBUG > 1:
print("RX1")
del self.read_data[0:508]
elif block_type == b'UCP':
if len(self.read_data) < 64:
return
if self.have_version is False:
self.have_version = True
self.fw_version = self.read_data[11:15] + self.read_data[61:64]
self.frame_msg += ' F/W version - ' + self.fw_version.decode('utf-8')
self.application.main_frame.SetConfigText(self.frame_msg)
self.pc_control[11:64] = self.read_data[11:64] # save UCP content with defaults
self.got_defaults = 1;
if DEBUG > 1:
print("UCP")
del self.read_data[0:64]
else:
del self.read_data[0:11]
if DEBUG:
print("Unknown USB block type")
+641 -163
View File
@@ -3,12 +3,12 @@
#include <math.h>
#include <sys/time.h>
#include <complex.h>
#include <string.h>
#include <fftw3.h>
#include "quisk.h"
#include <sys/types.h>
#include "microphone.h"
#include "filter.h"
#include "freedv.h"
#ifdef MS_WINDOWS
#include <winsock2.h>
@@ -73,8 +73,10 @@ static double modulation_index = 1.6; // For FM transmit, the modulation index
static int is_vox = 0; // Is the VOX level exceeded?
static int vox_level = CLIP16; // VOX trigger level as a number 0 to CLIP16
static int timeVOX = 2000; // VOX hang time in milliseconds
static int tx_sample_rate = 48000; // Used for SoapySDR
static int tx_sample_rate = 48000; // Used for SoapySDR and Tx samples from Python
static int reverse_tx_sideband;
static int PsEnable = 0;
static int PsCal = 0;
static int doTxCorrect = 0; // Corrections for UDP sample transmit
static double TxCorrectLevel;
@@ -243,11 +245,9 @@ static void init_alc(struct alc * pt, int size)
case DGT_U:
case DGT_L:
case DGT_IQ:
pt->gain_now[i] = 1.4;
break;
case FDV_U:
case FDV_L:
pt->gain_now[i] = 2.0;
pt->gain_now[i] = 1.4;
break;
default:
pt->gain_now[i] = 1.0;
@@ -615,7 +615,7 @@ static int tx_filter_digital(complex double * filtered, int count)
quisk_filt_dInit(&filter1, quiskDgtFilt48Coefs, sizeof(quiskDgtFilt48Coefs)/sizeof(double)); // pass 1350, stop 1650
}
if ( ! filtered) { // Change to rxMode
quisk_filt_tune(&filter1, 1650.0 / 48000, rxMode != DGT_L && rxMode != LSB);
quisk_filt_tune(&filter1, 1650.0 / 48000, rxMode != DGT_L && rxMode != LSB && rxMode != FDV_L);
return 0;
}
for (i = 0; i < count; i++) // FIR bandpass filter; separate into I and Q
@@ -624,140 +624,6 @@ static int tx_filter_digital(complex double * filtered, int count)
return count;
}
static int tx_filter_freedv(complex double * filtered, int count, int encode)
{ // Input samples are creal(filtered), output is filtered.
// This filter is used for digital voice.
// Changes by Dave Roberts, G8KBB, for additional modes June, 2020.
// Modified by N2ADR January, 2024.
int i;
// The input sample rate is mic_sample_rate, either 8000 or 48000 sps.
// The FreeDV codec requires an input speech_sample_rate of either 8000 or 16000 sps depending on the mode. Input is always real.
// The FreeDV codec will output samples at modem_sample_rate of 8000 or 48000 sps depending on the mode, and output may be real or complex.
double dtmp, magn, dsample;
int modem_sample_rate, speech_sample_rate, use_filter, is_real;
static int samples_size = 0;
static int do_init = 1;
static double aaa, bbb, ccc, Xmin, Xmax, Ymax;
static double time_long, time_short;
static double inMax=0.3;
static double * dsamples = NULL;
static struct quisk_cFilter filter1, filter2;
static struct quisk_dFilter filtDecim48to16, filtDecim16to8;;
static struct quisk_cFilter cfiltInterp8to48;
if (do_init) { // initialization
//QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16);
do_init = 0;
quisk_filt_cInit(&filter1, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // at 48000 sps pass 3000 stop 4000 times two plus 3000
quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // at 8000 sps pass 1500 stop 1800 times two plus 1500
quisk_filt_cInit(&cfiltInterp8to48, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
quisk_filt_dInit(&filtDecim48to16, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double)); // pass 6000 stop 8000
quisk_filt_dInit(&filtDecim16to8, quiskFilt16dec8Coefs, sizeof(quiskFilt16dec8Coefs)/sizeof(double)); // pass 3000 stop 4000
Ymax = pow(10.0, - 1 / 20.0); // maximum y
Xmax = pow(10.0, 3 / 20.0); // x where slope is zero; for x > Xmax, y == Ymax
Xmin = Ymax - fabs(Ymax - Xmax); // x where slope is 1 and y = x; start of compression
//printf ("Xmin %f\n", Xmin);
aaa = 1.0 / (2.0 * (Xmin - Xmax)); // quadratic
bbb = -2.0 * aaa * Xmax;
ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax;
}
switch (freedv_current_mode) { // n_modem_sample_rate and n_speech_sample_rate may not be valid
case FREEDV_MODE_2400A:
case FREEDV_MODE_2400B:
use_filter = 1; // center frequency 3000
is_real = 1;
modem_sample_rate = 48000;
speech_sample_rate = 8000;
break;
case FREEDV_MODE_2020:
use_filter = 0;
is_real = 0;
modem_sample_rate = 8000;
speech_sample_rate = 16000;
break;
case FREEDV_MODE_800XA:
use_filter = 2; // center frequency 1500
is_real = 1;
modem_sample_rate = 8000;
speech_sample_rate = 8000;
break;
default:
use_filter = 0;
is_real = 0;
modem_sample_rate = 8000;
speech_sample_rate = 8000;
break;
}
if ( ! filtered) { // rxMode changed
quisk_filt_tune((struct quisk_dFilter *)&filter1, 3000.0 / 48000.0, rxMode != FDV_L);
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1500.0 / 8000.0, rxMode != FDV_L);
return 0;
}
// check size of dsamples[] buffer
if (count > samples_size) {
samples_size = count * 2;
if (dsamples)
free(dsamples);
dsamples = (double *)malloc(samples_size * sizeof(double));
}
// copy to dsamples[]
for (i = 0; i < count; i++)
dsamples[i] = creal(filtered[i]) / CLIP16; // normalize to 1.0000
// Decimate to 8000 Hz or to 16000 depending on mode (and hence setting of sample rate)
if (quisk_sound_state.mic_sample_rate == 48000) {
switch (speech_sample_rate) {
case 16000:
count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3);
break;
case 8000:
count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3);
count = quisk_dDecimate(dsamples, count, &filtDecim16to8, 2);
break;
default:
QuiskPrintf("Failure to convert speech input rate in tx_filter_freedv\n");
break;
}
}
else if (quisk_sound_state.mic_sample_rate == 8000 && speech_sample_rate != 8000) {
QuiskPrintf("Failure to convert input rate in tx_filter_freedv\n");
}
// Measure average peak input audio level and limit
dtmp = 1.0 / speech_sample_rate; // sample time
time_long = 1.0 - exp(- dtmp / 3.000);
time_short = 1.0 - exp(- dtmp / 0.005);
for (i = 0; i < count; i++) {
dsample = dsamples[i];
magn = fabs(dsample);
if (magn > inMax)
inMax = inMax * (1 - time_short) + time_short * magn;
else if(magn > mic_agc_level)
inMax = inMax * (1 - time_long) + time_long * magn;
else
inMax = inMax * (1 - time_long) + time_long * mic_agc_level;
dsample = dsample / inMax * Xmin * 0.7;
magn = fabs(dsample);
if (magn < Xmin)
dsamples[i] = dsample;
else if (magn > Xmax)
dsamples[i] = copysign(Ymax, dsample);
else
dsamples[i] = copysign(aaa * magn * magn + bbb * magn + ccc, dsample);
dsamples[i] = dsamples[i] * CLIP16;
}
//QuiskWavWriteD(&hWav, dsamples, count);
if (encode && pt_quisk_freedv_tx) // Encode audio into digital modulation
count = (* pt_quisk_freedv_tx)(filtered, dsamples, count, is_real);
//quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0);
if (use_filter == 1) // convert float samples to complex with an analytic filter
count = quisk_cCDecimate(filtered, count, &filter1, 1);
else if (use_filter == 2)
count = quisk_cCDecimate(filtered, count, &filter2, 1);
// Interpolate up to 48000
if (MIC_OUT_RATE != modem_sample_rate )
count = quisk_cInterpolate(filtered, count, &cfiltInterp8to48, MIC_OUT_RATE / modem_sample_rate);
return count;
}
PyObject * quisk_get_tx_filter(PyObject * self, PyObject * args)
{ // return the TX filter response to display on the graph
// This is for debugging. Change quisk.py to call QS.get_tx_filter() instead
@@ -894,6 +760,8 @@ static void quisk_hermes_tx_add(complex double * cSamples, int tx_count, int key
if (hermes_read_index >= HERMES_TX_BUF_SHORTS)
hermes_read_index -= HERMES_TX_BUF_SHORTS;
}
// Send the transmit samples to the predistortion logic. Modify the samples in place.
PreDistort(NULL, NULL, 0, cSamples, tx_count);
hermes_num_samples += tx_count;
for (i = 0; i < tx_count; i++) { // Put transmit mic samples into the buffer
hermes_buf[hermes_write_index++] = (short)cimag(cSamples[i]);
@@ -909,6 +777,7 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
{ // Send one UDP block of mic samples using the Metis-Hermes protocol. Timing is from blocks received, rate is 48k.
// If the key is up we send samples anyway, but the samples are zero.
int i, j, offset, sent, ratio;
int PsInUse;
short s;
unsigned char sendbuf[1032];
unsigned char * pt_buf;
@@ -916,6 +785,7 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
static unsigned char C0_index = 0;
complex double cw_samples[63 * 2];
static double writequeue_time0 = 0;
static unsigned char rx1_freq[4]; // frequency of the first receiver
//printf("hermes_tx_send start 1: hermes_num_samples %d\n", hermes_num_samples);
if (tx_records == NULL) {
@@ -928,6 +798,7 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
last_play_state = quisk_play_state;
serial_key_samples(NULL, 0);
}
PsInUse = PsEnable || PsCal;
//printf("hermes_tx_send start 2: hermes_num_samples %d\n", hermes_num_samples);
ratio = quisk_sound_state.sample_rate / 48000; // send rate is 48 ksps
//printf ("quisk_hermes_tx_send ratio %d count %d\n", ratio, *tx_records);
@@ -971,15 +842,33 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
sendbuf[13] = quisk_pc_to_hermes[offset++]; // C2
sendbuf[14] = quisk_pc_to_hermes[offset++]; // C3
sendbuf[15] = quisk_pc_to_hermes[offset++]; // C4
if (C0_index == 0) { // Do not change receiver count without stopping Hermes and restarting
switch (C0_index) {
case 0: // Do not change receiver count without stopping Hermes and restarting
sendbuf[15] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
if (hermes_mox_bit) // send filter selection on J16
sendbuf[13] = hermes_filter_tx << 1;
else
sendbuf[13] = hermes_filter_rx << 1;
break;
case 2: // Rx1 frequency
rx1_freq[0] = sendbuf[12];
rx1_freq[1] = sendbuf[13];
rx1_freq[2] = sendbuf[14];
rx1_freq[3] = sendbuf[15];
break;
case 3: // Rx2 frequency
if (PsInUse && hermes_mox_bit) {
sendbuf[12] = rx1_freq[0];
sendbuf[13] = rx1_freq[1];
sendbuf[14] = rx1_freq[2];
sendbuf[15] = rx1_freq[3];
}
break;
case 10:
if (PsInUse && hermes_mox_bit)
sendbuf[13] |= 0x40; // assert pure signal
break;
}
else if ( ! quisk_is_vna && C0_index == 9) {
}
if (++C0_index > 16)
C0_index = 0;
pt_buf = sendbuf + 16;
@@ -1028,15 +917,33 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
sendbuf[525] = quisk_pc_to_hermes[offset++]; // C2
sendbuf[526] = quisk_pc_to_hermes[offset++]; // C3
sendbuf[527] = quisk_pc_to_hermes[offset++]; // C4
if (C0_index == 0) {
sendbuf[527] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
switch (C0_index) {
case 0: // Do not change receiver count without stopping Hermes and restarting
sendbuf[527] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
if (hermes_mox_bit) // send filter selection on J16
sendbuf[525] = hermes_filter_tx << 1;
else
sendbuf[525] = hermes_filter_rx << 1;
break;
case 2: // Rx1 frequency
rx1_freq[0] = sendbuf[524];
rx1_freq[1] = sendbuf[525];
rx1_freq[2] = sendbuf[526];
rx1_freq[3] = sendbuf[527];
break;
case 3: // Rx2 frequency
if (PsInUse && hermes_mox_bit) {
sendbuf[524] = rx1_freq[0];
sendbuf[525] = rx1_freq[1];
sendbuf[526] = rx1_freq[2];
sendbuf[527] = rx1_freq[3];
}
break;
case 10:
if (PsInUse && hermes_mox_bit)
sendbuf[525] |= 0x40; // assert pure signal
break;
}
else if ( ! quisk_is_vna && C0_index == 9) {
}
if (++C0_index > 16)
C0_index = 0;
}
@@ -1102,12 +1009,47 @@ static void transmit_udp(complex double * cSamples, int count)
static void transmit_mic_carrier(complex double * cSamples, int count, double level)
{ // send a CW carrier instead of mic samples
#if 0
// transmit a saw tooth
static int up = 1;
static int ramp = 0;
int i;
int max = 5000;
static int pause = 5000 / 10;
int half_max = (int)(0.1 / 0.2339 * max);
for (i = 0; i < count; i++) {
cSamples[i] = (double)ramp / max * CLIP16;
if (ramp == half_max && pause) {
pause--;
}
else if (up) {
if (ramp == max) {
up = 0;
pause = max / 10;
}
else {
ramp += 1;
}
}
else {
if (ramp == 0) {
up = 1;
pause = max / 10;
}
else {
ramp -= 1;
}
}
//printf("Ramp %d\n", ramp);
}
#endif
#if 1
// transmit a carrier equal to the number of samples
int i;
for (i = 0; i < count; i++)
cSamples[i] = level * CLIP16;
#else
#endif
#if 0
// replace mic samples with a sin wave
int i;
double freq;
@@ -1278,6 +1220,10 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
count *= interp;
transmit_mic_carrier(cSamples, count, quiskSpotLevel / 1000.0);
}
else if (tci_tx_audio_client) {
count = tx_filter_digital(cSamples, count); // filter samples, minimal processing
process_alc(cSamples, count, &tx_alc, DGT_U);
}
else switch (rxMode) {
case LSB: // LSB
case USB: // USB
@@ -1319,6 +1265,8 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
case DGT_L:
case DGT_IQ:
case DGT_FM:
case FDV_U: // FreeDV
case FDV_L:
count = tx_filter_digital(cSamples, count); // filter samples, minimal processing
process_alc(cSamples, count, &tx_alc, rxMode);
break;
@@ -1326,11 +1274,6 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
count *= interp;
transmit_mic_imd(cSamples, count, quiskImdLevel / 1000.0);
break;
case FDV_U: // FDV
case FDV_L:
count = tx_filter_freedv(cSamples, count, 1);
process_alc(cSamples, count, &tx_alc, rxMode);
break;
default:
break;
}
@@ -1362,7 +1305,7 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
; // hermes handles this itself
else if (quisk_play_state == SOFTWARE_CWKEY)
serial_key_samples(cSamples, count);
if(quisk_pt_sample_write) { // Used for SoapySDR
if(quisk_pt_sample_write && key_down) { // Used for SoapySDR and samples from Python
// Interpolate the mic samples to the Tx sample rate
//printf("Tx sample rate %i\n", tx_sample_rate);
switch (tx_sample_rate) {
@@ -1421,11 +1364,6 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
count = tx_filter(cSamples, count);
process_alc(cSamples, count, &tx_alc, rxMode);
break;
case FDV_U: // FDV
case FDV_L:
count = tx_filter_freedv(cSamples, count, 0);
process_alc(cSamples, count, &tx_alc, rxMode);
break;
default:
for (i = 0; i < count; i++)
cSamples[i] = 0;
@@ -1447,14 +1385,31 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
return count;
}
PyObject * quisk_get_tx_audio(PyObject * self, PyObject * args)
{
const char * name;
if (!PyArg_ParseTuple (args, "s", &name))
return NULL;
if (strcmp(name, "PsEnable") == 0)
return PyInt_FromLong(PsEnable);
if (strcmp(name, "PsCal") == 0)
return PyInt_FromLong(PsCal);
Py_INCREF (Py_None);
return Py_None;
}
PyObject * quisk_set_tx_audio(PyObject * self, PyObject * args, PyObject * keywds)
{ /* Call with keyword arguments ONLY; change Tx audio parameters */
static char * kwlist[] = {"vox_level", "vox_time", "mic_clip", "mic_preemphasis", "tx_sample_rate",
"reverse_tx_sideband", NULL} ;
"reverse_tx_sideband", "PsEnable", "PsCal", NULL} ;
int vlevel = -9999, clevel = -9999;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiidii", kwlist,
&vlevel, &timeVOX, &clevel, &quisk_mic_preemphasis, &tx_sample_rate, &reverse_tx_sideband))
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiidiiii", kwlist,
&vlevel, &timeVOX, &clevel, &quisk_mic_preemphasis, &tx_sample_rate, &reverse_tx_sideband,
&PsEnable, &PsCal))
return NULL;
if (vlevel != -9999)
vox_level = (int)(pow(10.0, vlevel / 20.0) * CLIP16); // Convert dB to 16-bit sample
@@ -1578,7 +1533,6 @@ void quisk_set_tx_mode(void) // called when the mode rxMode is changed
tx_filter(NULL, 0);
tx_filter_digital(NULL, 0);
transmit_udp(NULL, 0);
tx_filter_freedv(NULL, 0, 0);
serial_key_samples(NULL, 0);
}
@@ -1624,3 +1578,527 @@ static void serial_key_samples(complex double * cSamples, int count) // called f
cSamples[i] = ampl * CLIP16;
}
}
static void spline(double x[], double y[], int n, double yp1, double ypn, double y2[])
{
int i,k;
double p,qn,sig,un,*u;
u = (double *)malloc(n * sizeof(double));
if (yp1 > 0.99e30)
y2[1]=u[1]=0.0;
else {
y2[1] = -0.5;
u[1]=(3.0/(x[2]-x[1]))*((y[2]-y[1])/(x[2]-x[1])-yp1);
}
for (i=2;i<=n-1;i++) {
sig=(x[i]-x[i-1])/(x[i+1]-x[i-1]);
p=sig*y2[i-1]+2.0;
y2[i]=(sig-1.0)/p;
u[i]=(y[i+1]-y[i])/(x[i+1]-x[i]) - (y[i]-y[i-1])/(x[i]-x[i-1]);
u[i]=(6.0*u[i]/(x[i+1]-x[i-1])-sig*u[i-1])/p;
}
if (ypn > 0.99e30)
qn=un=0.0;
else {
qn=0.5;
un=(3.0/(x[n]-x[n-1]))*(ypn-(y[n]-y[n-1])/(x[n]-x[n-1]));
}
y2[n]=(un-qn*u[n-1])/(qn*y2[n-1]+1.0);
for (k=n-1;k>=1;k--)
y2[k]=y2[k]*y2[k+1]+u[k];
free(u);
}
static void polint(double xa[], double ya[], double x, double *y)
{
int i,m,ns=1;
double den,dif,dift,ho,hp,w, dy;
double c[4], d[4];
dif=fabs(x-xa[1]);
for (i=1;i<=3;i++) {
if ( (dift=fabs(x-xa[i])) < dif) {
ns=i;
dif=dift;
}
c[i]=ya[i];
d[i]=ya[i];
}
*y=ya[ns--];
for (m=1;m<3;m++) {
for (i=1;i<=3-m;i++) {
ho=xa[i]-x;
hp=xa[i+m]-x;
w=c[i+1]-d[i];
den = ho - hp;
if ( (den=ho-hp) == 0.0)
printf("Error in routine polint\n");
den=w/den;
d[i]=hp*den;
c[i]=ho*den;
}
*y += (dy=(2*ns < (3-m) ? c[ns+1] : d[ns--]));
}
}
static void splint(double xa[], double ya[], double y2a[], int n, double x, double *y)
{
static int klo = 1;
int khi,k;
double h,b,a;
khi = klo + 1;
if (x < xa[klo] || x > xa[khi]) {
klo=1;
khi=n;
while (khi-klo > 1) {
k=(khi+klo) >> 1;
if (xa[k] > x) khi=k;
else klo=k;
}
}
h=xa[khi]-xa[klo];
if (h == 0.0)
printf("Bad xa input to routine splint\n");
a=(xa[khi]-x)/h;
b=(x-xa[klo])/h;
*y=a*ya[klo]+b*ya[khi]+((a*a*a-a)*y2a[klo]+(b*b*b-b)*y2a[khi])*(h*h)/6.0;
}
#define NUM_BINS 21
#define BIN_SPACING (1.0 / (NUM_BINS - 1))
#define BIN_TOL (BIN_SPACING * 0.20)
#define BLOCK_MSECS 2000 // Time of one data block.
#define MILLISECS(x) (48 * (x)) // Convert milliseconds to samples at 48000 samples per second.
#define DEBUG_PS 0
#define HOLD 0 // Make calibration run permanent
#define RX1_GAIN 0.238
void PreDistort(complex double * amp_in_samples, complex double * amp_out_samples, int nSamples, complex double * tx_samples, int num_tx)
{
int i, bin, num_bin_data;
double G0, phase, mag_in, mag_out, new_mag, aaa, ppp;
double error, xp, xpg, xp0, xpg0;
double quadX[4], quadY[4], quadZ[4];
complex double in_sample, out_sample;
static int state = 0;
static int Fault;
static int isCalibrated = 0;
static int ramp_counter, ramp_count0;
static int input_samples = 0;
static double tx_to_RX1_gain = 0;
static double max_out_mag, starting_phase;
#if DEBUG_PS
static double clip_tx, clip_mag_in, clip_aaa;
#endif
#if DEBUG_PS > 2
static int old_state;
static double time0;
#endif
static int spline_P_size;
static double spline_P_X[NUM_BINS + 1];
static double spline_P_mag[NUM_BINS + 1];
static double spline_P_mag2[NUM_BINS + 1];
static double spline_P_phase[NUM_BINS + 1];
static double spline_P_phase2[NUM_BINS + 1];
struct BPD {
int XP_count;
double table_XP;
double XP_mag;
double mag_X;
double mag_P;
double phase_P;
complex double XP_gain;
double XP_gain_mag;
double XP_gain_phase;
};
static struct BPD Bins[NUM_BINS];
static enum {
NothingHappens,
SendCW,
StopCW
} tx_state = NothingHappens;
if (quisk_multirx_count == 0) {
state = 0;
return;
}
if (PsCal) {
if (state == 0) {
isCalibrated = 0;
state = 10;
#if DEBUG_PS > 2
time0 = QuiskTimeSec();
#endif
}
}
else if ( ! PsEnable) {
state = 0;
return;
}
else if ( ! hermes_mox_bit) {
state = 0;
return;
}
if (num_tx) {
input_samples += num_tx;
if (isCalibrated) { // perform predistortion of Tx samples; multiply input X by P
for (i = 0; i < num_tx; i++) {
mag_in = cabs(tx_samples[i]) / CLIP16;
splint(spline_P_X, spline_P_mag, spline_P_mag2, spline_P_size, mag_in, &aaa);
splint(spline_P_X, spline_P_phase, spline_P_phase2, spline_P_size, mag_in, &ppp);
tx_samples[i] *= aaa * cexp(I * ppp);
new_mag = mag_in * aaa;
if (new_mag > 1.0) {
tx_samples[i] /= new_mag;
#if DEBUG_PS
if (new_mag > 1.0001 && new_mag > clip_tx) {
clip_tx = new_mag;
clip_mag_in = mag_in;
clip_aaa = aaa;
}
#endif
}
}
}
switch (tx_state) {
case NothingHappens:
default:
break;
case SendCW: // Start sending a CW carrier.
for (i = 0; i < num_tx; i++) {
if (ramp_counter > 0) // slow increase
tx_samples[i] = CLIP16 * (1.0 - (double)ramp_counter-- / ramp_count0);
else
tx_samples[i] = CLIP16;
}
break;
case StopCW: // Stop sending a CW carrier.
for (i = 0; i < num_tx; i++) {
if (ramp_counter > 0) // slow decrease
tx_samples[i] = (double)CLIP16 * ramp_counter-- / ramp_count0;
else
tx_samples[i] = 0;
}
break;
}
}
if (nSamples <= 0)
return;
#if DEBUG_PS > 2
if (state != old_state) {
aaa = cabs(amp_in_samples[0] / CLIP32);
printf("State %2d time %6.3lf sample %12.6f\n", old_state, QuiskTimeSec() - time0, aaa);
old_state = state;
time0 = QuiskTimeSec();
}
#endif
switch (state) {
case 0: // Start a timer
tx_state = NothingHappens;
input_samples = 0;
starting_phase = 0;
if ( ! HOLD)
state++;
break;
case 1:
if (input_samples >= MILLISECS(200)) {
#if DEBUG_PS
clip_tx = 0.0;
#endif
for (bin = 0; bin < NUM_BINS; bin++) { // initialize
Bins[bin].XP_count = 0;
Bins[bin].XP_mag = 0;
Bins[bin].XP_gain = 0;
}
input_samples = 0;
state++;
}
break;
case 2:
case 15:
if (input_samples >= MILLISECS(BLOCK_MSECS)) {
num_bin_data = 0;
Fault = 0;
for (bin = 0; bin < NUM_BINS; bin++) {
if (Bins[bin].XP_count) {
num_bin_data++;
Bins[bin].XP_mag /= Bins[bin].XP_count;
Bins[bin].XP_gain /= Bins[bin].XP_count;
if (starting_phase == 0 && bin >= NUM_BINS / 2) {
starting_phase = carg(Bins[bin].XP_gain);
#if DEBUG_PS
printf("starting_phase %.3f\n", starting_phase);
#endif
}
}
}
if (PsCal && num_bin_data < NUM_BINS - 4) {
printf("PS calibration failed to fill all bins ");
for (bin = 0; bin < NUM_BINS; bin++)
if (Bins[bin].XP_count)
printf("1");
else
printf("0");
printf("\n");
Fault = 1;
}
state++;
}
else {
for (i = 0; i < nSamples; i++) {
in_sample = amp_in_samples[i] / CLIP32 / RX1_GAIN;
mag_in = cabs(in_sample);
if (tx_to_RX1_gain < mag_in) {
tx_to_RX1_gain = mag_in;
#if DEBUG_PS > 2
printf("tx RX1 gain %.6f, max_out_mag %.6f\n", tx_to_RX1_gain, max_out_mag);
#endif
}
bin = (int)(mag_in / BIN_SPACING + 0.5);
if ( ! bin)
continue;
error = fabs(mag_in - Bins[bin].table_XP);
if (error < BIN_TOL) {
out_sample = amp_out_samples[i] / CLIP32;
Bins[bin].XP_gain += out_sample / in_sample;
Bins[bin].XP_mag += mag_in;
Bins[bin].XP_count++;
}
}
if (state == 15) {
if (tx_state != StopCW) {
ramp_counter = ramp_count0 = MILLISECS(BLOCK_MSECS - 500);
tx_state = StopCW;
}
}
}
break;
case 3:
case 16:
#if DEBUG_PS
if (clip_tx != 0.0) {
printf ("clip output sample %.6f, mag_in %.6f, predistort %.6f\n", clip_tx, clip_mag_in, clip_aaa);
clip_tx = 0.0;
}
#endif
for (bin = 1; bin < NUM_BINS - 1; bin++) {
if (Bins[bin].XP_count > 0) { // This bin has data.
Bins[bin].XP_gain_mag = cabs(Bins[bin].XP_gain);
phase = carg(Bins[bin].XP_gain) - starting_phase;
if (phase < - M_PI)
phase += 2 * M_PI;
else if (phase > M_PI)
phase -= 2 * M_PI;
Bins[bin].XP_gain_phase = phase;
}
else {
Bins[bin].XP_gain_mag = 1.0;
Bins[bin].XP_gain_phase = 0.0;
}
}
Bins[0].XP_count = 1;
Bins[0].XP_mag = 0.0;
i = 1;
for (bin = 1; bin < 6; bin++) {
if (Bins[bin].XP_count) {
quadX[i] = Bins[bin].XP_mag;
quadY[i] = Bins[bin].XP_gain_mag;
quadZ[i] = Bins[bin].XP_gain_phase;
if (i == 3)
break;
i++;
}
}
if (i == 3) {
polint(quadX, quadY, 0.0, &Bins[0].XP_gain_mag);
polint(quadX, quadZ, 0.0, &Bins[0].XP_gain_phase);
}
else {
Bins[0].XP_gain_mag = 1.0;
Bins[0].XP_gain_phase = 0.0;
Fault = 1;
if (DEBUG_PS || PsCal)
printf("Failure to find G0 at zero\n");
}
Bins[NUM_BINS - 1].XP_count = 1;
Bins[NUM_BINS - 1].XP_mag = 1.0;
i = 3;
for (bin = NUM_BINS - 2; bin >= NUM_BINS - 6; bin--) {
if (Bins[bin].XP_count) {
quadX[i] = Bins[bin].XP_mag;
quadY[i] = Bins[bin].XP_gain_mag;
quadZ[i] = Bins[bin].XP_gain_phase;
if (--i == 0)
break;
}
}
if (i == 0) {
polint(quadX, quadY, 1.0, &Bins[NUM_BINS - 1].XP_gain_mag);
polint(quadX, quadZ, 1.0, &Bins[NUM_BINS - 1].XP_gain_phase);
}
else {
Bins[NUM_BINS - 1].XP_gain_mag = 1.0;
Bins[NUM_BINS - 1].XP_gain_phase = 0.0;
Fault = 1;
if (DEBUG_PS || PsCal)
printf("Failure to find G0 at one\n");
}
state++;
break;
case 4:
case 17:
G0 = Bins[NUM_BINS - 1].XP_gain_mag;
num_bin_data = 0;
xp0 = -1.0;
xpg0 = -1.0;
for (bin = 0; bin < NUM_BINS; bin++) {
if (bin == NUM_BINS - 1) {
num_bin_data++;
Bins[NUM_BINS - 1].mag_P = 1.0;
Bins[NUM_BINS - 1].phase_P = - Bins[NUM_BINS - 1].XP_gain_phase;
Bins[NUM_BINS - 1].mag_X = 1.0;
}
else if (Bins[bin].XP_count) {
num_bin_data++;
Bins[bin].mag_P = G0 / Bins[bin].XP_gain_mag;
Bins[bin].phase_P = - Bins[bin].XP_gain_phase;
Bins[bin].mag_X = Bins[bin].XP_mag / Bins[bin].mag_P;
xp = Bins[bin].mag_X * Bins[bin].mag_P;
if (xp < xp0) {
if (DEBUG_PS || PsCal)
printf("xp is not monotonic at bin %d\n", bin);
Fault = 1;
}
xpg = xp * Bins[bin].XP_gain_mag;
if (xpg < xpg0) {
if (DEBUG_PS || PsCal)
printf("xpg is not monotonic at bin %d\n", bin);
Fault = 1;
}
xp0 = xp;
xpg0 = xpg;
}
else {
Bins[bin].mag_P = 0.0;
Bins[bin].phase_P = 0.0;
Bins[bin].mag_X = 0.0;
}
}
if (num_bin_data < NUM_BINS * 7 / 10) {
if (DEBUG_PS || state == 17)
printf("Not enough data for corrections %d\n", num_bin_data);
Fault = 1;
}
if ( ! Fault) {
#if DEBUG_PS
printf("Create a new predistortion\n");
#endif
spline_P_size = 0;
for (bin = 0; bin < NUM_BINS; bin++) {
if (Bins[bin].XP_count) {
spline_P_size++;
spline_P_X[spline_P_size] = Bins[bin].mag_X;
spline_P_mag[spline_P_size] = Bins[bin].mag_P;
spline_P_phase[spline_P_size] = Bins[bin].phase_P;
}
}
spline(spline_P_X, spline_P_mag, spline_P_size, 2E30, 2E30, spline_P_mag2);
spline(spline_P_X, spline_P_phase, spline_P_size, 2E30, 2E30, spline_P_phase2);
}
state++;
break;
case 5:
case 18:
#if DEBUG_PS
if (Fault)
printf("Data for predistortion was rejected\n");
printf("\n");
#endif
input_samples = 0;
state++;
break;
case 6:
if ( ! HOLD)
state = 1;
input_samples = 0;
break;
case 10:
isCalibrated = 0;
input_samples = 0;
quisk_set_key_down(1);
max_out_mag = 0;
tx_to_RX1_gain = 0;
starting_phase = 0;
ramp_counter = ramp_count0 = MILLISECS(10);
#if DEBUG_PS
clip_tx = 0.0;
#endif
for (bin = 0; bin < NUM_BINS; bin++) {
Bins[bin].table_XP = BIN_SPACING * bin;
Bins[bin].XP_mag = 0;
Bins[bin].XP_count = 0;
Bins[bin].XP_gain = 0;
}
tx_state = SendCW;
state++;
break;
case 11:
if (input_samples >= MILLISECS(400)) {
input_samples = 0;
state++;
}
if (input_samples >= MILLISECS(50) && cabs(amp_in_samples[0]) / CLIP32 / RX1_GAIN > 0.1) {
input_samples = 0;
state++;
}
break;
case 12:
if (input_samples >= MILLISECS(200)) {
state++;
}
else {
for (i = 0; i < nSamples; i++) {
in_sample = amp_in_samples[i] / CLIP32;
mag_in = cabs(in_sample);
if (tx_to_RX1_gain < mag_in)
tx_to_RX1_gain = mag_in;
out_sample = amp_out_samples[i] / CLIP32;
mag_out = cabs(out_sample);
if (max_out_mag < mag_out)
max_out_mag = mag_out;
}
}
break;
case 13:
#if DEBUG_PS
printf("tx RX1 gain %.5f, max_out_mag %.6f\n", tx_to_RX1_gain, max_out_mag);
#endif
if (fabs(tx_to_RX1_gain - RX1_GAIN) / RX1_GAIN > 1E-2)
printf("Measured %.6f and assumed %.6f RX1 gain differ\n", tx_to_RX1_gain, RX1_GAIN);
input_samples = 0;
state++;
break;
case 14:
input_samples = 0;
state++;
break;
case 19:
quisk_set_key_down(0);
tx_state = NothingHappens;
PsCal = 0;
if ( ! Fault)
isCalibrated = 1;
input_samples = 0;
state = 0;
break;
}
}
+22 -1
View File
@@ -91,6 +91,8 @@ class Hardware(BaseHardware):
conf.si570_direct_control = False
conf.si570_xtal_freq = 114285000
conf.repeater_delay = 0.25
self.ptt_count = 200
self.ptt_on = 0
BaseHardware.__init__(self, app, conf)
QS.set_sparams(multus_cw_samples=1)
self.use_softrock = True
@@ -104,6 +106,13 @@ class Hardware(BaseHardware):
self.usb_dev.ctrl_transfer(OUT, address, self.si570_i2c_address + 0x700, 0, message)
if DEBUG:
print ("USB send to 0x%X" % address, message)
def TransferIn(self, address, length):
if self.usb_dev:
recv = self.usb_dev.ctrl_transfer(IN, address, self.si570_i2c_address + 0x700, 0, length)
if DEBUG:
print ("USB receive from 0x%X" % address, recv)
return recv
return None
def open(self):
text = BaseHardware.open(self)
self.InitKeyer()
@@ -120,8 +129,20 @@ class Hardware(BaseHardware):
print ("Mode is not CW")
self.TransferOut(0x70, cw_mode)
return ret
def PollCwKey(self): # Called frequently by Quisk to check the CW key status
def PollCwKey(self): # Called frequently from the sound thread to check the CW key status
return # Quisk is always in Rx
def PollGuiControl(self): # Called frequently from the GUI thread
self.ptt_count -= 1
if self.ptt_count <= 0:
self.ptt_count = 200
reply = self.TransferIn(0xA5, 1)
if DEBUG:
print ("PollGuiControl got", reply)
if reply:
ptt = reply[0] # This is 255 for error
if ptt in (0, 1) and ptt != self.ptt_on:
self.ptt_on = ptt
self.application.pttButton.SetValue(ptt, True)
def InitKeyer(self):
# Initialize the keyer parameters
conf = self.conf
+3 -9
View File
@@ -1,8 +1,5 @@
# This is the hardware control file for my shack.
# It is for the Hermes-Lite2 5 watt output which uses only the antenna tuner.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
# It is for the Hermes-Lite2 RF1 output and Alt Rx input.
from hermes.quisk_hardware import Hardware as BaseHw
from n2adr import station_hardware
@@ -17,7 +14,7 @@ class Hardware(BaseHw):
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:
if False: # Start the station control GUI
from n2adr.station_hardware import StationControlGUI
self.GUI = StationControlGUI(self.application.main_frame, self, self.application, self.conf)
self.GUI.Show()
@@ -29,12 +26,11 @@ class Hardware(BaseHw):
return BaseHw.close(self)
def ChangeFilterFrequency(self, tx_freq):
if tx_freq and tx_freq > 0:
self.v2filter.SetTxFreq(tx_freq)
if self.GUI:
self.GUI.SetTxFreq(tx_freq)
self.GUI.freq_entry.ChangeValue("%.3f" % (tx_freq * 1E-6))
else:
self.anttuner.SetTxFreq(tx_freq)
self.v2filter.SetTxFreq(tx_freq)
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.ChangeFilterFrequency(tx_freq)
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
@@ -42,8 +38,6 @@ class Hardware(BaseHw):
# 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
+4 -50
View File
@@ -1,56 +1,10 @@
# 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
from hermes.quisk_widgets import BottomWidgets as BaseWidgets
class BottomWidgets(BaseWidgets): # 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)
BaseWidgets.__init__(self, app, hardware, conf, frame, gbs, vertBox)
+85 -123
View File
@@ -1,9 +1,5 @@
# This file supports various hardware boxes at my shack
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, struct, math, socket, select, time, traceback, os
import wx, wx.lib.buttons
@@ -411,18 +407,15 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
self.set_HighZ = 0
self.antnum = 0 # Antenna number 0 or 1
self.dipole2 = AntennaControl(app, conf) # Control the KI8BV dipole
if False and conf.use_rx_udp == 10: # Hermes UDP protocol for Hermes-Lite2
path = 'TunerLCZ_HL2.txt'
else:
path = 'TunerLCZ.txt'
path = 'TunerLCZ.txt'
if sys.platform == "win32":
path = 'C:/pub/' + path
self.path = 'C:/pub/' + path
else:
path = '/home/jim/pub/' + path
fp = open(path, "r")
self.path = '/home/jim/pub/' + path
fp = open(self.path, "r")
lines = fp.readlines()
fp.close()
self.TunerLCZ = [(0, 0, 0, 0), (999111000, 0, 0, 0)] # Add dummy first and last entry.
self.TunerLCZ = []
for line in lines:
freq, antL, antC, hilo = line.split()
freq = int(freq)
@@ -451,7 +444,12 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
pass
def ChangeBand(self, band):
pass
def FindLCZ(self, tx_freq):
def SaveLCZ(self):
fp = open(self.path, 'w')
for tup in self.TunerLCZ:
fp.write("%12d %2d %2d %d\n" % tup)
fp.close()
def FindIndexTup(self, tx_freq):
i1 = 0
i2 = len(self.TunerLCZ) - 1
while 1: # binary partition
@@ -462,20 +460,14 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
i2 = i
if i2 - i1 <= 1:
break
# The correct setting is between i1 and i2.
# The correct setting is between i1 and i2, or below i1 or above i2.
# Choose the index that is closest in frequency.
delta1 = tx_freq - self.TunerLCZ[i1][0]
delta2 = self.TunerLCZ[i2][0] - tx_freq
if i1 == 0: # below the first frequency
if abs(tx_freq - self.TunerLCZ[i1][0]) < abs(tx_freq - self.TunerLCZ[i2][0]):
index = i1
elif i2 == len(self.TunerLCZ) - 1: # above the last frequency
else:
index = i2
elif delta2 < delta1: # i2 is closer
index = i2
else: # i1 is closer
index = i1
if DEBUG: print ("AntennaTuner FindLCZ", tx_freq, i1, i2, len(self.TunerLCZ), self.TunerLCZ[index])
return self.TunerLCZ[index]
if DEBUG: print ("StatuinControlGUI FindIndexTup", tx_freq, i1, i2, len(self.TunerLCZ), index, self.TunerLCZ[index])
return index, self.TunerLCZ[index]
def SetTxFreq(self, tx_freq, no_tune=False):
#if DEBUG: print("AntennaTuner SetTxFreq", tx_freq)
self.tx_freq = tx_freq
@@ -484,10 +476,10 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
if tx_freq < 17000000:
if DEBUG and self.antnum == 1: print ("antnum 0")
self.antnum = 0
else:
elif self.dipole2:
if DEBUG and self.antnum == 0: print ("antnum 1")
self.antnum = 1
if self.antnum == 1:
if self.antnum == 1 and self.dipole2:
self.dipole2.SetTxFreq(tx_freq)
if not self.socket:
return
@@ -496,7 +488,7 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
if abs(self.tune_freq - tx_freq) < 5000: # ignore small changes
return
self.tune_freq = tx_freq
f, newL, newC, newH = self.FindLCZ(tx_freq)
index, (f, newL, newC, newH) = self.FindIndexTup(tx_freq)
if newH != self.set_HighZ or self.set_C != newC or self.set_L != newL:
self.set_HighZ = newH
self.set_C = newC
@@ -521,7 +513,8 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
self.timer = 0
else:
self.timer = 0
self.dipole2.HeartBeat()
if self.dipole2:
self.dipole2.HeartBeat()
def Send(self):
try:
self.socket.send(self.want_data)
@@ -539,26 +532,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
self.conf = conf
self.data_saving = False
self.tx_freq = 0
if sys.platform == "win32":
self.filename = 'C:/pub/TunerLCZ.tmp'
else:
self.filename = '/home/jim/pub/TunerLCZ.tmp'
try:
fp = open(self.filename, "r")
except FileNotFoundError:
lines = ()
else:
lines = fp.readlines()
fp.close()
self.TunerLCZ = [(0, 0, 0, 0), (999111000, 0, 0, 0)] # Add dummy first and last entry.
for line in lines:
freq, antL, antC, hilo = line.split()
freq = int(freq)
antL = int(antL)
antC = int(antC)
hilo = int(hilo)
self.TunerLCZ.append((freq, antL, antC, hilo))
self.TunerLCZ.sort()
self.anttuner = self.hware.anttuner
self.SetBackgroundColour('light steel blue')
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyDown)
sizer = wx.GridBagSizer(hgap=5, vgap=3)
@@ -568,6 +542,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
self.bands = []
for band in ('160', '80', '60', '40', '30', '20', '17', '15', '12', '10'):
b = wx.lib.buttons.GenToggleButton(self, -1, band)
b.Enable(False)
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
self.bands.append(b)
@@ -604,6 +579,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
szr = wx.BoxSizer(wx.HORIZONTAL)
szr.AddSpacer(20)
b = wx.lib.buttons.GenToggleButton(self, -1, "Key Down")
b.Enable(False)
b.SetFont(font)
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
@@ -611,6 +587,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
szr.Add(b)
szr.AddSpacer(10)
b = wx.lib.buttons.GenToggleButton(self, -1, "FilterV2 Preamp")
b.Enable(False)
b.SetFont(font)
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
@@ -622,16 +599,31 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
b.SetFont(font)
szr.Add(b, flag=wx.ALIGN_CENTER_VERTICAL)
szr.AddSpacer(10)
self.freq_entry = wx.TextCtrl(self, value='0000.000', style=wx.TE_PROCESS_ENTER)
self.freq_entry = wx.StaticText(self, -1, "0000.000")
#self.freq_entry = wx.TextCtrl(self, value='0000.000', style=wx.TE_PROCESS_ENTER)
self.freq_entry.SetFont(font)
self.Bind(wx.EVT_TEXT_ENTER, self.OnFreqEntry, self.freq_entry)
szr.Add(self.freq_entry)
#self.Bind(wx.EVT_TEXT_ENTER, self.OnFreqEntry, self.freq_entry)
szr.Add(self.freq_entry, flag=wx.ALIGN_CENTER_VERTICAL)
szr.AddSpacer(10)
b = wx.lib.buttons.GenButton(self, -1, "Search Freq")
b = wx.lib.buttons.GenButton(self, -1, "Freq-")
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
b.SetFont(font)
self.Bind(wx.EVT_BUTTON, self.OnButtonSearch, b)
self.Bind(wx.EVT_BUTTON, self.OnButtonDown, b)
szr.Add(b)
szr.AddSpacer(10)
b = wx.lib.buttons.GenButton(self, -1, "Freq+")
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
b.SetFont(font)
self.Bind(wx.EVT_BUTTON, self.OnButtonUp, b)
szr.Add(b)
szr.AddSpacer(10)
b = wx.lib.buttons.GenButton(self, -1, "Delete")
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
b.SetFont(font)
self.Bind(wx.EVT_BUTTON, self.OnButtonDelete, b)
szr.Add(b)
sizer.Add(szr, pos=(row, 0), span=(1, 3))
if frame is None:
@@ -647,46 +639,22 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
self.Bind(wx.EVT_TIMER, self.HeartBeat)
self.timer.Start(100)
def SetTxFreq(self, tx_freq):
#print("StationControlGUI SetTxFreq", tx_freq)
tx_freq = ((tx_freq + 500) // 1000) * 1000
self.tx_freq = tx_freq
index, tup = self.FindIndexTup(tx_freq)
anttuner = self.anttuner
index, tup = anttuner.FindIndexTup(tx_freq)
self.sliderL.SetValue(tup[1])
self.sliderC.SetValue(tup[2])
self.btnHiLo.SetValue(tup[3])
anttuner = self.hware.anttuner
fff, anttuner.set_L, anttuner.set_C, anttuner.set_HighZ = tup
if tx_freq < 17000000:
anttuner.antnum = 0
else:
elif anttuner.dipole2:
anttuner.antnum = 1
if anttuner.antnum == 1:
if anttuner.antnum == 1 and anttuner.dipole2:
anttuner.dipole2.SetTxFreq(tx_freq)
anttuner.WantData()
def FindIndexTup(self, tx_freq):
i1 = 0
i2 = len(self.TunerLCZ) - 1
while 1: # binary partition
i = (i1 + i2) // 2
if self.TunerLCZ[i][0] < tx_freq:
i1 = i
else:
i2 = i
if i2 - i1 <= 1:
break
# The correct setting is between i1 and i2.
# Choose the index that is closest in frequency.
delta1 = tx_freq - self.TunerLCZ[i1][0]
delta2 = self.TunerLCZ[i2][0] - tx_freq
if i1 == 0: # below the first frequency
index = i1
elif i2 == len(self.TunerLCZ) - 1: # above the last frequency
index = i2
elif delta2 < delta1: # i2 is closer
index = i2
else: # i1 is closer
index = i1
if DEBUG: print ("StatuinControlGUI FindLCZ", tx_freq, i1, i2, len(self.TunerLCZ), index, self.TunerLCZ[index])
return index, self.TunerLCZ[index]
self.freq_entry.SetLabel("%.3f" % (tx_freq * 1E-6))
def OnBtnClose(self, event):
self.hware.anttuner.close()
self.hware.controlbox.close()
@@ -730,16 +698,15 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
f = ((f + 500) // 1000) * 1000
if self.hware.v2filter:
self.hware.v2filter.SetTxFreq(f)
self.hware.anttuner.SetTxFreq(f, no_tune=True)
self.anttuner.SetTxFreq(f, no_tune=True)
self.freq_entry.ChangeValue("%.3f" % (f * 1E-6))
self.SetTxFreq(f)
def OnButtonHiLo(self, event):
anttuner = self.hware.anttuner
if self.btnHiLo.GetValue():
anttuner.set_HighZ = 1
self.anttuner.set_HighZ = 1
else:
anttuner.set_HighZ = 0
anttuner.WantData()
self.anttuner.set_HighZ = 0
self.anttuner.WantData()
def OnFreqEntry(self, event=None):
freq = self.freq_entry.GetValue()
freq = float(freq) * 1E6
@@ -751,11 +718,27 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
freq = int(freq + 0.1)
self.tx_freq = freq
self.SetTxFreq(self.tx_freq)
def OnButtonDown(self, event):
index, tup = self.anttuner.FindIndexTup(self.tx_freq)
index -= 1
if index >= 0:
self.SetTxFreq(self.anttuner.TunerLCZ[index][0])
def OnButtonUp(self, event):
index, tup = self.anttuner.FindIndexTup(self.tx_freq)
index += 1
if index < len(self.anttuner.TunerLCZ):
self.SetTxFreq(self.anttuner.TunerLCZ[index][0])
def OnButtonDelete(self, event):
index, tup = self.anttuner.FindIndexTup(self.tx_freq)
if self.tx_freq == self.anttuner.TunerLCZ[index][0]:
del self.anttuner.TunerLCZ[index]
if index > 0:
self.SetTxFreq(self.anttuner.TunerLCZ[index - 1][0])
elif len(self.anttuner.TunerLCZ) > 0:
self.SetTxFreq(self.anttuner.TunerLCZ[0][0])
else:
self.SetTxFreq(0)
def OnButtonSave(self, event):
freq = self.freq_entry.GetValue()
freq = float(freq) * 1E6
freq = int(freq + 0.1)
self.tx_freq = freq
L = self.sliderL.GetValue()
C = self.sliderC.GetValue()
if self.btnHiLo.GetValue():
@@ -763,22 +746,19 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
else:
hilo = 0
tup = (self.tx_freq, L, C, hilo)
index, t = self.FindIndexTup(self.tx_freq)
if self.tx_freq == self.TunerLCZ[index][0]:
self.TunerLCZ[index] = tup
index, t = self.anttuner.FindIndexTup(self.tx_freq)
if self.tx_freq == self.anttuner.TunerLCZ[index][0]:
self.anttuner.TunerLCZ[index] = tup
else:
self.TunerLCZ.append(tup)
self.TunerLCZ.sort()
fp = open(self.filename, 'w')
for tup in self.TunerLCZ[1:-1]: # Throw away dummy entries
fp.write("%12d %2d %2d %d\n" % tup)
fp.close()
self.anttuner.TunerLCZ.append(tup)
self.anttuner.TunerLCZ.sort()
self.anttuner.SaveLCZ()
def OnSliderL(self, event=None):
self.hware.anttuner.set_L = self.sliderL.GetValue()
self.hware.anttuner.WantData()
self.anttuner.set_L = self.sliderL.GetValue()
self.anttuner.WantData()
def OnSliderC(self, event=None):
self.hware.anttuner.set_C = self.sliderC.GetValue()
self.hware.anttuner.WantData()
self.anttuner.set_C = self.sliderC.GetValue()
self.anttuner.WantData()
def OnBtnV2Preamp(self, event):
if self.hware.v2filter:
self.hware.v2filter.SetPreamp(event.GetEventObject().GetValue())
@@ -1072,21 +1052,3 @@ class AT200PC: # Control an AT-200PC autotuner made by LDG
self.set_C += 1
elif text == 'C-':
self.set_C -= 1
class App(wx.App):
def OnInit(self):
if sys.path[0] != "'.'": # Make sure the current working directory is on path
sys.path.insert(0, '.')
import quisk_conf_defaults as conf
import quisk_hardware_model as hardware
self.bottom_widgets = None
hardware.anttuner = AntennaTuner(self, conf) # Control the antenna tuner
hardware.v2filter = FilterBoxV2(self, conf) # Control V2 filter box
hardware.controlbox = ControlBox(self, conf) # Control my Station Control Box
self.main_frame = frame = StationControlGUI (None, hardware, self, conf)
frame.Open()
frame.Show()
return True
if __name__ == '__main__':
App().MainLoop()
+21 -3
View File
@@ -1,7 +1,25 @@
MODULE_NAME := perseus
OBJDIR := ../build/local
perseus2:
python2 setup.py build_ext --force --inplace
CFLAGS := $(shell python3 ../makefil.py --cflags)
LDFLAGS := $(shell python3 ../makefil.py --ldflags)
EXTENSION_SUFFIX := $(shell python3 ../makefil.py --suffix)
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
perseus3:
# Default target: Link the shared library object
$(TARGET): perseus.o $(OBJDIR)/import_quisk_api.o
$(CC) -o $@ perseus.o $(LDFLAGS) $(OBJDIR)/import_quisk_api.o
# Compile the C source files into object files
perseus.o: perseus.c ../quisk.h ../filter.h
$(CC) -o $@ -c $< $(CFLAGS) -I..
.PHONY: clean
clean:
-rm -f *.o
-rm -f $(TARGET)
.PHONY: perseus-old
perseus-old:
python3 setup.py build_ext --force --inplace
+2 -2
View File
@@ -297,11 +297,11 @@ static PyObject * open_device(PyObject * self, PyObject * args)
main_cleanup:
return PyString_FromString("ERROR");
return PyUnicode_FromString("ERROR");
exit_success:
return PyString_FromString(buf128);
return PyUnicode_FromString(buf128);
}
+1 -1
View File
@@ -18,7 +18,7 @@ from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 1
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
################ Receivers PerseusSDR, The PerseusSDR interface to multiple hardware SDRs.
################ Receivers PerseusSDR, The PerseusSDR interface to multiple SDRs.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'perseuspkg/quisk_hardware.py'
+47
View File
@@ -0,0 +1,47 @@
[build-system]
requires = ["setuptools>=61.0.0"]
build-backend = "setuptools.build_meta"
[project]
name = "quisk"
version = "4.2.54"
requires-python = ">=3.13"
description = 'QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.'
license = "GPL-2.0-or-later"
license-files = ["license.txt"]
authors = [
{name = "James C. Ahlstrom", email="jahlstr@gmail.com"}
]
keywords = ['quisk', 'sdr', 'software defined radio']
classifiers = [
'Development Status :: 6 - Mature',
'Environment :: X11 Applications',
'Environment :: Win32 (MS Windows)',
'Intended Audience :: End Users/Desktop',
'Natural Language :: English',
'Operating System :: POSIX :: Linux',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python :: 3',
'Programming Language :: C',
'Topic :: Communications :: Ham Radio',
]
dynamic = ["readme"]
[project.gui-scripts]
quisk = "quisk.quisk:main"
[project.urls]
Homepage = "https://james.ahlstrom.name/quisk/index.html"
Documentation = "https://james.ahlstrom.name/quisk/docs.html"
Repository = "https://github.com/jimahlstrom/quisk"
Changelog = "https://james.ahlstrom.name/quisk/CHANGELOG.txt"
[tool.setuptools.package-data]
"quisk.icons" = ["*"]
[tool.setuptools]
package-dir = {"quisk" = "."}
packages = ["quisk", "quisk.n2adr", "quisk.softrock", "quisk.freedvpkg",
"quisk.hermes", "quisk.hiqsdr", "quisk.afedrinet", "quisk.soapypkg",
"quisk.sdrmicronpkg", "quisk.perseuspkg", "quisk.ac2yd", "quisk.multuspkg"]
+225 -185
View File
@@ -3,6 +3,7 @@
#include <stdlib.h>
#include <math.h>
#include <complex.h> // Use native C99 complex type for fftw3
#include <string.h>
#include <fftw3.h>
#include <sys/types.h>
#include <stdbool.h>
@@ -114,6 +115,8 @@ int quiskTxHoldState; // hold Tx until the repeater frequency shift is complet
int quisk_is_vna; // zero for normal program, one for the VNA program
static int py_sample_rx_bytes=2; // number of bytes in each I or Q sample: 1, 2, 3, or 4
static int py_sample_rx_endian; // order of sample array: 0 == little endian; 1 == big endian
static int py_sample_tx_bytes; // number of bytes in each I or Q sample: 2, 3, or 4
static int py_sample_tx_endian; // order of sample array: 0 == little endian; 1 == big endian
static int py_bscope_bytes;
static int py_bscope_endian;
static int quisk_auto_notch; // auto notch control
@@ -151,7 +154,6 @@ static int waterfall_scroll_mode = 1; // draw the first lines multiple times
static int quisk_use_sidetone; // is there a sidetone volume control?
static int hl2_txbuf_errors; // errors in the Hermes-Lite2 Tx buffer
static int hl2_txbuf_state; // state machine for errors in the Hermes-Lite2 Tx buffer
static int freedv_monitor; // pass the Freedv audio to the speakers instead of to the demodulator
static int softrock_correct_freq; // gain and phase corrections for SoftRock
static double softrock_correct_gain;
static double softrock_correct_phase;
@@ -159,6 +161,8 @@ static int softrock_correct_active; // 0 No correction adjustment; 1 Manual adju
static complex double PySampleBuf[SAMP_BUFFER_SIZE]; // buffer for samples returned from Python
static int PySampleCount; // count of samples in buffer
static uint8_t PySampleTxBytes[SAMP_BUFFER_SIZE * 8]; // buffer for Tx sample bytes to send to the hardware
static int PySampleTxCount;
static bool quisk_tx_inhibit;
static int multirx_data_width; // width of graph data to return
@@ -219,14 +223,17 @@ static unsigned char quisk_hermes_to_pc[5 * 4]; // data received from the Herme
static unsigned char quisk_hermeslite_response[5]; // response from Hermes-Lite commands
unsigned int quisk_hermes_code_version = -1; // code version returned by the Hermes hardware
unsigned int quisk_hermes_board_id = -1; // board ID returned by the Hermes hardware
static double hermes_temperature; // average temperature
static double hermes_fwd_power; // average forward power
static double hermes_rev_power; // average reverse power
static double hermes_fwd_peak; // peak forward power
static double hermes_rev_peak; // peak reverse power
static double hermes_pa_current; // average power amp current
static int hermes_count_temperature; // number of temperature samples
static int hermes_count_current; // number of current samples
static double hpsdr_AIN1_avg; // average forward power
static double hpsdr_AIN1_peak; // peak forward power
static double hpsdr_AIN2_avg; // average reverse power
static double hpsdr_AIN2_peak; // peak reverse power
static double hpsdr_AIN3; // average power amp current
static double hpsdr_AIN4;
static double hpsdr_AIN5; // average temperature
static double hpsdr_AIN6;
static int hpsdr_AIN3_count; // number of current samples
static int hpsdr_AIN4_count; // number of current samples
static int hpsdr_AIN5_count; // number of current samples
static int hardware_ptt; // hardware PTT switch
int quisk_hardware_cwkey; // hardware CW key from UDP or USB
@@ -569,15 +576,10 @@ void QuiskWavClose(struct QuiskWav * hWav)
}
#endif
// These are used for digital voice codecs
ty_dvoice_codec_rx pt_quisk_freedv_rx;
ty_dvoice_codec_tx pt_quisk_freedv_tx;
void quisk_dvoice_freedv(ty_dvoice_codec_rx rx, ty_dvoice_codec_tx tx)
void quisk_dvoice_freedv(void) // Not used.
{
pt_quisk_freedv_rx = rx;
pt_quisk_freedv_tx = tx;
}
#if 0
static int fFracDecim(double * dSamples, int nSamples, double fdecim)
{ // fractional decimation by fdecim > 1.0
@@ -1851,10 +1853,8 @@ static int quisk_process_decimate(complex double * cSamples, int nSamples, int b
static int quisk_process_demodulate(complex double * cSamples, double * dsamples, int nSamples, int bank, int nFilter, rx_mode_type rx_mode)
{ // Changes here will require changes to get_filter_rate();
int i;
bool isLSB;
complex double cx;
double d, di, dd;
static struct AgcState Agc1 = {0.3, 16000, 0}, Agc2 = {0.3, 16000, 0};
//static int count=0;
//static double phase=0;
static struct stStorage {
@@ -2090,6 +2090,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
MeasureSquelch[bank].squelch_active = MeasureSquelch[bank].squelch < squelch_level;
break;
case DGT_U: // digital mode DGT-U at 48 ksps
case FDV_U:
if (filter_bandwidth[nFilter] < DGT_NARROW_FREQ) { // filter at 6 ksps
quisk_filter_srate = quisk_decim_srate / 8;
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &Storage[bank].HalfBand5);
@@ -2116,6 +2117,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
}
break;
case DGT_L: // digital mode DGT-L
case FDV_L:
if (filter_bandwidth[nFilter] < DGT_NARROW_FREQ) { // filter at 6 ksps
quisk_filter_srate = quisk_decim_srate / 8;
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &Storage[bank].HalfBand5);
@@ -2154,102 +2156,6 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
}
}
break;
case FDV_U: // digital voice
case FDV_L: // Extra modes added by Dave Roberts, G8KBB, June 2020. Modified by N2ADR 2024.
quisk_check_freedv_mode();
// current coding assumes input rate n_modem_sample_rate is 8000 or 48000.
// decimate:
switch (n_modem_sample_rate) {
case 8000:
quisk_filter_srate = quisk_decim_srate / 6;
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3);
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2);
break;
case 48000:
quisk_filter_srate = quisk_decim_srate;
break;
default:
quisk_filter_srate = quisk_decim_srate / 6;
QuiskPrintf("Bad FreeDV modem rate in quisk_process_demodulate\n");
break;
}
// filter and demodulate to mono audio:
isLSB = (rx_mode == FDV_L);
complex double cBuf[nSamples];
for (i = 0; i < nSamples; i++) {
cx = cRxFilterOut(cSamples[i], bank, nFilter);
if (isLSB)
dd = creal(cx) + cimag(cx);
else
dd = creal(cx) - cimag(cx);
if(bank == 0) {
measure_audio_sum += dd * dd;
measure_audio_count += 1;
}
cBuf[i] = dd;
}
// Perhaps decimate by an additional fraction
if (quisk_decim_srate != 48000) {
dd = quisk_decim_srate / 48000.0;
nSamples = cFracDecim(cBuf, nSamples, dd);
quisk_decim_srate = 48000;
}
if (bank == 0)
process_agc(&Agc1, cBuf, nSamples, 0);
else
process_agc(&Agc2, cBuf, nSamples, 0);
// pass data decimated down to n_modem_sample_rate
// return data is at n_speech_sample_rate
#if 0
static double dmax_in = 0;
for (i = 0; i < nSamples; i++) {
dd = fabs(creal(cBuf[i])) / CLIP16;
if (dd > dmax_in)
dmax_in = dd;
}
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
//printf("Max sample in %.0lf\n", dmax_in);
dmax_in = 0;
}
#endif
if (nSamples > n_max_modem_samples) {
printf ("FreeDV: nSamples %d exceeds max_modem_samples %d\n", nSamples, n_max_modem_samples);
nSamples = n_max_modem_samples;
}
short sBuf[nSamples];
for (i = 0; i < nSamples; i++)
sBuf[i] = (short)(creal(cBuf[i]) / CLIP16);
if (freedv_monitor) {
for (i = 0; i < nSamples; i++)
dsamples[i] = (float)sBuf[i] * CLIP16;
}
else {
if (pt_quisk_freedv_rx)
nSamples = (* pt_quisk_freedv_rx)(sBuf, dsamples, nSamples, bank);
}
#if 0
static double dmax_out = 0;
for (i = 0; i < nSamples; i++) {
dd = fabs(dsamples[i]) / CLIP16;
if (dd > dmax_out)
dmax_out = dd;
}
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
//printf(" Max sample out %.0lf\n", dmax_out);
dmax_out = 0;
}
#endif
// current coding assumes output rate n_speech_sample_rate is 8000 or 16000
switch (n_speech_sample_rate) {
case 8000:
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
break;
case 16000:
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
break;
}
break;
}
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
measured_audio = sqrt(measure_audio_sum / measure_audio_count) / CLIP32 * 1e6;
@@ -2544,7 +2450,8 @@ int quisk_process_samples(complex double * cSamples, int nSamples)
}
}
NoiseBlanker(cSamples, nSamples);
if ( ! quisk_is_key_down())
NoiseBlanker(cSamples, nSamples);
// Put samples into the fft input array.
// Thanks to WB4JFI for the code to add a third FFT buffer, July 2010.
@@ -2756,7 +2663,7 @@ int quisk_process_samples(complex double * cSamples, int nSamples)
quisk_decim_srate = 48000;
}
// Process the Rx path with the WDSP library
nSamples = wdspFexchange0(QUISK_WDSP_RX, (double *)cSamples, nSamples);
nSamples = wdspFexchange0(QUISK_WDSP_RX, cSamples, nSamples);
// Interpolate the samples from 48000 sps to the play rate.
switch (quisk_sound_state.playback_rate / 48000) {
@@ -2784,9 +2691,6 @@ start_agc:
if (rxMode == EXT || rxMode == DGT_IQ) { // Ext and DGT-IQ stereo sound
process_agc(&Agc1, cSamples, nSamples, 1);
}
else if (rxMode == FDV_U || rxMode == FDV_L) { // Agc already done
;
}
else if (split_rxtx || multirx_play_channel >= 0) { // separate AGC for left and right channels
for (i = 0; i < nSamples; i++) {
orig_cSamples[i] = cimag(cSamples[i]);
@@ -2944,6 +2848,8 @@ static PyObject * get_filter_rate(PyObject * self, PyObject * args)
break;
case DGT_U: // digital modes DGT-*
case DGT_L:
case FDV_U: // digital voice
case FDV_L:
if (bandwidth < DGT_NARROW_FREQ)
filter_srate = decim_srate / 8;
else
@@ -2952,10 +2858,6 @@ static PyObject * get_filter_rate(PyObject * self, PyObject * args)
case DGT_IQ: // digital mode at 48 ksps
filter_srate = decim_srate;
break;
case FDV_U: // digital voice
case FDV_L:
filter_srate = n_modem_sample_rate;
break;
}
//QuiskPrintf("Filter rate %d\n", filter_srate);
return PyInt_FromLong(filter_srate);
@@ -3120,6 +3022,17 @@ static PyObject * add_bscope_samples(PyObject * self, PyObject * args)
return Py_None;
}
static PyObject * get_tx_samples(PyObject * self, PyObject * args)
{
PyObject * by;
if (!PyArg_ParseTuple (args, ""))
return NULL;
by = PyBytes_FromStringAndSize((const char *)PySampleTxBytes, PySampleTxCount);
PySampleTxCount = 0;
return by;
}
static void py_sample_start(void)
{
}
@@ -3142,6 +3055,83 @@ static int py_sample_read(complex double * cSamples)
return n;
}
static int py_sample_tx_save(complex double * cSamples, int nSamples)
{ // These cSamples are 16-bit integers from the microphone
int i;
int16_t rr, ii;
uint8_t rrH, rrL, iiH, iiL;
if (PySampleTxCount + nSamples * 8 >= SAMP_BUFFER_SIZE * 8) {
QuiskPrintf ("py_sample_tx_save: buffer is too full\n");
return 0;
}
for (i = 0; i < nSamples; i++) {
rr = (int16_t)creal(cSamples[i]);
ii = (int16_t)cimag(cSamples[i]);
rrH = 0xFF & (rr >> 8);
rrL = 0xFF & rr;
iiH = 0xFF & (ii >> 8);
iiL = 0xFF & ii;
if (py_sample_tx_endian == 0) { // byte order of samples is little-endian
switch(py_sample_tx_bytes) {
case 2:
PySampleTxBytes[PySampleTxCount++] = rrL;
PySampleTxBytes[PySampleTxCount++] = rrH;
PySampleTxBytes[PySampleTxCount++] = iiL;
PySampleTxBytes[PySampleTxCount++] = iiH;
break;
case 3:
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = rrL;
PySampleTxBytes[PySampleTxCount++] = rrH;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = iiL;
PySampleTxBytes[PySampleTxCount++] = iiH;
break;
case 4:
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = rrL;
PySampleTxBytes[PySampleTxCount++] = rrH;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = iiL;
PySampleTxBytes[PySampleTxCount++] = iiH;
break;
}
}
else { // byte order of samples is big-endian
switch(py_sample_tx_bytes) {
case 2:
PySampleTxBytes[PySampleTxCount++] = rrH;
PySampleTxBytes[PySampleTxCount++] = rrL;
PySampleTxBytes[PySampleTxCount++] = iiH;
PySampleTxBytes[PySampleTxCount++] = iiL;
break;
case 3:
PySampleTxBytes[PySampleTxCount++] = rrH;
PySampleTxBytes[PySampleTxCount++] = rrL;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = iiH;
PySampleTxBytes[PySampleTxCount++] = iiL;
PySampleTxBytes[PySampleTxCount++] = 0;
break;
case 4:
PySampleTxBytes[PySampleTxCount++] = rrH;
PySampleTxBytes[PySampleTxCount++] = rrL;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = iiH;
PySampleTxBytes[PySampleTxCount++] = iiL;
PySampleTxBytes[PySampleTxCount++] = 0;
PySampleTxBytes[PySampleTxCount++] = 0;
break;
}
}
}
return 0;
}
static PyObject * get_params(PyObject * self, PyObject * args)
{
const char * name;
@@ -3195,19 +3185,22 @@ static PyObject * set_params(PyObject * self, PyObject * args, PyObject * keywds
{ /* Call with keyword arguments ONLY; change local parameters */
static char * kwlist[] = {"quisk_is_vna", "rx_bytes", "rx_endian", "read_error", "clip",
"bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause",
"freedv_monitor", NULL} ;
"tx_bytes", "tx_endian",
NULL} ;
int i, nbytes, read_error, clip, bscope_size, hermes_pause;
nbytes = read_error = clip = bscope_size = hermes_pause = -1;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidii", kwlist,
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidiii", kwlist,
&quisk_is_vna, &nbytes, &py_sample_rx_endian, &read_error, &clip,
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause,
&freedv_monitor))
&py_sample_tx_bytes, &py_sample_tx_endian))
return NULL;
if (nbytes != -1) {
py_sample_rx_bytes = nbytes;
quisk_sample_source4(py_sample_start, py_sample_stop, py_sample_read, NULL);
quisk_sample_source(py_sample_start, py_sample_stop, py_sample_read);
}
if (py_sample_tx_bytes)
quisk_pt_sample_write = py_sample_tx_save;
if (read_error != -1)
quisk_sound_state.read_error++;
if (clip != -1)
@@ -3243,31 +3236,43 @@ static PyObject * get_hermes_TFRC(PyObject * self, PyObject * args)
if (!PyArg_ParseTuple (args, ""))
return NULL;
if (hermes_count_temperature > 0) {
hermes_temperature /= hermes_count_temperature;
hermes_fwd_power /= hermes_count_temperature;
if (hpsdr_AIN5_count > 0) {
hpsdr_AIN5 /= hpsdr_AIN5_count;
hpsdr_AIN1_avg /= hpsdr_AIN5_count;
}
else {
hermes_temperature = 0.0;
hermes_fwd_power = 0.0;
hpsdr_AIN5 = 0.0;
hpsdr_AIN1_avg = 0.0;
}
if (hermes_count_current > 0) {
hermes_rev_power /= hermes_count_current;
hermes_pa_current /= hermes_count_current;
if (hpsdr_AIN3_count > 0) {
hpsdr_AIN2_avg /= hpsdr_AIN3_count;
hpsdr_AIN3 /= hpsdr_AIN3_count;
}
else {
hermes_rev_power = 0.0;
hermes_pa_current = 0.0;
hpsdr_AIN2_avg = 0.0;
hpsdr_AIN3 = 0.0;
}
ret = Py_BuildValue("dddddd", hermes_temperature, hermes_fwd_power, hermes_rev_power, hermes_pa_current, hermes_fwd_peak, hermes_rev_peak);
hermes_temperature = 0;
hermes_fwd_power = 0;
hermes_rev_power = 0;
hermes_fwd_peak = 0;
hermes_rev_peak = 0;
hermes_pa_current = 0;
hermes_count_temperature = 0;
hermes_count_current = 0;
if (hpsdr_AIN4_count > 0) {
hpsdr_AIN4 /= hpsdr_AIN4_count;
hpsdr_AIN6 /= hpsdr_AIN4_count;
}
else {
hpsdr_AIN4 = 0.0;
hpsdr_AIN6 = 0.0;
}
ret = Py_BuildValue("dddddddd", hpsdr_AIN1_avg, hpsdr_AIN1_peak, hpsdr_AIN2_avg, hpsdr_AIN2_peak,
hpsdr_AIN3, hpsdr_AIN4, hpsdr_AIN5, hpsdr_AIN6);
hpsdr_AIN1_avg = 0;
hpsdr_AIN1_peak = 0;
hpsdr_AIN2_avg = 0;
hpsdr_AIN2_peak = 0;
hpsdr_AIN3 = 0;
hpsdr_AIN4 = 0;
hpsdr_AIN5 = 0;
hpsdr_AIN6 = 0;
hpsdr_AIN3_count = 0;
hpsdr_AIN4_count = 0;
hpsdr_AIN5_count = 0;
return ret;
}
@@ -3815,19 +3820,24 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
quisk_set_play_state();
}
}
else if(dindex == 1) { // temperature and forward power
hermes_temperature += quisk_hermes_to_pc[4] << 8 | quisk_hermes_to_pc[5];
else if(dindex == 1) {
hpsdr_AIN5 += quisk_hermes_to_pc[4] << 8 | quisk_hermes_to_pc[5];
power = quisk_hermes_to_pc[6] << 8 | quisk_hermes_to_pc[7];
hermes_fwd_power += power;
hermes_fwd_peak = fmax(hermes_fwd_peak, (double)power);
hermes_count_temperature++;
hpsdr_AIN1_avg += power;
hpsdr_AIN1_peak = fmax(hpsdr_AIN1_peak, (double)power);
hpsdr_AIN5_count++;
}
else if (dindex == 2) { // reverse power and current
else if (dindex == 2) {
power = quisk_hermes_to_pc[8] << 8 | quisk_hermes_to_pc[9];
hermes_rev_power += power;
hermes_rev_peak = fmax(hermes_rev_peak, (double)power);
hermes_pa_current += quisk_hermes_to_pc[10] << 8 | quisk_hermes_to_pc[11];
hermes_count_current++;
hpsdr_AIN2_avg += power;
hpsdr_AIN2_peak = fmax(hpsdr_AIN2_peak, (double)power);
hpsdr_AIN3 += quisk_hermes_to_pc[10] << 8 | quisk_hermes_to_pc[11];
hpsdr_AIN3_count++;
}
else if (dindex == 3) {
hpsdr_AIN4 += quisk_hermes_to_pc[12] << 8 | quisk_hermes_to_pc[13];
hpsdr_AIN6 += quisk_hermes_to_pc[14] << 8 | quisk_hermes_to_pc[15];
hpsdr_AIN4_count++;
}
// convert 24-bit samples to 32-bit samples; int must be 32 bits.
index = start + 5;
@@ -3850,6 +3860,43 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
}
}
}
PreDistort(multirx_cSamples[0], samp, nSamples, NULL, 0); //amp in samples, amp out samples, count
#if 0
float mag_in, mag_out;
complex float gain;
complex double * amp_in_samples;
complex double * amp_out_samples;
static int up = 0;
static float old_mag_in = 0;
amp_in_samples = multirx_cSamples[0];
amp_out_samples = samp;
//if (hermes_mox_bit && quisk_multirx_count > 0) {
if (quisk_multirx_count == 1) {
for (i = 0; i < nSamples; i++) {
//mag_in = cabs(amp_in_samples[i]) / 0.236875 / CLIP32;
//mag_out = cabs(amp_out_samples[i]) / 0.088037 / CLIP32;
mag_in = cabs(amp_in_samples[i]) / CLIP32;
mag_out = cabs(amp_out_samples[i]) / CLIP32;
//if (0.100 - 0.01 < mag_in && mag_in < 0.100 + 0.01) {
//if (mag_in > .001) {
if (mag_in > 0.001 && mag_in < 0.234) {
if (up && mag_in < old_mag_in) {
//printf("\n");
up = 0;
}
else if ( ! up && mag_in > old_mag_in) {
//printf("\n");
up = 1;
}
old_mag_in = mag_in;
//gain = amp_out_samples[i] / amp_in_samples[i] / (0.088037 / 0.236875);
gain = amp_out_samples[i] / amp_in_samples[i];
printf("In %12.6f %8.3f Out %12.6f %8.3f Gain %12.6f %8.3f\n",
mag_in, cargf(amp_in_samples[i]), mag_out, cargf(amp_out_samples[i]), cabsf(gain), cargf(gain));
}
}
}
#endif
if ((quisk_pc_to_hermes[3] >> 3 & 0x7) != quisk_multirx_count && // change in number of receivers
( ! quisk_multirx_count || multirx_fft_next_state == 2)) { // wait until the current FFT is finished
quisk_multirx_state = 0; // Do not change receiver count without stopping Hermes and restarting
@@ -4077,7 +4124,7 @@ static PyObject * open_rx_udp(PyObject * self, PyObject * args)
wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
sprintf(buf, "Failed to initialize Winsock (WSAStartup)");
return PyString_FromString(buf);
return PyUnicode_FromString(buf);
}
else {
cleanupWSA = 1;
@@ -4126,7 +4173,7 @@ static PyObject * open_rx_udp(PyObject * self, PyObject * args)
else {
sprintf(buf, "Failed to open socket");
}
return PyString_FromString(buf);
return PyUnicode_FromString(buf);
}
static PyObject * open_sound(PyObject * self, PyObject * args)
@@ -4340,7 +4387,7 @@ static PyObject * mixer_set(PyObject * self, PyObject * args)
return NULL;
quisk_alsa_mixer_set(card_name, numid, value, err_msg, QUISK_SC_SIZE);
return PyString_FromString(err_msg);
return PyUnicode_FromString(err_msg);
}
static PyObject * pc_to_hermes(PyObject * self, PyObject * args)
@@ -4543,12 +4590,12 @@ static PyObject * ip_interfaces(PyObject * self, PyObject * args)
baddr |= ~netmask;
tup = PyTuple_New(4);
if (name == NULL)
PyTuple_SetItem(tup, 0, PyString_FromString("unnamed"));
PyTuple_SetItem(tup, 0, PyUnicode_FromString("unnamed"));
else
PyTuple_SetItem(tup, 0, PyString_FromString(name));
PyTuple_SetItem(tup, 1, PyString_FromString(Win_NtoA(ipAddr)));
PyTuple_SetItem(tup, 2, PyString_FromString(Win_NtoA(netmask)));
PyTuple_SetItem(tup, 3, PyString_FromString(Win_NtoA(baddr)));
PyTuple_SetItem(tup, 0, PyUnicode_FromString(name));
PyTuple_SetItem(tup, 1, PyUnicode_FromString(Win_NtoA(ipAddr)));
PyTuple_SetItem(tup, 2, PyUnicode_FromString(Win_NtoA(netmask)));
PyTuple_SetItem(tup, 3, PyUnicode_FromString(Win_NtoA(baddr)));
PyList_Append(pylist, tup);
Py_DECREF(tup);
}
@@ -4567,10 +4614,10 @@ static PyObject * ip_interfaces(PyObject * self, PyObject * args)
while(p) {
if ((p->ifa_addr) && p->ifa_addr->sa_family == AF_INET) {
tup = PyTuple_New(4);
PyTuple_SetItem(tup, 0, PyString_FromString(p->ifa_name));
PyTuple_SetItem(tup, 1, PyString_FromString(Lin_NtoA(p->ifa_addr)));
PyTuple_SetItem(tup, 2, PyString_FromString(Lin_NtoA(p->ifa_netmask)));
PyTuple_SetItem(tup, 3, PyString_FromString(Lin_NtoA(p->ifa_broadaddr)));
PyTuple_SetItem(tup, 0, PyUnicode_FromString(p->ifa_name));
PyTuple_SetItem(tup, 1, PyUnicode_FromString(Lin_NtoA(p->ifa_addr)));
PyTuple_SetItem(tup, 2, PyUnicode_FromString(Lin_NtoA(p->ifa_netmask)));
PyTuple_SetItem(tup, 3, PyUnicode_FromString(Lin_NtoA(p->ifa_broadaddr)));
PyList_Append(pylist, tup);
Py_DECREF(tup);
}
@@ -5710,7 +5757,7 @@ static PyObject * Xdft(PyObject * pyseq, int inverse, int window)
static fftw_complex * samples; // complex data for fft
static fftw_plan planF, planB; // fft plan for fftW
static double * fft_window; // window function
Py_complex pycx; // Python C complex value
complex double pycx;
if (PySequence_Check(pyseq) != 1) {
PyErr_SetString (QuiskError, "DFT input data is not a sequence");
@@ -5745,22 +5792,20 @@ static PyObject * Xdft(PyObject * pyseq, int inverse, int window)
for (i = 0; i < size; i++) {
obj = PySequence_GetItem(pyseq, j);
if (PyComplex_Check(obj)) {
pycx = PyComplex_AsCComplex(obj);
pycx = PyComplex_RealAsDouble(obj) + I * PyComplex_ImagAsDouble(obj);
}
else if (PyFloat_Check(obj)) {
pycx.real = PyFloat_AsDouble(obj);
pycx.imag = 0;
pycx = PyFloat_AsDouble(obj);
}
else if (PyInt_Check(obj)) {
pycx.real = PyInt_AsLong(obj);
pycx.imag = 0;
pycx = PyInt_AsLong(obj);
}
else {
Py_XDECREF(obj);
PyErr_SetString (QuiskError, "DFT input data is not a complex/float/int number");
return NULL;
}
samples[i] = pycx.real + I * pycx.imag;
samples[i] = pycx;
if (++j >= size)
j = 0;
Py_XDECREF(obj);
@@ -5786,9 +5831,7 @@ static PyObject * Xdft(PyObject * pyseq, int inverse, int window)
pyseq = PyList_New(fft_size);
j = (size - 1) / 2; // zero frequency in input
for (i = 0; i < fft_size; i++) {
pycx.real = creal(samples[i]);
pycx.imag = cimag(samples[i]);
PyList_SetItem(pyseq, j, PyComplex_FromCComplex(pycx));
PyList_SetItem(pyseq, j, PyComplex_FromDoubles(creal(samples[i]), cimag(samples[i])));
if (++j >= size)
j = 0;
}
@@ -6143,6 +6186,7 @@ static PyMethodDef QuiskMethods[] = {
{"get_filter_rate", get_filter_rate, METH_VARARGS, "Return the sample rate used for the filters."},
{"get_tx_filter", quisk_get_tx_filter, METH_VARARGS, "Return the frequency response of the transmit filter."},
{"get_audio_graph", get_audio_graph, METH_VARARGS, "Return a tuple of the audio graph data."},
{"get_tx_samples", get_tx_samples, METH_VARARGS, "Return Tx samples to send to the hardware."},
{"softrock_corrections", softrock_corrections, METH_VARARGS, "Control and return SoftRock amplitude and phase corrections."},
{"measure_frequency", measure_frequency, METH_VARARGS, "Set the method, return the measured frequency."},
{"measure_audio", measure_audio, METH_VARARGS, "Set the method, return the measured audio voltage."},
@@ -6190,6 +6234,7 @@ static PyMethodDef QuiskMethods[] = {
{"XXset_transmit_mode", set_transmit_mode, METH_VARARGS, "Change the radio to transmit mode independent of key_down."},
{"set_volume", set_volume, METH_VARARGS, "Set the audio output volume."},
{"set_tx_audio", (PyCFunction)quisk_set_tx_audio, METH_VARARGS|METH_KEYWORDS, "Set the transmit audio parameters."},
{"get_tx_audio", (PyCFunction)quisk_get_tx_audio, METH_VARARGS, "Return some transmit audio parameters."},
{"is_vox", quisk_is_vox, METH_VARARGS, "return the VOX state zero or one."},
{"set_split_rxtx", set_split_rxtx, METH_VARARGS, "Set split for rx/tx."},
{"set_tune", set_tune, METH_VARARGS, "Set the tuning frequency."},
@@ -6230,13 +6275,6 @@ static PyMethodDef QuiskMethods[] = {
{"set_cwkey", set_hardware_cwkey, METH_VARARGS, "Change the CW key up/down state."},
{"set_remote_cwkey", set_remote_cwkey, METH_VARARGS, "Change the remote control CW key up/down state."},
{"set_PTT", set_PTT, METH_VARARGS, "Record the PTT button state."},
{"freedv_open", quisk_freedv_open, METH_VARARGS, "Open FreeDV."},
{"freedv_close", quisk_freedv_close, METH_VARARGS, "Close FreeDV."},
{"freedv_get_snr", quisk_freedv_get_snr, METH_VARARGS, "Return the signal to noise ratio in dB."},
{"freedv_get_version", quisk_freedv_get_version, METH_VARARGS, "Return the codec2 API version."},
{"freedv_get_rx_char", quisk_freedv_get_rx_char, METH_VARARGS, "Get text characters received from freedv."},
{"freedv_set_options", (PyCFunction)quisk_freedv_set_options, METH_VARARGS|METH_KEYWORDS, "Set the freedv parameters."},
{"freedv_set_squelch_en", quisk_freedv_set_squelch_en, METH_VARARGS, "Enable or disable FreeDV squelch."},
{"wdsp_set_parameter", (PyCFunction)quisk_wdsp_set_parameter, METH_VARARGS|METH_KEYWORDS, "Set parameters for the WDSP SDR library."},
{"tmp_record_save", tmp_record_save, METH_VARARGS, "Save the temporary recording in a WAV file."},
{"watfall_RgbData", watfall_RgbData, METH_VARARGS, "Return a cookie for the Waterfall pixel data."},
@@ -6244,6 +6282,8 @@ static PyMethodDef QuiskMethods[] = {
{"watfall_GetPixels", watfall_GetPixels, METH_VARARGS, "Write the Waterfall image to be displayed."},
{"write_fftw_wisdom", write_fftw_wisdom, METH_VARARGS, "Write the current fftw wisdom to the wisdom file."},
{"read_fftw_wisdom", read_fftw_wisdom, METH_VARARGS, "Return the current fftw wisdom as a byte array."},
{"tci_get_params", (PyCFunction)quisk_tci_get_params, METH_VARARGS, "Return parameters from TCI."},
{"tci_set_params", (PyCFunction)quisk_tci_set_params, METH_VARARGS|METH_KEYWORDS, "Set parameters for TCI."},
// Remote Quisk control head and slave by Ben, AC2YD
{"start_control_head_remote_sound", quisk_start_control_head_remote_sound, METH_VARARGS, "Start running UDP remote sound on control_head."},
{"stop_control_head_remote_sound", quisk_stop_control_head_remote_sound, METH_VARARGS, "Stop running UDP remote sound on control_head."},
+16 -21
View File
@@ -29,6 +29,8 @@
#define QUISK_CWKEY_DOWN (quisk_hardware_cwkey || quisk_serial_key_down || quisk_midi_cwkey || quisk_remote_cwkey)
#include <stdio.h>
#include <stdlib.h>
#ifdef MS_WINDOWS
#define QUISK_SHUT_RD SD_RECEIVE
#define QUISK_SHUT_BOTH SD_BOTH
@@ -260,6 +262,14 @@ extern PyObject * quisk_stop_remote_radio_remote_sound(PyObject * self, PyObject
extern int receive_graph_data(double * fft_avg);
extern void send_graph_data(double * fft_avg, int fft_size, double zoom, double deltaf, int fft_sample_rate, double scale);
// TCI server
PyObject * quisk_tci_set_params(PyObject * self, PyObject * args, PyObject * keywds);
PyObject * quisk_tci_get_params(PyObject * self, PyObject * args);
void tci_send_audio(complex double * cSamples, int nSamples);
void tci_send_iq(complex double * cSamples, int nSamples);
int tci_get_mic(complex double * cSamples, int mic_count);
extern uint64_t tci_tx_audio_client; // This is ws_cli_conn_t defined in ws.h.
void QuiskWavClose(struct QuiskWav *);
int QuiskWavWriteOpen(struct QuiskWav *, char *, short, short, short, int, double);
void QuiskWavWriteC(struct QuiskWav *, complex double *, int);
@@ -312,10 +322,6 @@ extern int quisk_active_sidetone; // Whether and how to generate a sidetone
extern int quisk_isFDX; // Are we in full duplex mode?
extern int quisk_use_serial_port; // Are we using the serial port for CW key or PTT?
extern play_state_t quisk_play_state; // startup, receiving, sidetone
extern int freedv_current_mode; // current FreeDV mode; 700D, @)@) etc.
extern int n_modem_sample_rate; // Receive data, decimate to modem_sample_rate, FreeDV codec output data at speech_sample_rate
extern int n_speech_sample_rate; // Microphone decimate to speech_sample_rate, Freedv codec output data at modem_sample_rate, interpolate to 48000
extern int n_max_modem_samples; // maximum input to freedv_rx()
extern int quisk_start_cw_delay; // milliseconds to delay output on serial or MIDI CW key down
extern int quisk_start_ssb_delay; // milliseconds to discard output for all modes except CW
extern struct sound_dev * quiskPlaybackDevices[]; // array of Playback sound devices
@@ -339,20 +345,14 @@ 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_get_tx_audio(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 *);
extern PyObject * quisk_freedv_set_squelch_en(PyObject *, PyObject *);
extern PyObject * quisk_open_key(PyObject *, PyObject *, PyObject *);
extern PyObject * quisk_close_key(PyObject *, PyObject *);
extern PyObject * quisk_set_sound_name(PyObject *, PyObject *);
@@ -362,7 +362,7 @@ extern PyObject * quisk_wasapi_control_midi(PyObject *, PyObject *, PyObject *);
// WDSP interface
#define QUISK_WDSP_RX 1
extern PyObject * quisk_wdsp_set_parameter(PyObject *, PyObject *, PyObject *);
extern int wdspFexchange0(int, double *, int);
extern int wdspFexchange0(int, complex double *, int);
// These function pointers are the Start/Stop/Read interface for
// the SDR-IQ and any other C-language extension modules that return
@@ -396,7 +396,6 @@ 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);
double QuiskDeltaSec(int);
void * quisk_make_sidetone(struct sound_dev *, int);
@@ -404,12 +403,8 @@ void * quisk_make_txIQ(struct sound_dev *, int);
int quisk_play_sidetone(struct sound_dev *);
void quisk_set_play_state(void);
void quisk_poll_hardware_key(void);
// Functions supporting digital voice codecs
typedef int (* ty_dvoice_codec_rx)(short *, double *, int, int);
typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int, int);
extern ty_dvoice_codec_rx pt_quisk_freedv_rx;
extern ty_dvoice_codec_tx pt_quisk_freedv_tx;
void PreDistort(complex double * amp_in_samples, complex double * amp_out_samples, int nSamples, complex double * tx_samples, int num_tx);
int CircularBuffer(int channel, complex double * cSamples, int nRead, int nWrite);
// Driver function definitions=================================================
int quisk_read_alsa(struct sound_dev *, complex double *);
@@ -466,7 +461,7 @@ int import_quisk_api(void); // used to initialize Quisk_API
#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_dvoice_freedv (*( void (*) (void) )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])
#define strMcpy (*( char * (*) (char *, const char *, size_t) )Quisk_API[11])
@@ -481,7 +476,7 @@ 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);
void quisk_dvoice_freedv(void);
int quisk_is_key_down(void);
void quisk_sample_source4(ty_sample_start, ty_sample_stop, ty_sample_read, ty_sample_write);
char * strMcpy(char *, const char *, size_t);
+492 -208
View File
File diff suppressed because it is too large Load Diff
+204 -79
View File
@@ -48,7 +48,7 @@ quisk_widgets = None
################ Receivers SoftRock USB, Devices controlled by USB that capture samples from a sound card, and (for Tx) play samples to a sound card
################ Receivers SoftRock USB, Sound card radios controlled by USB.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'softrock/hardware_usb.py'
@@ -133,7 +133,7 @@ digital_tx_level = 100
################ Receivers SoftRock Fixed, Fixed frequency devices that capture samples from a sound card, and (for Tx) play samples to a sound card
################ Receivers SoftRock Fixed, Fixed frequency sound card radios.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'quisk_hardware_fixed.py'
@@ -174,7 +174,7 @@ digital_tx_level = 100
################ Receivers HiQSDR, The original N2ADR hardware and the improved HiQSDR using UDP
################ Receivers HiQSDR, The original N2ADR and HiQSDR radios.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hiqsdr/quisk_hardware.py'
@@ -258,7 +258,7 @@ sndp_active = True
################ Receivers Hermes, The Hermes-Lite Project and possibly other hardware with the Hermes FPGA code.
################ Receivers Hermes, The Hermes-Lite project by Steve Haynal.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
@@ -275,7 +275,7 @@ sndp_active = True
# You can set these options:
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for the Hermes protocol.
# This is the type of UDP hardware. Use 10 for the Hermes Lite2 protocol.
#use_rx_udp = 10
## udp_rx_ip Hermes known IP, text
@@ -373,6 +373,7 @@ hermes_disable_sync = False
# be sure to write it down. To use this address you must set "Eeprom IP Usage".
# And you may want to set "Hermes known IP" too. Make sure the address does not conflict
# with your DHCP server.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromIP = '192.168.1.6'
#Hware_Hl2_EepromIP = '192.168.1.241'
@@ -380,6 +381,7 @@ Hware_Hl2_EepromIP = '192.168.1.6'
# This is the way the EEPROM IP address is used at power on.
# "Ignore" means it is not used at all. "Set address" means DHCP is not used and the EEPROM IP address is used.
# "Use DHCP first" means the EEPROM IP address is only used if DHCP fails.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromIPUse = 'Ignore'
#Hware_Hl2_EepromIPUse = 'Use DHCP first'
#Hware_Hl2_EepromIPUse = 'Set address'
@@ -390,6 +392,7 @@ Hware_Hl2_EepromIPUse = 'Ignore'
# at power on instead of the factory supplied MAC address 0:1c:c0:a2:13:dd. It is only used at power on,
# and it is only used if it is marked valid by setting "Eeprom MAC usage" to "Set address".
# If you have two HL2's, you must have a unique MAC address for each.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromMAC = '0xA1 0x6B'
#Hware_Hl2_EepromMAC = '0x4C 0x33'
@@ -398,11 +401,13 @@ Hware_Hl2_EepromMAC = '0xA1 0x6B'
# the HL2 will use the last two MAC bytes from "Eeprom MAC address"; otherwise the default MAC is used.
# If you change this item, the bit is written to the HL2.
# The HL2 only uses these bits at power on.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromMACUse = 'Ignore'
#Hware_Hl2_EepromMACUse = 'Set address'
## hermes_TxLNA_dB LNA during Tx dB, integer
# During transmit the low noise Rx amplifier gain changes to this value (in dB) if the hardware supports it.
# During transmit the low noise Rx amplifier gain changes to this value (in dB).
# The LNA gain is -12 to 48 dB. Use -12 for Pure Signal.
# Changes are immediate (no need to restart).
hermes_TxLNA_dB = -12
@@ -473,13 +478,6 @@ hermes_iob_rxin = 'J10 available'
#hermes_iob_rxin = 'HL2 Rx to J9'
#hermes_iob_rxin = 'Use J9 and J10'
## hermes_lite2_enable Hermes Lite2 protocol, boolean
# This turns on some of the protocol extensions for the Hermes Lite2 that are not part
# of the official Hermes Protocol 1. Currently this only enables Tx Inhibit (CN8).
# Changes are immediate (no need to restart).
hermes_lite2_enable = True
#hermes_lite2_enable = False
# These are known power meter calibration tables. This table is not present in the JSON settings file.
power_meter_std_calibrations = {}
@@ -490,6 +488,7 @@ power_meter_std_calibrations['HL2FilterE3'] = [[ 0, 0.0 ], [ 25.865384615384617,
[ 4093.1785714285716, 7.28994845808807 ], [ 4502.496428571429, 8.820837634286566 ], [ 4952.746071428572, 10.673213537486745 ] ]
#power_meter_std_calibrations['HL2FilterE1'] = [[0, 0.0], [9.07, 0.002], [54.98, 0.014], [148.6, 0.057],
# [328.0, 0.208], [611.1, 0.646], [807.0, 1.098], [982.1, 1.6], [1223.3, 2.471], [1517.7, 3.738], [1758.7, 5.02]]
power_meter_std_calibrations['Show ADC'] = []
## Hermes_BandDict Rx IO Bus, dict
# The Hermes_BandDict sets the 7 bits on the J16 connector for Rx.
@@ -507,17 +506,17 @@ Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
################ Receivers Red Pitaya, The Red Pitaya Project by Pavel Demin. This uses the Hermes FPGA code.
################ Receivers Red Pitaya, The Red Pitaya project by Pavel Demin.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = ''
#widgets_file_name = 'hermes/hpsdr_widgets.py'
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for the Hermes protocol.
# This is the type of UDP hardware. Use 10 for the Red Pitaya using the OpenHPSDR protocol.
#use_rx_udp = 10
## rx_udp_ip IP change, text
@@ -585,7 +584,123 @@ Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
################ Receivers SoapySDR, The SoapySDR interface to multiple hardware SDRs.
################ Receivers OpenHPSDR, Radios using the HPSDR Protocol 1.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = 'hermes/hpsdr_widgets.py'
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for hardware using the OpenHPSDR protocol 1.
#use_rx_udp = 10
## rx_udp_ip IP change, text
# This item should be left blank. It is used to change the IP address of the hardware to a different
# IP once the hardware is found. Not all Hermes firmware supports changing the IP address.
#rx_udp_ip = ""
## rx_udp_port Hardware UDP port, integer
# This is the UDP port number of your hardware.
#rx_udp_port = 1024
## rx_udp_ip_netmask Network netmask, text
# This is the netmask for the network.
#rx_udp_ip_netmask = '255.255.255.0'
## tx_ip Transmit IP, text
# Leave this blank to use the same IP address as the receive hardware. Otherwise, enter "disable"
# to disable sending transmit I/Q samples, or enter the actual IP address. You must enter "disable"
# if you have multiple hardwares on the network, and only one should transmit. This item is normally blank.
tx_ip = ""
#tx_ip = "disable"
## tx_audio_port Tx audio UDP port, integer
# This is the UDP port for transmit audio I/Q samples. Enter zero to calculate this from the
# base hardware port. Otherwise enter the special custom port.
tx_audio_port = 0
## rx_udp_clock Clock frequency Hertz, integer
# This is the clock frequency of the hardware in Hertz.
#rx_udp_clock = 122880000
#rx_udp_clock = 76800000
#rx_udp_clock = 125000000
## tx_level Tx Level, dict
# This sets the transmit level 0 to 255 for each band.
# The config screen has a slider 0 to 100% so you can reduce the transmit power.
# The hardware only supports a limited adjustment range, so zero is still a small amount of power.
tx_level = {}
## digital_tx_level Digital Tx power %, integer
# Digital modes reduce power by the percentage on the config screen.
# This is the maximum value of the slider.
#digital_tx_level = 100
## hermes_code_version Hermes code version, integer
# There can be multiple Hermes devices on a network, but Quisk can only use one of these. If you have multiple
# hermes devices, you can use this to specify a unique device. Or use -1 to accept any board.
hermes_code_version = -1
## hermes_board_id Hermes board ID, integer
# There can be multiple Hermes devices on a network, but Quisk can only use one of these. If you have multiple
# hermes devices, you can use this to specify a unique device. Or use -1 to accept any board.
hermes_board_id = -1
## Hermes_BandDict Hermes Bus, dict
# The Hermes_BandDict sets the 7 bits on the J16 connector.
Hermes_BandDict = {
'160':0b0000001, '80':0b0000010, '60':0b0000100, '40':0b0001000, '30':0b0010000, '20':0b0100000, '15':0b1000000}
## Hermes_BandDictTx Tx IO Bus, dict
# The Hermes_BandDictTx sets the 7 bits on the J16 connector for Tx if enabled.
Hermes_BandDictTx = {'160':0, '80':0, '60':0, '40':0, '30':0, '20':0, '17':0, '15':0, '12':0, '10':0}
## Hermes_BandDictEnTx Enable Tx Filt, boolean
# Enable the separate Rx and Tx settings for the J16 connector.
Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
## calibrate_temp_20 PA temp ADC at 20\u2103, integer
# This is the ADC code returned for a temperature of 20C.
# To see the ADC code being returned, enter zero for both temperature codes.
# Linear interpolation is used to calculate the temperature.
# Changes are immediate (no need to restart).
calibrate_temp_20 = 0
## calibrate_temp_40 PA temp ADC at 40\u2103, integer
# This is the ADC code returned for a temperature of 40C.
# To see the ADC code being returned, enter zero for both temperature codes.
# Linear interpolation is used to calculate the temperature.
# Changes are immediate (no need to restart).
calibrate_temp_40 = 0
## calibrate_current_0 PA current ADC at zero, integer
# This is the ADC code returned for a PA current of zero.
# To see the ADC code being returned, enter zero for both current codes.
# Linear interpolation is used to calculate the current.
# Changes are immediate (no need to restart).
calibrate_current_0 = 0
## calibrate_current_1 PA current ADC at 1 amp, integer
# This is the ADC code returned for a PA current of one amp.
# To see the ADC code being returned, enter zero for both current codes.
# Linear interpolation is used to calculate the current.
# Changes are immediate (no need to restart).
calibrate_current_1 = 0
## power_meter_calib_name Power meter calibration, text choice
# This is the calibration table used to convert the power sensor voltage measured by the ADC to the transmit power display.
# It is a table of ADC codes and the corresponding measured power level. If you have a power meter, you can create your own
# table by selecting "New". Then enter ten or more power measurements from low to full power.
# For the Hermes Lite2 version E3 filter board, use the built-in table "HL2FilterE3".
# Changes are immediate (no need to restart).
power_meter_calib_name = 'HL2FilterE3'
################ Receivers SoapySDR, The SoapySDR interface to multiple SDRs.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'soapypkg/quisk_hardware.py'
@@ -601,7 +716,7 @@ Hermes_BandDictEnTx = False
# Further items are present in the radio dictionary with names soapy_*
################ Receivers SdrIQ, The SDR-IQ receiver by RfSpace
################ Receivers SdrIQ, The SDR-IQ receiver by RfSpace.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
hardware_file_name = 'quisk_hardware_sdriq.py'
@@ -632,7 +747,7 @@ sdriq_decimation = 1250
################ Receivers Odyssey, The Odyssey project using a UDP protocol similar to the HiQSDR
################ Receivers Odyssey, The Odyssey project.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hiqsdr/quisk_hardware.py'
@@ -743,7 +858,7 @@ sndp_active = True
#radio_sound_mic_boost = True
################ Receivers Odyssey2, The Odyssey-2 project using the HPSDR Hermes protocol
################ Receivers Odyssey2, The Odyssey-2 project.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
@@ -758,7 +873,7 @@ sndp_active = True
# You can set these options:
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for the Hermes protocol.
# This is the type of UDP hardware. Use 10 for the Odyssey-2.
#use_rx_udp = 10
## rx_udp_ip IP change, text
@@ -827,7 +942,7 @@ Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
################ Receivers Afedri, The Afedri SDR receiver with the Ethernet interface.
################ Receivers Afedri, The Afedri SDR receiver with Ethernet.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'afedrinet/quisk_hardware.py'
@@ -859,7 +974,7 @@ Hermes_BandDictEnTx = False
#default_rf_gain = 11
################ Receivers Control Head, Use Quisk to control a remote radio. No real hardware is used here.
################ Receivers Control Head, Use Quisk to control a remote Quisk radio.
## hardware_file_name Hardware file path, rfile
# Quisk can be used as a control head to control a real radio located remotely.
# This file contains the control logic for remote radios of type Hermes, SoftRock or HiQSDR.
@@ -1094,6 +1209,11 @@ file_play_level = 1.000
#file_play_level = 0.500
#file_play_level = 0.100
## IQ_Server_IP Pulse server IP address, text
# IP Address for remote PulseAudio IQ server.
IQ_Server_IP = ""
#IQ_Server_IP = "192.168.230.102"
# Sound card names:
#
# In PortAudio, soundcards have an index number 0, 1, 2, ... and a name.
@@ -1303,51 +1423,28 @@ fixed_tune_offset = 0
################ Remote
# DX cluster telent login data, thanks to DJ4CM. Must have station_display_lines > 0.
## dxClHost Dx cluster host name, text
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClHost is the telnet host name.
dxClHost = ''
#dxClHost = 'example.host.net'
## tci_ip IP address for TCI, text
# Quisk can connect to WSJT-X and other digital programs using the TCI protocol.
# Enter the TCI server host name.
tci_ip = "127.0.0.1"
## dxClPort Dx cluster port number, integer
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClPort is the telnet port number.
dxClPort = 7373
## user_call_sign Call sign for Dx cluster, text
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# user_call_sign is your call sign which may be needed for login.
user_call_sign = ''
## dxClPassword Password for Dx cluster, text
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClPassword is the telnet password for the server.
dxClPassword = ''
#dxClPassword = 'getsomedx'
## dxClExpireTime Dx cluster expire minutes, integer
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClExpireTime is the time in minutes until DX Cluster entries are removed.
dxClExpireTime = 20
## IQ_Server_IP Pulse server IP address, text
#IP Address for remote PulseAudio IQ server.
IQ_Server_IP = ""
## tci_port IP port for TCI, integer
# Quisk can connect to WSJT-X and other digital programs using the TCI protocol.
# Enter the TCI port number. If the port number is zero, TCI is turned off.
# The preferred port is 40001.
tci_port = 0
#tci_port = 40001
#tci_port = 50001
## hamlib_ip IP address for Hamlib Rig 2, text
# You can control Quisk from Hamlib. Set the Hamlib rig to 2 and the device for rig 2 to
# localhost:4575. Or choose a different name and port here. Set the same name and port
# in the controlling program.
# hamlib_ip is the IP name or address.
hamlib_ip = "localhost"
hamlib_ip = ""
#hamlib_ip = "localhost"
## hamlib_port IP port for Hamlib, integer
## hamlib_port IP port for Hamlib Rig 2, integer
# You can control Quisk from Hamlib. For direct control, set the external program to rig 2
# "Hamlib NET rigctl", and set the Quisk hamlib port to 4532. To use the rigctld program to control
# Quisk, set the Quisk hamlib port to 4575. To turn off Hamlib control, set the Quisk port to zero.
@@ -1398,19 +1495,64 @@ win_hamlib_com2_name = ""
#win_hamlib_com2_name = "COM15"
#win_hamlib_com2_name = "COM16"
## remote_radio_ip Remote radio IP or name, text
## remote_radio_ip IP or name of the remote radio, text
# Quisk can be used as a control head to control a real radio located remotely.
# This is the IP adddress or the host name of the remote radio.
remote_radio_ip = ""
#remote_radio_ip = "192.168.1.56"
## remote_radio_password Password for remote radio, text
## remote_radio_password Password for the remote radio, text
# Quisk can be used as a control head to control a real radio located remotely.
# For security it requires the same password to be entered on both computers.
# Enter a fairly long pass phrase of 20 or more characters.
# It is only necessary to enter it once on each computer.
remote_radio_password = ""
## k4_tcp_ip IP address for K4 TCP, text
# This is the Quisk IP address for the TCP server implementing K4 commands.
k4_tcp_ip = ""
#k4_tcp_ip = "localhost"
#k4_tcp_ip = "127.0.0.1"
## k4_tcp_port Port number for K4 TCP, integer
# This is the Quisk port for the TCP server implementing K4 commands.
k4_tcp_port = 0
#k4_tcp_port = 9200
#k4_tcp_port = 50004
# DX cluster telent login data, thanks to DJ4CM. Must have station_display_lines > 0.
## dxClHost Dx cluster host name, text
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClHost is the telnet host name.
dxClHost = ''
#dxClHost = 'example.host.net'
## dxClPort Dx cluster port number, integer
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClPort is the telnet port number.
dxClPort = 7373
## user_call_sign Call sign for Dx cluster, text
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# user_call_sign is your call sign which may be needed for login.
user_call_sign = ''
## dxClPassword Password for Dx cluster, text
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClPassword is the telnet password for the server.
dxClPassword = ''
#dxClPassword = 'getsomedx'
## dxClExpireTime Dx cluster expire minutes, integer
# The Dx cluster options log into a Dx cluster server, and put station information
# on the station window under the graph and waterfall screens.
# dxClExpireTime is the time in minutes until DX Cluster entries are removed.
dxClExpireTime = 20
hamlib_com1_name = ""
@@ -1782,23 +1924,6 @@ add_extern_demod = ""
add_fdx_button = 0
#add_fdx_button = 1
## add_freedv_button Add FreeDv button, integer choice
# These buttons add up to two additional mode buttons after CW, USB, etc.
# Set this to add the FDV mode button for digital voice:
add_freedv_button = 1
#add_freedv_button = 0
## freedv_tx_msg FreeDv Tx message, text
# For freedv, this is the text message to send.
freedv_tx_msg = ''
#freedv_tx_msg = 'N2XXX Jim, New Jersey, USA \n'
# This is the list of FreeDV modes and their index number. The starting mode is the first listed.
freedv_modes = (('Mode 1600', 0),
('Mode 2400A', 3), ('Mode 2400B', 4), ('Mode 800XA', 5),
('Mode 700C', 6), ('Mode 700D', 7), ('Mode 700E', 13), ('Mode 2020', 8))
# These are the filter bandwidths for each mode. Quisk has built-in optimized filters
# for these values, but you can change them if you want.
FilterBwCW = (200, 400, 600, 1000, 1500, 3000)
+2 -1
View File
@@ -29,9 +29,10 @@ class Hardware(BaseHardware):
# self.hamlib_rigctld_port = 4532 # Standard rigctld control port
# self.hamlib_poll_seconds = 0.2 # Time interval to poll for changes
def open(self):
# Thanks to Peter, DC6AR, Rig ID changed from 229 to 2045.
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
os.system("rigctld -m 2045 -r /dev/ttyUSB0 -s 4800 & ") # Check the baud rate menu setting
# If this fails, start rigctld by hand.
return ret
+13 -2
View File
@@ -118,15 +118,24 @@ class Hardware:
# length is the number of samples in each block of ADC samples, and equals the FFT size.
QS.set_params(bscope_bytes=int_size, bscope_endian=endian, bscope_size=length)
self.application.bandscope_clock = clock
def InitTxSamples(self, int_size, endian, rate): # Tx sample initialization; you must call this from your hardware __init__().
# int_size is the number of bytes in each I or Q sample: 2, 3, or 4
# endian is the order of bytes in the sample: 0 == little endian; 1 == big endian
# This can be called again to change the format. For example, a different number of bytes for different sample rates.
QS.set_tx_audio(tx_sample_rate=rate) # Available rates are 48, 50, 96, 100, 192 ksps
QS.set_params(tx_bytes=int_size, tx_endian=endian)
#def PollCwKey(self): # Optional. Called frequently by the sound thread to check the CW key status.
# pass # Do not define if not needed.
def StartSamples(self): # Quisk calls this from the sound thread to start sending samples.
#def PollGuiControl(self): # Optional. Called frequently by the GUI thread to change GUI settings (PTT etc.)
# pass # Do not define if not needed.
def StartSamples(self): # Quisk calls this from the sound thread to start samples.
# If you return a string, it replaces the string returned from hardware open()
pass
def StopSamples(self): # Quisk calls this from the sound thread to stop sending samples.
def StopSamples(self): # Quisk calls this from the sound thread to stop samples.
pass
def GetRxSamples(self): # Quisk calls this frequently from the sound thread. Poll your hardware for samples.
# Return any available samples by calling AddRxSamples() and perhaps AddBscopeSamples() from within this method.
# Call GetTxSamples() from within this method to return any Tx samples that must be sent.
pass
def AddRxSamples(self, samples): # Call this from within GetRxSamples() to record the Rx samples.
# "samples" is int_size of integer I data followed by int_size of integer Q data, repeated.
@@ -140,6 +149,8 @@ class Hardware:
# For Python 2, "samples" must be a byte array or bytes or a string.
# The number of bytes in "samples" must equal the block length times the bytes per sample.
QS.add_bscope_samples(samples)
def GetTxSamples(self): # Call this from within GetRxSamples() to return the Tx samples to send.
return QS.get_tx_samples() # Return a Python bytes object
def GotClip(self): # Call this to indicate that samples were received with the clip (overrange) indicator true.
QS.set_params(clip=1)
def GotReadError(self, print_msg, msg): # Call this to indicate an error in receiving the Rx samples.
+37 -27
View File
@@ -1,61 +1,71 @@
// This module provides C access to the WDSP SDR library.
#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <complex.h>
#define CLIP32 2147483647
#define CLIP16 32767
#define MAX_CHANNELS 32
#define SAMPLE_BYTES (sizeof(double) * 2)
static struct _channel {
double * cBuf; // save samples until nBuf reaches in_size
int sizeBuf; // current size of cBuf in count of complex samples
int nBuf; // current number of complex samples in cBuf
int in_size; // count of complex samples needed for wdsp_fexchange0
int in_use; // is this channel being used?
complex double * cBuf; // circular sample buffer; save samples until nBuf reaches in_size
int sizeBuf; // current size of cBuf in complex samples; a multiple of in_size
int nBuf; // current number of complex samples in cBuf
int in_size; // count of complex samples needed for wdsp_fexchange0
int in_use; // is this channel being used?
int Windex; // index of write
int Rindex; // index of read
} wdspChannel[MAX_CHANNELS];
static void (*wdsp_fexchange0) (int channel, double * in, double * out, int * error);
int wdspFexchange0(int channel, double * cSamples, int nSamples)
int wdspFexchange0(int channel, complex double * cSamples, int nSamples)
{
// Call with an arbitrary number of input samples nSamples. cSamples is scaled to CLIP32.
// When we have enough samples process the samples by calling wdsp. Return the samples in the same array.
// cSamples is complex double. We treat it as interleaved Real/Imag doubles twice as long.
// nSamples is the count of complex samples.
// When we have enough samples process the samples by calling wdsp. Return the samples in cSamples.
// cSamples is complex double. nSamples is the count of complex samples.
struct _channel * ptChannel = wdspChannel + channel;
int i, error;
double * ptD, * ptS;
int i, error, in_size;
if ( ! ptChannel->in_use)
if ( ! ptChannel->in_use) {
ptChannel->Windex = 0;
ptChannel->Rindex = 0;
ptChannel->nBuf = 0;
return nSamples;
}
if ( ! wdsp_fexchange0)
return nSamples;
if (nSamples <= 0)
return nSamples;
if (ptChannel->nBuf + nSamples >= ptChannel->sizeBuf) {
ptChannel->sizeBuf = ptChannel->nBuf + nSamples * 3;
ptChannel->cBuf = (double *)realloc(ptChannel->cBuf, ptChannel->sizeBuf * SAMPLE_BYTES);
in_size = ptChannel->in_size;
i = nSamples / in_size + 3; // blocks need for samples plus a partial block
if (i * in_size > ptChannel->sizeBuf) {
i *= in_size;
ptChannel->sizeBuf = i;
ptChannel->cBuf = (complex double *)realloc(ptChannel->cBuf, i * sizeof(complex double));
}
ptS = cSamples; // Copy samples from cSamples to cBuf
ptD = ptChannel->cBuf + ptChannel->nBuf * 2;
for (i = 0; i < nSamples; i++) {
*ptD++ = *ptS++ / CLIP32;
*ptD++ = *ptS++ / CLIP32;
for (i = 0; i < nSamples; i++) { // Copy samples from cSamples to cBuf
ptChannel->cBuf[ptChannel->Windex++] = cSamples[i] / CLIP32;
if (ptChannel->Windex >= ptChannel->sizeBuf)
ptChannel->Windex = 0;
}
ptChannel->nBuf += nSamples;
nSamples = 0;
while (ptChannel->nBuf >= ptChannel->in_size) {
(*wdsp_fexchange0)(channel, ptChannel->cBuf, cSamples + nSamples * 2, &error);
while (ptChannel->nBuf >= in_size) {
(*wdsp_fexchange0)(channel, (double *)(ptChannel->cBuf + ptChannel->Rindex), (double *)(cSamples + nSamples), &error);
if (error)
printf("WDSP: wdsp_fexchange0 error %d\n", error);
nSamples += ptChannel->in_size;
ptChannel->nBuf -= ptChannel->in_size;
memmove(ptChannel->cBuf, ptChannel->cBuf + ptChannel->in_size * 2, ptChannel->nBuf * SAMPLE_BYTES);
ptChannel->Rindex += in_size;
if (ptChannel->Rindex >= ptChannel->sizeBuf)
ptChannel->Rindex = 0;
nSamples += in_size;
ptChannel->nBuf -= in_size;
}
for (i = 0; i < nSamples * 2; i++)
for (i = 0; i < nSamples; i++)
cSamples[i] *= CLIP32;
return nSamples;
}
+14 -10
View File
@@ -25,16 +25,20 @@ class Cwdsp:
pass
else:
try:
self.Lib = ctypes.CDLL("./libwdsp.so")
Log ("Found private wdsp")
self.Lib = ctypes.CDLL("./wdsp/libwdsp.so")
Log ("Found private wdsp in quisk/wdsp")
except:
name = ctypes.util.find_library("wdsp")
if name:
try:
self.Lib = ctypes.CDLL(name)
Log ("Found public wdsp")
except:
pass
try:
self.Lib = ctypes.CDLL("./libwdsp.so")
Log ("Found private wdsp in quisk/")
except:
name = ctypes.util.find_library("wdsp")
if name:
try:
self.Lib = ctypes.CDLL(name)
Log ("Found public wdsp")
except:
pass
if not self.Lib:
Log("Wdsp was not found")
return
@@ -73,7 +77,7 @@ class Cwdsp:
dsp_size = 256
QS.wdsp_set_parameter(channel, in_size=in_size)
Lib.OpenChannel (channel, in_size, dsp_size, 48000, 48000, 48000, 0, 1,
ctypes.c_double(0.010), ctypes.c_double(0.025), ctypes.c_double(0.0), ctypes.c_double(0.010), 0)
ctypes.c_double(0.010), ctypes.c_double(0.025), ctypes.c_double(0.0), ctypes.c_double(0.010), 1)
Lib.SetRXAShiftRun (channel, 0)
Lib.RXANBPSetRun (channel, 0)
Lib.SetRXAAMSQRun (channel, 0)
+7 -1
View File
@@ -582,6 +582,7 @@ class QuiskPushbutton(QuiskButtons, wx.lib.buttons.GenButton):
self.Bind(wx.EVT_BUTTON, self.OnButton)
self.InitButtons(text, text_color)
self.direction = 1
self.normal_brush = self.up_brush
if use_right:
self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
@@ -603,7 +604,12 @@ class QuiskPushbutton(QuiskButtons, wx.lib.buttons.GenButton):
event = wx.PyEvent()
event.SetEventObject(self)
self.command(event)
def SetColorNormal(self):
self.up_brush = self.normal_brush
self.Refresh()
def SetColor(self, color):
self.up_brush = wx.Brush(color)
self.Refresh()
class QuiskRepeatbutton(QuiskButtons, wx.lib.buttons.GenButton):
"""A push button that repeats when held down."""
+1
View File
@@ -0,0 +1 @@
#
+569
View File
@@ -0,0 +1,569 @@
# SDR-TRX Quisk Configuration File
# This file is to integrate the function of control of the SDR-TRX wint Quisk and with WSJT-X.
# This is for control of the SDR-TRX with using the UDP port port 12346. WSJT-X interfaces with this
# locally using UDP on port 2237. Capital letters begin commands to the SDR-TRX from Quisk,
# and lowercase letters are for commands coming from WSJT-X (through this Quisk interface).
# This is for usinng the SDR-TRX without a 3.5 mm audio card to the soundcard of the computer,
# and the WSJT-X software using UDP, and the data coming over UDP part 12345 too.
# It requires the libldl/ directory from weakmon to be in the directory you run this from.
# It also requires the WSJTXClass.py file from wsjtx_transceiver_interface to be in the lib directory
# and the version of that class. I have supplied a pruned down versions of ft8.py and ft4.45, because we don't need much
# other than FT8Send and FT4Send from those files. You need to edit transceiver_config.yml to have your callsign and grid, etc.
import socket
import time
import yaml
import struct
import datetime
import sys
import os
from ft8 import FT8Send
from ft4 import FT4Send
from quisk_hardware_model import Hardware as BaseHardware
import struct
import collections
import lib.WSJTXClass as WSJTXClass # To use encoders
sys.path.append(os.path.expandvars('$WEAKMON'))
DEBUG_ON = True
#
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
#
################ Receivers SdrTrx, The SdrTrx radio by Rob Frohne, KL7NA
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'sdrtrxpkg/quisk_hardware.py'
## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = ''
## si570_xtal_freq Si570 crystal frequency, integer
# This is the Si570 startup frequency in Hz. 114.285MHz is the typical
# value from the data sheet; you can use 'usbsoftrock calibrate' to find
# the value for your device.
si570_xtal_freq = 114285000
## rx_max_amplitude_correct Max ampl correct, number
# If you get your I/Q samples from a sound card, you will need to correct the
# amplitude and phase for inaccuracies in the analog hardware. The correction is
# entered using the controls from the "Rx Phase" button on the config screen.
# You must enter a positive number. This controls the range of the control.
rx_max_amplitude_correct = 0.2
## rx_max_phase_correct Max phase correct, number
# If you get your I/Q samples from a sound card, you will need to correct the
# amplitude and phase for inaccuracies in the analog hardware. The correction is
# entered using the controls from the "Rx Phase" button on the config screen.
# You must enter a positive number. This controls the range of the control in degrees.
rx_max_phase_correct = 10.0
# SDR-TRX Hardware Control Class
#
class Hardware(BaseHardware):
# These are "static variable substitutes" since python doesn't have them.
# They are shared by all instances of the class and don't get redefinede each time a method is called.
# Move these into the Hardware class.
# Uncomment these lines if you wish to use Pulseaudio
name_of_sound_play = "pulse"
# Radio's Frequency limits.
radio_lower = 3500000
radio_upper = 30000000
PACKET_SIZE = 1444 # 4 bytes for packet number + 360 * 4 bytes for int32_t pairs
# Using 16 bit samples, so 2 bytes per sample.
# Set the number of Hz the signal is tuned to above the center frequency to avoid 1/f noise.
vfo_Center_Offset = 10000
tx_ready_wsjtx = False
tx_ready_wsjtx_sent = False
tx_now = False
# This is the open code for the WSJT server.
ft8_encoder = FT8Send()
ft4_encoder = FT4Send()
class Packet:
def __init__(self, data):
self.number = struct.unpack('<I', data[:4])[0]
self.pairs = [struct.unpack('<hh', data[i:i+4]) for i in range(4, len(data), 4)]
class PacketQueue:
def __init__(self):
self.queue = collections.deque()
def add_packet(self, packet):
self.queue.append(packet)
self.queue = collections.deque(sorted(self.queue, key=lambda p: p.number))
def check_missing_packets(self):
for i in range(1, len(self.queue)):
if self.queue[i].number != self.queue[i-1].number + 1:
avg_pairs = [((self.queue[i-1].pairs[j][0] + self.queue[i].pairs[j][0]) // 2,
(self.queue[i-1].pairs[j][1] + self.queue[i].pairs[j][1]) // 2)
for j in range(len(self.queue[i-1].pairs))]
self.queue.insert(i, Packet(struct.pack('<I', self.queue[i-1].number + 1) +
b''.join(struct.pack('<ii', pair[0], pair[1]) for pair in avg_pairs)))
def get_packets(self):
packets = list(self.queue)
self.queue.clear()
return packets
def open(self):
# Connection for WSJT-X and data
self.PICO_UDP_IP = None # Will be set to the IP of the machine sending data
self.COMMAND_UDP_PORT = 12346 # This is the port the Pico listens on for UDP commands coming from quisk.
self.DATA_UDP_PORT = 12345 # This is the port the quisk listens on for UDP IQ data coming from the Pico W.
self.BROADCAST_PORT = 12347 # This is the port the Pico gets our IP address from.-
self.broadcast_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.broadcast_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.broadcast_message = "Quisk"
self.broadcast_sock.setblocking(False)
self.isConnected = False
self.command_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.command_sock.setblocking(False)
self.data_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.data_sock.bind(('', self.DATA_UDP_PORT))
print("Data socket bound to IP:", self.data_sock.getsockname()[0])
self.data_sock.setblocking(False)
self.establish_connection()
self.InitSamples(2, 0) # Two bytes per sample, little endian.
self.queue = self.PacketQueue()
time.sleep(2)
# Poll for version. Should probably confirm the response on this.
version = str(self.get_parameter("VER")) # The way the firmware is now, this sets it up to use the UART or UDP.
# UART is used if get_parameter uses the UART, and UDP is used if it uses the UDP.
print(version)
# Return an informative message for the config screen
t = str(version) + ". Capture from sound card %s." % self.conf.name_of_sound_capt
configs_file = open('transceiver_config.yml', 'r')
configs = yaml.load(configs_file, Loader=yaml.BaseLoader)
# Global variables
self.callsign = configs['callsign']
self.grid = configs['grid']
self.current_msg = ''
self.rx_callsign = ''
self.mode = 'FT8'
self.tx_freq = 1200
# Connection for WSJT-X
WSJTX_UDP_IP = "127.0.0.1" # Assuming you are running WSJT-X on the same computer as Quisk...
WSJTX_UDP_PORT = 2237
self.wsjtx_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.wsjtx_sock.bind((WSJTX_UDP_IP, WSJTX_UDP_PORT))
self.wsjtx_sock.setblocking(False)
self.StartSamples()
return t
def close(self):
# Called once to close the Hardware
self.get_parameter("RX") # Make sure we are in RX mode.
time.sleep(1) # Wait for RX to be set.
self.StopSamples()
self.command_sock.close()
self.wsjtx_sock.close()
self.broadcast_sock.close()
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
# Called whenever quisk requests a frequency change.
# This sends the FREQ command dto set the centre frequency of the radio,
# and will also move the 'tune' frequency (the section within the RX passband
# which is to be demodulated) if it falls outside the passband (+/- sample_rate/2).
print("Setting VFO to %d, with tune to %d." % (vfo, tune))
print("Setting VFO to %d." % vfo)
if vfo < self.radio_lower:
vfo = self.radio_lower
print("Outside range! Setting to %d" % self.radio_lower)
if vfo > self.radio_upper:
vfo = self.radio_upper
print("Outside range! Setting to %d" % self.radio_upper)
# print("sample_rate =", sample_rate) # I believe sample_rate comes from quisk.
# If the tune frequency is outside the RX bandwidth, set it to somewhere within that bandwidth.
if tune > (vfo + sample_rate / 2) or tune < (vfo - sample_rate / 2):
tune = vfo + self.vfo_Center_Offset
print("Bringing tune frequency back into the RX bandwidth.")
# success = self.set_parameter("FREQ",str(vfo))
self.set_parameter("FREQ", str(vfo))
self.set_parameter("TX_FREQ", str(tune))
return tune, vfo
def OnButtonPTT(self, event):
btn = event.GetEventObject()
if btn.GetValue():
self.get_parameter("TX")
print("TX")
else:
self.get_parameter("RX")
print("RX")
return BaseHardware.OnButtonPTT(self, event)
def establish_connection(self):
while not self.isConnected:
print("isConnected is: ", self.isConnected)
# Check if a connection is established
# Wait for data to arrive and set PICO_UDP_IP to the sender's IP
while self.PICO_UDP_IP is None: # This needs to be fixed. It continues to send broadcast packets.
try:
print("Sending broadcast message...")
self.broadcast_sock.sendto(self.broadcast_message.encode(), ('<broadcast>', self.BROADCAST_PORT))
time.sleep(1) # Wait for 1 second before sending the next broadcast message
except Exception as e:
print("Broadcast message did not work: {0}".format(e))
break
try:
data, addr = self.data_sock.recvfrom(self.PACKET_SIZE) # Adjust the buffer size as needed
self.PICO_UDP_IP = addr[0]
print("PICO_UDP_IP set to:", self.PICO_UDP_IP)
# if data == self.broadcast_message:
self.isConnected = True
except socket.error:
if self.PICO_UDP_IP is not None:
print("Connection established with Pico W at IP:", self.PICO_UDP_IP, "but exception occurred.")
break # Exit the loop if self.PICO_UDP_IP is not None
pass # No data available yet
def GetRxSamples(self):
# Initialize packet counter and time marker
# This routine is made for FT8 and FT4, where we need to send a packet every 15 seconds.
# The Pico W sending the data has a sample rate of 48046 samples per second > 48 ks/s.
# We made the packets so they carry 180 samples per packet. That gives 4000 packets
# per 15 seconds. We discard any packets after that four times a minute at 59, 14, 29,
# and 44 seconds. This should make four ticks per minute, and we should be able to ignore
# # them if we are doing FT8 or FT4. Something else might be more optimum for other modes.
packet_counter = 0
next_time_marker = 0
send_packets = False
while True:
# Check if we've reached the next time marker
current_time = time.time()
current_seconds = current_time % 60
# Define the time markers
if 59 <= current_seconds < 60:
next_time_marker = current_time + 1 - current_seconds
send_packets = True
elif 14 <= current_seconds < 15:
next_time_marker = current_time + 1 - (current_seconds - 14)
send_packets = True
elif 29 <= current_seconds < 30:
next_time_marker = current_time + 1 - (current_seconds - 29)
send_packets = True
elif 44 <= current_seconds < 45:
next_time_marker = current_time + 1 - (current_seconds - 44)
send_packets = True
# If we've reached the next time marker, reset the packet counter
if current_time >= next_time_marker and send_packets:
packet_counter = 0
send_packets = False
try:
data, addr = self.data_sock.recvfrom(self.PACKET_SIZE)
self.no_data_repeat = 0
except socket.error:
# Send a broadcast message to the Pico W to get the IP address in case it has reset.
self.broadcast_sock.sendto(self.broadcast_message.encode(), ('<broadcast>', self.BROADCAST_PORT))
break # No more packets available
packet = self.Packet(data)
self.queue.add_packet(packet)
# Check for missing packets and fill them in
self.queue.check_missing_packets()
# Get the packets and pass them to the processing thread
packets = self.queue.get_packets()
for packet in packets:
data = b''.join(struct.pack('<hh', pair[0], pair[1]) for pair in packet.pairs)
# if len(data) % 4 != 0: # Each I/Q pair is now 4 bytes (2 * 2 bytes)
# self.GotReadError(self, DEBUG_ON, "Error: Mismatched number of I and Q samples")
# continue
# If we've already sent 4000 packets since the last time marker, discard the rest
if packet_counter >= 4000:
continue
self.AddRxSamples(data)
packet_counter += 1
#
# UDP comms functions, to communicate with the Raspberry Pi Pico W board used in the SDR-TRX.
#
def get_parameter(self, string):
string = string + "\n"
self.command_sock.sendto(string.encode(), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
return self.get_argument()
def set_parameter(self, string, arg):
string = string + "," + arg + "\r" + "\n"
self.command_sock.sendto(string.encode(), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
print('arg is: ', arg)
temp_arg = self.get_argument()
print('temp_arg is: ', temp_arg)
return True
def get_argument(self):
self.command_sock.setblocking(True)
try:
data1 = self.command_sock.recv(1024)
print('Received on line 178: ', data1)
except Exception as e:
print("Exception occurred: {0}".format(e))
return -1
self.command_sock.setblocking(False)
if not data1.startswith(b'OK'):
print('Received on line 185: ', data1)
# Maybe we didn't catch an OK line?
else:
while True:
try:
data1 = self.command_sock.recv(1024)
print('Received on line 190: ', data1)
break
except BlockingIOError:
pass
# Check to see if we have a comma in the string. If not, there is no argument.
if data1.find(b',') == -1:
return -1
data1 = data1.split(b',')[1].rstrip(b'\r\n')
print("data1 =")
print(data1)
# Check for the OK string. Should we wait for the OK string?
try:
data2 = self.command_sock.recv(1024)
if data2.startswith(b'OK'):
return data2
except BlockingIOError:
return -1
# We might want to integrate the UDP reads below into the stuff above.
# The code below is to interface WSJT-X with SDR-TRX.
def encode_ft8(self, msg):
try:
a77 = self.ft8_encoder.pack(msg, 1)
symbols = self.ft8_encoder.make_symbols(a77)
except Exception as e:
print("FT8 encoder error, check message!")
print(f"Exception type: {type(e).__name__}")
print(f"Exception message: {str(e)}")
symbols = None
time.sleep(3)
return symbols
def encode_ft4(self, msg):
try:
a77 = self.ft4_encoder.pack(msg, 1)
symbols = self.ft4_encoder.make_symbols(a77)
except:
print("FT4 encoder error, check message!")
symbols = None
time.sleep(3)
return symbols
def load_symbols(self, symbols):
print("Load symbols into transmitter..")
self.command_sock.sendto(b'm', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
count = 0
for symbol in symbols:
self.command_sock.sendto(struct.pack('>B', symbol), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
count += 1
# Wait to avoid Arduino serial buffer overflow. This may not be needed with the Pico.
if count % 50 == 0:
time.sleep(0.05)
self.command_sock.sendto(b'\0', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
resp = self.command_sock.recv(1)
if resp == b'm':
print("Load OK")
else:
print(resp)
def change_freq(self, new_freq):
#global self.tx_freq
self.command_sock.setblocking(True)
print("Change TX frequency to:", new_freq)
self.command_sock.sendto(b'o', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
self.command_sock.sendto(struct.pack('<H', new_freq), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
time.sleep(0.05)
resp = self.command_sock.recv(1)
print("resp after changing offset is: ", resp)
self.command_sock.setblocking(False)
if resp == b'o':
print("New freq OK")
self.tx_freq = new_freq
def set_mode(self, new_mode): # New more robust protocol.
#global mode
if new_mode == 'FT8':
self.command_sock.sendto(b'e', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
time.sleep(.05)
resp = self.command_sock.read(1)
print("resp = ", resp)
if resp == b'e':
self.mode = new_mode
print("Switched to: {0}".format(new_mode))
self.current_msg = ''
return True
elif new_mode == 'FT4':
self.command_sock.sendto(b'f', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
time.sleep(0.05)
resp = self.command_sock.recv(1)
print("resp = ", resp)
if resp == b'f':
self.mode = new_mode
self.current_msg = ''
print("Switched to: {0}".format(new_mode))
return True
else:
return False
def new_msg(self, msg):
#global self.current_msg
#global self.mode
if msg != self.current_msg:
print("Message: {0}".format(msg))
if 'FT8' in self.mode:
symbols = self.encode_ft8(msg)
else:
symbols = self.encode_ft4(msg)
if symbols.any():
# symbols = [kk for kk in range(79)]
self.load_symbols(symbols)
self.current_msg = msg
else:
return
else:
time.sleep(0.005) # Do we need this?
def transmit(self):
if False: # not current_msg:
print("No previous message!")
time.sleep(1)
else:
print("TX!")
self.command_sock.sendto(b't', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
# self.tx_now = False
def check_time_window(self, utc_time):
time_window = 15 if 'FT8' in self.mode else 7
print("Time window: ", time_window) # Is correct for FT8 and FT4.
rm = utc_time.second % time_window
if rm > 1 and rm < time_window - 1: # Within one second of exact time for FT8.
return False
else:
return True
# See if HeartBeat will work for this:
def HeartBeat(self): # Should be called at 10 Hz from the main.
# Check transmitter is initialized
#raise Exception("In HeartBeat")
# print("\n\nWait for transmitter ready...")
if self.tx_ready_wsjtx == False:
if self.tx_ready_wsjtx_sent == False:
self.command_sock.sendto(b'r', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
time.sleep(0.05)
self.tx_ready_wsjtx_sent = True
while True:
try:
x = self.command_sock.recv(1)
if x == b'r':
print("Transmitter ready!")
self.tx_ready_wsjtx = True
break
except BlockingIOError:
pass
if self.tx_ready_wsjtx == True:
try:
fileContent = self.wsjtx_sock.recv(1024)
except Exception as e:
if e == BlockingIOError: # This is our way of pollig the socket.
# Maybe use select to stop the errors printing after we quit.
return BaseHardware.HeartBeat(self)
else:
# print("Error in HeartBeat")
# print(f"Exception type: {type(e).__name__}")
# print(f"Exception message: {str(e)}")
pass
try:
NewPacket = WSJTXClass.WSJTX_Packet(fileContent, 0)
NewPacket.Decode()
if NewPacket.PacketType == 1:
# print('New Packet is type 1')
StatusPacket = WSJTXClass.WSJTX_Status(fileContent, NewPacket.index)
StatusPacket.Decode()
# Check TX frequency and update transceiver
new_freq = StatusPacket.TxDF
new_mode = StatusPacket.TxMode.strip()
if new_freq != self.tx_freq:
# time.sleep(0.03)
print('Changing frequency from {0} to {1}'.format(self.tx_freq, new_freq))
try:
self.change_freq(new_freq)
except Exception as e:
print(f"Exception occurred: {e}")
print ('It is now ', self.tx_freq)
# print('new_mode', new_mode, ' self.mode is: ', self.mode)
if new_mode != self.mode:
print("Mode before: {0}".format(self.mode))
self.set_mode(new_mode)
print("New mode after: {0}".format(self.mode))
# Check if TX is enabled
if StatusPacket.Transmitting == 1:
# Check time, avoid transmitting out of the time slot
current_time = datetime.datetime.now()
utc_time = current_time.astimezone(datetime.timezone.utc)
self.tx_now = self.check_time_window(utc_time)
if self.tx_now:
self.command_sock.sendto(b'p', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
message = StatusPacket.TxMessage
message = message.replace('<', '')
message = message.replace('>', '')
print('Message is: ', message)
self.new_msg(message.strip())
print("Encoded message is: ", self.current_msg)
# print("tx_now is: ", self.tx_now)
if self.tx_now:
print('Transmitting')
self.transmit()
print("Time: {0}:{1}:{2}".format(utc_time.hour, utc_time.minute, utc_time.second))
else:
pass
# print('Not Transmitting')
return BaseHardware.HeartBeat(self)
except Exception as e:
# print("Error in HeartBeat")
# print(f"Exception type: {type(e).__name__}")
# print(f"Exception message: {str(e)}")
# traceback.print_exc()
return BaseHardware.HeartBeat(self)
+5 -10
View File
@@ -4,19 +4,14 @@ import os
# This file is used to build the Linux and Mac versions of Quisk. Windows builds are not included.
# You must define the version here. A title string including
# the version will be written to __init__.py and read by quisk.py.
Version = '4.2.37'
fp = open("__init__.py", "w") # write title string
fp.write("#Quisk version %s\n" % Version)
fp.write("from .quisk import main\n")
fp = open('__init__.py') # Read in the version
Version = fp.readline().strip()[15:]
fp.close()
sources = ['quisk.c', 'sound.c', 'is_key_down.c', 'microphone.c', 'utility.c',
'sound_alsa.c', 'sound_pulseaudio.c', 'sound_portaudio.c', 'sound_directx.c', 'sound_wasapi.c',
'filter.c', 'extdemod.c', 'freedv.c', 'quisk_wdsp.c', 'ac2yd/remote.c']
'filter.c', 'extdemod.c', 'freedv.c', 'quisk_wdsp.c', 'ac2yd/remote.c',
'tci.c', 'base64.c', 'handshake.c', 'sha1.c', 'utf8.c', 'ws.c']
# Afedri hardware support added by Alex, Alex@gmail.com
mAfedri = Extension ('quisk.afedrinet.afedrinet_io',
@@ -87,7 +82,7 @@ N1MM+ and software that uses Hamlib.
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg',
'quisk.sdrmicronpkg', 'quisk.perseuspkg', 'quisk.ac2yd', 'quisk.multuspkg'],
package_dir = {'quisk' : '.'},
package_data = {'' : ['*.txt', '*.html', '*.so', '*.dll']},
package_data = {'' : ['*.txt', '*.html', '*.so', '*.dll'], 'quisk' : ['icons/*']},
entry_points = {'gui_scripts' : ['quisk = quisk.quisk:main', 'quisk_vna = quisk.quisk_vna:main']},
ext_modules = Modules,
provides = ['quisk'],
+389
View File
@@ -0,0 +1,389 @@
/*
* sha1.c
*
* Description:
* This file implements the Secure Hashing Algorithm 1 as
* defined in FIPS PUB 180-1 published April 17, 1995.
*
* The SHA-1, produces a 160-bit message digest for a given
* data stream. It should take about 2**n steps to find a
* message with the same digest as a given message and
* 2**(n/2) to find any two messages with the same digest,
* when n is the digest size in bits. Therefore, this
* algorithm can serve as a means of providing a
* "fingerprint" for a message.
*
* Portability Issues:
* SHA-1 is defined in terms of 32-bit "words". This code
* uses <stdint.h> (included via "sha1.h" to define 32 and 8
* bit unsigned integer types. If your C compiler does not
* support 32 bit unsigned integers, this code is not
* appropriate.
*
* Caveats:
* SHA-1 is designed to work with messages less than 2^64 bits
* long. Although SHA-1 allows a message digest to be generated
* for messages of any number of bits less than 2^64, this
* implementation only works with messages with a length that is
* a multiple of the size of an 8-bit character.
*
*/
#include "sha1.h"
/*
* Define the SHA1 circular left shift macro
*/
#define SHA1CircularShift(bits,word) \
(((word) << (bits)) | ((word) >> (32-(bits))))
/* Local Function Prototyptes */
void SHA1PadMessage(SHA1Context *);
void SHA1ProcessMessageBlock(SHA1Context *);
/*
* SHA1Reset
*
* Description:
* This function will initialize the SHA1Context in preparation
* for computing a new SHA1 message digest.
*
* Parameters:
* context: [in/out]
* The context to reset.
*
* Returns:
* sha Error Code.
*
*/
int SHA1Reset(SHA1Context *context)
{
if (!context)
{
return shaNull;
}
context->Length_Low = 0;
context->Length_High = 0;
context->Message_Block_Index = 0;
context->Intermediate_Hash[0] = 0x67452301;
context->Intermediate_Hash[1] = 0xEFCDAB89;
context->Intermediate_Hash[2] = 0x98BADCFE;
context->Intermediate_Hash[3] = 0x10325476;
context->Intermediate_Hash[4] = 0xC3D2E1F0;
context->Computed = 0;
context->Corrupted = 0;
return shaSuccess;
}
/*
* SHA1Result
*
* Description:
* This function will return the 160-bit message digest into the
* Message_Digest array provided by the caller.
* NOTE: The first octet of hash is stored in the 0th element,
* the last octet of hash in the 19th element.
*
* Parameters:
* context: [in/out]
* The context to use to calculate the SHA-1 hash.
* Message_Digest: [out]
* Where the digest is returned.
*
* Returns:
* sha Error Code.
*
*/
int SHA1Result( SHA1Context *context,
uint8_t Message_Digest[SHA1HashSize])
{
int i;
if (!context || !Message_Digest)
{
return shaNull;
}
if (context->Corrupted)
{
return context->Corrupted;
}
if (!context->Computed)
{
SHA1PadMessage(context);
for(i=0; i<64; ++i)
{
/* message may be sensitive, clear it out */
context->Message_Block[i] = 0;
}
context->Length_Low = 0; /* and clear length */
context->Length_High = 0;
context->Computed = 1;
}
for(i = 0; i < SHA1HashSize; ++i)
{
Message_Digest[i] = context->Intermediate_Hash[i>>2]
>> 8 * ( 3 - ( i & 0x03 ) );
}
return shaSuccess;
}
/*
* SHA1Input
*
* Description:
* This function accepts an array of octets as the next portion
* of the message.
*
* Parameters:
* context: [in/out]
* The SHA context to update
* message_array: [in]
* An array of characters representing the next portion of
* the message.
* length: [in]
* The length of the message in message_array
*
* Returns:
* sha Error Code.
*
*/
int SHA1Input( SHA1Context *context,
const uint8_t *message_array,
unsigned length)
{
if (!length)
{
return shaSuccess;
}
if (!context || !message_array)
{
return shaNull;
}
if (context->Computed)
{
context->Corrupted = shaStateError;
return shaStateError;
}
if (context->Corrupted)
{
return context->Corrupted;
}
while(length-- && !context->Corrupted)
{
context->Message_Block[context->Message_Block_Index++] =
(*message_array & 0xFF);
context->Length_Low += 8;
if (context->Length_Low == 0)
{
context->Length_High++;
if (context->Length_High == 0)
{
/* Message is too long */
context->Corrupted = 1;
}
}
if (context->Message_Block_Index == 64)
{
SHA1ProcessMessageBlock(context);
}
message_array++;
}
return shaSuccess;
}
/*
* SHA1ProcessMessageBlock
*
* Description:
* This function will process the next 512 bits of the message
* stored in the Message_Block array.
*
* Parameters:
* None.
*
* Returns:
* Nothing.
*
* Comments:
* Many of the variable names in this code, especially the
* single character names, were used because those were the
* names used in the publication.
*
*
*/
void SHA1ProcessMessageBlock(SHA1Context *context)
{
const uint32_t K[] = { /* Constants defined in SHA-1 */
0x5A827999,
0x6ED9EBA1,
0x8F1BBCDC,
0xCA62C1D6
};
int t; /* Loop counter */
uint32_t temp; /* Temporary word value */
uint32_t W[80]; /* Word sequence */
uint32_t A, B, C, D, E; /* Word buffers */
/*
* Initialize the first 16 words in the array W
*/
for(t = 0; t < 16; t++)
{
W[t] = (uint32_t)context->Message_Block[t * 4] << 24;
W[t] |= (uint32_t)context->Message_Block[t * 4 + 1] << 16;
W[t] |= (uint32_t)context->Message_Block[t * 4 + 2] << 8;
W[t] |= (uint32_t)context->Message_Block[t * 4 + 3];
}
for(t = 16; t < 80; t++)
{
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
}
A = context->Intermediate_Hash[0];
B = context->Intermediate_Hash[1];
C = context->Intermediate_Hash[2];
D = context->Intermediate_Hash[3];
E = context->Intermediate_Hash[4];
for(t = 0; t < 20; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | ((~B) & D)) + E + W[t] + K[0];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 20; t < 40; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 40; t < 60; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 60; t < 80; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
context->Intermediate_Hash[0] += A;
context->Intermediate_Hash[1] += B;
context->Intermediate_Hash[2] += C;
context->Intermediate_Hash[3] += D;
context->Intermediate_Hash[4] += E;
context->Message_Block_Index = 0;
}
/*
* SHA1PadMessage
*
* Description:
* According to the standard, the message must be padded to an even
* 512 bits. The first padding bit must be a '1'. The last 64
* bits represent the length of the original message. All bits in
* between should be 0. This function will pad the message
* according to those rules by filling the Message_Block array
* accordingly. It will also call the ProcessMessageBlock function
* provided appropriately. When it returns, it can be assumed that
* the message digest has been computed.
*
* Parameters:
* context: [in/out]
* The context to pad
* ProcessMessageBlock: [in]
* The appropriate SHA*ProcessMessageBlock function
* Returns:
* Nothing.
*
*/
void SHA1PadMessage(SHA1Context *context)
{
/*
* Check to see if the current message block is too small to hold
* the initial padding bits and length. If so, we will pad the
* block, process it, and then continue padding into a second
* block.
*/
if (context->Message_Block_Index > 55)
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 64)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
SHA1ProcessMessageBlock(context);
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
else
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
/*
* Store the message length as the last 8 octets
*/
context->Message_Block[56] = context->Length_High >> 24;
context->Message_Block[57] = context->Length_High >> 16;
context->Message_Block[58] = context->Length_High >> 8;
context->Message_Block[59] = context->Length_High;
context->Message_Block[60] = context->Length_Low >> 24;
context->Message_Block[61] = context->Length_Low >> 16;
context->Message_Block[62] = context->Length_Low >> 8;
context->Message_Block[63] = context->Length_Low;
SHA1ProcessMessageBlock(context);
}
+73
View File
@@ -0,0 +1,73 @@
/*
* sha1.h
*
* Description:
* This is the header file for code which implements the Secure
* Hashing Algorithm 1 as defined in FIPS PUB 180-1 published
* April 17, 1995.
*
* Many of the variable names in this code, especially the
* single character names, were used because those were the names
* used in the publication.
*
* Please read the file sha1.c for more information.
*
*/
#ifndef _SHA1_H_
#define _SHA1_H_
#include <stdint.h>
/*
* If you do not have the ISO standard stdint.h header file, then you
* must typdef the following:
* name meaning
* uint32_t unsigned 32 bit integer
* uint8_t unsigned 8 bit integer (i.e., unsigned char)
* int_least16_t integer of >= 16 bits
*
*/
#ifndef _SHA_enum_
#define _SHA_enum_
enum
{
shaSuccess = 0,
shaNull, /* Null pointer parameter */
shaInputTooLong, /* input data too long */
shaStateError /* called Input after Result */
};
#endif
#define SHA1HashSize 20
/*
* This structure will hold context information for the SHA-1
* hashing operation
*/
typedef struct SHA1Context
{
uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
uint32_t Length_Low; /* Message length in bits */
uint32_t Length_High; /* Message length in bits */
/* Index into message block array */
int_least16_t Message_Block_Index;
uint8_t Message_Block[64]; /* 512-bit message blocks */
int Computed; /* Is the digest computed? */
int Corrupted; /* Is the message digest corrupted? */
} SHA1Context;
/*
* Function Prototypes
*/
int SHA1Reset( SHA1Context *);
int SHA1Input( SHA1Context *,
const uint8_t *,
unsigned int);
int SHA1Result( SHA1Context *,
uint8_t Message_Digest[SHA1HashSize]);
#endif
+21 -4
View File
@@ -1,7 +1,24 @@
MODULE_NAME := soapy
OBJDIR := ../build/local
soapy2:
python2 setup.py build_ext --force --inplace
CFLAGS := $(shell python3 ../makefil.py --cflags)
LDFLAGS := $(shell python3 ../makefil.py --ldflags)
EXTENSION_SUFFIX := $(shell python3 ../makefil.py --suffix)
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
soapy3:
# Default target: Link the shared library object
$(TARGET): soapy.o $(OBJDIR)/import_quisk_api.o
$(CC) -o $@ soapy.o $(LDFLAGS) -lSoapySDR $(OBJDIR)/import_quisk_api.o
# Compile the C source files into object files
soapy.o: soapy.c ../quisk.h ../filter.h
$(CC) -o $@ -c $< $(CFLAGS) -I..
.PHONY: clean
clean:
-rm -f *.o
-rm -f $(MODULE_NAME)*.so
.PHONY: soapy-old
soapy-old:
python3 setup.py build_ext --force --inplace
+8 -7
View File
@@ -1,5 +1,6 @@
#include <Python.h>
#include <complex.h>
#include <string.h>
#include <SoapySDR/Device.h>
#include <SoapySDR/Formats.h>
#include <SoapySDR/Version.h>
@@ -223,7 +224,7 @@ static PyObject * open_device(PyObject * self, PyObject * args)
else {
snprintf(buf128, 128, "SoapySDRDevice_make fail: %s", SoapySDRDevice_lastError());
}
return PyString_FromString(buf128);
return PyUnicode_FromString(buf128);
}
static void get_direc_len(const char * name, int * direction, int * length)
@@ -254,7 +255,7 @@ static PyObject * get_device_list(PyObject * self, PyObject * args) // Called fr
for (i = 0; i < length; i++) {
dict = PyDict_New();
for (j = 0; j < results[i].size; j++)
PyDict_SetItemString(dict, results[i].keys[j], PyString_FromString(results[i].vals[j]));
PyDict_SetItemString(dict, results[i].keys[j], PyUnicode_FromString(results[i].vals[j]));
PyList_Append(devices, dict);
Py_DECREF(dict);
}
@@ -329,7 +330,7 @@ static PyObject * set_parameter(PyObject * self, PyObject * args) // Called from
}
}
if (msg200[0])
return PyString_FromString(msg200);
return PyUnicode_FromString(msg200);
Py_INCREF (Py_None);
return Py_None;
}
@@ -377,7 +378,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
pylist = PyList_New(0);
names = SoapySDRDevice_listAntennas(sdev, direction, 0, &len_list);
for (i = 0; i < len_list; i++) {
pyobj = PyString_FromString(names[i]);
pyobj = PyUnicode_FromString(names[i]);
PyList_Append(pylist, pyobj);
Py_DECREF(pyobj);
}
@@ -440,7 +441,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
pylist = PyList_New(0);
names = SoapySDRDevice_listGains(sdev, direction, 0, &len_list);
for (i = 0; i < len_list; i++) {
pyobj = PyString_FromString(names[i]);
pyobj = PyUnicode_FromString(names[i]);
PyList_Append(pylist, pyobj);
Py_DECREF(pyobj);
}
@@ -450,7 +451,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
else if ( ! strncmp(name, "soapy_listGainsValues", length)) {
pylist = PyList_New(0);
pylst2 = PyList_New(0); // First element is the total gain
pyobj = PyString_FromString("total");
pyobj = PyUnicode_FromString("total");
PyList_Append(pylst2, pyobj);
Py_DECREF(pyobj);
range = SoapySDRDevice_getGainRange(sdev, direction, 0);
@@ -460,7 +461,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
names = SoapySDRDevice_listGains(sdev, direction, 0, &len_list);
for (i = 0; i < len_list; i++) {
pylst2 = PyList_New(0);
pyobj = PyString_FromString(names[i]);
pyobj = PyUnicode_FromString(names[i]);
PyList_Append(pylst2, pyobj);
Py_DECREF(pyobj);
range = SoapySDRDevice_getGainElementRange(sdev, direction, 0, names[i]);
+19 -7
View File
@@ -4,6 +4,7 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <complex.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>
@@ -113,7 +114,7 @@ void quisk_sample_level(const char * msg, complex double * cSamples, int nSample
level = d;
}
if (QuiskTimeSec() - time0 > 0.1) {
printf ("sample_level %s: %10.6lf count %8d\n", msg, level / scale, count);
printf ("peak sample level %s: %14.6lf count %8d\n", msg, level / scale, count);
level = 0;
count = 0;
time0 = QuiskTimeSec();
@@ -986,6 +987,8 @@ int quisk_read_sound(void) // Called from sound thread
// Perhaps replace the samples with samples from a file
if (quisk_record_state == FILE_PLAY_SAMPLES)
quisk_play_samples(cSamples, nSamples);
// Send radio IQ samples to TCI
tci_send_iq(cSamples, nSamples);
#if ! DEBUG_MIC
nSamples = quisk_process_samples(cSamples, nSamples);
#endif
@@ -1000,22 +1003,28 @@ int quisk_read_sound(void) // Called from sound thread
if (remote_control_slave)
send_remote_radio_sound_socket(cSamples, nSamples);
is_DGT = rxMode == DGT_U || rxMode == DGT_L || rxMode == DGT_IQ || rxMode == DGT_FM;
is_DGT = (rxMode == DGT_U || rxMode == DGT_L || rxMode == DGT_IQ || rxMode == DGT_FM || rxMode == FDV_U || rxMode == FDV_L);
if (quisk_record_state == TMP_PLAY_SPKR_MIC)
quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound
else if (quisk_record_state == FILE_PLAY_SPKR_MIC)
quisk_file_playback(cSamples, nSamples, file_play_level); // replace radio sound
// Play the demodulated audio
#if DEBUG_MIC != 2
if ( ! quisk_play_sidetone(&quisk_Playback)) // play sidetone
play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, quisk_audioVolume); // else play radio sound
if ( ! quisk_play_sidetone(&quisk_Playback)) { // play sidetone
if (rxMode == FDV_U || rxMode == FDV_L)
play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, 0); // play silence
else
play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, quisk_audioVolume); // play radio sound
}
#endif
if (radio_sound_socket != INVALID_SOCKET)
send_radio_sound_socket(cSamples, nSamples, quisk_audioVolume);
// Play digital if required
if (is_DGT)
play_sound_interface(&DigitalOutput, nSamples, cSamples, 1, digital_output_level);
//if (is_DGT)
play_sound_interface(&DigitalOutput, nSamples, cSamples, 1, digital_output_level);
// Send radio sound to TCI
tci_send_audio(cSamples, nSamples);
// Perhaps record the speaker audio to a file
if ( ! key_state && file_rec_audio.fp)
@@ -1061,7 +1070,10 @@ int quisk_read_sound(void) // Called from sound thread
for (i = 0; i < mic_count; i++)
cSamples[i] = 0;
}
//quisk_sample_level("read mic or DGT", cSamples, mic_count, CLIP16);
// Receive microphone sound from TCI
//quisk_sample_level("read mic or DGT", cSamples, mic_count, CLIP32);
mic_count = tci_get_mic(cSamples, mic_count);
//quisk_sample_level("quisk_tci", cSamples, mic_count, CLIP32);
// Perhaps record the microphone audio to the speaker audio file
if (key_state && file_rec_audio.fp)
quisk_record_audio(&file_rec_audio, cSamples, mic_count);
+5 -4
View File
@@ -2,6 +2,7 @@
* This modue provides sound access for QUISK using the ALSA
* library for Linux.
*/
#include <string.h>
#ifdef QUISK_HAVE_ALSA
#include <Python.h>
#include <complex.h>
@@ -454,7 +455,7 @@ static int device_list(PyObject * py, snd_pcm_stream_t stream, char * name)
}
snprintf(buf100, 100, "%s %s (hw:%d,%d)", card_text, pcm_text, card, dev);
if (py) { // add to list of devices
PyList_Append(py, PyString_FromString(buf100));
PyList_Append(py, PyUnicode_FromString(buf100));
}
if (name) { // return the "hw:" name
if (strstr(buf100, name)) {
@@ -1129,10 +1130,10 @@ static void midi_in_devices(PyObject * pylist, int just_names)
}
else {
pytup = PyTuple_New(2);
PyList_Append(pylist, pytup);
PyTuple_SET_ITEM(pytup, 0, PyUnicode_DecodeUTF8(friendly_name, strlen(friendly_name), "replace"));
PyTuple_SetItem(pytup, 0, PyUnicode_DecodeUTF8(friendly_name, strlen(friendly_name), "replace"));
snprintf(card_name, 32, "hw:%d,%d,%d", card, device, sub);
PyTuple_SET_ITEM(pytup, 1, PyUnicode_DecodeUTF8(card_name, strlen(card_name), "replace"));
PyTuple_SetItem(pytup, 1, PyUnicode_DecodeUTF8(card_name, strlen(card_name), "replace"));
PyList_Append(pylist, pytup);
}
}
}
+11 -16
View File
@@ -1,3 +1,5 @@
#include <string.h>
#ifdef QUISK_HAVE_DIRECTX
#include <Python.h>
@@ -55,14 +57,7 @@ static int match_name(LPCTSTR lpszDesc, const char * name)
Py_DECREF(py_unicode);
return 0;
}
#if PY_MAJOR_VERSION >= 3
if (PyUnicode_READY(py_unicode) == 0)
length = PyUnicode_GET_LENGTH(py_unicode);
else
length = 0;
#else
length = PyUnicode_GET_SIZE(py_unicode);
#endif
length = PyUnicode_GetLength(py_unicode);
if (length <= 0) {
Py_DECREF(py_unicode);
Py_DECREF(py_substring);
@@ -199,14 +194,14 @@ static int quisk_open_capture(struct sound_dev * dev)
DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)DsEnumCapture, dev);
if (errFound != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device name %s not found", dev->name);
"DirectSound capture device name %.200s not found", dev->name);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", dev->dev_errmsg);
return 1;
}
if (errOpen != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device %s open failed", dev->name);
"DirectSound capture device %.200s open failed", dev->name);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", dev->dev_errmsg);
return 1;
@@ -226,7 +221,7 @@ static int quisk_open_capture(struct sound_dev * dev)
}
else {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device %s buffer create failed (0x%lX)", dev->name, hr);
"DirectSound capture device %.200s buffer create failed (0x%lX)", dev->name, hr);
return 1;
}
ptBuf = (LPDIRECTSOUNDCAPTUREBUFFER)dev->buffer;
@@ -235,7 +230,7 @@ static int quisk_open_capture(struct sound_dev * dev)
if (quisk_sound_state.verbose_sound)
QuiskPrintf("Capture start error 0x%lX", hr);
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device %s capture start failed", dev->name);
"DirectSound capture device %.200s capture start failed", dev->name);
return 1;
}
if (quisk_sound_state.verbose_sound) {
@@ -270,18 +265,18 @@ static int quisk_open_playback(struct sound_dev * dev)
DirectSoundEnumerate((LPDSENUMCALLBACK)DsEnumPlay, dev);
if (errFound != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device name %s not found", dev->name);
"DirectSound play device name %.200s not found", dev->name);
return 1;
}
if (errOpen != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device %s open failed", dev->name);
"DirectSound play device %.200s open failed", dev->name);
return 1;
}
hr = IDirectSound_SetCooperativeLevel ((LPDIRECTSOUND8)dev->handle, quisk_mainwin_handle, DSSCL_PRIORITY);
if (hr != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device %s cooperative level failed", dev->name);
"DirectSound play device %.200s cooperative level failed", dev->name);
return 1;
}
dev->sample_bytes = 4;
@@ -299,7 +294,7 @@ static int quisk_open_playback(struct sound_dev * dev)
}
else {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device %s buffer create failed (0x%X)", dev->name, (unsigned int)hr);
"DirectSound play device %.180s buffer create failed (0x%X)", dev->name, (unsigned int)hr);
return 1;
}
if (quisk_sound_state.verbose_sound) {
+3 -1
View File
@@ -1,6 +1,8 @@
/*
* This module provides sound access for QUISK using the portaudio library.
*/
#include <string.h>
#ifdef QUISK_HAVE_PORTAUDIO
#include <Python.h>
@@ -475,7 +477,7 @@ static int device_list(PyObject * py, int input)
// found one
if (py) {
snprintf(buf100, 100, "%s", info->name);
PyList_Append(py, PyString_FromString(buf100));
PyList_Append(py, PyUnicode_FromString(buf100));
}
}
}
+55 -56
View File
@@ -34,6 +34,8 @@
* 2020 Apr by N2ADR Changes in printf's
*/
#include <string.h>
#ifdef QUISK_HAVE_PULSEAUDIO
#include <Python.h>
@@ -64,12 +66,20 @@ volatile int streams_to_start;
// remember all open devices for easy cleanup on exit
static pa_stream *OpenPulseDevices[PA_LIST_SIZE * 2] = {NULL};
static int have_QuiskDigitalInput; // Do we need to create this null sink?
static int have_QuiskDigitalOutput; // Do we need to create this null sink?
static int underrunPlayback;
static int tlength_bytes;
// list of additional sink devices to add
struct _NewSinks {
char * name; // name of the new sink
int create; // create the sink if it doesn't exist
};
#define NUM_NEW_SINKS 2
static struct _NewSinks sink_list[NUM_NEW_SINKS] = {
{"QuiskDigitalInput", 1},
{"QuiskDigitalOutput", 1}
};
/* This callback happens any time a stream changes state. Here, it's primary used to
* tell the quisk thread when streams are ready.
*/
@@ -1022,20 +1032,20 @@ static void source_sink(const char * name, const char * descr, pa_proplist * pro
PyObject * pytup;
pytup = PyTuple_New(3);
PyList_Append(pylist, pytup);
PyTuple_SET_ITEM(pytup, 0, PyString_FromString(name));
PyTuple_SET_ITEM(pytup, 1, PyString_FromString(descr));
PyTuple_SetItem(pytup, 0, PyUnicode_FromString(name));
PyTuple_SetItem(pytup, 1, PyUnicode_FromString(descr));
value = pa_proplist_gets(props, "device.api");
if (value && ! strcmp(value, "alsa")) {
snprintf(buf300, 300, "%s %s (hw:%s,%s)", pa_proplist_gets(props, "alsa.card_name"), pa_proplist_gets(props, "alsa.name"),
pa_proplist_gets(props, "alsa.card"), pa_proplist_gets(props, "alsa.device"));
PyTuple_SET_ITEM(pytup, 2, PyString_FromString(buf300));
PyTuple_SetItem(pytup, 2, PyUnicode_FromString(buf300));
}
else {
PyTuple_SET_ITEM(pytup, 2, PyString_FromString(""));
PyTuple_SetItem(pytup, 2, PyUnicode_FromString(""));
}
PyList_Append(pylist, pytup);
}
@@ -1043,26 +1053,30 @@ static void source_sink(const char * name, const char * descr, pa_proplist * pro
// pa_mainloop will call this function when it's ready to tell us about a sink.
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
if (eol > 0) // If eol is set to a positive number, you're at the end of the list
int i;
if (eol > 0) // If eol is set to a positive number, you're at the end of the list
return;
source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
if ( ! strcmp(l->name, "QuiskDigitalInput"))
have_QuiskDigitalInput = 1;
if ( ! strcmp(l->name, "QuiskDigitalOutput"))
have_QuiskDigitalOutput = 1;
source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
for (i = 0; i < NUM_NEW_SINKS; i++) {
if ( ! strcmp(l->name, sink_list[i].name)) {
sink_list[i].create = 0;
}
}
}
static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) {
if (eol > 0)
static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata)
{
if (eol > 0)
return;
source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
}
static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
//printf("%u\n", idx);
}
PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args)
PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args) // Called from the GUI thread
{ // Return a list of PulseAudio device names [pycapt, pyplay]
PyObject * pylist, * pycapt, * pyplay;
pa_mainloop *pa_names_ml;
@@ -1070,6 +1084,9 @@ PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args)
pa_operation *pa_op=NULL;
pa_context *pa_names_ctx;
int state = 0;
int sink_index = 0;
char * sink_name;
char buf256[256];
if (!PyArg_ParseTuple (args, ""))
return NULL;
@@ -1113,59 +1130,41 @@ PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args)
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
case 2:
// Now we wait for our operation to complete. When it's
// complete our pa_output_devicelist is filled out, and we move
// along to the next state
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
// Now we perform another operation to get the source
// (input device) list just like before.
pa_op = pa_context_get_source_info_list(pa_names_ctx, pa_sourcelist_cb, pycapt);
// Update the state so we know what to do next
state++;
}
pa_mainloop_iterate(pa_names_ml, 1, NULL);
else
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
// The following loads null sinks for digital input and output if they are not present.
case 3:
if (sink_index >= NUM_NEW_SINKS) {
state = 5;
continue;
}
if (sink_list[sink_index].create) {
sink_name = sink_list[sink_index].name;
snprintf(buf256, 256, "sink_name=%s sink_properties=device.description=%s", sink_name, sink_name);
pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink", buf256, index_callback, NULL);
state++;
}
sink_index++;
break;
case 4:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
state = 4;
state = 3;
}
else
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
// The following loads modules for digital input and output if they are not present.
case 4:
if ( ! have_QuiskDigitalInput) {
pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink",
"sink_name=QuiskDigitalInput sink_properties=device.description=QuiskDigitalInput",
index_callback, NULL);
state = 5;
pa_mainloop_iterate(pa_names_ml, 1, NULL);
}
else
state = 6;
break;
case 5:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
state = 6;
}
else
pa_mainloop_iterate(pa_names_ml, 1, NULL);
pa_op = pa_context_get_source_info_list(pa_names_ctx, pa_sourcelist_cb, pycapt);
state++;
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
case 6:
if ( ! have_QuiskDigitalOutput) {
pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink",
"sink_name=QuiskDigitalOutput sink_properties=device.description=QuiskDigitalOutput",
index_callback, NULL);
state = 7;
pa_mainloop_iterate(pa_names_ml, 1, NULL);
}
else
state = 9;
break;
case 7:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
state = 9;
+9 -6
View File
@@ -7,6 +7,9 @@
* Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
*/
#include <string.h>
#ifdef QUISK_HAVE_WASAPI
#define UNICODE
@@ -1125,10 +1128,10 @@ PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Call
EXIT_ON_ERROR(hr)
// data items are (name, id, is_raw)
pytup = PyTuple_New(3);
PyTuple_SetItem(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
PyTuple_SetItem(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
PyTuple_SetItem(pytup, 2, PyInt_FromLong(1));
PyList_Append(pyplay, pytup);
PyTuple_SET_ITEM(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
PyTuple_SET_ITEM(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
PyTuple_SET_ITEM(pytup, 2, PyInt_FromLong(1));
CoTaskMemFree(pwszID);
pwszID = NULL;
PropVariantClear(&varName);
@@ -1155,10 +1158,10 @@ PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Call
EXIT_ON_ERROR(hr)
// data items are (name, id, is_raw)
pytup = PyTuple_New(3);
PyTuple_SetItem(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
PyTuple_SetItem(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
PyTuple_SetItem(pytup, 2, PyInt_FromLong(1));
PyList_Append(pycapt, pytup);
PyTuple_SET_ITEM(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
PyTuple_SET_ITEM(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
PyTuple_SET_ITEM(pytup, 2, PyInt_FromLong(1));
CoTaskMemFree(pwszID);
pwszID = NULL;
PropVariantClear(&varName);
+970
View File
@@ -0,0 +1,970 @@
#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "ws.h"
#include <pthread.h>
#include <complex.h>
#include <time.h>
#include "quisk.h"
#include "filter.h"
#define TCI_STREAM_DATA_BYTES 16384
#define TCI_RX_BUF_SIZE 1024
#define TCI_COMMAND_SIZE 64
#define BAD_TCI_IF 999999999
static int verbose; // non-zero for verbose log messages
static int tci_port;
// Implement the ExpertSDR2 Version 1.4 protocol:
// The Tx audio parameters are copied from the Rx audio stream.
// The only sample type is FLOAT32 type 3.
// It is unclear whether Stream.length is the number of floats, or half that for two channels. And version 1.4 lacks Stream.channels.
// The protocol name for TCI version 1.4 is "protocol:ESDR,1.4;" and the port is 40001.
// The protocol name for TCI version 2.0 is "protocol:ExpertSDR3,2.0;" and the port is 50001.
// The TCI modulations are: am, sam, dsb, lsb, usb, cw, nfm, wfm, spec, digl, digu, drm.
// These are TCI parameters:
static long long quisk_dds;
static long long client_dds=-1;
static long long quisk_vfo;
static long long client_vfo=-1;
static int quisk_if;
static int client_if=BAD_TCI_IF;
static int quisk_trx;
static int client_trx=-1;
static int quisk_split_enable;
static int client_split_enable=-1;
static char quisk_modulation[TCI_COMMAND_SIZE];
static char client_modulation[TCI_COMMAND_SIZE]={0};
static ws_cli_conn_t tci_clients_list[MAX_CLIENTS]; // list of TCI clients
static int tci_clients_count;
static int tci_started; // Did we start the TCI server?
// Tx Audio
ws_cli_conn_t tci_tx_audio_client; // single client providing Tx audio
double tci_tx_audio_time; // seconds since the start of Tx audio
uint64_t tci_tx_audio_samples; // number of samples requested since the start of Tx audio
int tci_tx_audio_rate; // samples per second
int tci_tx_audio_request; // the Stream.length to request in TX_CHRONO
static pthread_mutex_t clients_list_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t tx_buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
enum StreamType // ExpertSDR3
{
IQ_STREAM = 0, // Receiver IQ signal stream
RX_AUDIO_STREAM = 1, // Receiver audio stream
TX_AUDIO_STREAM = 2, // Audio stream for transmitter
TX_CHRONO = 3, // Time marker flow for audio signal transmission
LINEOUT_STREAM = 4 // Linear audio output audio stream
} ;
enum SampleType // ExpertSDR3
{
TCI_INT16 = 0, // 16-bit integral character type
TCI_INT24 = 1, // 24-bit integral character type
TCI_INT32 = 2, // 32-bit integral character type
TCI_FLOAT32 = 3, // 32-bit type with a floating point
} ;
struct ClientData { // data for each client
char msg_buf[TCI_RX_BUF_SIZE];
int msg_length;
int send_Rx_audio_stream;
int audio_stream_samplerate;
int audio_stream_sample_type;
int audio_stream_channels;
int audio_stream_samples;
int audio_stream_bytes_per_sample;
// Rx stream decimation filters
struct quisk_cHB45Filter HalfBand1;
struct quisk_cFilter filtDecim3;
struct quisk_cFilter filtDecim48to24;
// Tx stream interpolation filters
struct quisk_cHB45Filter HalfBandTx1;
struct quisk_cHB45Filter HalfBandTx2;
struct quisk_cFilter filtTxInterp3;
int send_IQ_stream;
int IQ_stream_samplerate;
} ;
struct _Stream {
uint32_t receiver; // receiver number
uint32_t sample_rate; // sampling rate
uint32_t format; // sample type determined in SampleType
uint32_t codec; // compression algorithm (not implemented), always 0
uint32_t crc; // checksum (not implemented), always 0
int32_t length; // number of samples
uint32_t type; // stream type determined in StreamType
uint32_t channels; // number of channels for ExpertSDR3
uint32_t reserv[8]; // reserved
uint8_t data[TCI_STREAM_DATA_BYTES]; // samples
} ;
struct _TxChrono {
uint32_t receiver; // receiver number
uint32_t sample_rate; // sampling rate
uint32_t format; // sample type determined in SampleType
uint32_t codec; // compression algorithm (not implemented), always 0
uint32_t crc; // checksum (not implemented), always 0
int32_t length; // number of samples
uint32_t type; // stream type determined in StreamType
uint32_t channels; // number of channels for ExpertSDR3
uint32_t reserv[8]; // reserved
} ;
static struct _TxChrono TxChrono = {
.receiver = 0,
.sample_rate = 48000,
.format = TCI_FLOAT32,
.codec = 0,
.crc = 0,
.length = 0,
.type = TX_CHRONO,
.channels = 2,
.reserv = {0, 0, 0, 0, 0, 0, 0, 0}
} ;
struct _StreamTypes {
char * name;
time_t when;
} ;
#define NUM_STREAM_TYPES 5
struct _StreamTypes StreamTypes[NUM_STREAM_TYPES] = {
{"iq ", 0},
{"rx_audio ", 0},
{"tx_audio ", 0},
{"tx_chrono", 0},
{"lineout ", 0}
} ;
static int interpolate_tx_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx);
static int decimate_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx);
static void PrintStream(const char * msg, size_t size, int always)
{
struct _Stream * pt = (struct _Stream *)msg;
if (always || time(NULL) - StreamTypes[pt->type].when > 5) {
StreamTypes[pt->type].when = time(NULL);
if (pt->type == TX_AUDIO_STREAM)
QuiskPrintf("TCI Receiving %slength %5d:", StreamTypes[pt->type].name, (int)size);
else
QuiskPrintf("TCI Sending %s length %5d:", StreamTypes[pt->type].name, (int)size);
QuiskPrintf("%d %5d %d %d %d %5d %d %d\n", (int)(pt->receiver), (int)(pt->sample_rate), (int)(pt->format), (int)(pt->codec),
(int)(pt->crc), (int)(pt->length), (int)(pt->type), (int)(pt->channels));
}
}
static int sendframe_txt(ws_cli_conn_t client, const char * msg)
{
if (verbose)
QuiskPrintf("TCI Send text %s\n", msg);
return ws_sendframe_txt(client, msg);
}
static int sendframe_txt_bcast(uint16_t port, const char * msg)
{
if (verbose)
QuiskPrintf("TCI Broadcast text %s\n", msg);
return ws_sendframe_txt_bcast(port, msg);
}
static size_t sendframe_bin(ws_cli_conn_t client, const char * msg, size_t size)
{
if (verbose)
PrintStream(msg, size, 0);
return ws_sendframe_bin(client, msg, size);
}
static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
{ // This function alters the message ctx->msg_buf.
int i;
char char_buf[TCI_COMMAND_SIZE];
char * saveptr, * command;
char * arg1, * arg2, * arg3;
if (verbose)
QuiskPrintf("TCI Received text %s\n", ctx->msg_buf);
//QuiskPrintf("TCI Received text %s %20.6lf\n", ctx->msg_buf, QuiskTimeSec());
for (i = 0; ctx->msg_buf[i]; i++) {
char_buf[i] = ctx->msg_buf[i]; // make a copy of the command
ctx->msg_buf[i] = tolower(ctx->msg_buf[i]);
}
char_buf[i] = 0;
command = strtok_r(ctx->msg_buf, ":", &saveptr);
if (command == NULL)
return 0;
arg1 = strtok_r(NULL, ",;", &saveptr);
arg2 = strtok_r(NULL, ",;", &saveptr);
arg3 = strtok_r(NULL, ",;", &saveptr);
// The command will be broadcast to all clients unless the return value is zero.
switch (command[0]) {
case 'a':
if (strcmp(command, "audio_start") == 0) {
pthread_mutex_lock(&clients_list_mutex);
for (i = 0; i < tci_clients_count; i++) {
if (tci_clients_list[i] == client) {
ctx->send_Rx_audio_stream = 1;
// Rx stream filters
memset(&ctx->HalfBand1,
0, sizeof(struct quisk_cHB45Filter));
quisk_filt_cInit(&ctx->filtDecim3,
quiskFilt144D3Coefs, sizeof(quiskFilt144D3Coefs)/sizeof(double));
quisk_filt_cInit(&ctx->filtDecim48to24,
quiskFilt48dec24Coefs, sizeof(quiskFilt48dec24Coefs)/sizeof(double));
break;
}
}
pthread_mutex_unlock(&clients_list_mutex);
sendframe_txt(client, char_buf);
return 0;
}
else if (strcmp(command, "audio_stop") == 0) {
pthread_mutex_lock(&clients_list_mutex);
for (i = 0; i < tci_clients_count; i++) {
if (tci_clients_list[i] == client) {
ctx->send_Rx_audio_stream = 0;
break;
}
}
pthread_mutex_unlock(&clients_list_mutex);
sendframe_txt(client, char_buf);
return 0;
}
else if (strcmp(command, "audio_stream_sample_type") == 0) {
if (strcmp(arg1, "float32") == 0) {
ctx->audio_stream_sample_type = TCI_FLOAT32;
ctx->audio_stream_bytes_per_sample = 4;
sendframe_txt(client, char_buf);
}
return 0;
}
else if (strcmp(command, "audio_samplerate") == 0) {
i = atoi(arg1);
if (i != 48000 && i != 24000 && i != 12000 && i != 8000)
return 0;
ctx->audio_stream_samplerate = i;
sendframe_txt(client, char_buf);
return 0;
}
else if (strcmp(command, "audio_stream_channels") == 0) {
if (strcmp(arg1, "1") == 0)
ctx->audio_stream_channels = 1;
else if (strcmp(arg1, "2") == 0)
ctx->audio_stream_channels = 2;
else
return 0;
sendframe_txt(client, char_buf);
return 0;
}
else if (strcmp(command, "audio_stream_samples") == 0) {
return 0; // do not send
}
break;
case 'd':
if (strcmp(command, "dds") == 0) {
if (arg2) {
client_dds = atoll(arg2);
// Echo comes from quisk_tci_set_params()
}
else {
snprintf(char_buf, TCI_COMMAND_SIZE, "dds:0,%lld;", quisk_dds);
sendframe_txt(client, char_buf);
}
return 0;
}
break;
case 'i':
if (strcmp(command, "iq_start") == 0) {
pthread_mutex_lock(&clients_list_mutex);
for (i = 0; i < tci_clients_count; i++) {
if (tci_clients_list[i] == client) {
ctx->send_IQ_stream = 1;
break;
}
}
pthread_mutex_unlock(&clients_list_mutex);
sendframe_txt(client, char_buf);
return 0;
}
else if (strcmp(command, "iq_stop") == 0) {
pthread_mutex_lock(&clients_list_mutex);
for (i = 0; i < tci_clients_count; i++) {
if (tci_clients_list[i] == client) {
ctx->send_IQ_stream = 0;
break;
}
}
pthread_mutex_unlock(&clients_list_mutex);
sendframe_txt(client, char_buf);
return 0;
}
else if (strcmp(command, "iq_samplerate") == 0) {
ctx->IQ_stream_samplerate = quisk_sound_state.sample_rate;
snprintf(char_buf, TCI_COMMAND_SIZE, "iq_samplerate:%d;", quisk_sound_state.sample_rate);
sendframe_txt(client, char_buf);
return 0;
}
else if (strcmp(command, "if") == 0) {
if (arg3) {
client_if = atoi(arg3);
// Echo comes from quisk_tci_set_params() UNIMPLEMENTED
}
else {
snprintf(char_buf, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
sendframe_txt(client, char_buf);
}
return 0;
}
break;
case 'm':
if (strcmp(command, "modulation") == 0) {
if (arg2) {
strncpy(client_modulation, arg2, TCI_COMMAND_SIZE - 1);
// Echo comes from quisk_tci_set_params()
}
else {
snprintf(char_buf, TCI_COMMAND_SIZE, "modulation:0,%.9s;", quisk_modulation);
sendframe_txt(client, char_buf);
}
return 0;
}
break;
case 's':
if (strcmp(command, "split_enable") == 0) {
if (arg2) {
if (strcmp(arg2, "true") == 0)
client_split_enable = 1;
else
client_split_enable = 0;
// Echo comes from quisk_tci_set_params()
}
else {
if (quisk_split_enable)
sendframe_txt(client, "split_enable:0,true;");
else
sendframe_txt(client, "split_enable:0,false;");
}
return 0;
}
break;
case 't':
if (strcmp(command, "trx") == 0) {
if (arg2) {
if (strcmp(arg2, "true") == 0) {
if (quisk_trx == 0) {
pthread_mutex_lock(&tx_buffer_mutex);
client_trx = 1;
tci_tx_audio_request = TCI_STREAM_DATA_BYTES /
ctx->audio_stream_bytes_per_sample / ctx->audio_stream_channels;
CircularBuffer(0, NULL, tci_tx_audio_request * 2, 0);
tci_tx_audio_client = client;
tci_tx_audio_time = QuiskTimeSec();
tci_tx_audio_samples = 0;
tci_tx_audio_rate = ctx->audio_stream_samplerate;
pthread_mutex_unlock(&tx_buffer_mutex);
// Tx stream filters
memset(&ctx->HalfBandTx1,
0, sizeof(struct quisk_cHB45Filter));
memset(&ctx->HalfBandTx2,
0, sizeof(struct quisk_cHB45Filter));
quisk_filt_cInit(&ctx->filtTxInterp3,
quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
}
}
else if (client == tci_tx_audio_client) {
client_trx = 0;
tci_tx_audio_client = 0;
}
// Echo comes from quisk_tci_set_params()
}
else {
if (quisk_trx)
sendframe_txt(client, "trx:0,true;");
else
sendframe_txt(client, "trx:0,false;");
}
return 0;
}
else if (strcmp(command, "tx_stream_audio_buffering") == 0) {
return 0; // do not send
}
break;
case 'v':
if (strcmp(command, "vfo") == 0) {
if (arg3) {
client_vfo = atoll(arg3);
// Echo comes from quisk_tci_set_params()
}
else {
snprintf(char_buf, TCI_COMMAND_SIZE, "vfo:0,0,%lld;", quisk_vfo);
sendframe_txt(client, char_buf);
}
return 0;
}
break;
}
return 1; // send to all clients
}
static void onopen(ws_cli_conn_t client)
{
char *cli;
char command[TCI_COMMAND_SIZE];
struct ClientData * ctx;
// Make parameters for each client:
ctx = malloc(sizeof(struct ClientData));
ctx->msg_length = 0;
ctx->send_Rx_audio_stream = 0;
ctx->send_IQ_stream = 0;
ctx->audio_stream_samplerate = 48000;
ctx->IQ_stream_samplerate = quisk_sound_state.sample_rate;
ctx->audio_stream_sample_type = TCI_FLOAT32;
ctx->audio_stream_channels = 2;
ctx->audio_stream_samples = 0;
ctx->audio_stream_bytes_per_sample = 4;
ws_set_connection_context(client, ctx);
pthread_mutex_lock(&clients_list_mutex);
tci_clients_list[tci_clients_count++] = client;
pthread_mutex_unlock(&clients_list_mutex);
if (verbose) {
cli = ws_getaddress(client);
QuiskPrintf("TCI *Connection opened, addr: %s\n", cli);
}
sendframe_txt(client, "protocol:ExpertSDR3,2.0;");
//sendframe_txt(client, "vfo_limits:30000,30000000;"); Not known due to transverters
int if_limit = quisk_sound_state.sample_rate / 2;
snprintf(command, TCI_COMMAND_SIZE, "if_limits:%d,%d;", - if_limit, if_limit);
sendframe_txt(client, command);
sendframe_txt(client, "trx_count:1;");
sendframe_txt(client, "channel_count:1;");
sendframe_txt(client, "device:QuiskSDR;");
sendframe_txt(client, "receive_only:false;");
sendframe_txt(client, "modulations_list:usb,lsb,cw,am,fm,digl,digu;");
sendframe_txt(client, "ready;");
sendframe_txt(client, "start;");
sendframe_txt(client, "tx_enable:0,true;");
snprintf(command, TCI_COMMAND_SIZE, "dds:0,%lld;", quisk_dds);
sendframe_txt(client, command);
snprintf(command, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
sendframe_txt(client, command);
snprintf(command, TCI_COMMAND_SIZE, "modulation:0,%.9s;", quisk_modulation);
sendframe_txt(client, command);
snprintf(command, TCI_COMMAND_SIZE, "vfo:0,0,%lld;", quisk_vfo);
sendframe_txt(client, command);
if (quisk_trx)
sendframe_txt(client, "trx:0,true;");
else
sendframe_txt(client, "trx:0,false;");
if (quisk_split_enable)
sendframe_txt(client, "split_enable:0,true;");
else
sendframe_txt(client, "split_enable:0,false;");
}
static void onclose(ws_cli_conn_t client)
{
int i, count;
pthread_mutex_lock(&clients_list_mutex);
count = tci_clients_count;
tci_clients_count = 0;
for (i = 0; i < count; i++)
if (tci_clients_list[i] != client)
tci_clients_list[tci_clients_count++] = tci_clients_list[i];
pthread_mutex_unlock(&clients_list_mutex);
free(ws_get_connection_context(client));
if (verbose) {
char * cli = ws_getaddress(client);
QuiskPrintf("TCI *Connection closed, addr: %s\n", cli);
}
}
static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t size, int type)
{
char echo_copy[TCI_RX_BUF_SIZE];
int n;
static complex double * cCopy = NULL;
static int cCopy_size = 0;
struct ClientData * ctx = ws_get_connection_context(client);
if (type == WS_FR_OP_TXT) {
if (size > TCI_RX_BUF_SIZE / 2)
return; // prevent overflow
if (ctx->msg_length + size >= TCI_RX_BUF_SIZE - 2)
ctx->msg_length = 0; // prevent overflow
memcpy(ctx->msg_buf + ctx->msg_length, msg, size); // append msg to buffer
ctx->msg_length += size;
ctx->msg_buf[ctx->msg_length] = '\0';
char * semicolon;
char save;
while ((semicolon = strchr(ctx->msg_buf, ';')) != NULL) {
save = *(semicolon + 1);
*(semicolon + 1) = '\0';
strncpy(echo_copy, ctx->msg_buf, TCI_RX_BUF_SIZE);
if (text_message(client, ctx)) // should we send to all clients?
sendframe_txt_bcast(tci_port, echo_copy);
*(semicolon + 1) = save;
size_t processed_len = (semicolon - ctx->msg_buf) + 1;
size_t remaining_len = ctx->msg_length - processed_len;
memmove(ctx->msg_buf, ctx->msg_buf + processed_len, remaining_len);
ctx->msg_length = remaining_len;
ctx->msg_buf[ctx->msg_length] = '\0';
}
}
else if (type == WS_FR_OP_BIN) {
struct _Stream * pt = (struct _Stream *)msg;
int count = 0;
float re, im;
int channels = TxChrono.channels;
int two_channels = channels == 2;
int header = 16 * sizeof(uint32_t);
const unsigned char * vpt = msg + header;
const unsigned char * vpt_end = vpt + (size - header);
const unsigned char * vpt2;
const unsigned char * vpt2_end;
int count2;
if (verbose >= 3) {
QuiskPrintf("\n\nMSG size %ld length %d\n 0 ", size, pt->length);
vpt2 = msg + header;
vpt2_end = vpt2 + (size - header);
count2 = 0;
while (vpt2 < vpt2_end) {
re = *(float *)vpt2;
vpt2 += sizeof(float);
im = *(float *)vpt2;
vpt2 += sizeof(float);
if (fabsf(re) > 1.1)
re = 9;
if (fabsf(im) > 1.1)
im = 9;
count2++;
QuiskPrintf("%8.4f %8.4f ", re, im);
if (count2 % 5 == 0)
QuiskPrintf("\n%4d ", count2);
}
QuiskPrintf("\n");
}
if (pt->type == TX_AUDIO_STREAM) {
n = pt->length * 7 + 100; // Make room for 6X interpolation
if (cCopy_size < n) {
if (cCopy)
free(cCopy);
cCopy_size = n;
cCopy = (complex double *)malloc(cCopy_size * sizeof(complex double));
}
if (pt->length > 0 && client == tci_tx_audio_client) {
n = 0;
switch (TxChrono.format) {
case TCI_INT16:
break;
case TCI_FLOAT32:
// pt->length is the number of floats
while (vpt < vpt_end && count < pt->length) {
re = *(float *)vpt;
vpt += sizeof(float);
count++;
if (two_channels) {
im = *(float *)vpt;
vpt += sizeof(float);
count++;
}
else {
im = re;
}
cCopy[n++] = (re + I * im) * (CLIP32 / 2);
}
if (verbose && count != pt->length)
QuiskPrintf("TCI *TX count %ld %d %d\n", size, count, pt->length);
n = interpolate_tx_audio_stream(cCopy, n, ctx);
pthread_mutex_lock(&tx_buffer_mutex);
CircularBuffer(0, cCopy, 0, n);
pthread_mutex_unlock(&tx_buffer_mutex);
break;
}
}
}
if (verbose)
PrintStream((const char *)msg, size, 0);
}
}
static void tci_startup(void)
{
struct ws_server tci = {
.host = NULL,
.port = 0,
.thread_loop = 1,
.timeout_ms = 1000,
.evs.onopen = &onopen,
.evs.onclose = &onclose,
.evs.onmessage = &onmessage
};
char host32[32];
tci_clients_count = 0;
tci_port = QuiskGetConfigInt("tci_port", 40001);
strncpy(host32, QuiskGetConfigString("tci_ip", "127.0.0.1"), 31);
tci.host = host32;
tci.port = tci_port;
ws_socket(&tci);
if (verbose)
QuiskPrintf("TCI Start server on host %s port %d\n", tci.host, tci.port);
return;
}
static int decimate_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx)
{
switch(ctx->audio_stream_samplerate) { // Starting audio rate is 48000
case 48000:
break;
case 24000:
nSamples = quisk_cDecimate(cSamples, nSamples, &ctx->filtDecim48to24, 2);
break;
case 12000:
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &ctx->HalfBand1);
nSamples = quisk_cDecimate(cSamples, nSamples, &ctx->filtDecim48to24, 2);
break;
case 8000:
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &ctx->HalfBand1);
nSamples = quisk_cDecimate(cSamples, nSamples, &ctx->filtDecim3, 3);
break;
default:
nSamples = 0;
break;
}
return nSamples;
}
static int interpolate_tx_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx)
{
switch(tci_tx_audio_rate) { // Ending audio rate is 48000
case 48000:
break;
case 24000:
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx1);
break;
case 12000:
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx1);
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx2);
break;
case 8000:
nSamples = quisk_cInterpolate(cSamples, nSamples, &ctx->filtTxInterp3, 3);
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx1);
break;
default:
nSamples = 0;
break;
}
return nSamples;
}
void tci_send_audio(complex double * cSamples, int nSamples) // called from the sound thread
{
int i, n;
struct _Stream stream;
struct ClientData * ctx;
static complex double * cCopy = NULL;
static int cCopy_size = 0;
complex double * cAudio;
if (tci_clients_count <= 0)
return;
if (nSamples <= 0)
return;
pthread_mutex_lock(&clients_list_mutex);
for (n = 0; n < tci_clients_count; n++) {
ctx = ws_get_connection_context(tci_clients_list[n]);
if (ctx->send_Rx_audio_stream == 0)
continue;
memset(&stream, 0, 16 * sizeof(uint32_t));
stream.sample_rate = ctx->audio_stream_samplerate;
stream.format = ctx->audio_stream_sample_type;
stream.type = RX_AUDIO_STREAM;
stream.channels = ctx->audio_stream_channels;
int two_channels = stream.channels == 2;
int bytes_per_sample = ctx->audio_stream_bytes_per_sample;
void * vpt = &stream.data;
void * vpt_end = vpt + TCI_STREAM_DATA_BYTES;
if (ctx->audio_stream_samplerate != 48000) { // decimation must not change cSamples
if (cCopy_size < nSamples) {
if (cCopy)
free(cCopy);
cCopy_size = nSamples * 2;
cCopy = (complex double *)malloc(cCopy_size * sizeof(complex double));
}
memcpy(cCopy, cSamples, nSamples * sizeof(complex double));
nSamples = decimate_audio_stream(cCopy, nSamples, ctx);
cAudio = cCopy;
}
else {
cAudio = cSamples;
}
switch (ctx->audio_stream_sample_type) {
case TCI_FLOAT32:
stream.length = 0; // make a frame
for (i = 0; i < nSamples; i++) {
*(float *)vpt = (float)(creal(cAudio[i]) * (1.0 / 2147483648.0 / 2));
vpt += bytes_per_sample;
stream.length++;
if (two_channels) {
*(float *)vpt = (float)(cimag(cAudio[i]) * (1.0 / 2147483648.0 / 2));
vpt += bytes_per_sample;
stream.length++;
}
if (vpt >= vpt_end || i == nSamples - 1) {
sendframe_bin(tci_clients_list[n], (const char *)&stream,
(16 * sizeof(uint32_t) + stream.length * bytes_per_sample));
stream.length = 0;
vpt = &stream.data;
}
}
break;
}
}
pthread_mutex_unlock(&clients_list_mutex);
}
void tci_send_iq(complex double * cSamples, int nSamples) // called from the sound thread
{
int i, n;
struct _Stream stream;
struct ClientData * ctx;
if (tci_clients_count <= 0)
return;
if (nSamples <= 0)
return;
pthread_mutex_lock(&clients_list_mutex);
for (n = 0; n < tci_clients_count; n++) {
ctx = ws_get_connection_context(tci_clients_list[n]);
if (ctx->send_IQ_stream == 0)
continue;
memset(&stream, 0, 16 * sizeof(uint32_t));
stream.sample_rate = ctx->IQ_stream_samplerate;
stream.format = ctx->audio_stream_sample_type;
stream.type = IQ_STREAM;
stream.channels = 2;
int bytes_per_sample = ctx->audio_stream_bytes_per_sample;
void * vpt = &stream.data;
void * vpt_end = vpt + TCI_STREAM_DATA_BYTES;
int original_length = nSamples;
switch (ctx->audio_stream_sample_type) {
case TCI_FLOAT32:
stream.length = 0; // make a frame
for (i = 0; i < nSamples; i++) {
*(float *)vpt = (float)(creal(cSamples[i]) * (1.0 / 2147483648.0 / 2));
vpt += bytes_per_sample;
stream.length++;
*(float *)vpt = (float)(cimag(cSamples[i]) * (1.0 / 2147483648.0 / 2));
vpt += bytes_per_sample;
stream.length++;
if (vpt >= vpt_end || i == nSamples - 1) {
sendframe_bin(tci_clients_list[n], (const char *)&stream,
(16 * sizeof(uint32_t) + stream.length * bytes_per_sample));
original_length -= stream.length / 2;
stream.length = 0;
vpt = &stream.data;
}
}
break;
}
if (verbose && original_length != 0)
QuiskPrintf("Error in tci_send_iq: Samples remain %d\n", original_length);
}
pthread_mutex_unlock(&clients_list_mutex);
}
int tci_get_mic(complex double * cSamples, int mic_count) // called from the sound thread
{
if (tci_tx_audio_client) {
pthread_mutex_lock(&tx_buffer_mutex); // Copy samples from the Tx buffer
int count = CircularBuffer(0, cSamples, mic_count, 0);
pthread_mutex_unlock(&tx_buffer_mutex);
if (count < mic_count) {
if (verbose >= 2)
QuiskPrintf("TCI *TX replace mic %d %d\n", count, mic_count);
while (count < mic_count)
cSamples[count++] = 0;
}
// Ask for more samples
double dtime = QuiskTimeSec();
if (tci_tx_audio_samples < (dtime - tci_tx_audio_time) * tci_tx_audio_rate) {
TxChrono.length = tci_tx_audio_request;
sendframe_bin(tci_tx_audio_client, (const char *)&TxChrono, 16 * sizeof(uint32_t));
//tci_tx_audio_samples += tci_tx_audio_request;
tci_tx_audio_samples += tci_tx_audio_request / 2; // Stream.length is floats, not samples
if (verbose >= 3)
QuiskPrintf("TCI Request more Tx samples\n");
}
}
return mic_count;
}
PyObject * quisk_tci_set_params(PyObject * self, PyObject * args, PyObject * keywds) // called from the GUI thread
{ /* Call with keyword arguments ONLY.
Sent from Quisk when parameters change. Broadcast the change to clients.*/
static char * kwlist[] = {"start", "close", "verbose", "tci_dds", "tci_if", "tci_vfo", "tci_trx", "tci_split_enable",
"tci_modulation", NULL} ;
long long new_vfo=-1;
long long new_dds=-1;
int start=-1, close=-1, new_trx=-1, new_split_enable=-1;
int new_if = BAD_TCI_IF;
char * mode=NULL;
char char_buf[TCI_COMMAND_SIZE];
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiLiLiis", kwlist,
&start, &close, &verbose, &new_dds, &new_if, &new_vfo, &new_trx, &new_split_enable,
&mode))
return NULL;
if (start == 1) {
tci_started = 1;
tci_startup(); // start the TCP server for TCI
}
if (tci_started == 0) {
Py_INCREF (Py_None);
return Py_None;
}
if (close != -1) {
sendframe_txt_bcast(tci_port, "stop;");
}
if (new_dds != -1) {
quisk_dds = new_dds;
snprintf(char_buf, TCI_COMMAND_SIZE, "dds:0,%lld;", quisk_dds);
sendframe_txt_bcast(tci_port, char_buf);
}
if (new_if != BAD_TCI_IF) {
quisk_if = new_if;
snprintf(char_buf, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
sendframe_txt_bcast(tci_port, char_buf);
}
if (new_vfo != -1) {
quisk_vfo = new_vfo;
snprintf(char_buf, TCI_COMMAND_SIZE, "vfo:0,0,%lld;", quisk_vfo);
sendframe_txt_bcast(tci_port, char_buf);
}
if (new_split_enable != -1) {
quisk_split_enable = new_split_enable;
if (quisk_split_enable)
sendframe_txt_bcast(tci_port, "split_enable:0,true;");
else
sendframe_txt_bcast(tci_port, "split_enable:0,false;");
}
if (new_trx != -1) {
quisk_trx = new_trx;
if (quisk_trx) {
sendframe_txt_bcast(tci_port, "trx:0,true;");
}
else {
tci_tx_audio_client = 0;
sendframe_txt_bcast(tci_port, "trx:0,false;");
}
}
if (mode) {
if (strcmp(mode, "DGT-U") == 0)
strcpy(quisk_modulation, "digu");
else if (strcmp(mode, "DGT-L") == 0)
strcpy(quisk_modulation, "digl");
else if (strcmp(mode, "CWL") == 0 || strcmp(mode, "CWU") == 0)
strcpy(quisk_modulation, "cw");
else if (strcmp(mode, "LSB") == 0)
strcpy(quisk_modulation, "lsb");
else if (strcmp(mode, "USB") == 0)
strcpy(quisk_modulation, "usb");
else if (strcmp(mode, "AM") == 0)
strcpy(quisk_modulation, "am");
else if (strcmp(mode, "FM") == 0)
strcpy(quisk_modulation, "fm");
else
strncpy(quisk_modulation, mode, TCI_COMMAND_SIZE - 1);
snprintf(char_buf, TCI_COMMAND_SIZE, "modulation:0,%.9s", quisk_modulation);
sendframe_txt_bcast(tci_port, char_buf);
}
Py_INCREF (Py_None);
return Py_None;
}
PyObject * quisk_tci_get_params(PyObject * self, PyObject * args) // called from the GUI thread
{ /* Return client request for a parameter change */
const char * name;
int ii;
long long ll;
char char_buf[TCI_COMMAND_SIZE];
if (!PyArg_ParseTuple (args, "s", &name))
return NULL;
if (strcmp(name, "tci_clients_count") == 0)
return PyLong_FromLong(tci_clients_count);
if (tci_started == 0) {
}
else if (strcmp(name, "tci_vfo") == 0 && client_vfo >= 0) {
ll = client_vfo;
client_vfo = -1;
return PyLong_FromLongLong(ll);
}
else if (strcmp(name, "tci_dds") == 0 && client_dds >= 0) {
ll = client_dds;
client_dds = -1;
return PyLong_FromLongLong(ll);
}
else if (strcmp(name, "tci_if") == 0 && client_if != BAD_TCI_IF) {
ii = client_if;
client_if = BAD_TCI_IF;
return PyLong_FromLong(ii);
}
else if (strcmp(name, "tci_split_enable") == 0 && client_split_enable >= 0) {
ii = client_split_enable;
client_split_enable = -1;
return PyLong_FromLong(ii);
}
else if (strcmp(name, "tci_trx") == 0 && client_trx >= 0) {
ii = client_trx;
client_trx = -1;
return PyLong_FromLong(ii);
}
else if (strcmp(name, "tci_modulation") == 0 && client_modulation[0] != 0) {
char_buf[0] = 0;
if (strcmp(client_modulation, "digu") == 0)
strcpy(char_buf, "DGT-U");
else if (strcmp(client_modulation, "digl") == 0)
strcpy(char_buf, "DGT-L");
else if (strcmp(client_modulation, "cw") == 0)
strcpy(char_buf, "CWU");
else if (strcmp(client_modulation, "lsb") == 0)
strcpy(char_buf, "LSB");
else if (strcmp(client_modulation, "usb") == 0)
strcpy(char_buf, "USB");
else if (strcmp(client_modulation, "am") == 0)
strcpy(char_buf, "AM");
else if (strcmp(client_modulation, "fm") == 0)
strcpy(char_buf, "FM");
client_modulation[0] = 0;
if (char_buf[0])
return PyUnicode_FromString(char_buf);
}
Py_INCREF (Py_None);
return Py_None;
}
+89
View File
@@ -0,0 +1,89 @@
/**
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* Amazing utf8 decoder & validator grabbed from:
* http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
*
* All rights goes to the original author.
*/
#include "utf8.h"
static const uint8_t utf8d[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
};
static
uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
uint32_t type = utf8d[byte];
*codep = (*state != UTF8_ACCEPT) ?
(byte & 0x3fu) | (*codep << 6) :
(0xff >> type) & (byte);
*state = utf8d[256 + *state*16 + type];
return *state;
}
int is_utf8(uint8_t *s) {
uint32_t codepoint, state = 0;
while (*s)
decode(&state, &codepoint, *s++);
return state == UTF8_ACCEPT;
}
int is_utf8_len(uint8_t *s, size_t len) {
uint32_t codepoint, state = 0;
size_t i;
for (i = 0; i < len; i++)
decode(&state, &codepoint, *s++);
return state == UTF8_ACCEPT;
}
uint32_t is_utf8_len_state(uint8_t *s, size_t len, uint32_t state) {
uint32_t codepoint;
size_t i;
codepoint = 0;
for (i = 0; i < len; i++)
decode(&state, &codepoint, *s++);
return state;
}
+37
View File
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef UTF8_DECODE_H
#define UTF8_DECODE_H
#include <inttypes.h>
#include <stddef.h>
/* UTF8 return state. */
#define UTF8_ACCEPT 0
#define UTF8_REJECT 1
extern int is_utf8(uint8_t* s);
extern int is_utf8_len(uint8_t *s, size_t len);
extern uint32_t is_utf8_len_state(uint8_t *s, size_t len, uint32_t state);
#endif
+84 -33
View File
@@ -6,6 +6,7 @@
#include <sys/time.h>
#endif
#include <complex.h>
#include <string.h>
#include "quisk.h"
// Access to config file attributes.
@@ -71,40 +72,27 @@ double QuiskGetConfigDouble(const char * name, double deflt)
char * QuiskGetConfigString(const char * name, char * deflt)
{ // Return the UTF-8 configuration string. Return deflt for failure.
char * res;
PyObject * attr;
#if PY_MAJOR_VERSION < 3
static char retbuf[QUISK_SC_SIZE];
#endif
Py_ssize_t size;
if (!quisk_pyConfig || PyErr_Occurred())
return deflt;
attr = PyObject_GetAttrString(quisk_pyConfig, name);
if (attr) {
#if PY_MAJOR_VERSION >= 3
res = (char *)PyUnicode_AsUTF8(attr);
#else
if (PyUnicode_Check(attr)) {
PyObject * pystr = PyUnicode_AsUTF8String(attr);
strMcpy(retbuf, PyString_AsString(pystr), QUISK_SC_SIZE);
retbuf[QUISK_SC_SIZE - 1] = 0;
res = retbuf;
Py_DECREF(pystr);
}
else {
res = PyString_AsString(attr);
}
#endif
Py_DECREF(attr);
if (res)
return res; // success
else
PyErr_Clear();
}
else {
PyErr_Clear();
}
return deflt; // failure
if (!quisk_pyConfig || PyErr_Occurred())
return deflt; // failure
PyObject * attr = PyObject_GetAttrString(quisk_pyConfig, name);
if ( ! attr) {
PyErr_Clear();
return deflt; // failure
}
if ( ! PyUnicode_Check(attr)) {
Py_DECREF(attr);
return deflt; // failure
}
const char * utf8_str = PyUnicode_AsUTF8AndSize(attr, &size);
if ( ! utf8_str) {
Py_DECREF(attr);
PyErr_Clear();
return deflt; // failure
}
Py_DECREF(attr);
return (char *)utf8_str;
}
double QuiskTimeSec(void)
@@ -321,3 +309,66 @@ PyObject * QuiskPrintf(char * format, ...)
return NULL;
}
#endif
#define MAX_CHANNELS 8
int CircularBuffer(int channel, complex double * cSamples, int nRead, int nWrite)
{
// Initialize with cSamples NULL and nRead equal to the buffer delay in samples.
// Read with nRead, write with nWrite. Only one can be non-zero for read/write. If both are zero, return nBuf.
// Return the number of samples read or written.
static struct _channel {
complex double * cBuf; // circular sample buffer; save samples until the buffer fills to nDelay
int sizeBuf; // current size of cBuf in complex samples
int nDelay; // number of samples needed to start reading the buffer
int nBuf; // current number of complex samples in cBuf
int filling; // if true, we are filling the buffer and not returning samples
int Windex; // index of write
int Rindex; // index of read
} Channels[MAX_CHANNELS] = {0};
struct _channel * ptChannel = Channels + channel;
int i, num;
if ( ! cSamples) { // initialize
ptChannel->nDelay = nRead;
ptChannel->sizeBuf = nRead * 16;
ptChannel->cBuf = (complex double *)realloc(ptChannel->cBuf, ptChannel->sizeBuf * sizeof(complex double));
memset(ptChannel->cBuf, 0, ptChannel->sizeBuf * sizeof(complex double));
ptChannel->nBuf = 0;
ptChannel->Windex = 0;
ptChannel->Rindex = 0;
ptChannel->filling = 1;
return 0;
}
if ( ! ptChannel->cBuf)
return 0;
if (nRead == 0 && nWrite == 0)
return ptChannel->nBuf;
num = 0; // number of samples read or written
if (nWrite > 0) { // Copy samples from cSamples to cBuf
for (i = 0; i < nWrite && ptChannel->nBuf < ptChannel->sizeBuf; i++) {
ptChannel->cBuf[ptChannel->Windex++] = cSamples[i];
if (ptChannel->Windex >= ptChannel->sizeBuf)
ptChannel->Windex = 0;
ptChannel->nBuf++;
num++;
}
if (ptChannel->nBuf >= ptChannel->nDelay)
ptChannel->filling = 0;
//if (num != nWrite)
// QuiskPrintf("Circ nWrite %d %d %d %d\n", num, nWrite, ptChannel->nBuf, ptChannel->sizeBuf);
}
else if (nRead > 0) {
if ( ! ptChannel->filling) { // Copy samples from cBuf to cSamples
for (i = 0; i < nRead && ptChannel->nBuf > 0; i++) {
cSamples[i] = ptChannel->cBuf[ptChannel->Rindex++];
if (ptChannel->Rindex >= ptChannel->sizeBuf)
ptChannel->Rindex = 0;
ptChannel->nBuf--;
num++;
}
if (ptChannel->nBuf <= 0)
ptChannel->filling = 1;
}
}
return num;
}
+340
View File
@@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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.
GNU GENERAL PUBLIC LICENSE
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General
Public License instead of this License.
+640
View File
@@ -0,0 +1,640 @@
#
# Makefile for WDSP library
# This one creates libwdsp.so, the shared library.
#
LIBS=-lfftw3 -lpthread
#CFLAGS?= -pthread -O3 -D_GNU_SOURCE -Wno-parentheses
CFLAGS=-g -fPIC -O3 -D _GNU_SOURCE
FFTWINCLUDE=`pkg-config --cflags fftw3`
COMPILE=$(CC) $(CFLAGS) $(FFTWINCLUDE)
SOURCES= amd.c\
ammod.c\
amsq.c\
analyzer.c\
anf.c\
anr.c\
bandpass.c\
calcc.c\
calculus.c\
cblock.c\
cfcomp.c\
cfir.c\
channel.c\
compress.c\
delay.c\
dexp.c\
div.c\
eer.c\
emnr.c\
emph.c\
eq.c\
fcurve.c\
fir.c\
firmin.c\
fmd.c\
fmmod.c\
fmsq.c\
gain.c\
gen.c\
icfir.c\
iir.c\
iobuffs.c\
iqc.c\
linux_port.c\
lmath.c\
main.c\
meter.c\
meterlog10.c\
nbp.c\
nob.c\
nobII.c\
osctrl.c\
patchpanel.c\
resample.c\
rmatch.c\
RXA.c\
sender.c\
shift.c\
siphon.c\
slew.c\
snb.c\
ssql.c \
syncbuffs.c\
TXA.c\
utilities.c\
varsamp.c\
version.c\
wcpAGC.c\
wisdom.c\
zetahat.c
HEADERS=amd.h\
ammod.h\
amsq.h\
analyzer.h\
anf.h\
anr.h\
bandpass.h\
calcc.h\
calculus.h\
cblock.h\
cfcomp.h\
cfir.h\
channel.h\
comm.h\
compress.h\
delay.h\
dexp.h\
div.h\
eer.h\
emnr.h\
emph.h\
eq.h\
fastmath.h\
fcurve.h\
fir.h\
firmin.h \
fmd.h\
fmmod.h\
fmsq.h\
gain.h\
gen.h\
icfir.h\
iir.h\
iobuffs.h\
iqc.h\
linux_port.h\
lmath.h\
main.h\
meter.h\
meterlog10.h\
nbp.h\
nob.h\
nobII.h\
osctrl.h\
patchpanel.h\
resample.h\
resource.h\
rmatch.h\
RXA.h\
sender.h\
shift.h\
siphon.h\
slew.h\
snb.h\
ssql.h \
syncbuffs.h\
TXA.h\
utilities.h\
wcpAGC.h \
zetahat.h
OBJS=linux_port.o\
amd.o\
ammod.o\
amsq.o\
analyzer.o\
anf.o\
anr.o\
bandpass.o\
calcc.o\
calculus.o\
cblock.o\
cfcomp.o\
cfir.o\
channel.o\
compress.o\
delay.o\
dexp.o\
div.o\
eer.o\
emnr.o\
emph.o\
eq.o\
fcurve.o\
fir.o\
firmin.o\
fmd.o\
fmmod.o\
fmsq.o\
gain.o\
gen.o\
icfir.o\
iir.o\
iobuffs.o\
iqc.o\
lmath.o\
main.o\
meter.o\
meterlog10.o\
nbp.o\
nob.o\
nobII.o\
osctrl.o\
patchpanel.o\
resample.o\
rmatch.o\
RXA.o\
sender.o\
shift.o\
siphon.o\
slew.o\
snb.o\
ssql.o \
syncbuffs.o\
TXA.o\
utilities.o\
version.o\
varsamp.o\
wcpAGC.o\
wisdom.o\
zetahat.o
libwdsp.so: $(OBJS)
$(CC) -shared -o libwdsp.so $(OBJS) $(LIBS)
.c.o:
$(COMPILE) -c -o $@ $<
clean:
-rm -f libwdsp.so *.o
#############################################################################
#
# What follows is automatically generated by the "makedepend" program
#
#############################################################################
# DO NOT DELETE
RXA.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
RXA.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
RXA.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
RXA.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
RXA.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
RXA.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
RXA.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
TXA.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
TXA.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
TXA.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
TXA.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
TXA.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
TXA.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
TXA.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
amd.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
amd.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
amd.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
amd.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
amd.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
amd.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
amd.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
ammod.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
ammod.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
ammod.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
ammod.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
ammod.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
ammod.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
ammod.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
amsq.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
amsq.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
amsq.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
amsq.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
amsq.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
amsq.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
amsq.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
analyzer.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
analyzer.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
analyzer.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
analyzer.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
analyzer.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
analyzer.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
analyzer.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
analyzer.o: utilities.h
anf.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
anf.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
anf.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
anf.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
anf.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
anf.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
anf.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
anr.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
anr.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
anr.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
anr.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
anr.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
anr.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
anr.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
bandpass.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
bandpass.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
bandpass.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
bandpass.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
bandpass.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
bandpass.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
bandpass.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
bandpass.o: utilities.h
calcc.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
calcc.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
calcc.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
calcc.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
calcc.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
calcc.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
calcc.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
cblock.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
cblock.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
cblock.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
cblock.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
cblock.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
cblock.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
cblock.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
cfcomp.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
cfcomp.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
cfcomp.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
cfcomp.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
cfcomp.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
cfcomp.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
cfcomp.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
cfir.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
cfir.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
cfir.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
cfir.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
cfir.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
cfir.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
cfir.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
channel.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
channel.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
channel.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
channel.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
channel.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
channel.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
channel.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
channel.o: utilities.h
comm.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
comm.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
comm.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
comm.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
comm.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
comm.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
comm.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
compress.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
compress.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
compress.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
compress.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
compress.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
compress.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
compress.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
compress.o: utilities.h
delay.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
delay.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
delay.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
delay.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
delay.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
delay.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
delay.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
dexp.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
dexp.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
dexp.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
dexp.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
dexp.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
dexp.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
dexp.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
div.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
div.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
div.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
div.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
div.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
div.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
div.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
eer.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
eer.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
eer.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
eer.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
eer.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
eer.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
eer.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
emnr.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
emnr.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
emnr.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
emnr.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
emnr.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
emnr.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
emnr.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h calculus.h
emph.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
emph.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
emph.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
emph.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
emph.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
emph.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
emph.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
eq.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
eq.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
eq.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
eq.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
eq.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
eq.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
eq.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
fcurve.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
fcurve.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
fcurve.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
fcurve.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
fcurve.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
fcurve.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
fcurve.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
fir.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
fir.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
fir.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
fir.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
fir.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
fir.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
fir.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
firmin.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
firmin.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
firmin.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
firmin.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
firmin.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
firmin.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
firmin.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
fmd.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
fmd.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
fmd.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
fmd.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
fmd.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
fmd.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
fmd.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
fmmod.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
fmmod.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
fmmod.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
fmmod.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
fmmod.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
fmmod.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
fmmod.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
fmsq.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
fmsq.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
fmsq.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
fmsq.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
fmsq.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
fmsq.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
fmsq.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
gain.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
gain.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
gain.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
gain.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
gain.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
gain.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
gain.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
gen.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
gen.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
gen.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
gen.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
gen.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
gen.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
gen.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
icfir.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
icfir.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
icfir.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
icfir.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
icfir.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
icfir.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
icfir.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
iir.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
iir.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
iir.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
iir.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
iir.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
iir.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
iir.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
iobuffs.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
iobuffs.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
iobuffs.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
iobuffs.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
iobuffs.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
iobuffs.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
iobuffs.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
iobuffs.o: utilities.h
iqc.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
iqc.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
iqc.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
iqc.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
iqc.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
iqc.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
iqc.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
linux_port.o: linux_port.h comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h
linux_port.o: bandpass.h firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h
linux_port.o: cfir.h channel.h compress.h dexp.h div.h eer.h emnr.h emph.h
linux_port.o: eq.h fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h
linux_port.o: gen.h icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h
linux_port.o: nob.h nobII.h osctrl.h patchpanel.h resample.h rmatch.h
linux_port.o: varsamp.h RXA.h sender.h shift.h siphon.h slew.h snb.h ssql.h
linux_port.o: syncbuffs.h TXA.h utilities.h
lmath.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
lmath.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
lmath.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
lmath.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
lmath.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
lmath.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
lmath.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
main.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
main.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
main.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
main.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
main.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
main.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
main.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
meter.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
meter.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
meter.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
meter.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
meter.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
meter.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
meter.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
meterlog10.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
meterlog10.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
meterlog10.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
meterlog10.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
meterlog10.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
meterlog10.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h
meterlog10.o: RXA.h sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h
meterlog10.o: TXA.h utilities.h
nbp.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
nbp.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
nbp.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
nbp.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
nbp.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
nbp.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
nbp.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
nob.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
nob.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
nob.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
nob.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
nob.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
nob.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
nob.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
nobII.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
nobII.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
nobII.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
nobII.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
nobII.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
nobII.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
nobII.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
osctrl.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
osctrl.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
osctrl.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
osctrl.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
osctrl.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
osctrl.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
osctrl.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
patchpanel.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
patchpanel.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
patchpanel.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
patchpanel.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
patchpanel.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
patchpanel.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h
patchpanel.o: RXA.h sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h
patchpanel.o: TXA.h utilities.h
resample.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
resample.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
resample.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
resample.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
resample.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
resample.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
resample.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
resample.o: utilities.h
rmatch.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
rmatch.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
rmatch.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
rmatch.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
rmatch.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
rmatch.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
rmatch.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
sender.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
sender.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
sender.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
sender.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
sender.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
sender.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
sender.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
shift.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
shift.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
shift.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
shift.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
shift.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
shift.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
shift.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
siphon.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
siphon.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
siphon.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
siphon.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
siphon.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
siphon.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
siphon.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
slew.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
slew.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
slew.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
slew.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
slew.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
slew.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
slew.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
snb.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h firmin.h
snb.o: calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h compress.h
snb.o: dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h iir.h
snb.o: wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h main.h
snb.o: meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
snb.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
snb.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
ssql.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
ssql.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
ssql.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h fmd.h
ssql.o: iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h iqc.h
ssql.o: main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h patchpanel.h
ssql.o: resample.h rmatch.h varsamp.h RXA.h sender.h shift.h siphon.h slew.h
ssql.o: snb.h ssql.h syncbuffs.h TXA.h utilities.h
syncbuffs.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
syncbuffs.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
syncbuffs.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
syncbuffs.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
syncbuffs.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
syncbuffs.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h
syncbuffs.o: RXA.h sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h
syncbuffs.o: TXA.h utilities.h
utilities.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
utilities.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
utilities.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
utilities.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
utilities.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
utilities.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h
utilities.o: RXA.h sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h
utilities.o: TXA.h utilities.h
varsamp.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
varsamp.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
varsamp.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
varsamp.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
varsamp.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
varsamp.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
varsamp.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
varsamp.o: utilities.h
version.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
version.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h
version.o: channel.h compress.h dexp.h div.h eer.h emnr.h emph.h eq.h
version.o: fcurve.h fir.h fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h
version.o: icfir.h iobuffs.h iqc.h main.h meter.h meterlog10.h nbp.h nob.h
version.o: nobII.h osctrl.h patchpanel.h resample.h rmatch.h varsamp.h RXA.h
version.o: sender.h shift.h siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h
version.o: utilities.h
wcpAGC.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
wcpAGC.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
wcpAGC.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
wcpAGC.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
wcpAGC.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
wcpAGC.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
wcpAGC.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
wisdom.o: comm.h amd.h ammod.h amsq.h analyzer.h anf.h anr.h bandpass.h
wisdom.o: firmin.h calcc.h delay.h lmath.h cblock.h cfcomp.h cfir.h channel.h
wisdom.o: compress.h dexp.h div.h eer.h emnr.h emph.h eq.h fcurve.h fir.h
wisdom.o: fmd.h iir.h wcpAGC.h fmmod.h fmsq.h gain.h gen.h icfir.h iobuffs.h
wisdom.o: iqc.h main.h meter.h meterlog10.h nbp.h nob.h nobII.h osctrl.h
wisdom.o: patchpanel.h resample.h rmatch.h varsamp.h RXA.h sender.h shift.h
wisdom.o: siphon.h slew.h snb.h ssql.h syncbuffs.h TXA.h utilities.h
+4
View File
@@ -0,0 +1,4 @@
# wdsp
WDSP by Warren Pratt, NR0V
DSP Library originally written for Windows
Ported to Linux and Android by John Melton g0orx/n6lyt
+7
View File
@@ -0,0 +1,7 @@
To use this Linux library with Quisk, enter "make". It supports the NR2 and SNB features.
The resulting libwdsp.so file is used in-place. No installation is necessary.
For Windows, the libwdsp.dll file is included in the quisk directory.
The WDSP library was originally written for Windows by Warren Pratt, NR0V.
John Melton, g0orx/n6lyt, contributed a Linux port.
Christian, DL1YCF, contributed further improvements.
+958
View File
@@ -0,0 +1,958 @@
/* RXA.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2014, 2015, 2016, 2023 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
struct _rxa rxa[MAX_CHANNELS];
void create_rxa (int channel)
{
rxa[channel].mode = RXA_LSB;
rxa[channel].inbuff = (double *) malloc0 (1 * ch[channel].dsp_insize * sizeof (complex));
rxa[channel].outbuff = (double *) malloc0 (1 * ch[channel].dsp_outsize * sizeof (complex));
rxa[channel].midbuff = (double *) malloc0 (2 * ch[channel].dsp_size * sizeof (complex));
// shift to select a slice of spectrum
rxa[channel].shift.p = create_shift (
1, // run
ch[channel].dsp_insize, // input buffer size
rxa[channel].inbuff, // pointer to input buffer
rxa[channel].inbuff, // pointer to output buffer
ch[channel].in_rate, // samplerate
0.0); // amount to shift (Hz)
// resample to dsp rate for main processing
rxa[channel].rsmpin.p = create_resample (
0, // run - will be turned ON below if needed
ch[channel].dsp_insize, // input buffer size
rxa[channel].inbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ch[channel].in_rate, // input samplerate
ch[channel].dsp_rate, // output samplerate
0.0, // select cutoff automatically
0, // select ncoef automatically
1.0); // gain
// signal generator
rxa[channel].gen0.p = create_gen (
0, // run
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // input buffer
rxa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // sample rate
2); // mode
// adc (input) meter
rxa[channel].adcmeter.p = create_meter (
1, // run
0, // optional pointer to another 'run'
ch[channel].dsp_size, // size
rxa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
rxa[channel].meter, // result vector
rxa[channel].pmtupdate, // locks for meter access
RXA_ADC_AV, // index for average value
RXA_ADC_PK, // index for peak value
-1, // index for gain value
0); // pointer for gain computation
// notch database
rxa[channel].ndb.p = create_notchdb (
0, // master run for all nbp's
1024); // max number of notches
// notched bandpass
rxa[channel].nbp0.p = create_nbp (
1, // run, always runs
0, // run the notches
0, // position
ch[channel].dsp_size, // buffer size
max(2048, ch[channel].dsp_size), // number of coefficients
0, // minimum phase flag
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
-4150.0, // lower filter frequency
-150.0, // upper filter frequency
ch[channel].dsp_rate, // sample rate
0, // wintype
1.0, // gain
1, // auto-increase notch width
1025, // max number of passbands
&rxa[channel].ndb.p); // addr of database pointer
// bandpass for snba
rxa[channel].bpsnba.p = create_bpsnba (
0, // bpsnba run flag
0, // run the notches
0, // position
ch[channel].dsp_size, // size
max(2048, ch[channel].dsp_size), // number of filter coefficients
0, // minimum phase flag
rxa[channel].midbuff, // input buffer
rxa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // samplerate
+ 250.0, // abs value of cutoff nearest zero
+ 5700.0, // abs value of cutoff farthest zero
- 5700.0, // current low frequency
- 250.0, // current high frequency
0, // wintype
1.0, // gain
1, // auto-increase notch width
1025, // max number of passbands
&rxa[channel].ndb.p); // addr of database pointer
// send spectrum display
rxa[channel].sender.p = create_sender (
channel == 0, // run
0, // flag
0, // mode
ch[channel].dsp_size, // size
rxa[channel].midbuff, // pointer to input buffer
0, // arg0 <- disp
1, // arg1 <- ss
0, // arg2 <- LO
0); // arg3 <- NOT USED
// S-meter
rxa[channel].smeter.p = create_meter (
1, // run
0, // optional pointer to another 'run'
ch[channel].dsp_size, // size
rxa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
rxa[channel].meter, // result vector
rxa[channel].pmtupdate, // locks for meter access
RXA_S_AV, // index for average value
RXA_S_PK, // index for peak value
-1, // index for gain value
0); // pointer for gain computation
// AM squelch
rxa[channel].amsq.p = create_amsq (
0, // run
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to signal input buffer used by xamsq
rxa[channel].midbuff, // pointer to signal output buffer used by xamsq
rxa[channel].midbuff, // pointer to trigger buffer that xamsqcap will capture
ch[channel].dsp_rate, // sample rate
0.010, // time constant for averaging signal level
0.070, // signal up transition time
0.070, // signal down transition time
0.009, // signal level to initiate tail
0.010, // signal level to initiate unmute
0.000, // minimum tail length
1.500, // maximum tail length
0.0); // muted gain
// AM demod
rxa[channel].amd.p = create_amd (
0, // run - OFF by default
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
0, // mode: 0->AM, 1->SAM
1, // levelfade: 0->OFF, 1->ON
0, // sideband mode: 0->OFF
ch[channel].dsp_rate, // sample rate
-2000.0, // minimum lock frequency
+2000.0, // maximum lock frequency
1.0, // zeta
250.0, // omegaN
0.02, // tauR
1.4); // tauI
// FM demod
rxa[channel].fmd.p = create_fmd (
0, // run
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ch[channel].dsp_rate, // sample rate
5000.0, // deviation
300.0, // f_low
3000.0, // f_high
-8000.0, // fmin
+8000.0, // fmax
1.0, // zeta
20000.0, // omegaN
0.02, // tau - for dc removal
0.5, // audio gain
1, // run tone filter
254.1, // ctcss frequency
max(2048, ch[channel].dsp_size), // # coefs for de-emphasis filter
0, // min phase flag for de-emphasis filter
max(2048, ch[channel].dsp_size), // # coefs for audio cutoff filter
0); // min phase flag for audio cutoff filter
// FM squelch
rxa[channel].fmsq.p = create_fmsq (
0, // run
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to input signal buffer
rxa[channel].midbuff, // pointer to output signal buffer
rxa[channel].fmd.p->audio, // pointer to trigger buffer
ch[channel].dsp_rate, // sample rate
5000.0, // cutoff freq for noise filter (Hz)
&rxa[channel].fmd.p->pllpole, // pointer to pole frequency of the fmd pll (Hz)
0.100, // delay time after channel flush
0.001, // tau for noise averaging
0.100, // tau for long noise averaging
0.050, // signal up transition time
0.010, // signal down transition time
0.750, // noise level to initiate tail
0.562, // noise level to initiate unmute
0.000, // minimum tail time
1.200, // maximum tail time
max(2048, ch[channel].dsp_size), // number of coefficients for noise filter
0); // minimum phase flag
// snba
rxa[channel].snba.p = create_snba (
0, // run
rxa[channel].midbuff, // input buffer
rxa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // input / output sample rate
12000, // internal processing sample rate
ch[channel].dsp_size, // buffer size
4, // overlap factor to use
256, // frame size to use; sized for 12K rate
64, // asize
2, // npasses
8.0, // k1
20.0, // k2
10, // b
2, // pre
2, // post
0.5, // pmultmin
200.0, // output resampler low cutoff
5400.0); // output resampler high cutoff
// EQ
{
double default_F[11] = {0.0, 32.0, 63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0, 16000.0};
//double default_G[11] = {0.0, -12.0, -12.0, -12.0, -1.0, +1.0, +4.0, +9.0, +12.0, -10.0, -10.0};
double default_G[11] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
rxa[channel].eqp.p = create_eqp (
0, // run - OFF by default
ch[channel].dsp_size, // buffer size
max(2048, ch[channel].dsp_size), // number of filter coefficients
0, // minimum phase flag
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
10, // number of frequencies
default_F, // frequency vector
default_G, // gain vector
0, // cutoff mode
0, // wintype
ch[channel].dsp_rate); // sample rate
}
// ANF
rxa[channel].anf.p = create_anf (
0, // run - OFF by default
0, // position
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ANF_DLINE_SIZE, // dline_size
64, // taps
16, // delay
0.0001, // two_mu
0.1, // gamma
1.0, // lidx
0.0, // lidx_min
200.0, // lidx_max
6.25e-12, // ngamma
6.25e-10, // den_mult
1.0, // lincr
3.0); // ldecr
// ANR
rxa[channel].anr.p = create_anr (
0, // run - OFF by default
0, // position
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ANR_DLINE_SIZE, // dline_size
64, // taps
16, // delay
0.0001, // two_mu
0.1, // gamma
120.0, // lidx
120.0, // lidx_min
200.0, // lidx_max
0.001, // ngamma
6.25e-10, // den_mult
1.0, // lincr
3.0); // ldecr
// EMNR
rxa[channel].emnr.p = create_emnr (
0, // run
0, // position
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // input buffer
rxa[channel].midbuff, // output buffer
4096, // FFT size
4, // overlap
ch[channel].dsp_rate, // samplerate
0, // window type
1.0, // gain
2, // gain method
0, // npe_method
1); // ae_run
// AGC
rxa[channel].agc.p = create_wcpagc (
1, // run
3, // mode
1, // peakmode = envelope
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ch[channel].dsp_size, // buffer size
ch[channel].dsp_rate, // sample rate
0.001, // tau_attack
0.250, // tau_decay
4, // n_tau
10000.0, // max_gain
1.5, // var_gain
1000.0, // fixed_gain
1.0, // max_input
1.0, // out_target
0.250, // tau_fast_backaverage
0.005, // tau_fast_decay
5.0, // pop_ratio
1, // hang_enable
0.500, // tau_hang_backmult
0.250, // hangtime
0.250, // hang_thresh
0.100); // tau_hang_decay
// agc gain meter
rxa[channel].agcmeter.p = create_meter (
1, // run
0, // optional pointer to another 'run'
ch[channel].dsp_size, // size
rxa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
rxa[channel].meter, // result vector
rxa[channel].pmtupdate, // locks for meter access
RXA_AGC_AV, // index for average value
RXA_AGC_PK, // index for peak value
RXA_AGC_GAIN, // index for gain value
&rxa[channel].agc.p->gain); // pointer for gain computation
// bandpass filter
rxa[channel].bp1.p = create_bandpass (
1, // run - used only with ( AM || ANF || ANR || EMNR)
0, // position
ch[channel].dsp_size, // buffer size
max(2048, ch[channel].dsp_size), // number of coefficients
0, // flag for minimum phase
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
-4150.0, // lower filter frequency
-150.0, // upper filter frequency
ch[channel].dsp_rate, // sample rate
1, // wintype
1.0); // gain
// pull phase & scope display data
rxa[channel].sip1.p = create_siphon (
1, // run - needed only for phase display
0, // position
0, // mode
0, // disp
ch[channel].dsp_size, // size of input buffer
rxa[channel].midbuff, // input buffer
4096, // number of samples to store
4096, // fft size for spectrum
0); // specmode
// carrier block
rxa[channel].cbl.p = create_cbl (
0, // run - needed only if set to ON
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
0, // mode
ch[channel].dsp_rate, // sample rate
0.02); // tau
// peaking filter
rxa[channel].speak.p = create_speak (
0, // run
ch[channel].dsp_size, // buffer size,
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ch[channel].dsp_rate, // sample rate
600.0, // center frequency
100.0, // bandwidth
2.0, // gain
4, // number of stages
1); // design
// multiple peak filter
{
int def_enable[2] = {1, 1};
double def_freq[2] = {2125.0, 2295.0};
double def_bw[2] = {75.0, 75.0};
double def_gain[2] = {1.0, 1.0};
rxa[channel].mpeak.p = create_mpeak (
0, // run
ch[channel].dsp_size, // size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ch[channel].dsp_rate, // sample rate
2, // number of peaking filters
def_enable, // enable vector
def_freq, // frequency vector
def_bw, // bandwidth vector
def_gain, // gain vector
4 ); // number of stages
}
// syllabic squelch
rxa[channel].ssql.p = create_ssql(
0, // run
ch[channel].dsp_size, // size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ch[channel].dsp_rate, // sample rate
0.070, // signal up transition time
0.070, // signal down transition time
0.0, // muted gain
0.1, // mute time-constant
0.1, // unmute time-constant
0.08, // window threshold
0.8197, // trigger threshold
2400, // ring size for f_to_v converter
2000.0); // max freq for f_to_v converter
// patchpanel
rxa[channel].panel.p = create_panel (
channel, // channel number
1, // run
ch[channel].dsp_size, // size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
4.0, // gain1
1.0, // gain2I
1.0, // gain2Q
3, // 3 for I and Q
0); // no copy
// resample
rxa[channel].rsmpout.p = create_resample (
0, // run - will be turned ON below if needed
ch[channel].dsp_size, // input buffer size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].outbuff, // pointer to output buffer
ch[channel].dsp_rate, // input sample rate
ch[channel].out_rate, // output sample rate
0.0, // select cutoff automatically
0, // select ncoef automatically
1.0); // gain
// turn OFF / ON resamplers as needed
RXAResCheck (channel);
}
void destroy_rxa (int channel)
{
destroy_resample (rxa[channel].rsmpout.p);
destroy_panel (rxa[channel].panel.p);
destroy_ssql (rxa[channel].ssql.p);
destroy_mpeak (rxa[channel].mpeak.p);
destroy_speak (rxa[channel].speak.p);
destroy_cbl (rxa[channel].cbl.p);
destroy_siphon (rxa[channel].sip1.p);
destroy_bandpass (rxa[channel].bp1.p);
destroy_meter (rxa[channel].agcmeter.p);
destroy_wcpagc (rxa[channel].agc.p);
destroy_emnr (rxa[channel].emnr.p);
destroy_anr (rxa[channel].anr.p);
destroy_anf (rxa[channel].anf.p);
destroy_eqp (rxa[channel].eqp.p);
destroy_snba (rxa[channel].snba.p);
destroy_fmsq (rxa[channel].fmsq.p);
destroy_fmd (rxa[channel].fmd.p);
destroy_amd (rxa[channel].amd.p);
destroy_amsq (rxa[channel].amsq.p);
destroy_meter (rxa[channel].smeter.p);
destroy_sender (rxa[channel].sender.p);
destroy_bpsnba (rxa[channel].bpsnba.p);
destroy_nbp (rxa[channel].nbp0.p);
destroy_notchdb (rxa[channel].ndb.p);
destroy_meter (rxa[channel].adcmeter.p);
destroy_gen (rxa[channel].gen0.p);
destroy_resample (rxa[channel].rsmpin.p);
destroy_shift (rxa[channel].shift.p);
_aligned_free (rxa[channel].midbuff);
_aligned_free (rxa[channel].outbuff);
_aligned_free (rxa[channel].inbuff);
}
void flush_rxa (int channel)
{
memset (rxa[channel].inbuff, 0, 1 * ch[channel].dsp_insize * sizeof (complex));
memset (rxa[channel].outbuff, 0, 1 * ch[channel].dsp_outsize * sizeof (complex));
memset (rxa[channel].midbuff, 0, 2 * ch[channel].dsp_size * sizeof (complex));
flush_shift (rxa[channel].shift.p);
flush_resample (rxa[channel].rsmpin.p);
flush_gen (rxa[channel].gen0.p);
flush_meter (rxa[channel].adcmeter.p);
flush_nbp (rxa[channel].nbp0.p);
flush_bpsnba (rxa[channel].bpsnba.p);
flush_sender (rxa[channel].sender.p);
flush_meter (rxa[channel].smeter.p);
flush_amsq (rxa[channel].amsq.p);
flush_amd (rxa[channel].amd.p);
flush_fmd (rxa[channel].fmd.p);
flush_fmsq (rxa[channel].fmsq.p);
flush_snba (rxa[channel].snba.p);
flush_eqp (rxa[channel].eqp.p);
flush_anf (rxa[channel].anf.p);
flush_anr (rxa[channel].anr.p);
flush_emnr (rxa[channel].emnr.p);
flush_wcpagc (rxa[channel].agc.p);
flush_meter (rxa[channel].agcmeter.p);
flush_bandpass (rxa[channel].bp1.p);
flush_siphon (rxa[channel].sip1.p);
flush_cbl (rxa[channel].cbl.p);
flush_speak (rxa[channel].speak.p);
flush_mpeak (rxa[channel].mpeak.p);
flush_ssql (rxa[channel].ssql.p);
flush_panel (rxa[channel].panel.p);
flush_resample (rxa[channel].rsmpout.p);
}
void xrxa (int channel)
{
xshift (rxa[channel].shift.p);
xresample (rxa[channel].rsmpin.p);
xgen (rxa[channel].gen0.p);
xmeter (rxa[channel].adcmeter.p);
xbpsnbain (rxa[channel].bpsnba.p, 0);
xnbp (rxa[channel].nbp0.p, 0);
xmeter (rxa[channel].smeter.p);
xsender (rxa[channel].sender.p);
xamsqcap (rxa[channel].amsq.p);
xbpsnbaout (rxa[channel].bpsnba.p, 0);
xamd (rxa[channel].amd.p);
xfmd (rxa[channel].fmd.p);
xfmsq (rxa[channel].fmsq.p);
xbpsnbain (rxa[channel].bpsnba.p, 1);
xbpsnbaout (rxa[channel].bpsnba.p, 1);
xsnba (rxa[channel].snba.p);
xeqp (rxa[channel].eqp.p);
xanf (rxa[channel].anf.p, 0);
xanr (rxa[channel].anr.p, 0);
xemnr (rxa[channel].emnr.p, 0);
xbandpass (rxa[channel].bp1.p, 0);
xwcpagc (rxa[channel].agc.p);
xanf (rxa[channel].anf.p, 1);
xanr (rxa[channel].anr.p, 1);
xemnr (rxa[channel].emnr.p, 1);
xbandpass (rxa[channel].bp1.p, 1);
xmeter (rxa[channel].agcmeter.p);
xsiphon (rxa[channel].sip1.p, 0);
xcbl (rxa[channel].cbl.p);
xspeak (rxa[channel].speak.p);
xmpeak (rxa[channel].mpeak.p);
xssql (rxa[channel].ssql.p);
xpanel (rxa[channel].panel.p);
xamsq (rxa[channel].amsq.p);
xresample (rxa[channel].rsmpout.p);
}
void setInputSamplerate_rxa (int channel)
{
// buffers
_aligned_free (rxa[channel].inbuff);
rxa[channel].inbuff = (double *)malloc0(1 * ch[channel].dsp_insize * sizeof(complex));
// shift
setBuffers_shift (rxa[channel].shift.p, rxa[channel].inbuff, rxa[channel].inbuff);
setSize_shift (rxa[channel].shift.p, ch[channel].dsp_insize);
setSamplerate_shift (rxa[channel].shift.p, ch[channel].in_rate);
// input resampler
setBuffers_resample (rxa[channel].rsmpin.p, rxa[channel].inbuff, rxa[channel].midbuff);
setSize_resample (rxa[channel].rsmpin.p, ch[channel].dsp_insize);
setInRate_resample (rxa[channel].rsmpin.p, ch[channel].in_rate);
RXAResCheck (channel);
}
void setOutputSamplerate_rxa (int channel)
{
// buffers
_aligned_free (rxa[channel].outbuff);
rxa[channel].outbuff = (double *)malloc0(1 * ch[channel].dsp_outsize * sizeof(complex));
// output resampler
setBuffers_resample (rxa[channel].rsmpout.p, rxa[channel].midbuff, rxa[channel].outbuff);
setOutRate_resample (rxa[channel].rsmpout.p, ch[channel].out_rate);
RXAResCheck (channel);
}
void setDSPSamplerate_rxa (int channel)
{
// buffers
_aligned_free (rxa[channel].inbuff);
rxa[channel].inbuff = (double *)malloc0(1 * ch[channel].dsp_insize * sizeof(complex));
_aligned_free (rxa[channel].outbuff);
rxa[channel].outbuff = (double *)malloc0(1 * ch[channel].dsp_outsize * sizeof(complex));
// shift
setBuffers_shift (rxa[channel].shift.p, rxa[channel].inbuff, rxa[channel].inbuff);
setSize_shift (rxa[channel].shift.p, ch[channel].dsp_insize);
// input resampler
setBuffers_resample (rxa[channel].rsmpin.p, rxa[channel].inbuff, rxa[channel].midbuff);
setSize_resample (rxa[channel].rsmpin.p, ch[channel].dsp_insize);
setOutRate_resample (rxa[channel].rsmpin.p, ch[channel].dsp_rate);
// dsp_rate blocks
setSamplerate_gen (rxa[channel].gen0.p, ch[channel].dsp_rate);
setSamplerate_meter (rxa[channel].adcmeter.p, ch[channel].dsp_rate);
setSamplerate_nbp (rxa[channel].nbp0.p, ch[channel].dsp_rate);
setSamplerate_bpsnba (rxa[channel].bpsnba.p, ch[channel].dsp_rate);
setSamplerate_meter (rxa[channel].smeter.p, ch[channel].dsp_rate);
setSamplerate_sender (rxa[channel].sender.p, ch[channel].dsp_rate);
setSamplerate_amsq (rxa[channel].amsq.p, ch[channel].dsp_rate);
setSamplerate_amd (rxa[channel].amd.p, ch[channel].dsp_rate);
setSamplerate_fmd (rxa[channel].fmd.p, ch[channel].dsp_rate);
setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio);
setSamplerate_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_rate);
setSamplerate_snba (rxa[channel].snba.p, ch[channel].dsp_rate);
setSamplerate_eqp (rxa[channel].eqp.p, ch[channel].dsp_rate);
setSamplerate_anf (rxa[channel].anf.p, ch[channel].dsp_rate);
setSamplerate_anr (rxa[channel].anr.p, ch[channel].dsp_rate);
setSamplerate_emnr (rxa[channel].emnr.p, ch[channel].dsp_rate);
setSamplerate_bandpass (rxa[channel].bp1.p, ch[channel].dsp_rate);
setSamplerate_wcpagc (rxa[channel].agc.p, ch[channel].dsp_rate);
setSamplerate_meter (rxa[channel].agcmeter.p, ch[channel].dsp_rate);
setSamplerate_siphon (rxa[channel].sip1.p, ch[channel].dsp_rate);
setSamplerate_cbl (rxa[channel].cbl.p, ch[channel].dsp_rate);
setSamplerate_speak (rxa[channel].speak.p, ch[channel].dsp_rate);
setSamplerate_mpeak (rxa[channel].mpeak.p, ch[channel].dsp_rate);
setSamplerate_ssql (rxa[channel].ssql.p, ch[channel].dsp_rate);
setSamplerate_panel (rxa[channel].panel.p, ch[channel].dsp_rate);
// output resampler
setBuffers_resample (rxa[channel].rsmpout.p, rxa[channel].midbuff, rxa[channel].outbuff);
setInRate_resample (rxa[channel].rsmpout.p, ch[channel].dsp_rate);
RXAResCheck (channel);
}
void setDSPBuffsize_rxa (int channel)
{
// buffers
_aligned_free(rxa[channel].inbuff);
rxa[channel].inbuff = (double *)malloc0(1 * ch[channel].dsp_insize * sizeof(complex));
_aligned_free (rxa[channel].midbuff);
rxa[channel].midbuff = (double *)malloc0(2 * ch[channel].dsp_size * sizeof(complex));
_aligned_free (rxa[channel].outbuff);
rxa[channel].outbuff = (double *)malloc0(1 * ch[channel].dsp_outsize * sizeof(complex));
// shift
setBuffers_shift (rxa[channel].shift.p, rxa[channel].inbuff, rxa[channel].inbuff);
setSize_shift (rxa[channel].shift.p, ch[channel].dsp_insize);
// input resampler
setBuffers_resample (rxa[channel].rsmpin.p, rxa[channel].inbuff, rxa[channel].midbuff);
setSize_resample (rxa[channel].rsmpin.p, ch[channel].dsp_insize);
// dsp_size blocks
setBuffers_gen (rxa[channel].gen0.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_gen (rxa[channel].gen0.p, ch[channel].dsp_size);
setBuffers_meter (rxa[channel].adcmeter.p, rxa[channel].midbuff);
setSize_meter (rxa[channel].adcmeter.p, ch[channel].dsp_size);
setBuffers_nbp (rxa[channel].nbp0.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_nbp (rxa[channel].nbp0.p, ch[channel].dsp_size);
setBuffers_bpsnba (rxa[channel].bpsnba.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_bpsnba (rxa[channel].bpsnba.p, ch[channel].dsp_size);
setBuffers_meter (rxa[channel].smeter.p, rxa[channel].midbuff);
setSize_meter (rxa[channel].smeter.p, ch[channel].dsp_size);
setBuffers_sender (rxa[channel].sender.p, rxa[channel].midbuff);
setSize_sender (rxa[channel].sender.p, ch[channel].dsp_size);
setBuffers_amsq (rxa[channel].amsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_amsq (rxa[channel].amsq.p, ch[channel].dsp_size);
setBuffers_amd (rxa[channel].amd.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_amd (rxa[channel].amd.p, ch[channel].dsp_size);
setBuffers_fmd (rxa[channel].fmd.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_fmd (rxa[channel].fmd.p, ch[channel].dsp_size);
setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio);
setSize_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_size);
setBuffers_snba (rxa[channel].snba.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_snba (rxa[channel].snba.p, ch[channel].dsp_size);
setBuffers_eqp (rxa[channel].eqp.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_eqp (rxa[channel].eqp.p, ch[channel].dsp_size);
setBuffers_anf (rxa[channel].anf.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_anf (rxa[channel].anf.p, ch[channel].dsp_size);
setBuffers_anr (rxa[channel].anr.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_anr (rxa[channel].anr.p, ch[channel].dsp_size);
setBuffers_emnr (rxa[channel].emnr.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_emnr (rxa[channel].emnr.p, ch[channel].dsp_size);
setBuffers_bandpass (rxa[channel].bp1.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_bandpass (rxa[channel].bp1.p, ch[channel].dsp_size);
setBuffers_wcpagc (rxa[channel].agc.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_wcpagc (rxa[channel].agc.p, ch[channel].dsp_size);
setBuffers_meter (rxa[channel].agcmeter.p, rxa[channel].midbuff);
setSize_meter (rxa[channel].agcmeter.p, ch[channel].dsp_size);
setBuffers_siphon (rxa[channel].sip1.p, rxa[channel].midbuff);
setSize_siphon (rxa[channel].sip1.p, ch[channel].dsp_size);
setBuffers_cbl (rxa[channel].cbl.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_cbl (rxa[channel].cbl.p, ch[channel].dsp_size);
setBuffers_speak (rxa[channel].speak.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_speak (rxa[channel].speak.p, ch[channel].dsp_size);
setBuffers_mpeak (rxa[channel].mpeak.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_mpeak (rxa[channel].mpeak.p, ch[channel].dsp_size);
setBuffers_ssql (rxa[channel].ssql.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_ssql (rxa[channel].ssql.p, ch[channel].dsp_size);
setBuffers_panel (rxa[channel].panel.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_panel (rxa[channel].panel.p, ch[channel].dsp_size);
// output resampler
setBuffers_resample (rxa[channel].rsmpout.p, rxa[channel].midbuff, rxa[channel].outbuff);
setSize_resample (rxa[channel].rsmpout.p, ch[channel].dsp_size);
}
/********************************************************************************************************
* *
* RXA Mode & Filter Controls *
* *
********************************************************************************************************/
PORT
void SetRXAMode (int channel, int mode)
{
if (rxa[channel].mode != mode)
{
int amd_run = (mode == RXA_AM) || (mode == RXA_SAM);
RXAbpsnbaCheck (channel, mode, rxa[channel].ndb.p->master_run);
RXAbp1Check (channel, amd_run, rxa[channel].snba.p->run, rxa[channel].emnr.p->run,
rxa[channel].anf.p->run, rxa[channel].anr.p->run);
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].mode = mode;
rxa[channel].amd.p->run = 0;
rxa[channel].fmd.p->run = 0;
rxa[channel].agc.p->run = 1;
switch (mode)
{
case RXA_AM:
rxa[channel].amd.p->run = 1;
rxa[channel].amd.p->mode = 0;
break;
case RXA_SAM:
rxa[channel].amd.p->run = 1;
rxa[channel].amd.p->mode = 1;
break;
case RXA_DSB:
break;
case RXA_FM:
rxa[channel].fmd.p->run = 1;
rxa[channel].agc.p->run = 0;
break;
default:
break;
}
RXAbp1Set (channel);
RXAbpsnbaSet (channel); // update variables
LeaveCriticalSection (&ch[channel].csDSP);
}
}
void RXAResCheck (int channel)
{
// turn OFF/ON resamplers depending upon whether they're needed
RESAMPLE a = rxa[channel].rsmpin.p;
if (ch[channel].in_rate != ch[channel].dsp_rate) a->run = 1;
else a->run = 0;
a = rxa[channel].rsmpout.p;
if (ch[channel].dsp_rate != ch[channel].out_rate) a->run = 1;
else a->run = 0;
}
void RXAbp1Check (int channel, int amd_run, int snba_run,
int emnr_run, int anf_run, int anr_run)
{
BANDPASS a = rxa[channel].bp1.p;
double gain;
if (amd_run ||
snba_run ||
emnr_run ||
anf_run ||
anr_run) gain = 2.0;
else gain = 1.0;
if (a->gain != gain)
setGain_bandpass (a, gain, 0);
}
void RXAbp1Set (int channel)
{
BANDPASS a = rxa[channel].bp1.p;
int old = a->run;
if ((rxa[channel].amd.p->run == 1) ||
(rxa[channel].snba.p->run == 1) ||
(rxa[channel].emnr.p->run == 1) ||
(rxa[channel].anf.p->run == 1) ||
(rxa[channel].anr.p->run == 1)) a->run = 1;
else a->run = 0;
if (!old && a->run) flush_bandpass (a);
setUpdate_fircore (a->p);
}
void RXAbpsnbaCheck (int channel, int mode, int notch_run)
{
// for BPSNBA: set run, position, freqs, run_notches
// call this upon change in RXA_mode, snba_run, notch_master_run
BPSNBA a = rxa[channel].bpsnba.p;
double f_low = 0.0, f_high = 0.0;
int run_notches = 0;
switch (mode)
{
case RXA_LSB:
case RXA_CWL:
case RXA_DIGL:
f_low = -a->abs_high_freq;
f_high = -a->abs_low_freq;
run_notches = notch_run;
break;
case RXA_USB:
case RXA_CWU:
case RXA_DIGU:
f_low = +a->abs_low_freq;
f_high = +a->abs_high_freq;
run_notches = notch_run;
break;
case RXA_AM:
case RXA_SAM:
case RXA_DSB:
f_low = +a->abs_low_freq;
f_high = +a->abs_high_freq;
run_notches = 0;
break;
case RXA_FM:
f_low = +a->abs_low_freq;
f_high = +a->abs_high_freq;
run_notches = 0;
break;
case RXA_DRM:
case RXA_SPEC:
break;
}
// 'run' and 'position' are examined at run time; no filter changes required.
// Recalculate filter if frequencies OR 'run_notches' changed.
if ((a->f_low != f_low ) ||
(a->f_high != f_high ) ||
(a->run_notches != run_notches))
{
a->f_low = f_low;
a->f_high = f_high;
a->run_notches = run_notches;
// f_low, f_high, run_notches are needed for the filter recalculation
recalc_bpsnba_filter (a, 0);
}
}
void RXAbpsnbaSet (int channel)
{
// for BPSNBA: set run, position, freqs, run_notches
// call this upon change in RXA_mode, snba_run, notch_master_run
BPSNBA a = rxa[channel].bpsnba.p;
switch (rxa[channel].mode)
{
case RXA_LSB:
case RXA_CWL:
case RXA_DIGL:
a->run = rxa[channel].snba.p->run;
a->position = 0;
break;
case RXA_USB:
case RXA_CWU:
case RXA_DIGU:
a->run = rxa[channel].snba.p->run;
a->position = 0;
break;
case RXA_AM:
case RXA_SAM:
case RXA_DSB:
a->run = rxa[channel].snba.p->run;
a->position = 1;
break;
case RXA_FM:
a->run = rxa[channel].snba.p->run;
a->position = 1;
break;
case RXA_DRM:
case RXA_SPEC:
a->run = 0;
break;
}
setUpdate_fircore (a->bpsnba->p);
}
/********************************************************************************************************
* *
* Collectives *
* *
********************************************************************************************************/
PORT
void RXASetPassband (int channel, double f_low, double f_high)
{
SetRXABandpassFreqs (channel, f_low, f_high);
SetRXASNBAOutputBandwidth (channel, f_low, f_high);
RXANBPSetFreqs (channel, f_low, f_high);
}
PORT
void RXASetNC (int channel, int nc)
{
int oldstate = SetChannelState (channel, 0, 1);
RXANBPSetNC (channel, nc);
RXABPSNBASetNC (channel, nc);
SetRXABandpassNC (channel, nc);
SetRXAEQNC (channel, nc);
SetRXAFMSQNC (channel, nc);
SetRXAFMNCde (channel, nc);
SetRXAFMNCaud (channel, nc);
SetChannelState (channel, oldstate, 0);
}
PORT
void RXASetMP (int channel, int mp)
{
RXANBPSetMP (channel, mp);
RXABPSNBASetMP (channel, mp);
SetRXABandpassMP (channel, mp);
SetRXAEQMP (channel, mp);
SetRXAFMSQMP (channel, mp);
SetRXAFMMPde (channel, mp);
SetRXAFMMPaud (channel, mp);
}
+201
View File
@@ -0,0 +1,201 @@
/* RXA.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2014, 2015, 2016 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _rxa_h
#define _rxa_h
#include "comm.h"
enum rxaMode
{
RXA_LSB,
RXA_USB,
RXA_DSB,
RXA_CWL,
RXA_CWU,
RXA_FM,
RXA_AM,
RXA_DIGU,
RXA_SPEC,
RXA_DIGL,
RXA_SAM,
RXA_DRM
};
enum rxaMeterType
{
RXA_S_PK,
RXA_S_AV,
RXA_ADC_PK,
RXA_ADC_AV,
RXA_AGC_GAIN,
RXA_AGC_PK,
RXA_AGC_AV,
RXA_METERTYPE_LAST
};
struct _rxa
{
double* inbuff;
double* outbuff;
double* midbuff;
int mode;
double meter[RXA_METERTYPE_LAST];
CRITICAL_SECTION* pmtupdate[RXA_METERTYPE_LAST];
struct
{
METER p;
} smeter, adcmeter, agcmeter;
struct
{
SHIFT p;
} shift;
struct
{
RESAMPLE p;
} rsmpin, rsmpout;
struct
{
GEN p;
} gen0;
struct
{
BANDPASS p;
} bp1;
struct
{
NOTCHDB p;
} ndb;
struct
{
NBP p;
} nbp0;
struct
{
BPSNBA p;
} bpsnba;
struct
{
SNBA p;
} snba;
struct
{
SENDER p;
} sender;
struct
{
AMSQ p;
} amsq;
struct
{
AMD p;
} amd;
struct
{
FMD p;
} fmd;
struct
{
FMSQ p;
} fmsq;
struct
{
EQP p;
} eqp;
struct
{
ANF p;
} anf;
struct
{
ANR p;
} anr;
struct
{
EMNR p;
} emnr;
struct
{
WCPAGC p;
} agc;
struct
{
SPEAK p;
} speak;
struct
{
MPEAK p;
} mpeak;
struct
{
PANEL p;
} panel;
struct
{
SIPHON p;
} sip1;
struct
{
CBL p;
} cbl;
struct
{
SSQL p;
} ssql;
};
extern struct _rxa rxa[];
extern void create_rxa (int channel);
extern void destroy_rxa (int channel);
extern void flush_rxa (int channel);
extern void xrxa (int channel);
extern void setInputSamplerate_rxa (int channel);
extern void setOutputSamplerate_rxa (int channel);
extern void setDSPSamplerate_rxa (int channel);
extern void setDSPBuffsize_rxa (int channel);
// RXA Properties
extern __declspec (dllexport) void SetRXAMode (int channel, int mode);
extern void RXAResCheck (int channel);
extern void RXAbp1Check (int channel, int amd_run, int snba_run, int emnr_run, int anf_run, int anr_run);
extern void RXAbp1Set (int channel);
extern void RXAbpsnbaCheck (int channel, int mode, int notch_run);
extern void RXAbpsnbaSet (int channel);
#endif
+935
View File
@@ -0,0 +1,935 @@
/* TXA.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2014, 2016, 2017, 2021, 2023 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
struct _txa txa[MAX_CHANNELS];
void create_txa (int channel)
{
txa[channel].mode = TXA_LSB;
txa[channel].f_low = -5000.0;
txa[channel].f_high = - 100.0;
txa[channel].inbuff = (double *) malloc0 (1 * ch[channel].dsp_insize * sizeof (complex));
txa[channel].outbuff = (double *) malloc0 (1 * ch[channel].dsp_outsize * sizeof (complex));
txa[channel].midbuff = (double *) malloc0 (2 * ch[channel].dsp_size * sizeof (complex));
txa[channel].rsmpin.p = create_resample (
0, // run - will be turned on below if needed
ch[channel].dsp_insize, // input buffer size
txa[channel].inbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
ch[channel].in_rate, // input sample rate
ch[channel].dsp_rate, // output sample rate
0.0, // select cutoff automatically
0, // select ncoef automatically
1.0); // gain
txa[channel].gen0.p = create_gen (
0, // run
ch[channel].dsp_size, // buffer size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // sample rate
2); // mode
txa[channel].panel.p = create_panel (
channel, // channel number
1, // run
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
1.0, // gain1
1.0, // gain2I
1.0, // gain2Q
2, // 1 to use Q, 2 to use I for input
0); // 0, no copy
txa[channel].phrot.p = create_phrot (
0, // run
ch[channel].dsp_size, // size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // samplerate
338.0, // 1/2 of phase frequency
8); // number of stages
txa[channel].micmeter.p = create_meter (
1, // run
0, // optional pointer to another 'run'
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
txa[channel].meter, // result vector
txa[channel].pmtupdate, // locks for meter access
TXA_MIC_AV, // index for average value
TXA_MIC_PK, // index for peak value
-1, // index for gain value
0); // pointer for gain computation
txa[channel].amsq.p = create_amsq (
0, // run
ch[channel].dsp_size, // size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
txa[channel].midbuff, // trigger buffer
ch[channel].dsp_rate, // sample rate
0.010, // time constant for averaging signal
0.004, // up-slew time
0.004, // down-slew time
0.180, // signal level to initiate tail
0.200, // signal level to initiate unmute
0.000, // minimum tail length
0.025, // maximum tail length
0.200); // muted gain
{
double default_F[11] = {0.0, 32.0, 63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0, 16000.0};
double default_G[11] = {0.0, -12.0, -12.0, -12.0, -1.0, +1.0, +4.0, +9.0, +12.0, -10.0, -10.0};
//double default_G[11] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
txa[channel].eqp.p = create_eqp (
0, // run - OFF by default
ch[channel].dsp_size, // size
max(2048, ch[channel].dsp_size), // number of filter coefficients
0, // minimum phase flag
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
10, // nfreqs
default_F, // vector of frequencies
default_G, // vector of gain values
0, // cutoff mode
0, // wintype
ch[channel].dsp_rate); // samplerate
}
txa[channel].eqmeter.p = create_meter (
1, // run
&(txa[channel].eqp.p->run), // pointer to eqp 'run'
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
txa[channel].meter, // result vector
txa[channel].pmtupdate, // locks for meter access
TXA_EQ_AV, // index for average value
TXA_EQ_PK, // index for peak value
-1, // index for gain value
0); // pointer for gain computation
txa[channel].preemph.p = create_emphp (
0, // run
1, // position
ch[channel].dsp_size, // size
max(2048, ch[channel].dsp_size), // number of filter coefficients
0, // minimum phase flag
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer,
ch[channel].dsp_rate, // sample rate
0, // pre-emphasis type
300.0, // f_low
3000.0); // f_high
txa[channel].leveler.p = create_wcpagc (
0, // run - OFF by default
5, // mode
0, // 0 for max(I,Q), 1 for envelope
txa[channel].midbuff, // input buff pointer
txa[channel].midbuff, // output buff pointer
ch[channel].dsp_size, // io_buffsize
ch[channel].dsp_rate, // sample rate
0.001, // tau_attack
0.500, // tau_decay
6, // n_tau
1.778, // max_gain
1.0, // var_gain
1.0, // fixed_gain
1.0, // max_input
1.05, // out_targ
0.250, // tau_fast_backaverage
0.005, // tau_fast_decay
5.0, // pop_ratio
0, // hang_enable
0.500, // tau_hang_backmult
0.500, // hangtime
2.000, // hang_thresh
0.100); // tau_hang_decay
txa[channel].lvlrmeter.p = create_meter (
1, // run
&(txa[channel].leveler.p->run), // pointer to leveler 'run'
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
txa[channel].meter, // result vector
txa[channel].pmtupdate, // locks for meter access
TXA_LVLR_AV, // index for average value
TXA_LVLR_PK, // index for peak value
TXA_LVLR_GAIN, // index for gain value
&txa[channel].leveler.p->gain); // pointer for gain computation
{
double default_F[5] = {200.0, 1000.0, 2000.0, 3000.0, 4000.0};
double default_G[5] = {0.0, 5.0, 10.0, 10.0, 5.0};
double default_E[5] = {7.0, 7.0, 7.0, 7.0, 7.0};
txa[channel].cfcomp.p = create_cfcomp(
0, // run
0, // position
0, // post-equalizer run
ch[channel].dsp_size, // size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
2048, // fft size
4, // overlap
ch[channel].dsp_rate, // samplerate
1, // window type
0, // compression method
5, // nfreqs
0.0, // pre-compression
0.0, // pre-postequalization
default_F, // frequency array
default_G, // compression array
default_E, // eq array
0.25, // metering time constant
0.50); // display time constant
}
txa[channel].cfcmeter.p = create_meter (
1, // run
&(txa[channel].cfcomp.p->run), // pointer to eqp 'run'
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
txa[channel].meter, // result vector
txa[channel].pmtupdate, // locks for meter access
TXA_CFC_AV, // index for average value
TXA_CFC_PK, // index for peak value
TXA_CFC_GAIN, // index for gain value
&txa[channel].cfcomp.p->gain); // pointer for gain computation
txa[channel].bp0.p = create_bandpass (
1, // always runs
0, // position
ch[channel].dsp_size, // size
max(2048, ch[channel].dsp_size), // number of coefficients
0, // flag for minimum phase
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
txa[channel].f_low, // low freq cutoff
txa[channel].f_high, // high freq cutoff
ch[channel].dsp_rate, // samplerate
1, // wintype
2.0); // gain
txa[channel].compressor.p = create_compressor (
0, // run - OFF by default
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
3.0); // gain
txa[channel].bp1.p = create_bandpass (
0, // ONLY RUNS WHEN COMPRESSOR IS USED
0, // position
ch[channel].dsp_size, // size
max(2048, ch[channel].dsp_size), // number of coefficients
0, // flag for minimum phase
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
txa[channel].f_low, // low freq cutoff
txa[channel].f_high, // high freq cutoff
ch[channel].dsp_rate, // samplerate
1, // wintype
2.0); // gain
txa[channel].osctrl.p = create_osctrl (
0, // run
ch[channel].dsp_size, // size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // sample rate
1.95); // gain for clippings
txa[channel].bp2.p = create_bandpass (
0, // ONLY RUNS WHEN COMPRESSOR IS USED
0, // position
ch[channel].dsp_size, // size
max(2048, ch[channel].dsp_size), // number of coefficients
0, // flag for minimum phase
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
txa[channel].f_low, // low freq cutoff
txa[channel].f_high, // high freq cutoff
ch[channel].dsp_rate, // samplerate
1, // wintype
1.0); // gain
txa[channel].compmeter.p = create_meter (
1, // run
&(txa[channel].compressor.p->run), // pointer to compressor 'run'
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
txa[channel].meter, // result vector
txa[channel].pmtupdate, // locks for meter access
TXA_COMP_AV, // index for average value
TXA_COMP_PK, // index for peak value
-1, // index for gain value
0); // pointer for gain computation
txa[channel].alc.p = create_wcpagc (
1, // run - always ON
5, // mode
1, // 0 for max(I,Q), 1 for envelope
txa[channel].midbuff, // input buff pointer
txa[channel].midbuff, // output buff pointer
ch[channel].dsp_size, // io_buffsize
ch[channel].dsp_rate, // sample rate
0.001, // tau_attack
0.010, // tau_decay
6, // n_tau
1.0, // max_gain
1.0, // var_gain
1.0, // fixed_gain
1.0, // max_input
1.0, // out_targ
0.250, // tau_fast_backaverage
0.005, // tau_fast_decay
5.0, // pop_ratio
0, // hang_enable
0.500, // tau_hang_backmult
0.500, // hangtime
2.000, // hang_thresh
0.100); // tau_hang_decay
txa[channel].ammod.p = create_ammod (
0, // run - OFF by default
0, // mode: 0=>AM, 1=>DSB
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
0.5); // carrier level
txa[channel].fmmod.p = create_fmmod (
0, // run - OFF by default
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to input buffer
ch[channel].dsp_rate, // samplerate
5000.0, // deviation
300.0, // low cutoff frequency
3000.0, // high cutoff frequency
1, // ctcss run control
0.10, // ctcss level
100.0, // ctcss frequency
1, // run bandpass filter
max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter
0); // minimum phase flag
txa[channel].gen1.p = create_gen (
0, // run
ch[channel].dsp_size, // buffer size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // sample rate
0); // mode
txa[channel].uslew.p = create_uslew (
channel, // channel
&ch[channel].iob.ch_upslew, // pointer to channel upslew flag
ch[channel].dsp_size, // buffer size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // sample rate
0.000, // delay time
0.005); // upslew time
txa[channel].alcmeter.p = create_meter (
1, // run
0, // optional pointer to a 'run'
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to buffer
ch[channel].dsp_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
txa[channel].meter, // result vector
txa[channel].pmtupdate, // locks for meter access
TXA_ALC_AV, // index for average value
TXA_ALC_PK, // index for peak value
TXA_ALC_GAIN, // index for gain value
&txa[channel].alc.p->gain); // pointer for gain computation
txa[channel].sip1.p = create_siphon (
1, // run
0, // position
0, // mode
0, // disp
ch[channel].dsp_size, // input buffer size
txa[channel].midbuff, // input buffer
16384, // number of samples to buffer
16384, // fft size for spectrum
1); // specmode
txa[channel].calcc.p = create_calcc (
channel, // channel number
1, // run calibration
1024, // input buffer size
ch[channel].in_rate, // samplerate
16, // ints
256, // spi
(1.0 / 0.4072), // hw_scale
0.1, // mox delay
0.0, // loop delay
0.8, // ptol
0, // mox
0, // solidmox
1, // pin mode
1, // map mode
0, // stbl mode
256, // pin samples
0.9); // alpha
txa[channel].iqc.p0 = txa[channel].iqc.p1 = create_iqc (
0, // run
ch[channel].dsp_size, // size
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
(double)ch[channel].dsp_rate, // sample rate
16, // ints
0.005, // changeover time
256); // spi
txa[channel].cfir.p = create_cfir(
0, // run
ch[channel].dsp_size, // size
max(2048, ch[channel].dsp_size), // number of filter coefficients
0, // minimum phase flag
txa[channel].midbuff, // input buffer
txa[channel].midbuff, // output buffer
ch[channel].dsp_rate, // input sample rate
ch[channel].out_rate, // CIC input sample rate
1, // CIC differential delay
640, // CIC interpolation factor
5, // CIC integrator-comb pairs
20000.0, // cutoff frequency
2, // brick-wall windowed rolloff
0.0, // raised-cosine transition width
0); // window type
txa[channel].rsmpout.p = create_resample (
0, // run - will be turned ON below if needed
ch[channel].dsp_size, // input size
txa[channel].midbuff, // pointer to input buffer
txa[channel].outbuff, // pointer to output buffer
ch[channel].dsp_rate, // input sample rate
ch[channel].out_rate, // output sample rate
0.0, // select cutoff automatically
0, // select ncoef automatically
0.980); // gain
txa[channel].outmeter.p = create_meter (
1, // run
0, // optional pointer to another 'run'
ch[channel].dsp_outsize, // size
txa[channel].outbuff, // pointer to buffer
ch[channel].out_rate, // samplerate
0.100, // averaging time constant
0.100, // peak decay time constant
txa[channel].meter, // result vector
txa[channel].pmtupdate, // locks for meter access
TXA_OUT_AV, // index for average value
TXA_OUT_PK, // index for peak value
-1, // index for gain value
0); // pointer for gain computation
// turn OFF / ON resamplers as needed
TXAResCheck (channel);
}
void destroy_txa (int channel)
{
// in reverse order, free each item we created
destroy_meter (txa[channel].outmeter.p);
destroy_resample (txa[channel].rsmpout.p);
destroy_cfir(txa[channel].cfir.p);
destroy_calcc (txa[channel].calcc.p);
destroy_iqc (txa[channel].iqc.p0);
destroy_siphon (txa[channel].sip1.p);
destroy_meter (txa[channel].alcmeter.p);
destroy_uslew (txa[channel].uslew.p);
destroy_gen (txa[channel].gen1.p);
destroy_fmmod (txa[channel].fmmod.p);
destroy_ammod (txa[channel].ammod.p);
destroy_wcpagc (txa[channel].alc.p);
destroy_meter (txa[channel].compmeter.p);
destroy_bandpass (txa[channel].bp2.p);
destroy_osctrl (txa[channel].osctrl.p);
destroy_bandpass (txa[channel].bp1.p);
destroy_compressor (txa[channel].compressor.p);
destroy_bandpass (txa[channel].bp0.p);
destroy_meter (txa[channel].cfcmeter.p);
destroy_cfcomp (txa[channel].cfcomp.p);
destroy_meter (txa[channel].lvlrmeter.p);
destroy_wcpagc (txa[channel].leveler.p);
destroy_emphp (txa[channel].preemph.p);
destroy_meter (txa[channel].eqmeter.p);
destroy_eqp (txa[channel].eqp.p);
destroy_amsq (txa[channel].amsq.p);
destroy_meter (txa[channel].micmeter.p);
destroy_phrot (txa[channel].phrot.p);
destroy_panel (txa[channel].panel.p);
destroy_gen (txa[channel].gen0.p);
destroy_resample (txa[channel].rsmpin.p);
_aligned_free (txa[channel].midbuff);
_aligned_free (txa[channel].outbuff);
_aligned_free (txa[channel].inbuff);
}
void flush_txa (int channel)
{
memset (txa[channel].inbuff, 0, 1 * ch[channel].dsp_insize * sizeof (complex));
memset (txa[channel].outbuff, 0, 1 * ch[channel].dsp_outsize * sizeof (complex));
memset (txa[channel].midbuff, 0, 2 * ch[channel].dsp_size * sizeof (complex));
flush_resample (txa[channel].rsmpin.p);
flush_gen (txa[channel].gen0.p);
flush_panel (txa[channel].panel.p);
flush_phrot (txa[channel].phrot.p);
flush_meter (txa[channel].micmeter.p);
flush_amsq (txa[channel].amsq.p);
flush_eqp (txa[channel].eqp.p);
flush_meter (txa[channel].eqmeter.p);
flush_emphp (txa[channel].preemph.p);
flush_wcpagc (txa[channel].leveler.p);
flush_meter (txa[channel].lvlrmeter.p);
flush_cfcomp (txa[channel].cfcomp.p);
flush_meter (txa[channel].cfcmeter.p);
flush_bandpass (txa[channel].bp0.p);
flush_compressor (txa[channel].compressor.p);
flush_bandpass (txa[channel].bp1.p);
flush_osctrl (txa[channel].osctrl.p);
flush_bandpass (txa[channel].bp2.p);
flush_meter (txa[channel].compmeter.p);
flush_wcpagc (txa[channel].alc.p);
flush_ammod (txa[channel].ammod.p);
flush_fmmod (txa[channel].fmmod.p);
flush_gen (txa[channel].gen1.p);
flush_uslew (txa[channel].uslew.p);
flush_meter (txa[channel].alcmeter.p);
flush_siphon (txa[channel].sip1.p);
flush_iqc (txa[channel].iqc.p0);
flush_cfir(txa[channel].cfir.p);
flush_resample (txa[channel].rsmpout.p);
flush_meter (txa[channel].outmeter.p);
}
void xtxa (int channel)
{
xresample (txa[channel].rsmpin.p); // input resampler
xgen (txa[channel].gen0.p); // input signal generator
xpanel (txa[channel].panel.p); // includes MIC gain
xphrot (txa[channel].phrot.p); // phase rotator
xmeter (txa[channel].micmeter.p); // MIC meter
xamsqcap (txa[channel].amsq.p); // downward expander capture
xamsq (txa[channel].amsq.p); // downward expander action
xeqp (txa[channel].eqp.p); // pre-EQ
xmeter (txa[channel].eqmeter.p); // EQ meter
xemphp (txa[channel].preemph.p, 0); // FM pre-emphasis (first option)
xwcpagc (txa[channel].leveler.p); // Leveler
xmeter (txa[channel].lvlrmeter.p); // Leveler Meter
xcfcomp (txa[channel].cfcomp.p, 0); // Continuous Frequency Compressor with post-EQ
xmeter (txa[channel].cfcmeter.p); // CFC+PostEQ Meter
xbandpass (txa[channel].bp0.p, 0); // primary bandpass filter
xcompressor (txa[channel].compressor.p); // COMP compressor
xbandpass (txa[channel].bp1.p, 0); // aux bandpass (runs if COMP)
xosctrl (txa[channel].osctrl.p); // CESSB Overshoot Control
xbandpass (txa[channel].bp2.p, 0); // aux bandpass (runs if CESSB)
xmeter (txa[channel].compmeter.p); // COMP meter
xwcpagc (txa[channel].alc.p); // ALC
xammod (txa[channel].ammod.p); // AM Modulator
xemphp (txa[channel].preemph.p, 1); // FM pre-emphasis (second option)
xfmmod (txa[channel].fmmod.p); // FM Modulator
xgen (txa[channel].gen1.p); // output signal generator (TUN and Two-tone)
xuslew (txa[channel].uslew.p); // up-slew for AM, FM, and gens
xmeter (txa[channel].alcmeter.p); // ALC Meter
xsiphon (txa[channel].sip1.p, 0); // siphon data for display
xiqc (txa[channel].iqc.p0); // PureSignal correction
xcfir(txa[channel].cfir.p); // compensating FIR filter (used Protocol_2 only)
xresample (txa[channel].rsmpout.p); // output resampler
xmeter (txa[channel].outmeter.p); // output meter
// print_peak_env ("env_exception.txt", ch[channel].dsp_outsize, txa[channel].outbuff, 0.7);
}
void setInputSamplerate_txa (int channel)
{
// buffers
_aligned_free (txa[channel].inbuff);
txa[channel].inbuff = (double *)malloc0(1 * ch[channel].dsp_insize * sizeof(complex));
// input resampler
setBuffers_resample (txa[channel].rsmpin.p, txa[channel].inbuff, txa[channel].midbuff);
setSize_resample (txa[channel].rsmpin.p, ch[channel].dsp_insize);
setInRate_resample (txa[channel].rsmpin.p, ch[channel].in_rate);
TXAResCheck (channel);
}
void setOutputSamplerate_txa (int channel)
{
// buffers
_aligned_free (txa[channel].outbuff);
txa[channel].outbuff = (double *)malloc0(1 * ch[channel].dsp_outsize * sizeof(complex));
// cfir - needs to know input rate of firmware CIC
setOutRate_cfir (txa[channel].cfir.p, ch[channel].out_rate);
// output resampler
setBuffers_resample (txa[channel].rsmpout.p, txa[channel].midbuff, txa[channel].outbuff);
setOutRate_resample (txa[channel].rsmpout.p, ch[channel].out_rate);
TXAResCheck (channel);
// output meter
setBuffers_meter (txa[channel].outmeter.p, txa[channel].outbuff);
setSize_meter (txa[channel].outmeter.p, ch[channel].dsp_outsize);
setSamplerate_meter (txa[channel].outmeter.p, ch[channel].out_rate);
}
void setDSPSamplerate_txa (int channel)
{
// buffers
_aligned_free (txa[channel].inbuff);
txa[channel].inbuff = (double *)malloc0(1 * ch[channel].dsp_insize * sizeof(complex));
_aligned_free (txa[channel].outbuff);
txa[channel].outbuff = (double *)malloc0(1 * ch[channel].dsp_outsize * sizeof(complex));
// input resampler
setBuffers_resample (txa[channel].rsmpin.p, txa[channel].inbuff, txa[channel].midbuff);
setSize_resample (txa[channel].rsmpin.p, ch[channel].dsp_insize);
setOutRate_resample (txa[channel].rsmpin.p, ch[channel].dsp_rate);
// dsp_rate blocks
setSamplerate_gen (txa[channel].gen0.p, ch[channel].dsp_rate);
setSamplerate_panel (txa[channel].panel.p, ch[channel].dsp_rate);
setSamplerate_phrot (txa[channel].phrot.p, ch[channel].dsp_rate);
setSamplerate_meter (txa[channel].micmeter.p, ch[channel].dsp_rate);
setSamplerate_amsq (txa[channel].amsq.p, ch[channel].dsp_rate);
setSamplerate_eqp (txa[channel].eqp.p, ch[channel].dsp_rate);
setSamplerate_meter (txa[channel].eqmeter.p, ch[channel].dsp_rate);
setSamplerate_emphp (txa[channel].preemph.p, ch[channel].dsp_rate);
setSamplerate_wcpagc (txa[channel].leveler.p, ch[channel].dsp_rate);
setSamplerate_meter (txa[channel].lvlrmeter.p, ch[channel].dsp_rate);
setSamplerate_cfcomp (txa[channel].cfcomp.p, ch[channel].dsp_rate);
setSamplerate_meter (txa[channel].cfcmeter.p, ch[channel].dsp_rate);
setSamplerate_bandpass (txa[channel].bp0.p, ch[channel].dsp_rate);
setSamplerate_compressor (txa[channel].compressor.p, ch[channel].dsp_rate);
setSamplerate_bandpass (txa[channel].bp1.p, ch[channel].dsp_rate);
setSamplerate_osctrl (txa[channel].osctrl.p, ch[channel].dsp_rate);
setSamplerate_bandpass (txa[channel].bp2.p, ch[channel].dsp_rate);
setSamplerate_meter (txa[channel].compmeter.p, ch[channel].dsp_rate);
setSamplerate_wcpagc (txa[channel].alc.p, ch[channel].dsp_rate);
setSamplerate_ammod (txa[channel].ammod.p, ch[channel].dsp_rate);
setSamplerate_fmmod (txa[channel].fmmod.p, ch[channel].dsp_rate);
setSamplerate_gen (txa[channel].gen1.p, ch[channel].dsp_rate);
setSamplerate_uslew (txa[channel].uslew.p, ch[channel].dsp_rate);
setSamplerate_meter (txa[channel].alcmeter.p, ch[channel].dsp_rate);
setSamplerate_siphon (txa[channel].sip1.p, ch[channel].dsp_rate);
setSamplerate_iqc (txa[channel].iqc.p0, ch[channel].dsp_rate);
setSamplerate_cfir (txa[channel].cfir.p, ch[channel].dsp_rate);
// output resampler
setBuffers_resample (txa[channel].rsmpout.p, txa[channel].midbuff, txa[channel].outbuff);
setInRate_resample (txa[channel].rsmpout.p, ch[channel].dsp_rate);
TXAResCheck (channel);
// output meter
setBuffers_meter (txa[channel].outmeter.p, txa[channel].outbuff);
setSize_meter (txa[channel].outmeter.p, ch[channel].dsp_outsize);
}
void setDSPBuffsize_txa (int channel)
{
// buffers
_aligned_free (txa[channel].inbuff);
txa[channel].inbuff = (double *)malloc0(1 * ch[channel].dsp_insize * sizeof(complex));
_aligned_free (txa[channel].midbuff);
txa[channel].midbuff = (double *)malloc0(2 * ch[channel].dsp_size * sizeof(complex));
_aligned_free (txa[channel].outbuff);
txa[channel].outbuff = (double *)malloc0(1 * ch[channel].dsp_outsize * sizeof(complex));
// input resampler
setBuffers_resample (txa[channel].rsmpin.p, txa[channel].inbuff, txa[channel].midbuff);
setSize_resample (txa[channel].rsmpin.p, ch[channel].dsp_insize);
// dsp_size blocks
setBuffers_gen (txa[channel].gen0.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_gen (txa[channel].gen0.p, ch[channel].dsp_size);
setBuffers_panel (txa[channel].panel.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_panel (txa[channel].panel.p, ch[channel].dsp_size);
setBuffers_phrot (txa[channel].phrot.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_phrot (txa[channel].phrot.p, ch[channel].dsp_size);
setBuffers_meter (txa[channel].micmeter.p, txa[channel].midbuff);
setSize_meter (txa[channel].micmeter.p, ch[channel].dsp_size);
setBuffers_amsq (txa[channel].amsq.p, txa[channel].midbuff, txa[channel].midbuff, txa[channel].midbuff);
setSize_amsq (txa[channel].amsq.p, ch[channel].dsp_size);
setBuffers_eqp (txa[channel].eqp.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_eqp (txa[channel].eqp.p, ch[channel].dsp_size);
setBuffers_meter (txa[channel].eqmeter.p, txa[channel].midbuff);
setSize_meter (txa[channel].eqmeter.p, ch[channel].dsp_size);
setBuffers_emphp (txa[channel].preemph.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_emphp (txa[channel].preemph.p, ch[channel].dsp_size);
setBuffers_wcpagc (txa[channel].leveler.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_wcpagc (txa[channel].leveler.p, ch[channel].dsp_size);
setBuffers_meter (txa[channel].lvlrmeter.p, txa[channel].midbuff);
setSize_meter (txa[channel].lvlrmeter.p, ch[channel].dsp_size);
setBuffers_cfcomp (txa[channel].cfcomp.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_cfcomp (txa[channel].cfcomp.p, ch[channel].dsp_size);
setBuffers_meter (txa[channel].cfcmeter.p, txa[channel].midbuff);
setSize_meter (txa[channel].cfcmeter.p, ch[channel].dsp_size);
setBuffers_bandpass (txa[channel].bp0.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_bandpass (txa[channel].bp0.p, ch[channel].dsp_size);
setBuffers_compressor (txa[channel].compressor.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_compressor (txa[channel].compressor.p, ch[channel].dsp_size);
setBuffers_bandpass (txa[channel].bp1.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_bandpass (txa[channel].bp1.p, ch[channel].dsp_size);
setBuffers_osctrl (txa[channel].osctrl.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_osctrl (txa[channel].osctrl.p, ch[channel].dsp_size);
setBuffers_bandpass (txa[channel].bp2.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_bandpass (txa[channel].bp2.p, ch[channel].dsp_size);
setBuffers_meter (txa[channel].compmeter.p, txa[channel].midbuff);
setSize_meter (txa[channel].compmeter.p, ch[channel].dsp_size);
setBuffers_wcpagc (txa[channel].alc.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_wcpagc (txa[channel].alc.p, ch[channel].dsp_size);
setBuffers_ammod (txa[channel].ammod.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_ammod (txa[channel].ammod.p, ch[channel].dsp_size);
setBuffers_fmmod (txa[channel].fmmod.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_fmmod (txa[channel].fmmod.p, ch[channel].dsp_size);
setBuffers_gen (txa[channel].gen1.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_gen (txa[channel].gen1.p, ch[channel].dsp_size);
setBuffers_uslew (txa[channel].uslew.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_uslew (txa[channel].uslew.p, ch[channel].dsp_size);
setBuffers_meter (txa[channel].alcmeter.p, txa[channel].midbuff);
setSize_meter (txa[channel].alcmeter.p, ch[channel].dsp_size);
setBuffers_siphon (txa[channel].sip1.p, txa[channel].midbuff);
setSize_siphon (txa[channel].sip1.p, ch[channel].dsp_size);
setBuffers_iqc (txa[channel].iqc.p0, txa[channel].midbuff, txa[channel].midbuff);
setSize_iqc (txa[channel].iqc.p0, ch[channel].dsp_size);
setBuffers_cfir (txa[channel].cfir.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_cfir (txa[channel].cfir.p, ch[channel].dsp_size);
// output resampler
setBuffers_resample (txa[channel].rsmpout.p, txa[channel].midbuff, txa[channel].outbuff);
setSize_resample (txa[channel].rsmpout.p, ch[channel].dsp_size);
// output meter
setBuffers_meter (txa[channel].outmeter.p, txa[channel].outbuff);
setSize_meter (txa[channel].outmeter.p, ch[channel].dsp_outsize);
}
/********************************************************************************************************
* *
* TXA Properties *
* *
********************************************************************************************************/
PORT
void SetTXAMode (int channel, int mode)
{
if (txa[channel].mode != mode)
{
EnterCriticalSection (&ch[channel].csDSP);
txa[channel].mode = mode;
txa[channel].ammod.p->run = 0;
txa[channel].fmmod.p->run = 0;
txa[channel].preemph.p->run = 0;
switch (mode)
{
case TXA_AM:
case TXA_SAM:
txa[channel].ammod.p->run = 1;
txa[channel].ammod.p->mode = 0;
break;
case TXA_DSB:
txa[channel].ammod.p->run = 1;
txa[channel].ammod.p->mode = 1;
break;
case TXA_AM_LSB:
case TXA_AM_USB:
txa[channel].ammod.p->run = 1;
txa[channel].ammod.p->mode = 2;
break;
case TXA_FM:
txa[channel].fmmod.p->run = 1;
txa[channel].preemph.p->run = 1;
break;
default:
break;
}
TXASetupBPFilters (channel);
LeaveCriticalSection (&ch[channel].csDSP);
}
}
PORT
void SetTXABandpassFreqs (int channel, double f_low, double f_high)
{
if ((txa[channel].f_low != f_low) || (txa[channel].f_high != f_high))
{
txa[channel].f_low = f_low;
txa[channel].f_high = f_high;
TXASetupBPFilters (channel);
}
}
/********************************************************************************************************
* *
* TXA Internal Functions *
* *
********************************************************************************************************/
void TXAResCheck (int channel)
{
RESAMPLE a = txa[channel].rsmpin.p;
if (ch[channel].in_rate != ch[channel].dsp_rate) a->run = 1;
else a->run = 0;
a = txa[channel].rsmpout.p;
if (ch[channel].dsp_rate != ch[channel].out_rate) a->run = 1;
else a->run = 0;
}
int TXAUslewCheck (int channel)
{
return (txa[channel].ammod.p->run == 1) ||
(txa[channel].fmmod.p->run == 1) ||
(txa[channel].gen0.p->run == 1) ||
(txa[channel].gen1.p->run == 1);
}
void TXASetupBPFilters (int channel)
{
txa[channel].bp0.p->run = 1;
txa[channel].bp1.p->run = 0;
txa[channel].bp2.p->run = 0;
switch (txa[channel].mode)
{
case TXA_LSB:
case TXA_USB:
case TXA_CWL:
case TXA_CWU:
case TXA_DIGL:
case TXA_DIGU:
case TXA_SPEC:
case TXA_DRM:
CalcBandpassFilter (txa[channel].bp0.p, txa[channel].f_low, txa[channel].f_high, 2.0);
if (txa[channel].compressor.p->run)
{
CalcBandpassFilter (txa[channel].bp1.p, txa[channel].f_low, txa[channel].f_high, 2.0);
txa[channel].bp1.p->run = 1;
if (txa[channel].osctrl.p->run)
{
CalcBandpassFilter (txa[channel].bp2.p, txa[channel].f_low, txa[channel].f_high, 1.0);
txa[channel].bp2.p->run = 1;
}
}
break;
case TXA_DSB:
case TXA_AM:
case TXA_SAM:
case TXA_FM:
if (txa[channel].compressor.p->run)
{
CalcBandpassFilter (txa[channel].bp0.p, 0.0, txa[channel].f_high, 2.0);
CalcBandpassFilter (txa[channel].bp1.p, 0.0, txa[channel].f_high, 2.0);
txa[channel].bp1.p->run = 1;
if (txa[channel].osctrl.p->run)
{
CalcBandpassFilter (txa[channel].bp2.p, 0.0, txa[channel].f_high, 1.0);
txa[channel].bp2.p->run = 1;
}
}
else
{
CalcBandpassFilter (txa[channel].bp0.p, txa[channel].f_low, txa[channel].f_high, 1.0);
}
break;
case TXA_AM_LSB:
CalcBandpassFilter (txa[channel].bp0.p, -txa[channel].f_high, 0.0, 2.0);
if (txa[channel].compressor.p->run)
{
CalcBandpassFilter (txa[channel].bp1.p, -txa[channel].f_high, 0.0, 2.0);
txa[channel].bp1.p->run = 1;
if (txa[channel].osctrl.p->run)
{
CalcBandpassFilter (txa[channel].bp2.p, -txa[channel].f_high, 0.0, 1.0);
txa[channel].bp2.p->run = 1;
}
}
break;
case TXA_AM_USB:
CalcBandpassFilter (txa[channel].bp0.p, 0.0, txa[channel].f_high, 2.0);
if (txa[channel].compressor.p->run)
{
CalcBandpassFilter (txa[channel].bp1.p, 0.0, txa[channel].f_high, 2.0);
txa[channel].bp1.p->run = 1;
if (txa[channel].osctrl.p->run)
{
CalcBandpassFilter (txa[channel].bp2.p, 0.0, txa[channel].f_high, 1.0);
txa[channel].bp2.p->run = 1;
}
}
break;
}
}
/********************************************************************************************************
* *
* Collectives *
* *
********************************************************************************************************/
PORT
void TXASetNC (int channel, int nc)
{
int oldstate = SetChannelState (channel, 0, 1);
SetTXABandpassNC (channel, nc);
SetTXAFMEmphNC (channel, nc);
SetTXAEQNC (channel, nc);
SetTXAFMNC (channel, nc);
SetTXACFIRNC (channel, nc);
SetChannelState (channel, oldstate, 0);
}
PORT
void TXASetMP (int channel, int mp)
{
SetTXABandpassMP (channel, mp);
SetTXAFMEmphMP (channel, mp);
SetTXAEQMP (channel, mp);
SetTXAFMMP (channel, mp);
}
PORT
void SetTXAFMAFFilter (int channel, double low, double high)
{
SetTXAFMPreEmphFreqs (channel, low, high);
SetTXAFMAFFreqs (channel, low, high);
}
+193
View File
@@ -0,0 +1,193 @@
/* TXA.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2017 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _txa_h
#define _txa_h
#include "comm.h"
enum txaMode
{
TXA_LSB,
TXA_USB,
TXA_DSB,
TXA_CWL,
TXA_CWU,
TXA_FM,
TXA_AM,
TXA_DIGU,
TXA_SPEC,
TXA_DIGL,
TXA_SAM,
TXA_DRM,
TXA_AM_LSB,
TXA_AM_USB
};
enum txaMeterType
{
TXA_MIC_PK,
TXA_MIC_AV,
TXA_EQ_PK,
TXA_EQ_AV,
TXA_LVLR_PK,
TXA_LVLR_AV,
TXA_LVLR_GAIN,
TXA_CFC_PK,
TXA_CFC_AV,
TXA_CFC_GAIN,
TXA_COMP_PK,
TXA_COMP_AV,
TXA_ALC_PK,
TXA_ALC_AV,
TXA_ALC_GAIN,
TXA_OUT_PK,
TXA_OUT_AV,
TXA_METERTYPE_LAST
};
struct _txa
{
double* inbuff;
double* outbuff;
double* midbuff;
int mode;
double f_low;
double f_high;
double meter[TXA_METERTYPE_LAST];
CRITICAL_SECTION* pmtupdate[TXA_METERTYPE_LAST];
struct
{
METER p;
} micmeter, eqmeter, lvlrmeter, cfcmeter, compmeter, alcmeter, outmeter;
struct
{
RESAMPLE p;
} rsmpin, rsmpout;
struct
{
PANEL p;
} panel;
struct
{
AMSQ p;
} amsq;
struct
{
EQP p;
} eqp;
struct
{
PHROT p;
} phrot;
struct
{
CFCOMP p;
} cfcomp;
struct
{
COMPRESSOR p;
} compressor;
struct
{
BANDPASS p;
} bp0, bp1, bp2;
struct
{
OSCTRL p;
} osctrl;
struct
{
WCPAGC p;
} leveler, alc;
struct
{
AMMOD p;
} ammod;
struct
{
EMPHP p;
} preemph;
struct
{
FMMOD p;
} fmmod;
struct
{
SIPHON p;
} sip1;
struct
{
GEN p;
} gen0, gen1;
struct
{
USLEW p;
} uslew;
struct
{
CALCC p;
CRITICAL_SECTION cs_update;
} calcc;
struct
{
IQC p0, p1;
// p0 for dsp-synchronized reference, p1 for other
} iqc;
struct
{
CFIR p;
} cfir;
};
extern struct _txa txa[];
extern void create_txa (int channel);
extern void destroy_txa (int channel);
extern void flush_txa (int channel);
extern void xtxa (int channel);
extern int TXAUslewCheck (int channel);
extern void setInputSamplerate_txa (int channel);
extern void setOutputSamplerate_txa (int channel);
extern void setDSPSamplerate_txa (int channel);
extern void setDSPBuffsize_txa (int channel);
// TXA Properties
extern __declspec (dllexport) void SetTXAMode (int channel, int mode);
extern void TXAResCheck (int channel);
extern void TXASetupBPFilters (int channel);
#endif
+293
View File
@@ -0,0 +1,293 @@
/* amd.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2012, 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
AMD create_amd
(
int run,
int buff_size,
double *in_buff,
double *out_buff,
int mode,
int levelfade,
int sbmode,
int sample_rate,
double fmin,
double fmax,
double zeta,
double omegaN,
double tauR,
double tauI
)
{
AMD a = (AMD) malloc0 (sizeof(amd));
a->run = run;
a->buff_size = buff_size;
a->in_buff = in_buff;
a->out_buff = out_buff;
a->mode = mode;
a->levelfade = levelfade;
a->sbmode = sbmode;
a->sample_rate = (double)sample_rate;
a->fmin = fmin;
a->fmax = fmax;
a->zeta = zeta;
a->omegaN = omegaN;
a->tauR = tauR;
a->tauI = tauI;
init_amd(a);
return a;
}
void destroy_amd(AMD a)
{
_aligned_free (a);
}
void init_amd(AMD a)
{
//pll
a->omega_min = TWOPI * a->fmin / a->sample_rate;
a->omega_max = TWOPI * a->fmax / a->sample_rate;
a->g1 = 1.0 - exp(-2.0 * a->omegaN * a->zeta / a->sample_rate);
a->g2 = -a->g1 + 2.0 * (1 - exp(-a->omegaN * a->zeta / a->sample_rate) * cos(a->omegaN / a->sample_rate * sqrt(1.0 - a->zeta * a->zeta)));
a->phs = 0.0;
a->fil_out = 0.0;
a->omega = 0.0;
//fade leveler
a->dc = 0.0;
a->dc_insert = 0.0;
a->mtauR = exp(-1.0 / (a->sample_rate * a->tauR));
a->onem_mtauR = 1.0 - a->mtauR;
a->mtauI = exp(-1.0 / (a->sample_rate * a->tauI));
a->onem_mtauI = 1.0 - a->mtauI;
//sideband separation
a->c0[0] = -0.328201924180698;
a->c0[1] = -0.744171491539427;
a->c0[2] = -0.923022915444215;
a->c0[3] = -0.978490468768238;
a->c0[4] = -0.994128272402075;
a->c0[5] = -0.998458978159551;
a->c0[6] = -0.999790306259206;
a->c1[0] = -0.0991227952747244;
a->c1[1] = -0.565619728761389;
a->c1[2] = -0.857467122550052;
a->c1[3] = -0.959123933111275;
a->c1[4] = -0.988739372718090;
a->c1[5] = -0.996959189310611;
a->c1[6] = -0.999282492800792;
}
void flush_amd (AMD a)
{
a->dc = 0.0;
a->dc_insert = 0.0;
}
void xamd (AMD a)
{
int i;
double audio;
double vco[2];
double corr[2];
double det;
double del_out;
double ai, bi, aq, bq;
double ai_ps, bi_ps, aq_ps, bq_ps;
int j, k;
if (a->run)
{
switch (a->mode)
{
case 0: //AM Demodulator
{
for (i = 0; i < a->buff_size; i++)
{
audio = sqrt(a->in_buff[2 * i + 0] * a->in_buff[2 * i + 0] + a->in_buff[2 * i + 1] * a->in_buff[2 * i + 1]);
if (a->levelfade)
{
a->dc = a->mtauR * a->dc + a->onem_mtauR * audio;
a->dc_insert = a->mtauI * a->dc_insert + a->onem_mtauI * audio;
audio += a->dc_insert - a->dc;
}
a->out_buff[2 * i + 0] = audio;
a->out_buff[2 * i + 1] = audio;
}
break;
}
case 1: //Synchronous AM Demodulator with Sideband Separation
{
for (i = 0; i < a->buff_size; i++)
{
vco[0] = cos(a->phs);
vco[1] = sin(a->phs);
ai = a->in_buff[2 * i + 0] * vco[0];
bi = a->in_buff[2 * i + 0] * vco[1];
aq = a->in_buff[2 * i + 1] * vco[0];
bq = a->in_buff[2 * i + 1] * vco[1];
if (a->sbmode != 0)
{
a->a[0] = a->dsI;
a->b[0] = bi;
a->c[0] = a->dsQ;
a->d[0] = aq;
a->dsI = ai;
a->dsQ = bq;
for (j = 0; j < STAGES; j++)
{
k = 3 * j;
a->a[k + 3] = a->c0[j] * (a->a[k] - a->a[k + 5]) + a->a[k + 2];
a->b[k + 3] = a->c1[j] * (a->b[k] - a->b[k + 5]) + a->b[k + 2];
a->c[k + 3] = a->c0[j] * (a->c[k] - a->c[k + 5]) + a->c[k + 2];
a->d[k + 3] = a->c1[j] * (a->d[k] - a->d[k + 5]) + a->d[k + 2];
}
ai_ps = a->a[OUT_IDX];
bi_ps = a->b[OUT_IDX];
bq_ps = a->c[OUT_IDX];
aq_ps = a->d[OUT_IDX];
for (j = OUT_IDX + 2; j > 0; j--)
{
a->a[j] = a->a[j - 1];
a->b[j] = a->b[j - 1];
a->c[j] = a->c[j - 1];
a->d[j] = a->d[j - 1];
}
}
corr[0] = +ai + bq;
corr[1] = -bi + aq;
switch(a->sbmode)
{
case 0: //both sidebands
{
audio = corr[0];
break;
}
case 1: //LSB
{
audio = (ai_ps - bi_ps) + (aq_ps + bq_ps);
break;
}
case 2: //USB
{
audio = (ai_ps + bi_ps) - (aq_ps - bq_ps);
break;
}
}
if (a->levelfade)
{
a->dc = a->mtauR * a->dc + a->onem_mtauR * audio;
a->dc_insert = a->mtauI * a->dc_insert + a->onem_mtauI * corr[0];
audio += a->dc_insert - a->dc;
}
a->out_buff[2 * i + 0] = audio;
a->out_buff[2 * i + 1] = audio;
if ((corr[0] == 0.0) && (corr[1] == 0.0)) corr[0] = 1.0;
det = atan2(corr[1], corr[0]);
del_out = a->fil_out;
a->omega += a->g2 * det;
if (a->omega < a->omega_min) a->omega = a->omega_min;
if (a->omega > a->omega_max) a->omega = a->omega_max;
a->fil_out = a->g1 * det + a->omega;
a->phs += del_out;
while (a->phs >= TWOPI) a->phs -= TWOPI;
while (a->phs < 0.0) a->phs += TWOPI;
}
break;
}
}
}
else if (a->in_buff != a->out_buff)
memcpy (a->out_buff, a->in_buff, a->buff_size * sizeof(complex));
}
void setBuffers_amd (AMD a, double* in, double* out)
{
a->in_buff = in;
a->out_buff = out;
}
void setSamplerate_amd (AMD a, int rate)
{
a->sample_rate = rate;
init_amd(a);
}
void setSize_amd (AMD a, int size)
{
a->buff_size = size;
}
/********************************************************************************************************
* *
* RXA Properties *
* *
********************************************************************************************************/
PORT void
SetRXAAMDRun(int channel, int run)
{
AMD a = rxa[channel].amd.p;
if (a->run != run)
{
RXAbp1Check (channel, run, rxa[channel].snba.p->run, rxa[channel].emnr.p->run,
rxa[channel].anf.p->run, rxa[channel].anr.p->run);
EnterCriticalSection (&ch[channel].csDSP);
a->run = run;
RXAbp1Set (channel);
LeaveCriticalSection (&ch[channel].csDSP);
}
}
PORT void
SetRXAAMDSBMode(int channel, int sbmode)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].amd.p->sbmode = sbmode;
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAAMDFadeLevel(int channel, int levelfade)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].amd.p->levelfade = levelfade;
LeaveCriticalSection (&ch[channel].csDSP);
}
+118
View File
@@ -0,0 +1,118 @@
/* amd.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2012, 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _amd_h
#define _amd_h
// ff defines for sbdemod
#ifndef STAGES
#define STAGES 7
#endif
#ifndef OUT_IDX
#define OUT_IDX (3 * STAGES)
#endif
typedef struct _amd
{
int run;
int buff_size; // buffer size
double *in_buff; // pointer to input buffer
double *out_buff; // pointer to output buffer
int mode; // demodulation mode
double sample_rate; // sample rate
double dc; // dc component in demodulated output
double fmin; // pll - minimum carrier freq to lock
double fmax; // pll - maximum carrier freq to lock
double omega_min; // pll - minimum lock check parameter
double omega_max; // pll - maximum lock check parameter
double zeta; // pll - damping factor; as coded, must be <=1.0
double omegaN; // pll - natural frequency
double phs; // pll - phase accumulator
double omega; // pll - locked pll frequency
double fil_out; // pll - filter output
double g1, g2; // pll - filter gain parameters
double tauR; // carrier removal time constant
double tauI; // carrier insertion time constant
double mtauR; // carrier removal multiplier
double onem_mtauR; // 1.0 - carrier_removal_multiplier
double mtauI; // carrier insertion multiplier
double onem_mtauI; // 1.0 - carrier_insertion_multiplier
double a[3 * STAGES + 3]; // Filter a variables
double b[3 * STAGES + 3]; // Filter b variables
double c[3 * STAGES + 3]; // Filter c variables
double d[3 * STAGES + 3]; // Filter d variables
double c0[STAGES]; // Filter coefficients - path 0
double c1[STAGES]; // Filter coefficients - path 1
double dsI; // delayed sample, I path
double dsQ; // delayed sample, Q path
double dc_insert; // dc component to insert in output
int sbmode; // sideband mode
int levelfade; // Fade Leveler switch
}amd, *AMD;
extern AMD create_amd
(
int run,
int buff_size,
double *in_buff,
double *out_buff,
int mode,
int levelfade,
int sbmode,
int sample_rate,
double fmin,
double fmax,
double zeta,
double omegaN,
double tauR,
double tauI
);
extern void init_amd (AMD a);
extern void destroy_amd (AMD a);
extern void flush_amd (AMD a);
extern void xamd (AMD a);
extern void setBuffers_amd (AMD a, double* in, double* out);
extern void setSamplerate_amd (AMD a, int rate);
extern void setSize_amd (AMD a, int size);
// RXA Properties
extern __declspec (dllexport) void SetRXAAMDRun(int channel, int run);
extern __declspec (dllexport) void SetRXAAMDSBMode(int channel, int sbmode);
extern __declspec (dllexport) void SetRXAAMDFadeLevel(int channel, int levelfade);
#endif
+110
View File
@@ -0,0 +1,110 @@
/* ammod.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2017 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
AMMOD create_ammod (int run, int mode, int size, double* in, double* out, double c_level)
{
AMMOD a = (AMMOD) malloc0 (sizeof (ammod));
a->run = run;
a->mode = mode;
a->size = size;
a->in = in;
a->out = out;
a->c_level = c_level;
a->a_level = 1.0 - a->c_level;
a->mult = 1.0 / sqrt (2.0);
return a;
}
void destroy_ammod (AMMOD a)
{
_aligned_free (a);
}
void flush_ammod (AMMOD a)
{
}
void xammod (AMMOD a)
{
if (a->run)
{
int i;
switch (a->mode)
{
case 0: // AM
for (i = 0; i < a->size; i++)
a->out[2 * i + 0] = a->out[2 * i + 1] = a->mult * (a->c_level + a->a_level * a->in[2 * i + 0]);
break;
case 1: // DSB
for (i = 0; i < a->size; i++)
a->out[2 * i + 0] = a->out[2 * i + 1] = a->mult * a->in[2 * i + 0];
break;
case 2: // SSB w/Carrier
for (i = 0; i < a->size; i++)
{
a->out[2 * i + 0] = a->mult * a->c_level + a->a_level * a->in[2 * i + 0];
a->out[2 * i + 1] = a->mult * a->c_level + a->a_level * a->in[2 * i + 1];
}
break;
}
}
else if (a->in != a->out)
memcpy (a->out, a->in, a->size * sizeof (complex));
}
void setBuffers_ammod (AMMOD a, double* in, double* out)
{
a->in = in;
a->out = out;
}
void setSamplerate_ammod (AMMOD a, int rate)
{
}
void setSize_ammod (AMMOD a, int size)
{
a->size = size;
}
/********************************************************************************************************
* *
* TXA Properties *
* *
********************************************************************************************************/
PORT void
SetTXAAMCarrierLevel (int channel, double c_level)
{
EnterCriticalSection (&ch[channel].csDSP);
txa[channel].ammod.p->c_level = c_level;
txa[channel].ammod.p->a_level = 1.0 - c_level;
LeaveCriticalSection (&ch[channel].csDSP);
}
+60
View File
@@ -0,0 +1,60 @@
/* ammod.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _ammod_h
#define _ammod_h
typedef struct _ammod
{
int run;
int mode;
int size;
double* in;
double* out;
double c_level;
double a_level;
double mult;
}ammod, *AMMOD;
extern AMMOD create_ammod (int run, int mode, int size, double* in, double* out, double c_level);
extern void destroy_ammod (AMMOD a);
extern void flush_ammod (AMMOD a);
extern void xammod (AMMOD a);
extern void setBuffers_ammod (AMMOD a, double* in, double* out);
extern void setSamplerate_ammod (AMMOD a, int rate);
extern void setSize_ammod (AMMOD a, int size);
// TXA Properties
extern __declspec (dllexport) void SetTXAAMCarrierLevel (int channel, double c_level);
#endif
+272
View File
@@ -0,0 +1,272 @@
/* amsq.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
void compute_slews(AMSQ a)
{
int i;
double delta, theta;
delta = PI / (double)a->ntup;
theta = 0.0;
for (i = 0; i <= a->ntup; i++)
{
a->cup[i] = a->muted_gain + (1.0 - a->muted_gain) * 0.5 * (1.0 - cos (theta));
theta += delta;
}
delta = PI / (double)a->ntdown;
theta = 0.0;
for (i = 0; i <= a->ntdown; i++)
{
a->cdown[i] = a->muted_gain + (1.0 - a->muted_gain) * 0.5 * (1.0 + cos (theta));
theta += delta;
}
}
void calc_amsq(AMSQ a)
{
// signal averaging
a->trigsig = (double *)malloc0(a->size * sizeof(complex));
a->avm = exp(-1.0 / (a->rate * a->avtau));
a->onem_avm = 1.0 - a->avm;
a->avsig = 0.0;
// level change
a->ntup = (int)(a->tup * a->rate);
a->ntdown = (int)(a->tdown * a->rate);
a->cup = (double *)malloc0((a->ntup + 1) * sizeof(double));
a->cdown = (double *)malloc0((a->ntdown + 1) * sizeof(double));
compute_slews(a);
// control
a->state = 0;
}
void decalc_amsq (AMSQ a)
{
_aligned_free (a->cdown);
_aligned_free (a->cup);
_aligned_free (a->trigsig);
}
AMSQ create_amsq (int run, int size, double* in, double* out, double* trigger, int rate, double avtau,
double tup, double tdown, double tail_thresh, double unmute_thresh, double min_tail, double max_tail, double muted_gain)
{
AMSQ a = (AMSQ) malloc0 (sizeof (amsq));
a->run = run;
a->size = size;
a->in = in;
a->out = out;
a->rate = (double)rate;
a->muted_gain = muted_gain;
a->trigger = trigger;
a->avtau = avtau;
a->tup = tup;
a->tdown = tdown;
a->tail_thresh = tail_thresh;
a->unmute_thresh = unmute_thresh;
a->min_tail = min_tail;
a->max_tail = max_tail;
calc_amsq (a);
return a;
}
void destroy_amsq (AMSQ a)
{
decalc_amsq (a);
_aligned_free (a);
}
void flush_amsq (AMSQ a)
{
memset (a->trigsig, 0, a->size * sizeof (complex));
a->avsig = 0.0;
a->state = 0;
}
enum _amsqstate
{
MUTED,
INCREASE,
UNMUTED,
TAIL,
DECREASE
};
void xamsq (AMSQ a)
{
if (a->run)
{
int i;
double sig, siglimit;
for (i = 0; i < a->size; i++)
{
sig = sqrt (a->trigsig[2 * i + 0] * a->trigsig[2 * i + 0] + a->trigsig[2 * i + 1] * a->trigsig[2 * i + 1]);
a->avsig = a->avm * a->avsig + a->onem_avm * sig;
switch (a->state)
{
case MUTED:
if (a->avsig > a->unmute_thresh)
{
a->state = INCREASE;
a->count = a->ntup;
}
a->out[2 * i + 0] = a->muted_gain * a->in[2 * i + 0];
a->out[2 * i + 1] = a->muted_gain * a->in[2 * i + 1];
break;
case INCREASE:
a->out[2 * i + 0] = a->in[2 * i + 0] * a->cup[a->ntup - a->count];
a->out[2 * i + 1] = a->in[2 * i + 1] * a->cup[a->ntup - a->count];
if (a->count-- == 0)
a->state = UNMUTED;
break;
case UNMUTED:
if (a->avsig < a->tail_thresh)
{
a->state = TAIL;
if ((siglimit = a->avsig) > 1.0) siglimit = 1.0;
a->count = (int)((a->min_tail + (a->max_tail - a->min_tail) * (1.0 - siglimit)) * a->rate);
}
a->out[2 * i + 0] = a->in[2 * i + 0];
a->out[2 * i + 1] = a->in[2 * i + 1];
break;
case TAIL:
a->out[2 * i + 0] = a->in[2 * i + 0];
a->out[2 * i + 1] = a->in[2 * i + 1];
if (a->avsig > a->unmute_thresh)
a->state = UNMUTED;
else if (a->count-- == 0)
{
a->state = DECREASE;
a->count = a->ntdown;
}
break;
case DECREASE:
a->out[2 * i + 0] = a->in[2 * i + 0] * a->cdown[a->ntdown - a->count];
a->out[2 * i + 1] = a->in[2 * i + 1] * a->cdown[a->ntdown - a->count];
if (a->count-- == 0)
a->state = MUTED;
break;
}
}
}
else if (a->in != a->out)
memcpy (a->out, a->in, a->size * sizeof (complex));
}
void xamsqcap (AMSQ a)
{
memcpy (a->trigsig, a->trigger, a->size * sizeof (complex));
}
void setBuffers_amsq (AMSQ a, double* in, double* out, double* trigger)
{
a->in = in;
a->out = out;
a->trigger = trigger;
}
void setSamplerate_amsq (AMSQ a, int rate)
{
decalc_amsq (a);
a->rate = rate;
calc_amsq (a);
}
void setSize_amsq (AMSQ a, int size)
{
decalc_amsq (a);
a->size = size;
calc_amsq (a);
}
/********************************************************************************************************
* *
* RXA Properties *
* *
********************************************************************************************************/
PORT
void SetRXAAMSQRun (int channel, int run)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].amsq.p->run = run;
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAAMSQThreshold (int channel, double threshold)
{
double thresh = pow (10.0, threshold / 20.0);
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].amsq.p->tail_thresh = 0.9 * thresh;
rxa[channel].amsq.p->unmute_thresh = thresh;
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAAMSQMaxTail (int channel, double tail)
{
AMSQ a;
EnterCriticalSection (&ch[channel].csDSP);
a = rxa[channel].amsq.p;
if (tail < a->min_tail) tail = a->min_tail;
a->max_tail = tail;
LeaveCriticalSection (&ch[channel].csDSP);
}
/********************************************************************************************************
* *
* TXA Properties *
* *
********************************************************************************************************/
PORT
void SetTXAAMSQRun (int channel, int run)
{
EnterCriticalSection (&ch[channel].csDSP);
txa[channel].amsq.p->run = run;
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetTXAAMSQMutedGain (int channel, double dBlevel)
{ // dBlevel is negative
AMSQ a;
EnterCriticalSection (&ch[channel].csDSP);
a = txa[channel].amsq.p;
a->muted_gain = pow (10.0, dBlevel / 20.0);
compute_slews(a);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetTXAAMSQThreshold (int channel, double threshold)
{
double thresh = pow (10.0, threshold / 20.0);
EnterCriticalSection (&ch[channel].csDSP);
txa[channel].amsq.p->tail_thresh = 0.9 * thresh;
txa[channel].amsq.p->unmute_thresh = thresh;
LeaveCriticalSection (&ch[channel].csDSP);
}
+89
View File
@@ -0,0 +1,89 @@
/* amsq.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _amsq_h
#define _amsq_h
typedef struct _amsq
{
int run; // 0 if squelch system is OFF; 1 if it's ON
int size; // size of input/output buffers
double* in; // squelch input signal buffer
double* out; // squelch output signal buffer
double* trigger; // pointer to trigger data source
double* trigsig; // buffer containing trigger signal
double rate; // sample rate
double avtau; // time constant for averaging noise
double avm;
double onem_avm;
double avsig;
int state; // state machine control
int count;
double tup;
double tdown;
int ntup;
int ntdown;
double* cup;
double* cdown;
double tail_thresh;
double unmute_thresh;
double min_tail;
double max_tail;
double muted_gain;
} amsq, *AMSQ;
extern AMSQ create_amsq (int run, int size, double* in, double* out, double* trigger, int rate, double avtau, double tup, double tdown, double tail_thresh, double unmute_thresh, double min_tail, double max_tail, double muted_gain);
extern void destroy_amsq (AMSQ a);
extern void flush_amsq (AMSQ a);
extern void xamsq (AMSQ a);
extern void xamsqcap (AMSQ a);
extern void setBuffers_amsq (AMSQ a, double* in, double* out, double* trigger);
extern void setSamplerate_amsq (AMSQ a, int rate);
extern void setSize_amsq (AMSQ a, int size);
// RXA Properties
extern __declspec (dllexport) void SetRXAAMSQRun (int channel, int run);
extern __declspec (dllexport) void SetRXAAMSQThreshold (int channel, double threshold);
extern __declspec (dllexport) void SetRXAAMSQMaxTail (int channel, double tail);
// TXA Properties
extern __declspec (dllexport) void SetTXAAMSQRun (int channel, int run);
extern __declspec (dllexport) void SetTXAAMSQMutedGain (int channel, double dBlevel);
extern __declspec (dllexport) void SetTXAAMSQThreshold (int channel, double threshold);
#endif
+1686
View File
File diff suppressed because it is too large Load Diff
+195
View File
@@ -0,0 +1,195 @@
/* analyzer.h
This file is part of a program that implements a Spectrum Analyzer
used in conjunction with software-defined-radio hardware.
Copyright (C) 2012, 2013, 2014, 2016, 2023 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _analyzer_h
#define _analyzer_h
#include "comm.h"
typedef struct _dp
{
int max_size; // maximum fft size to be used
int max_num_fft; // maximum number of LO positions per sub-span to be used
int max_stitch; // maximum number of sub-spans to be concatenated
// NOTE: max_size, max_num_fft, and max_stitch MUST BE <= THE
// CORRESPONDING VALUES IN <analyzer.h>!!
int num_fft; // current number of ffts in use
int num_pixout; // current number of detector/averages/pixel value outputs
int size; // current size of fft input sample vector
int out_size; // current size of fft output vector
int window_type; // type of the window function to be applied
int overlap; // number of samples re-used per fft, range 0 to size-1
int flip[dMAX_NUM_FFT]; // 0 for low-side LO => do NOT flip; 1 for high-side LO => FLIP
int clip; // number of bins to clip off on EACH end of the sub-span fft
// ASSUMES size/2 IS AN EVEN NUMBER!!!
double fsclipL; // number of intervals to clip off the lower end of the TOTAL SPAN
double fsclipH; // number of intervals to clip off the upper end of the TOTAL SPAN
int fscL; // fsclipL modulo (out_size - 2 * clip)
int fscH; // fsclipH modulo (out_size - 2 * clip)
int begin_ss; // number of first sub-span that is NOT completely clipped off
int end_ss; // number of last sub-span that is NOT completely clipped off
int ss_bins[dMAX_STITCH]; // number of bins delivered by eliminate()/Celiminate in each sub-span
volatile LONG input_busy[dMAX_STITCH][dMAX_NUM_FFT];
int num_pixels; // number of pixels requested
int num_stitch; // number of results to be stitched together to generate the pixel frame
unsigned long long stitch_flag;
int spec_flag[dMAX_STITCH]; // flags showing if all ffts for a sub-span are done so elimination can proceed
double pix_per_bin; // number of pixels per fft bin, note that this is fractional, not integral
double det_offset; // offset needed in detector
double bin_per_pix; // number of fft bins per pixel, this is fractional and != 1.0/pix_per_bin
double scale; // output amplitude scale factor
double PiAlpha; // parameter for Kaiser window function
int cal_set; // specifies which set of calibration data to use
double f_min; // frequency at first pixel (for calibration)
double f_max; // frequency at last pixel (for calibration)
int cal_changed; // flag to indicate that the calibration data has changed
double *window; // pointer to buffer to hold window coefficients
double *result[dMAX_STITCH]; // pointers to buffer to hold elimination results for each sub-span
dOUTREAL *pixels[dMAX_PIXOUTS][dNUM_PIXEL_BUFFS]; // pointers pixel output buffers
double *t_pixels[dMAX_PIXOUTS]; // pointer to temporary pixel buffer //pointer to temporary pixel buffer for non-averaged data
int w_pix_buff[dMAX_PIXOUTS]; // number of pixel buffer owned by writing process
int r_pix_buff[dMAX_PIXOUTS]; // number of pixel buffer owned by reading process
int last_pix_buff[dMAX_PIXOUTS]; // number of the last pixel buffer written
volatile LONG pb_ready[dMAX_PIXOUTS][dNUM_PIXEL_BUFFS]; // if value is 0, this data has already been read; 1 = fresh data to read
int num_average[dMAX_PIXOUTS]; // number of spans to average to create the pixels
int avail_frames[dMAX_PIXOUTS]; // number of pixel frames currently available to average
int av_in_idx[dMAX_PIXOUTS]; // input index in averaging pixel buffer ring
int av_out_idx[dMAX_PIXOUTS]; // output index in averaging pixel buffer ring
double *av_sum[dMAX_PIXOUTS]; // pointer to sum buffer for averaging
double *av_buff[dMAX_PIXOUTS][dMAX_AVERAGE]; // pointers to ring of buffers to hold pixel frames for averaging
double *pre_av_out;
int av_mode[dMAX_PIXOUTS];
double av_backmult[dMAX_PIXOUTS]; // back multiplier for weighted averaging
double *cd; // pointer to amplitude calibration buffer
int n_freqs[dMAX_CAL_SETS]; // number of frequencies in each calibration set
double *freqs[dMAX_CAL_SETS]; // pointers to vectors of calibration frequencies
double (*ac3[dMAX_CAL_SETS][dMAX_M]); // pointers to amplitude interpolant coefficients
double (*ac2[dMAX_CAL_SETS][dMAX_M]);
double (*ac1[dMAX_CAL_SETS][dMAX_M]);
double (*ac0[dMAX_CAL_SETS][dMAX_M]);
fftw_plan plan[dMAX_STITCH][dMAX_NUM_FFT]; // fftw plans
fftw_plan Cplan[dMAX_STITCH][dMAX_NUM_FFT];
double *fft_in[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw real input vectors
fftw_complex *Cfft_in[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw complex input vectors
fftw_complex *fft_out[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw complex output vectors
volatile LONG *pnum_threads; // pointer to current number of active worker threads
int stop; // when set, fft threads will be returned to the pool
int end_dispatcher; // set this flag to one to destroy the dispatcher thread
volatile int dispatcher; // one if the dispatcher thread is alive & active
int ss; // sub-span being processed
int LO; // LO (within current sub-span) being processed
int flag;
int have_samples[dMAX_STITCH][dMAX_NUM_FFT]; // number of unused samples remaining in a buffer
int type; // 0 for REAL, 1 for COMPLEX
int incr; // size - overlap
int buff_size; // amount of data to be stored each time an input buffer is opened and closed = JanusAudio/BlockSize
dINREAL* I_samples[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to current input position in I/Q buffers
dINREAL* Q_samples[dMAX_STITCH][dMAX_NUM_FFT];
int bsize; // size of I_samples[][] and Q_samples[][] (number of samples they hold)
int IQout_index[dMAX_STITCH][dMAX_NUM_FFT]; // current output index for I_samples[ss][LO] and Q_samples[ss][LO]
int IQO_idx[dMAX_STITCH][dMAX_NUM_FFT];
int IQin_index[dMAX_STITCH][dMAX_NUM_FFT]; // current input index for I_samples[ss][LO] and Q_samples[ss][LO]
volatile LONG buff_ready[dMAX_STITCH][dMAX_NUM_FFT]; // 1 if buffer ready to read; 0 if needs to be filled
int max_writeahead; // max allowed input samples ahead of where reading output samples
volatile LONG snap[dMAX_STITCH][dMAX_NUM_FFT]; // set to 1 to allow a snap of raw spectrum data
HANDLE hSnapEvent[dMAX_STITCH][dMAX_NUM_FFT]; // mutex handles; mutexes will be used to signal a snap is complete
double *snap_buff[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to buffers for the snap
CRITICAL_SECTION PB_ControlsSection[dMAX_PIXOUTS];
CRITICAL_SECTION SetAnalyzerSection;
CRITICAL_SECTION BufferControlSection[dMAX_STITCH][dMAX_NUM_FFT];
CRITICAL_SECTION StitchSection;
CRITICAL_SECTION EliminateSection[dMAX_STITCH];
CRITICAL_SECTION ResampleSection;
int det_type[dMAX_PIXOUTS]; // detector type
double inv_coherent_gain;
double inherent_power_gain;
double inv_enb;
double norm_oneHz; // dB factor to normalize to one Hz bandwidth
int sample_rate; // sample rate; used for normalization calculations
int normalize[dMAX_PIXOUTS];
} dp, *DP;
extern DP pdisp[];
extern __declspec( dllexport )
void CreateAnalyzer ( int disp,
int *success,
char *app_data_path);
extern __declspec( dllexport )
void XCreateAnalyzer ( int disp,
int *success, //writes '0' to success if all went well, <0 if mem alloc failed
int m_size, //maximum fft size to be used
int m_LO, //maximum number of LO positions per subspan
int m_stitch, //maximum number of subspans to be concatenated
char *app_data_path
);
extern __declspec( dllexport )
void DestroyAnalyzer(int disp);
extern __declspec( dllexport )
void SetCalibration ( int disp,
int set_num, //identifier for this calibration data set
int n_points, //number of calibration points in the set
double (*cal)[dMAX_M+1] //pointer to the calibration table, first
);
extern __declspec( dllexport )
void OpenBuffer(int disp, int ss, int LO, void **Ipointer, void **Qpointer);
extern __declspec( dllexport )
void CloseBuffer(int disp, int ss, int LO);
extern __declspec( dllexport )
void Spectrum(int disp, int ss, int LO, dINREAL* pI, dINREAL* pQ);
extern __declspec( dllexport )
void Spectrum2(int run, int disp, int ss, int LO, dINREAL* pbuff);
extern __declspec( dllexport )
void Spectrum0(int run, int disp, int ss, int LO, double* pbuff);
extern __declspec( dllexport )
void SnapSpectrum( int disp,
int ss,
int LO,
double *snap_buff);
extern __declspec( dllexport )
void SnapSpectrumTimeout (int disp,
int ss,
int LO,
double* snap_buff,
DWORD timeout,
int* flag);
#endif
+239
View File
@@ -0,0 +1,239 @@
/* anf.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2012, 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
ANF create_anf (
int run,
int position,
int buff_size,
double *in_buff,
double *out_buff,
int dline_size,
int n_taps,
int delay,
double two_mu,
double gamma,
double lidx,
double lidx_min,
double lidx_max,
double ngamma,
double den_mult,
double lincr,
double ldecr
)
{
ANF a = (ANF) malloc0 (sizeof(anf));
a->run = run;
a->position = position;
a->buff_size = buff_size;
a->in_buff = in_buff;
a->out_buff = out_buff;
a->dline_size = dline_size;
a->mask = dline_size - 1;
a->n_taps = n_taps;
a->delay = delay;
a->two_mu = two_mu;
a->gamma = gamma;
a->in_idx = 0;
a->lidx = lidx;
a->lidx_min = lidx_min;
a->lidx_max = lidx_max;
a->ngamma = ngamma;
a->den_mult = den_mult;
a->lincr = lincr;
a->ldecr = ldecr;
memset (a->d, 0, sizeof(double) * ANF_DLINE_SIZE);
memset (a->w, 0, sizeof(double) * ANF_DLINE_SIZE);
return a;
}
void destroy_anf (ANF a)
{
_aligned_free (a);
}
void xanf(ANF a, int position)
{
int i, j, idx;
double c0, c1;
double y, error, sigma, inv_sigp;
double nel, nev;
if (a->run && (a->position == position))
{
for (i = 0; i < a->buff_size; i++)
{
a->d[a->in_idx] = a->in_buff[2 * i + 0];
y = 0;
sigma = 0;
for (j = 0; j < a->n_taps; j++)
{
idx = (a->in_idx + j + a->delay) & a->mask;
y += a->w[j] * a->d[idx];
sigma += a->d[idx] * a->d[idx];
}
inv_sigp = 1.0 / (sigma + 1e-10);
error = a->d[a->in_idx] - y;
a->out_buff[2 * i + 0] = error;
a->out_buff[2 * i + 1] = 0.0;
if((nel = error * (1.0 - a->two_mu * sigma * inv_sigp)) < 0.0) nel = -nel;
if((nev = a->d[a->in_idx] - (1.0 - a->two_mu * a->ngamma) * y - a->two_mu * error * sigma * inv_sigp) < 0.0) nev = -nev;
if (nev < nel)
{
if ((a->lidx += a->lincr) > a->lidx_max) a->lidx = a->lidx_max;
}
else
{
if ((a->lidx -= a->ldecr) < a->lidx_min) a->lidx = a->lidx_min;
}
a->ngamma = a->gamma * (a->lidx * a->lidx) * (a->lidx * a->lidx) * a->den_mult;
c0 = 1.0 - a->two_mu * a->ngamma;
c1 = a->two_mu * error * inv_sigp;
for (j = 0; j < a->n_taps; j++)
{
idx = (a->in_idx + j + a->delay) & a->mask;
a->w[j] = c0 * a->w[j] + c1 * a->d[idx];
}
a->in_idx = (a->in_idx + a->mask) & a->mask;
}
}
else if (a->in_buff != a->out_buff)
memcpy (a->out_buff, a->in_buff, a->buff_size * sizeof (complex));
}
void flush_anf (ANF a)
{
memset (a->d, 0, sizeof(double) * ANF_DLINE_SIZE);
memset (a->w, 0, sizeof(double) * ANF_DLINE_SIZE);
a->in_idx = 0;
}
void setBuffers_anf (ANF a, double* in, double* out)
{
a->in_buff = in;
a->out_buff = out;
}
void setSamplerate_anf (ANF a, int rate)
{
flush_anf (a);
}
void setSize_anf (ANF a, int size)
{
a->buff_size = size;
flush_anf (a);
}
/********************************************************************************************************
* *
* RXA Properties *
* *
********************************************************************************************************/
PORT void
SetRXAANFRun (int channel, int run)
{
ANF a = rxa[channel].anf.p;
if (a->run != run)
{
RXAbp1Check (channel, rxa[channel].amd.p->run, rxa[channel].snba.p->run,
rxa[channel].emnr.p->run, run, rxa[channel].anr.p->run);
EnterCriticalSection (&ch[channel].csDSP);
a->run = run;
RXAbp1Set (channel);
flush_anf (a);
LeaveCriticalSection (&ch[channel].csDSP);
}
}
PORT void
SetRXAANFVals (int channel, int taps, int delay, double gain, double leakage)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->n_taps = taps;
rxa[channel].anf.p->delay = delay;
rxa[channel].anf.p->two_mu = gain; //try two_mu = 1e-4
rxa[channel].anf.p->gamma = leakage; //try gamma = 0.10
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFTaps (int channel, int taps)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->n_taps = taps;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFDelay (int channel, int delay)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->delay = delay;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFGain (int channel, double gain)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->two_mu = gain;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFLeakage (int channel, double leakage)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->gamma = leakage;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFPosition (int channel, int position)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->position = position;
rxa[channel].bp1.p->position = position;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
+107
View File
@@ -0,0 +1,107 @@
/* anf.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2012, 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _anf_h
#define _anf_h
#define ANF_DLINE_SIZE 2048
typedef struct _anf
{
int run;
int position;
int buff_size;
double *in_buff;
double *out_buff;
int dline_size;
int mask;
int n_taps;
int delay;
double two_mu;
double gamma;
double d [ANF_DLINE_SIZE];
double w [ANF_DLINE_SIZE];
int in_idx;
double lidx;
double lidx_min;
double lidx_max;
double ngamma;
double den_mult;
double lincr;
double ldecr;
} anf, *ANF;
extern ANF create_anf (
int run,
int position,
int buff_size,
double *in_buff,
double *out_buff,
int dline_size,
int n_taps,
int delay,
double two_mu,
double gamma,
double lidx,
double lidx_min,
double lidx_max,
double ngamma,
double den_mult,
double lincr,
double ldecr
);
extern void destroy_anf (ANF a);
extern void flush_anf (ANF a);
extern void xanf (ANF a, int position);
extern void setBuffers_anf (ANF a, double* in, double* out);
extern void setSamplerate_anf (ANF a, int rate);
extern void setSize_anf (ANF a, int size);
// RXA Properties
extern __declspec (dllexport) void SetRXAANFRun (int channel, int setit);
extern __declspec (dllexport) void SetRXAANFVals (int channel, int taps, int delay, double gain, double leakage);
extern __declspec (dllexport) void SetRXAANFTaps (int channel, int taps);
extern __declspec (dllexport) void SetRXAANFDelay (int channel, int delay);
extern __declspec (dllexport) void SetRXAANFGain (int channel, double gain);
extern __declspec (dllexport) void SetRXAANFLeakage (int channel, double leakage);
extern __declspec (dllexport) void SetRXAANFPosition (int channel, int position);
#endif
+238
View File
@@ -0,0 +1,238 @@
/* anr.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2012, 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
ANR create_anr (
int run,
int position,
int buff_size,
double *in_buff,
double *out_buff,
int dline_size,
int n_taps,
int delay,
double two_mu,
double gamma,
double lidx,
double lidx_min,
double lidx_max,
double ngamma,
double den_mult,
double lincr,
double ldecr
)
{
ANR a = (ANR) malloc0 (sizeof(anr));
a->run = run;
a->position = position;
a->buff_size = buff_size;
a->in_buff = in_buff;
a->out_buff = out_buff;
a->dline_size = dline_size;
a->mask = dline_size - 1;
a->n_taps = n_taps;
a->delay = delay;
a->two_mu = two_mu;
a->gamma = gamma;
a->in_idx = 0;
a->lidx = lidx;
a->lidx_min = lidx_min;
a->lidx_max = lidx_max;
a->ngamma = ngamma;
a->den_mult = den_mult;
a->lincr = lincr;
a->ldecr = ldecr;
memset (a->d, 0, sizeof(double) * ANR_DLINE_SIZE);
memset (a->w, 0, sizeof(double) * ANR_DLINE_SIZE);
return a;
}
void destroy_anr (ANR a)
{
_aligned_free (a);
}
void xanr (ANR a, int position)
{
int i, j, idx;
double c0, c1;
double y, error, sigma, inv_sigp;
double nel, nev;
if (a->run && (a->position == position))
{
for (i = 0; i < a->buff_size; i++)
{
a->d[a->in_idx] = a->in_buff[2 * i + 0];
y = 0;
sigma = 0;
for (j = 0; j < a->n_taps; j++)
{
idx = (a->in_idx + j + a->delay) & a->mask;
y += a->w[j] * a->d[idx];
sigma += a->d[idx] * a->d[idx];
}
inv_sigp = 1.0 / (sigma + 1e-10);
error = a->d[a->in_idx] - y;
a->out_buff[2 * i + 0] = y;
a->out_buff[2 * i + 1] = 0.0;
if((nel = error * (1.0 - a->two_mu * sigma * inv_sigp)) < 0.0) nel = -nel;
if((nev = a->d[a->in_idx] - (1.0 - a->two_mu * a->ngamma) * y - a->two_mu * error * sigma * inv_sigp) < 0.0) nev = -nev;
if (nev < nel)
{
if ((a->lidx += a->lincr) > a->lidx_max) a->lidx = a->lidx_max;
}
else
{
if ((a->lidx -= a->ldecr) < a->lidx_min) a->lidx = a->lidx_min;
}
a->ngamma = a->gamma * (a->lidx * a->lidx) * (a->lidx * a->lidx) * a->den_mult;
c0 = 1.0 - a->two_mu * a->ngamma;
c1 = a->two_mu * error * inv_sigp;
for (j = 0; j < a->n_taps; j++)
{
idx = (a->in_idx + j + a->delay) & a->mask;
a->w[j] = c0 * a->w[j] + c1 * a->d[idx];
}
a->in_idx = (a->in_idx + a->mask) & a->mask;
}
}
else if (a->in_buff != a->out_buff)
memcpy (a->out_buff, a->in_buff, a->buff_size * sizeof (complex));
}
void flush_anr (ANR a)
{
memset (a->d, 0, sizeof(double) * ANR_DLINE_SIZE);
memset (a->w, 0, sizeof(double) * ANR_DLINE_SIZE);
a->in_idx = 0;
}
void setBuffers_anr (ANR a, double* in, double* out)
{
a->in_buff = in;
a->out_buff = out;
}
void setSamplerate_anr (ANR a, int rate)
{
flush_anr(a);
}
void setSize_anr (ANR a, int size)
{
a->buff_size = size;
flush_anr(a);
}
/********************************************************************************************************
* *
* RXA Properties *
* *
********************************************************************************************************/
PORT void
SetRXAANRRun (int channel, int run)
{
ANR a = rxa[channel].anr.p;
if (a->run != run)
{
RXAbp1Check (channel, rxa[channel].amd.p->run, rxa[channel].snba.p->run,
rxa[channel].emnr.p->run, rxa[channel].anf.p->run, run);
EnterCriticalSection (&ch[channel].csDSP);
a->run = run;
RXAbp1Set (channel);
flush_anr (a);
LeaveCriticalSection (&ch[channel].csDSP);
}
}
PORT void
SetRXAANRVals (int channel, int taps, int delay, double gain, double leakage)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anr.p->n_taps = taps;
rxa[channel].anr.p->delay = delay;
rxa[channel].anr.p->two_mu = gain;
rxa[channel].anr.p->gamma = leakage;
flush_anr (rxa[channel].anr.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANRTaps (int channel, int taps)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anr.p->n_taps = taps;
flush_anr (rxa[channel].anr.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANRDelay (int channel, int delay)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anr.p->delay = delay;
flush_anr (rxa[channel].anr.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANRGain (int channel, double gain)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anr.p->two_mu = gain;
flush_anr (rxa[channel].anr.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANRLeakage (int channel, double leakage)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anr.p->gamma = leakage;
flush_anr (rxa[channel].anr.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANRPosition (int channel, int position)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anr.p->position = position;
rxa[channel].bp1.p->position = position;
flush_anr (rxa[channel].anr.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
+107
View File
@@ -0,0 +1,107 @@
/* anr.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2012, 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _anr_h
#define _anr_h
#define ANR_DLINE_SIZE 2048
typedef struct _anr
{
int run;
int position;
int buff_size;
double *in_buff;
double *out_buff;
int dline_size;
int mask;
int n_taps;
int delay;
double two_mu;
double gamma;
double d [ANR_DLINE_SIZE];
double w [ANR_DLINE_SIZE];
int in_idx;
double lidx;
double lidx_min;
double lidx_max;
double ngamma;
double den_mult;
double lincr;
double ldecr;
} anr, *ANR;
extern ANR create_anr (
int run,
int position,
int buff_size,
double *in_buff,
double *out_buff,
int dline_size,
int n_taps,
int delay,
double two_mu,
double gamma,
double lidx,
double lidx_min,
double lidx_max,
double ngamma,
double den_mult,
double lincr,
double ldecr
);
extern void destroy_anr (ANR a);
extern void flush_anr (ANR a);
extern void xanr (ANR a, int position);
extern void setBuffers_anr (ANR a, double* in, double* out);
extern void setSamplerate_anr (ANR a, int rate);
extern void setSize_anr (ANR a, int size);
// RXA Properties
extern __declspec (dllexport) void SetRXAANRRun (int channel, int setit);
extern __declspec (dllexport) void SetRXAANRVals (int channel, int taps, int delay, double gain, double leakage);
extern __declspec (dllexport) void SetRXAANRTaps (int channel, int taps);
extern __declspec (dllexport) void SetRXAANRDelay (int channel, int delay);
extern __declspec (dllexport) void SetRXAANRGain (int channel, double gain);
extern __declspec (dllexport) void SetRXAANRLeakage (int channel, double leakage);
extern __declspec (dllexport) void SetRXAANRPosition (int channel, int position);
#endif

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