Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e219a5b975 | ||
|
|
2a530e5d11 | ||
|
|
ceab3bed26 | ||
|
|
674bbad1ab | ||
|
|
67c6fbcad9 | ||
|
|
84e194c644 | ||
|
|
b45a71b017 | ||
|
|
e6b9d9dd05 | ||
|
|
1f68d05cd5 | ||
|
|
018fc0e871 | ||
|
|
1111d24333 | ||
|
|
d8aee9284d | ||
|
|
7121dacbbd | ||
|
|
0528e8bbbe | ||
|
|
458e43252d | ||
|
|
fd0a8a3d0e | ||
|
|
6c2e9012c4 | ||
|
|
f5c4732eba | ||
|
|
1c209457dc | ||
|
|
7e8a0846f7 | ||
|
|
fb888a2e08 | ||
|
|
78a1ca3d60 | ||
|
|
14a80f09d5 | ||
|
|
1c9f81254b | ||
|
|
76208643a0 | ||
|
|
31ecbce146 | ||
|
|
a1343c1264 | ||
|
|
71146c9eaf | ||
|
|
814e4ce08c | ||
|
|
36d717636c | ||
|
|
26773d5d09 | ||
|
|
15375aa4ea | ||
|
|
7f516653d5 |
@@ -1,3 +1,214 @@
|
||||
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
|
||||
Softrock radios that need a constant audio frequency to simplify amplitude and phase corrections.
|
||||
The value zero means no change to Quisk tuning. Otherwise enter the fixed frequency offset from center.
|
||||
If you are using the obsolete "mouse_tune_method = 1" please delete it and use this new feature.
|
||||
If you use softrock_tune_vfo.py as your hardware file, you should replace it with softrock/hardware_usb.py.
|
||||
|
||||
Quisk now works on Python 3.12. And I fixed a bug in the small screen graph width.
|
||||
|
||||
Quisk Version 4.2.36 August 2024
|
||||
=================================
|
||||
I added two new color schemes by Michael, DK1MI. The On/Off button color and text color can
|
||||
now be configured. (Michael, send me the final colors). There are a few other small changes.
|
||||
|
||||
Quisk Version 4.2.35 June 2024
|
||||
===============================
|
||||
There is a new item "File play level" on the Config/radio/Sound screen to reduce the volume when playing files.
|
||||
Use it to reduce the volume of the CQ message relative to radio sound.
|
||||
|
||||
Dan, AI2M, provided a patch to compile in MacOS on Apple Silicon. Thanks Dan!
|
||||
|
||||
I am working on better amplitude and phase adjustments when using SoftRock radios. The new corrections
|
||||
screen has a spreadsheet to display the VFO and tune offset. And there is a feature to automatically
|
||||
measure the corrections when using a signal generator as a source. If you use Quisk with SoftRock or similar
|
||||
radios, please let me know what you think.
|
||||
|
||||
Quisk Version 4.2.34 May 2024
|
||||
==============================
|
||||
For the Hermes-Lite2 the inhibit Tx function (CN8) now releases the Spot button.
|
||||
|
||||
Quisk displays the band plan below the X-axis as colored bars, and there are eighteen possible colors.
|
||||
If you hover the mouse over a bar, Quisk now displays the name of the mode. Recall that there is a dialog
|
||||
box so you can set the band plan to what you want.
|
||||
|
||||
Quisk Version 4.2.33 May 2024
|
||||
==============================
|
||||
Added the get_level Hamlib command "l STRENGTH". It returns the S-meter reading. I finally found some Hamlib
|
||||
documentation, often not an easy task. The return value of STRENGTH is in dB with zero dB equal to S9.
|
||||
There are 6 dB per S-unit. So a STRENGTH of 4 is 4dB over S9, and -6, -12, -18 is S8, S7, S6. I also added
|
||||
the corresponding FlexRadio command ZZSM.
|
||||
|
||||
Quisk Version 4.2.32 April 2024
|
||||
================================
|
||||
I found and fixed a bug in the microphone filters that can result in a buffer overrun. You should update Quisk.
|
||||
|
||||
Some Linux digital programs can not use the usual Quisk Pulse loopback device and must use an
|
||||
Alsa loopback. I added more documentation for Alsa loopbacks, added more information on the Sound
|
||||
screen and I changed the default Alsa sample size to 16 bit.
|
||||
|
||||
Quisk now responds to the Hermes Lite2 Tx inhibit input by displaying a message and extinguishing the "Tx" light.
|
||||
There is a new item "Hermes Lite2 protocol" on the Hardware screen to control this behavior.
|
||||
|
||||
Quisk Version 4.2.31 March 2024
|
||||
================================
|
||||
The "Tune" item was missing from the Midi menus. I fixed it but I didn't test it because I did
|
||||
not borrow Ben's Midi controller. Please test it yourself and let me know if it works. The Midi
|
||||
speed can be set from the menu too.
|
||||
|
||||
Quisk Version 4.2.30 March 2024
|
||||
================================
|
||||
A new option "Tx-Rx silence msec" on the Timing configuration screen controls how long
|
||||
to suppress Rx audio when switching from Tx to Rx. The previous fixed time was 50 msec.
|
||||
|
||||
The SSB squelch control now works on CW too. Please test.
|
||||
|
||||
Quisk Version 4.2.29 January 2024
|
||||
==================================
|
||||
This version updates the FreeDV library files and program logic. I added a "Monitor"
|
||||
item to the FreeDV menu. It routes the sound to the speakers instead of to the demodulator.
|
||||
Turn on both the monitor and FDX to listen to your transmitted FreeDV signals. Please test.
|
||||
|
||||
Modes 700x and 1600 are finished. The other modes will be added shortly. If you use any of them
|
||||
post here and I will move them to the top of the list.
|
||||
|
||||
Quisk Version 4.2.28 December 2023
|
||||
===================================
|
||||
This version includes a patch from Jon, AB8WU, to build Quisk on FreeBSD. Thanks Jon!
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Quisk Software Defined Radio by N2ADR
|
||||
**December 26, 2023**
|
||||
|
||||
This is Quisk, a Software Defined Radio (SDR).
|
||||
You supply radio hardware such as Hermes Lite2 or SoftRock to convert
|
||||
the antenna voltage to I/Q samples.
|
||||
Then send the samples to a computer running Quisk.
|
||||
The Quisk software will read the I/Q data, tune it, filter it,
|
||||
demodulate it, and send the audio to headphones or speakers.
|
||||
Quisk has a microphone input and a key input so it can operate as a
|
||||
complete transceiver.
|
||||
|
||||
The web page for this project is [https://groups.io/g/n2adr-sdr](https://groups.io/g/n2adr-sdr).
|
||||
|
||||
The change log is [here](CHANGELOG.txt).
|
||||
|
||||
The documentation is [here](docs.html).
|
||||
|
||||
The help file is [here](help.html).
|
||||
|
||||
This site is under construction. Check back soon.
|
||||
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.29
|
||||
from .quisk import main
|
||||
#Quisk version 4.2.54
|
||||
from .quisk import main
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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>
|
||||
@@ -131,7 +134,7 @@ button on the lower right.
|
||||
<h2>News</h2>
|
||||
<p>
|
||||
The newest versions of Linux and Raspberry Pi OS do not allow installing Quisk in the system Python package directory.
|
||||
I am working on a solution for this.
|
||||
Use the Linux Source Installation below instead.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
@@ -189,24 +192,23 @@ Many others contributed to Quisk, and are mentioned in comments in the source co
|
||||
<h2 id="Installation">Installation</h2>
|
||||
<p>
|
||||
Quisk is free open source software written in Python and C. It is hosted on the PyPi repository
|
||||
<a href="https://pypi.org">https://pypi.org</a>.
|
||||
It can be installed using the standard Python setup tools. See specific directions below.
|
||||
<a href="https://pypi.org">https://pypi.org</a> and it is available on github.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
If you have Python 3 installed on your computer, just continue to use that version.
|
||||
<b>Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
|
||||
If you want to install a more recent version, uninstall the old version first.
|
||||
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 the most recent version of 64-bit Python 3.
|
||||
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>Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</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
|
||||
@@ -240,17 +242,26 @@ python -m pip install --upgrade pyserial
|
||||
<br><br>
|
||||
python -m pip install --upgrade quisk
|
||||
<br><br>
|
||||
You should then be able to start Quisk with the command "quisk".
|
||||
You can also start Quisk with "python -m quisk".
|
||||
To create a Quisk shortcut on your desktop, right-click an empty space and select "New" and "Shortcut".
|
||||
Use "quisk.exe" as the command and "Quisk" as the name.
|
||||
The "quisk.exe" program is in <b>your_install_directory</b>\Scripts.
|
||||
Enter this as the location of the item:
|
||||
</p>
|
||||
<br>
|
||||
<pre>
|
||||
pythonw -m quisk
|
||||
</pre>
|
||||
<br>
|
||||
<p>
|
||||
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 "python quisk.py".
|
||||
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>
|
||||
@@ -280,186 +291,115 @@ python -m pip uninstall quisk
|
||||
</p>
|
||||
<h3 id="g0.2.1">Linux Initial Installation</h3>
|
||||
<p>
|
||||
<br>
|
||||
The libsoundio-dev module is NO LONGER required!
|
||||
<br>
|
||||
<br>
|
||||
Linux runs on a wide variety of computers with different processors and versions of Linux.
|
||||
Therefore Quisk is compiled for each one. To do this, Python and a number of other packages are required.
|
||||
Most likely both Python2 and Python3 are already installed on your computer. Either the 32-bit or 64-bit
|
||||
version will work. On my Ubuntu machine, "python2" starts Python2 and "python3" starts Python3.
|
||||
Most likely both Python2 and Python3 are already installed on your computer.
|
||||
On my Ubuntu machine, "python2" starts Python2 and "python3" starts Python3.
|
||||
Just "python" starts Python2, but this will change as Python2 is phased out.
|
||||
Python2 is obsolete, so you should only install Quisk to Python3, and use "python3" to start it.
|
||||
Install these packages. Some of these can be installed from Python using "pip",
|
||||
but it is better to use your package manager because pip and the package manager do not coordinate.
|
||||
<b>Python2 is obsolete</b>, so you should only install Quisk to Python3, and use "python3" to start it.
|
||||
Install these packages by using your package manager, not Python pip.
|
||||
If newer versions of these packages become available, Linux will notify you.
|
||||
Use the most recent version of python3-wxgtk available.
|
||||
Use the most recent version of python3-wxgtk available for your Python version.
|
||||
<br><br>
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Package Description</th>
|
||||
<th>Python3 Package</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Discrete fourier transform</td>
|
||||
<td>sudo apt-get install libfftw3-dev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alsa sound library</td>
|
||||
<td>sudo apt-get install libasound2-dev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Portaudio sound library</td>
|
||||
<td>sudo apt-get install portaudio19-dev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pulseaudio sound library</td>
|
||||
<td>sudo apt-get install libpulse-dev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python development</td>
|
||||
<td>sudo apt-get install python3-dev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python library development</td>
|
||||
<td>sudo apt-get install libpython3-dev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>wxPython GUI library</td>
|
||||
<td>sudo apt-get install python3-wxgtk4.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python USB</td>
|
||||
<td>sudo apt-get install python3-usb</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python serial port</td>
|
||||
<td>sudo apt-get install python3-serial</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python installation tools</td>
|
||||
<td>sudo apt-get install python3-setuptools</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pip package installer</td>
|
||||
<td>sudo apt-get install python3-pip</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
<br><br>
|
||||
These are part of your operating system. You only install them once unless it changes.
|
||||
</p>
|
||||
<br>
|
||||
<pre>
|
||||
sudo apt-get install libfftw3-dev
|
||||
sudo apt-get install libasound2-dev
|
||||
sudo apt-get install portaudio19-dev
|
||||
sudo apt-get install libpulse-dev
|
||||
sudo apt-get install python3-dev
|
||||
sudo apt-get install libpython3-dev
|
||||
</pre>
|
||||
<br>
|
||||
<p>
|
||||
These are part of Python. You only install them once unless the Python started by "python3" changes.
|
||||
</p>
|
||||
<br>
|
||||
<pre>
|
||||
sudo apt-get install python3-wxgtk4.0
|
||||
sudo apt-get install python3-usb
|
||||
sudo apt-get install python3-serial
|
||||
sudo apt-get install python3-setuptools
|
||||
</pre>
|
||||
<br>
|
||||
<p>
|
||||
If you want to use the SoapySDR module make sure that SoapySDR is installed from its Pothosware site before you build Quisk.
|
||||
Otherwise Quisk will not have the files it needs. You can install SoapySDR later, but then you
|
||||
will need to reinstall Quisk. This is also true of any other external packages that have their own C source.
|
||||
will need to run "make" again. This is also true of any other external packages that have their own C source.
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
<h3>Linux Pip Installation</h3>
|
||||
<br>
|
||||
<p>
|
||||
You can use the Python program "pip" to install Quisk into the Python package directory. This is the easiest
|
||||
method, but it will not install the source files. If you want the source files see below.
|
||||
<br>
|
||||
<br>
|
||||
sudo -H python3 -m pip install --upgrade quisk
|
||||
<br>
|
||||
<br>
|
||||
This will install the Python files and all other files except for the C source. This is the easiest method
|
||||
if you do not want to work in C.
|
||||
To run Quisk, use this command from a terminal:
|
||||
<br>
|
||||
<br>
|
||||
python3 -m quisk
|
||||
<br>
|
||||
<br>
|
||||
You can create a panel launcher on your desktop with the command to run Quisk.
|
||||
<br>
|
||||
<br>
|
||||
If the installation of Quisk fails, you can force re-installation:
|
||||
<br>
|
||||
<br>
|
||||
<b> Installation of Quisk using Python tools like pip is deprecated</b>.
|
||||
</p>
|
||||
<pre>
|
||||
sudo -H python3 -m pip uninstall quisk
|
||||
sudo -H python3 -m pip install --no-cache-dir --force-reinstall quisk
|
||||
</pre>
|
||||
<br>
|
||||
<p>
|
||||
Installation to the system Python is no longer allowed, and you may get the message "error: externally-managed-environment".
|
||||
If you used pip to install Quisk you can continue to upgrade with "sudo python3 -m pip install --upgrade quisk"
|
||||
until you get this error message. But it is best to uninstall the pip Quisk. If you installed with pipx, uninstall that too.
|
||||
</p>
|
||||
<br>
|
||||
You can also install an older version of Quisk. You may need to do that if the most recent version fails for some reason. Use:
|
||||
<pre>
|
||||
sudo python3 -m pip uninstall quisk
|
||||
pipx uninstall quisk
|
||||
</pre>
|
||||
<br>
|
||||
<p>
|
||||
Then use the Linux Source Installation.
|
||||
</p>
|
||||
<br>
|
||||
<br>
|
||||
sudo -H python3 -m pip install quisk==4.1.51
|
||||
<h3 id="LSI">Linux Source Installation</h3>
|
||||
<p>
|
||||
On Linux, Quisk is installed from github, and it is contained in a "quisk" directory. First you need to decide where to put the
|
||||
quisk directory. Anywhere will do, but it is best to use a directory where you have write permission so
|
||||
you don't need to use "sudo". The examples below assume you used your home directory indicated by "~". Just replace "~"
|
||||
by your desired location.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
To install a fresh copy of Quisk, change to your desired directory, clone the github site, change to the quisk directory and run "make".
|
||||
Then run the python program quisk.py.
|
||||
</p>
|
||||
<br>
|
||||
<pre>
|
||||
cd ~
|
||||
git clone https://github.com/jimahlstrom/quisk
|
||||
cd quisk
|
||||
make
|
||||
python3 quisk.py
|
||||
</pre>
|
||||
<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 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.
|
||||
<br>
|
||||
<br>
|
||||
To edit the Quisk Python files you need to know where they are. To find out, import quisk and print it:
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
<pre>
|
||||
jim@IntelNUC:~$ python3
|
||||
Python 3.6.9 (default, Oct 8 2020, 12:12:24)
|
||||
[GCC 8.4.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
--- import quisk
|
||||
--- quisk
|
||||
module 'quisk' from '/usr/local/lib/python3.6/dist-packages/quisk/__init__.py'
|
||||
---
|
||||
|
||||
</pre>
|
||||
<br>
|
||||
<h3>Linux Pip Upgrade</h3>
|
||||
<p>
|
||||
To upgrade to a newer version of Quisk, use pip:
|
||||
<br>
|
||||
<br>
|
||||
sudo python3 -m pip install --upgrade quisk
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
<h3>Linux Uninstall Quisk</h3>
|
||||
<p>
|
||||
sudo python3 -m pip uninstall quisk
|
||||
To uninstall Quisk, just delete the quisk directory.
|
||||
</p>
|
||||
<br>
|
||||
<br>
|
||||
<h3>Linux Source Installation</h3>
|
||||
<p>
|
||||
The previous method using pip is simple, but will not install the C source files. If you want those proceed as follows.
|
||||
Go to <a href="https://pypi.org/project/quisk/">https://pypi.org/project/quisk</a> and under "Navigation"
|
||||
select "Download files". Download the quisk-x-x-xx.tar.gz file. This is the source file. The ".whl" files are for Windows.
|
||||
Change directory to where the file was downloaded, probably "Downloads", and uncompress and untar it.
|
||||
<br><br>
|
||||
cd ~/Downloads
|
||||
<br>
|
||||
tar zxf quisk-x.x.xx.tar.gz
|
||||
<br><br>
|
||||
Change directories to the Quisk directory:
|
||||
<br><br>
|
||||
cd quisk-x.x.xx
|
||||
<br><br>
|
||||
Now compile Quisk with the "make" command:
|
||||
<br><br>
|
||||
make quisk3
|
||||
<br><br>
|
||||
If "make" fails, you probably have missing packages or missing "-dev" packages. Try to figure out what is missing from the error messages.
|
||||
Now you can run quisk with the command:
|
||||
<br><br>
|
||||
python3 quisk.py
|
||||
<br><br>
|
||||
At this point you have a choice of where to install Quisk. You could just rename the quisk-x.x.xx directory
|
||||
to a more convenient name, like "quisk" in your home directory, and run Quisk from there.
|
||||
This is convenient if you want to
|
||||
alter the Quisk code. Or you could install Quisk into the Python system.
|
||||
<br><br>
|
||||
python2 setup.py build
|
||||
<br>
|
||||
sudo python2 setup.py install
|
||||
<br><br>
|
||||
</p>
|
||||
<h3 id="g0.2.4">Quisk Files</h3>
|
||||
<p>
|
||||
<br>
|
||||
@@ -602,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>
|
||||
@@ -900,8 +840,14 @@ SoftRock radios use an analog mixer to change the RF signal to stereo audio, and
|
||||
A high quality sound card is advisable. The analog mixer is not perfect, and it will be necessary to adjust the I and Q
|
||||
signals to equal amplitude and 90 degrees phase difference. Use the Config/Config screen buttons to
|
||||
bring up an adjustment screen. Adjustments must be made for each band, and separately for transmit and receive.
|
||||
The adjustment depends on both the VFO frequency and the tuning offset from the VFO. See my paper
|
||||
The adjustment depends on both the VFO frequency and the tuning offset from the VFO.
|
||||
Quisk has an option to fix the tuning offset to a constant and to tune with the hardware.
|
||||
The tuning offset then has a constant correction and it is only necessary to correct for the VFO,
|
||||
a much less sensitive correction.
|
||||
See my paper
|
||||
<a href="http://james.ahlstrom.name/phase_corr.html">http://james.ahlstrom.name/phase_corr.html</a> for more information.
|
||||
<br>
|
||||
<br>
|
||||
A good strategy is to pick a VFO near the band center, and record corrections at an Rx frequency of -15000, -1000, 1000 and 15000 Hertz.
|
||||
If the corrections are sensitive to VFO, record the corrections for these same Rx frequencies at VFOs equal to
|
||||
or slightly outside the upper and lower band edges.
|
||||
@@ -910,15 +856,20 @@ You can add as many correction points as desired. The corrections are saved in t
|
||||
by hand if you are a Python expert. Otherwise you can just read the values.
|
||||
<br>
|
||||
<br>
|
||||
If you fix the tuning offset to a constant, you can create a correction for VFO at the band center, or
|
||||
the center and both edges.
|
||||
<br>
|
||||
<br>
|
||||
To create a Receive correction point for a given VFO and frequency, attach a signal generator to the SoftRock through an attenuator,
|
||||
and look at the image on the graph screen. If the signal is 3500 Hertz above the VFO, the image is 3500 Hertz below it.
|
||||
If you don't have a signal generator, find a strong station on the band and look at its image. Minimize the image by adjusting
|
||||
the amplitude and phase sliders on the adjustment screen, and then press "Save".
|
||||
Choose "Measure" to measure the corrections and then "Add Cell".
|
||||
If you don't have a signal generator, find a strong station on the band and look at its image. Choose "Manual adjustment".
|
||||
Minimize the image by adjusting the amplitude and phase sliders on the adjustment screen, and then press "Add cell".
|
||||
<br>
|
||||
<br>
|
||||
To create a Transmit correction point for a given VFO and frequency, attach the SoftRock RF output to a spectrum analyzer through an attenuator,
|
||||
and look at the image. If you don't have a spectrum analyzer use a second receiver tuned to the image. Minimize the image by adjusting
|
||||
the amplitude and phase sliders on the adjustment screen, and then press "Save".
|
||||
the amplitude and phase sliders on the adjustment screen, and then press "Add cell".
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
@@ -1486,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
|
||||
@@ -1544,28 +1521,47 @@ show the devices being used. Change the Fldigi playback to QuiskDigitalInput, an
|
||||
Monitor of QuiskDigitalOutput. Then everything should work. You do not need to use pavucontrol again because PulseAudio will
|
||||
remember the settings.
|
||||
<br>
|
||||
|
||||
<br>
|
||||
If you don't have PulseAudio or don't want to use it, you can use the ALSA loopback device.
|
||||
The ALSA loopback device works the same way as the Windows VAC. First
|
||||
create the loopback device with the command "modprobe snd-aloop" (you
|
||||
will need to be root). You can create the loopback device
|
||||
when the system starts, but the way to do that depends on your version of
|
||||
Linux. I added snd-aloop to /etc/modules. You could put the modprobe command in /etc/rc.local instead.
|
||||
Michael, DK1MI/N1BSD, has written a nice description of how to set up Fldigi. It is available
|
||||
at his site <a href="https://dk1mi.radio/quisk-fldigi/">https://dk1mi.radio/quisk-fldigi/</a>.
|
||||
<br>
|
||||
<br>
|
||||
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. The cards are also shown on the Quisk Sound
|
||||
screen. The Loopback card has one side that connects to Quisk and
|
||||
print out your sound cards, and you should see a "Loopback" card listed.
|
||||
The Loopback card has one side that connects to Quisk and
|
||||
another side that connects to your digital program. For the Quisk
|
||||
side connect both Digital Input and Digital Output to Loopback,0.
|
||||
side connect both Digital Input and Digital Output to the Loopback at hw:19,0.
|
||||
Note that the Loopback card is full duplex, and handles both
|
||||
input and output. There are actually eight loopbacks created at once,
|
||||
but we are only using subdevice 0. For the digital program side, set the input and
|
||||
output to "Loopback,1".
|
||||
output to the Loopback at hw:19,1.
|
||||
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>
|
||||
@@ -1641,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
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
#include <Python.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <complex.h>
|
||||
#include "quisk.h"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
@@ -586,7 +586,7 @@ double quiskAudio24p6Coefs[36] = { 0.001199140008010727, 0.005953815908571521, 0
|
||||
-0.008973143363910490, 0.011839435384729619, 0.003122601697996235, -0.008665733839154772, -0.000319602525571569, 0.008621055763448699,
|
||||
0.005953815908571521, 0.001199140008010727} ;
|
||||
|
||||
// Sample 48 kHz, pass 6, stop 8, ripple 0.5 dB, atten 80 dB. Stop 0.16667.
|
||||
// Sample 48 kHz, pass 6, stop 8, ripple 0.5 dB, atten 80 dB. Pass 0.125. Stop 0.16667.
|
||||
double quiskAudio48p6Coefs[71] = { 0.000324677779792651, 0.001038192405843851, 0.002082337464742640, 0.002938055116822665,
|
||||
0.002811674671240099, 0.001132278163758895, -0.001835394694865431, -0.004766409947196539, -0.005833007003128954, -0.003914015660218843,
|
||||
0.000296099005521212, 0.004239593489554206, 0.004918390640978499, 0.001100872619673516, -0.005241634309386784,
|
||||
@@ -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,707 +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 { // from freedv_api.h
|
||||
int interleave_frames;
|
||||
};
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
#include <windows.h>
|
||||
HMODULE hLib;
|
||||
#define GET_HANDLE1 hLib = LoadLibrary("libcodec2.dll")
|
||||
#define GET_HANDLE2 hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll")
|
||||
#define GET_HANDLE3 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll")
|
||||
#define GET_HANDLE4 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll")
|
||||
#define GET_ADDR(name) (void *)GetProcAddress(hLib, name)
|
||||
#define CLOSE_LIB FreeLibrary(hLib)
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
void * hLib;
|
||||
#define GET_HANDLE1 hLib = dlopen("libcodec2.so", RTLD_LAZY)
|
||||
#define GET_HANDLE2 hLib = dlopen("./freedvpkg/libcodec2.so", RTLD_LAZY)
|
||||
#define GET_HANDLE3 hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY)
|
||||
#define GET_HANDLE4 hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY)
|
||||
#define GET_ADDR(name) dlsym(hLib, name)
|
||||
#define CLOSE_LIB dlclose(hLib)
|
||||
#endif
|
||||
|
||||
static int requested_mode = -1; // requested mode
|
||||
int freedv_current_mode = -1; // the current running mode
|
||||
static int quisk_freedv_squelch;
|
||||
static int interleave_frames = 1;
|
||||
static int freedv_version = -1;
|
||||
static int quisk_set_tx_bpf = 1;
|
||||
static int handle_index = -1;
|
||||
// need access to these here and in quisk.c
|
||||
int n_speech_sample_rate = 8000;
|
||||
int n_modem_sample_rate = 8000;
|
||||
|
||||
#define SPEECH_BUF_SIZE 12000 // speech buffer size increased from 3000
|
||||
static struct _rx_channel{
|
||||
struct freedv * hFreedv;
|
||||
COMP * demod_in;
|
||||
int rxdata_index;
|
||||
short speech_out[SPEECH_BUF_SIZE]; // output buffer
|
||||
int speech_available; // number of samples in output buffer
|
||||
int playing; // are we currently returning speech samples?
|
||||
} rx_channel[MAX_RECEIVERS] = { 0 }; // safe initialisation to 0
|
||||
|
||||
// freedv_version is the library version number, or
|
||||
// -1 no library was found
|
||||
// -2 a library was found, but freedv_get_version is missing
|
||||
|
||||
// FreeDV API functions:
|
||||
// open, close
|
||||
struct freedv * (*freedv_open)(int mode);
|
||||
struct freedv * (*freedv_open_advanced)(int mode, struct freedv_advanced *adv);
|
||||
void (*freedv_close)(struct freedv *freedv);
|
||||
// Transmit
|
||||
void (*freedv_tx)(struct freedv *freedv, short *, short *);
|
||||
void (*freedv_comptx)(struct freedv *freedv, COMP *, short *);
|
||||
// Receive
|
||||
int (*freedv_nin)(struct freedv *freedv);
|
||||
int (*freedv_rx)(struct freedv *freedv, short *, short demod_in[]);
|
||||
int (*freedv_floatrx)(struct freedv *freedv, short *, float demod_in[]);
|
||||
int (*freedv_comprx)(struct freedv *freedv, short *, COMP demod_in[]);
|
||||
// Set parameters
|
||||
void (*freedv_set_callback_txt) (struct freedv *freedv, freedv_callback_rx rx, freedv_callback_tx tx, void *callback_state);
|
||||
void (*freedv_set_test_frames) (struct freedv *freedv, int test_frames);
|
||||
void (*freedv_set_smooth_symbols) (struct freedv *freedv, int smooth_symbols);
|
||||
void (*freedv_set_squelch_en) (struct freedv *freedv, int squelch_en);
|
||||
void (*freedv_set_snr_squelch_thresh) (struct freedv *freedv, float snr_squelch_thresh);
|
||||
void (*freedv_set_tx_bpf) (struct freedv *freedv, int val);
|
||||
// Get parameters
|
||||
int (*freedv_get_version)(void);
|
||||
void (*freedv_get_modem_stats)(struct freedv *freedv, int *sync, float *snr_est);
|
||||
int (*freedv_get_test_frames) (struct freedv *freedv);
|
||||
int (*freedv_get_n_speech_samples) (struct freedv *freedv);
|
||||
int (*freedv_get_n_max_modem_samples) (struct freedv *freedv);
|
||||
int (*freedv_get_n_nom_modem_samples) (struct freedv *freedv);
|
||||
int (*freedv_get_total_bits) (struct freedv *freedv);
|
||||
int (*freedv_get_total_bit_errors) (struct freedv *freedv);
|
||||
int (*freedv_get_modem_sample_rate) (struct freedv *freedv);
|
||||
int (*freedv_get_speech_sample_rate) (struct freedv *freedv);
|
||||
//
|
||||
int (*freedv_get_sync) (struct freedv *freedv);
|
||||
void (*freedv_set_callback_protocol) (struct freedv *freedv, freedv_callback_protorx rx, freedv_callback_prototx tx, void *callback_state);
|
||||
void (*freedv_set_callback_data) (struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state);
|
||||
|
||||
//
|
||||
// checkAvxSupport
|
||||
//
|
||||
// This function was copied from freedv, file fdmdv2_main.cpp and tweaked for use here
|
||||
//
|
||||
// Tests the underlying platform for AVX support. 2020 needs AVX support to run
|
||||
// in real-time, and old processors do not offer AVX support
|
||||
//
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
|
||||
#include <cpuid.h>
|
||||
static int checkAvxSupport(void)
|
||||
{
|
||||
|
||||
int isAvxPresent = 0;
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
eax = ebx = ecx = edx = 0;
|
||||
__cpuid(1, eax, ebx, ecx, edx);
|
||||
|
||||
if (ecx & (1<<27) && ecx & (1<<28)) {
|
||||
// CPU supports XSAVE and AVX
|
||||
uint32_t xcr0, xcr0_high;
|
||||
asm("xgetbv" : "=a" (xcr0), "=d" (xcr0_high) : "c" (0));
|
||||
isAvxPresent = (xcr0 & 6) == 6; // AVX state saving enabled?
|
||||
}
|
||||
return isAvxPresent;
|
||||
}
|
||||
#else
|
||||
static int checkAvxSupport(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Called when a new packet can be sent */
|
||||
void my_datatx(void *callback_state, unsigned char *packet, size_t *size) {
|
||||
*size = 0;
|
||||
}
|
||||
|
||||
static void GetAddrs(void)
|
||||
{
|
||||
handle_index = 1;
|
||||
GET_HANDLE1;
|
||||
if ( ! hLib) {
|
||||
handle_index = 2;
|
||||
GET_HANDLE2;
|
||||
if ( ! hLib) {
|
||||
handle_index = 3;
|
||||
GET_HANDLE3;
|
||||
if ( ! hLib) {
|
||||
handle_index = 4;
|
||||
GET_HANDLE4;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hLib) {
|
||||
freedv_get_version = GET_ADDR("freedv_get_version");
|
||||
if (freedv_get_version != NULL) {
|
||||
freedv_version = freedv_get_version();
|
||||
if (DEBUG)
|
||||
printf("FreeDV codec2 library %d found, version %d\n", handle_index, freedv_version);
|
||||
}
|
||||
else {
|
||||
freedv_version = -2;
|
||||
if (DEBUG)
|
||||
printf("FreeDV codec2 library %d found, but no freedv_get_version() !!!\n", handle_index);
|
||||
CLOSE_LIB;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
handle_index = -1;
|
||||
if (DEBUG)
|
||||
printf("Could not find the FreeDV codec2 shared library\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// open, close
|
||||
freedv_open = GET_ADDR("freedv_open");
|
||||
freedv_open_advanced = GET_ADDR("freedv_open_advanced");
|
||||
freedv_close = GET_ADDR("freedv_close");
|
||||
// Transmit
|
||||
freedv_tx = GET_ADDR("freedv_tx");
|
||||
freedv_comptx = GET_ADDR("freedv_comptx");
|
||||
// Receive
|
||||
freedv_nin = GET_ADDR("freedv_nin");
|
||||
freedv_rx = GET_ADDR("freedv_rx");
|
||||
freedv_floatrx = GET_ADDR("freedv_floatrx");
|
||||
freedv_comprx = GET_ADDR("freedv_comprx");
|
||||
// Set parameters
|
||||
freedv_set_callback_txt = GET_ADDR("freedv_set_callback_txt");
|
||||
freedv_set_callback_protocol = GET_ADDR("freedv_set_callback_protocol");
|
||||
freedv_set_callback_data = GET_ADDR("freedv_set_callback_data");
|
||||
freedv_set_test_frames = GET_ADDR("freedv_set_test_frames");
|
||||
freedv_set_smooth_symbols = GET_ADDR("freedv_set_smooth_symbols");
|
||||
freedv_set_squelch_en = GET_ADDR("freedv_set_squelch_en");
|
||||
freedv_set_snr_squelch_thresh = GET_ADDR("freedv_set_snr_squelch_thresh");
|
||||
freedv_set_tx_bpf = GET_ADDR("freedv_set_tx_bpf");
|
||||
// Get parameters
|
||||
freedv_get_modem_stats = GET_ADDR("freedv_get_modem_stats");
|
||||
freedv_get_test_frames = GET_ADDR("freedv_get_test_frames");
|
||||
freedv_get_n_speech_samples = GET_ADDR("freedv_get_n_speech_samples");
|
||||
freedv_get_n_max_modem_samples = GET_ADDR("freedv_get_n_max_modem_samples");
|
||||
freedv_get_n_nom_modem_samples = GET_ADDR("freedv_get_n_nom_modem_samples");
|
||||
freedv_get_total_bits = GET_ADDR("freedv_get_total_bits");
|
||||
freedv_get_total_bit_errors = GET_ADDR("freedv_get_total_bit_errors");
|
||||
freedv_get_sync = GET_ADDR("freedv_get_sync"); // requires version 11
|
||||
if( freedv_version >= 11)
|
||||
freedv_get_modem_sample_rate = GET_ADDR("freedv_get_modem_sample_rate"); // requires version 11 or later
|
||||
if (freedv_version >= 12)
|
||||
freedv_get_speech_sample_rate = GET_ADDR("freedv_get_speech_sample_rate"); // requires version 12
|
||||
return;
|
||||
}
|
||||
|
||||
static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int count, int bank) // Called from the sound thread.
|
||||
{ // Input digital modulation is cSamples; decoded voice is dsamples. Each "bank" is a stream of audio.
|
||||
// caller will have decimated input audio down to n_modem_sample_rate and reduced block size to match reduction
|
||||
// output data will be returned at n_speech_sample_rate with corresponding block sizes
|
||||
int i, nout, need, have, sync;
|
||||
int n_speech_samples;
|
||||
complex double cx;
|
||||
double scale = (double)CLIP32 / CLIP16; // convert 32 bits to 16 bits
|
||||
struct freedv * hF;
|
||||
struct _rx_channel * pCh;
|
||||
int nCountOut = 1; // In case we have a higher (or lower) value for o/p count
|
||||
|
||||
if (cSamples == NULL) { // shutdown
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
if (rx_channel[i].demod_in) {
|
||||
free(rx_channel[i].demod_in);
|
||||
rx_channel[i].demod_in = NULL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (bank < 0 || bank >= MAX_RECEIVERS)
|
||||
return 0;
|
||||
hF = rx_channel[bank].hFreedv;
|
||||
if ( ! hF)
|
||||
return 0;
|
||||
pCh = rx_channel + bank;
|
||||
n_speech_samples = freedv_get_n_speech_samples(hF);
|
||||
// if (say) input modem rate is the 2020 mode rate of 8000 sa/sec
|
||||
// and output rate is the corresponding 16000 sa/sec then for an input
|
||||
// block size of N, outbut 2N samples block size.
|
||||
// Apply a multiplier/divisor to 'count' for the output samples count
|
||||
if(n_speech_sample_rate >= n_modem_sample_rate ) {
|
||||
int nRatio = n_speech_sample_rate / n_modem_sample_rate;
|
||||
if( nRatio >= 1 && nRatio <= 6)
|
||||
nCountOut = nRatio * count;
|
||||
}
|
||||
else {
|
||||
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
|
||||
if( nRatio >= 1 && nRatio <= 6)
|
||||
nCountOut = count / nRatio;
|
||||
}
|
||||
nout = 0;
|
||||
need = freedv_nin(hF);
|
||||
for (i = 0; i < count; i++) {
|
||||
cx = cRxFilterOut(cSamples[i], bank, 0);
|
||||
if (rxMode == FDV_L) // lower sideband
|
||||
cx = conj(cx);
|
||||
#if 1 // Try it the other way and remove scaling factor
|
||||
pCh->demod_in[pCh->rxdata_index].real = creal(cx);
|
||||
pCh->demod_in[pCh->rxdata_index].imag = cimag(cx);
|
||||
#else
|
||||
pCh->demod_in[pCh->rxdata_index].real = (creal(cx) - cimag(cx));
|
||||
pCh->demod_in[pCh->rxdata_index].imag = 0;
|
||||
#endif
|
||||
pCh->rxdata_index++;
|
||||
if (pCh->rxdata_index >= need) {
|
||||
if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space
|
||||
have = freedv_comprx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in);
|
||||
if (freedv_version > 10)
|
||||
sync = freedv_get_sync(hF);
|
||||
else
|
||||
freedv_get_modem_stats(hF, &sync, NULL);
|
||||
if (freedv_current_mode == 0) { // mode 1600
|
||||
if (sync) // throw away speech if not in sync
|
||||
pCh->speech_available += have;
|
||||
}
|
||||
else if (pCh->speech_available < SPEECH_BUF_SIZE * 2 / 3) {
|
||||
pCh->speech_available += have; // keep speech if there is space
|
||||
}
|
||||
else {
|
||||
if (DEBUG) printf("Close to maximum in speech output buffer\n");
|
||||
}
|
||||
}
|
||||
else { // no space in buffer
|
||||
if (DEBUG) printf("Overflow in speech output buffer\n");
|
||||
}
|
||||
pCh->rxdata_index = 0;
|
||||
need = freedv_nin(hF);
|
||||
}
|
||||
}
|
||||
if ( ! pCh->playing) {
|
||||
if (pCh->speech_available >= 2 * n_speech_samples) {
|
||||
pCh->playing = 1;
|
||||
}
|
||||
else { // return zero samples
|
||||
for (i = 0; i < nCountOut; i++)
|
||||
dsamples[i] = 0;
|
||||
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
|
||||
return nCountOut;
|
||||
}
|
||||
}
|
||||
for (nout = 0; nout < pCh->speech_available && nout < nCountOut; nout++)
|
||||
dsamples[nout] = pCh->speech_out[nout] * scale * 0.7;
|
||||
if (nout) {
|
||||
pCh->speech_available -= nout;
|
||||
memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short));
|
||||
}
|
||||
if ( ! pCh->speech_available) {
|
||||
pCh->playing = 0;
|
||||
while (nout < nCountOut)
|
||||
dsamples[nout++] = 0;
|
||||
}
|
||||
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
|
||||
return nout;
|
||||
}
|
||||
|
||||
static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count) // Called from the sound thread.
|
||||
{ // Input voice samples are dsamples; output digital modulation is cSamples.
|
||||
int i, nout;
|
||||
int n_speech_samples;
|
||||
int n_nom_modem_samples;
|
||||
static COMP * mod_out = NULL;
|
||||
static short * speech_in = NULL;
|
||||
static int speech_index=0, mod_index=0;
|
||||
// g8kbb - add for 800XA
|
||||
static short *real_mod_out = NULL;
|
||||
|
||||
if (dsamples == NULL) { // shutdown
|
||||
if (mod_out)
|
||||
free(mod_out);
|
||||
mod_out = NULL;
|
||||
if (real_mod_out)
|
||||
free(real_mod_out);
|
||||
real_mod_out = NULL;
|
||||
if (speech_in)
|
||||
free(speech_in);
|
||||
speech_in = NULL;
|
||||
return 0;
|
||||
}
|
||||
if ( ! rx_channel[0].hFreedv)
|
||||
return 0;
|
||||
n_speech_samples = freedv_get_n_speech_samples(rx_channel[0].hFreedv);
|
||||
n_nom_modem_samples = freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv);
|
||||
// find if need to output at faster rate
|
||||
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
|
||||
if( nRatio < 1 || nRatio > 6 )
|
||||
nRatio = 1;
|
||||
if (mod_out == NULL) { // initialize
|
||||
mod_out = (COMP *)malloc(sizeof(COMP) * n_nom_modem_samples);
|
||||
memset(mod_out, 0, sizeof(COMP) * n_nom_modem_samples);
|
||||
speech_in = (short*)malloc(sizeof(short) * n_speech_samples);
|
||||
speech_index=0;
|
||||
mod_index=0;
|
||||
real_mod_out = (short *)malloc(sizeof(short) * n_nom_modem_samples);
|
||||
memset(real_mod_out, 0, sizeof(short) * n_nom_modem_samples);
|
||||
//if( DEBUG ) printf("Initialise tx buffer size = %d, i/p sample size = %d\n", n_nom_modem_samples, n_speech_samples );
|
||||
}
|
||||
nout = 0;
|
||||
for (i = 0; i < count; i++) {
|
||||
speech_in[speech_index++] = (short)dsamples[i];
|
||||
if (speech_index >= n_speech_samples) {
|
||||
// Calculate a new block, but first write out the rest of the old block
|
||||
// 800xa uses real not complex
|
||||
if( freedv_current_mode == FREEDV_MODE_800XA ) {
|
||||
for ( ; mod_index < n_nom_modem_samples; mod_index++)
|
||||
cSamples[nout++] = real_mod_out[mod_index];
|
||||
freedv_tx(rx_channel[0].hFreedv, real_mod_out, speech_in);
|
||||
}
|
||||
else {
|
||||
for ( ; mod_index < n_nom_modem_samples; mod_index++)
|
||||
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
|
||||
freedv_comptx(rx_channel[0].hFreedv, mod_out, speech_in);
|
||||
}
|
||||
//if( DEBUG ) printf("Block processed, speech index=%d, n_speech_samples=%d, count=%d\n", speech_index, n_speech_samples, count );
|
||||
mod_index = 0;
|
||||
speech_index = 0;
|
||||
}
|
||||
else { // write out samples
|
||||
for( int j=0; j<nRatio; j++) {
|
||||
if (mod_index < n_nom_modem_samples ) {
|
||||
if( freedv_current_mode == FREEDV_MODE_800XA ) {
|
||||
cSamples[nout++] = real_mod_out[mod_index];
|
||||
}
|
||||
else {
|
||||
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
|
||||
}
|
||||
mod_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rxMode == FDV_L)
|
||||
for (i = 0; i < nout; i++)
|
||||
cSamples[i] = conj(cSamples[i]);
|
||||
return nout;
|
||||
}
|
||||
|
||||
#define TX_MSG_SIZE 80
|
||||
static char quisk_tx_msg[TX_MSG_SIZE];
|
||||
|
||||
static char get_next_tx_char(void * callback_state)
|
||||
{
|
||||
char c;
|
||||
static int index = 0;
|
||||
|
||||
c = quisk_tx_msg[index++];
|
||||
if (index >= TX_MSG_SIZE)
|
||||
index = 0;
|
||||
if ( ! c) {
|
||||
index = 0;
|
||||
c = quisk_tx_msg[index++];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
#define RX_MSG_SIZE 80
|
||||
static char quisk_rx_msg[RX_MSG_SIZE + 1];
|
||||
|
||||
static void put_next_rx_char(void * callback_state, char ch)
|
||||
{
|
||||
if (ch == '\n' || ch == '\r')
|
||||
ch = ' ';
|
||||
if (ch < 32 || ch > 126) // printable characters
|
||||
return;
|
||||
if (strlen(quisk_rx_msg) < RX_MSG_SIZE)
|
||||
strncat(quisk_rx_msg, &ch, 1);
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_get_rx_char(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
PyObject * txt;
|
||||
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
txt = PyString_FromString(quisk_rx_msg);
|
||||
quisk_rx_msg[0] = 0;
|
||||
return txt;
|
||||
}
|
||||
|
||||
static void CloseFreedv(void) // Called from the GUI thread or sound thread
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
if (rx_channel[i].hFreedv) {
|
||||
freedv_close(rx_channel[i].hFreedv);
|
||||
rx_channel[i].hFreedv = NULL;
|
||||
}
|
||||
#if 0 // g8kbb - unnecessary - quisk_freedv_rx() will do it
|
||||
if (rx_channel[i].demod_in) {
|
||||
free(rx_channel[i].demod_in);
|
||||
rx_channel[i].demod_in = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
quisk_freedv_rx(NULL, NULL, 0, 0);
|
||||
quisk_freedv_tx(NULL, NULL, 0);
|
||||
freedv_current_mode = -1;
|
||||
}
|
||||
|
||||
static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
||||
{
|
||||
int i, n_max_modem_samples;
|
||||
struct freedv * hF;
|
||||
|
||||
if ( ! hLib)
|
||||
GetAddrs(); // Get the entry points for funtions in the codec2 library
|
||||
if (DEBUG) {
|
||||
switch (handle_index) {
|
||||
case 1:
|
||||
printf("freedv_open: system codec2 library found, version %d\n", freedv_version);
|
||||
break;
|
||||
case 2:
|
||||
printf("freedv_open: codec2 library freedvpkg/libcodec2.dll|so found, version %d\n", freedv_version);
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
printf("freedv_open: codec2 library freedvpkg/libcodec2_32|64.dll|so found, version %d\n", freedv_version);
|
||||
break;
|
||||
default:
|
||||
printf("freedv_open: Could not find the FreeDV codec2 shared library\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (freedv_version < 10) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
if (DEBUG)
|
||||
printf("freedv_open: Failure because version is less than 10\n");
|
||||
return 0; // failure
|
||||
}
|
||||
if (requested_mode == FREEDV_MODE_2020) {
|
||||
if (checkAvxSupport() == 0) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
if (DEBUG)
|
||||
printf("freedv_open: Failure because mode 2020 requires Avx support\n");
|
||||
return 0; // failure
|
||||
}
|
||||
if (handle_index >= 3) { // Quisk provided codec2
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
if (DEBUG)
|
||||
printf("freedv_open: Failure because mode 2020 requires a system installation of codec2\n");
|
||||
return 0; // failure
|
||||
}
|
||||
}
|
||||
if (requested_mode == FREEDV_MODE_700E) {
|
||||
if (handle_index >= 3) { // Quisk provided codec2
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
if (DEBUG)
|
||||
printf("freedv_open: Failure because mode 700E requires a system installation of codec2\n");
|
||||
return 0; // failure
|
||||
}
|
||||
}
|
||||
|
||||
if ((requested_mode == FREEDV_MODE_700D || requested_mode == FREEDV_MODE_700E) && freedv_open_advanced) {
|
||||
struct freedv_advanced adv;
|
||||
adv.interleave_frames = interleave_frames;
|
||||
hF = freedv_open_advanced(requested_mode, &adv);
|
||||
}
|
||||
else {
|
||||
hF = freedv_open(requested_mode);
|
||||
}
|
||||
if (hF == NULL) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
return 0; // failure
|
||||
}
|
||||
rx_channel[0].hFreedv = hF;
|
||||
quisk_dvoice_freedv(&quisk_freedv_rx, &quisk_freedv_tx);
|
||||
if (quisk_tx_msg[0])
|
||||
freedv_set_callback_txt(hF, &put_next_rx_char, &get_next_tx_char, NULL);
|
||||
else
|
||||
freedv_set_callback_txt(hF, &put_next_rx_char, NULL, NULL);
|
||||
if (freedv_set_callback_protocol)
|
||||
freedv_set_callback_protocol(hF, NULL, NULL, NULL);
|
||||
if (freedv_set_callback_data)
|
||||
freedv_set_callback_data(hF, NULL, my_datatx, NULL);
|
||||
freedv_set_squelch_en(hF, quisk_freedv_squelch);
|
||||
if (freedv_set_tx_bpf)
|
||||
freedv_set_tx_bpf(hF, quisk_set_tx_bpf);
|
||||
n_max_modem_samples = freedv_get_n_max_modem_samples(hF);
|
||||
n_speech_sample_rate = 8000;
|
||||
if (freedv_version >= 12 && freedv_get_speech_sample_rate)
|
||||
n_speech_sample_rate = freedv_get_speech_sample_rate(hF);
|
||||
n_modem_sample_rate = 8000;
|
||||
if (freedv_version >= 11 && freedv_get_modem_sample_rate)
|
||||
n_modem_sample_rate = freedv_get_modem_sample_rate(hF);
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
rx_channel[i].rxdata_index = 0;
|
||||
rx_channel[i].speech_available = 0;
|
||||
rx_channel[i].playing = 0;
|
||||
if (rx_channel[i].demod_in)
|
||||
free(rx_channel[i].demod_in);
|
||||
rx_channel[i].demod_in = (COMP *)malloc(sizeof(COMP) * n_max_modem_samples);
|
||||
if( rx_channel[i].demod_in == NULL) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
return 0; // failure
|
||||
}
|
||||
if (i > 0) {
|
||||
rx_channel[i].hFreedv = freedv_open(requested_mode);
|
||||
if (rx_channel[i].hFreedv)
|
||||
freedv_set_squelch_en(rx_channel[i].hFreedv, quisk_freedv_squelch);
|
||||
}
|
||||
}
|
||||
if (DEBUG) printf("n_nom_modem_samples %d\n", freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv));
|
||||
if (DEBUG) printf("n_speech_samples %d\n", freedv_get_n_speech_samples(rx_channel[0].hFreedv));
|
||||
if (DEBUG) printf("n_max_modem_samples %d\n", n_max_modem_samples);
|
||||
if (DEBUG) {
|
||||
if (freedv_version >= 11) printf("modem_sample_rate %d\n", n_modem_sample_rate);
|
||||
if (freedv_version >= 12) printf("speech_sample_rate %d\n", n_speech_sample_rate);
|
||||
}
|
||||
freedv_current_mode = requested_mode;
|
||||
return 1; // success
|
||||
}
|
||||
|
||||
void quisk_check_freedv_mode(void)
|
||||
{ // see if we need to change the mode
|
||||
if (requested_mode == freedv_current_mode)
|
||||
return;
|
||||
if (DEBUG) printf("Change in mode to %d\n", requested_mode);
|
||||
CloseFreedv();
|
||||
if (requested_mode >= 0)
|
||||
OpenFreedv();
|
||||
else
|
||||
requested_mode = -1;
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_open(PyObject * self, PyObject * args) // Called from the GUI thread before freedv is open
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
return PyInt_FromLong(OpenFreedv());
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_close(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
requested_mode = -1; // request close
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_set_options(PyObject * self, PyObject * args, PyObject * keywds) // Called from the GUI thread.
|
||||
{ // Call with keyword arguments ONLY to change parameters. Call before quisk_freedv_open() to set an initial mode.
|
||||
int mode=-1; // Call again to change the mode.
|
||||
int bpf=-1;
|
||||
char * ptMsg=NULL;
|
||||
static char * kwlist[] = {"mode", "tx_msg", "DEBUG", "squelch", "interleave_frames", "set_tx_bpf", NULL} ;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch, &interleave_frames, &bpf))
|
||||
return NULL;
|
||||
if (ptMsg)
|
||||
strMcpy(quisk_tx_msg, ptMsg, TX_MSG_SIZE);
|
||||
if (bpf != -1) {
|
||||
quisk_set_tx_bpf = bpf;
|
||||
if (freedv_set_tx_bpf && rx_channel[0].hFreedv)
|
||||
freedv_set_tx_bpf(rx_channel[0].hFreedv, quisk_set_tx_bpf);
|
||||
}
|
||||
if (mode == -1) { // mode was not entered
|
||||
;
|
||||
}
|
||||
else if (mode == FREEDV_MODE_700E && freedv_version >= 14) {
|
||||
requested_mode = mode;
|
||||
}
|
||||
else if (mode == FREEDV_MODE_2020) {
|
||||
if (checkAvxSupport() && handle_index <= 2 && freedv_version >= 13)
|
||||
requested_mode = mode;
|
||||
}
|
||||
else if (freedv_version == 10) {
|
||||
if (mode == 0)
|
||||
requested_mode = mode;
|
||||
}
|
||||
else if (freedv_version == 11) {
|
||||
if (mode <= 2)
|
||||
requested_mode = mode;
|
||||
}
|
||||
else {
|
||||
requested_mode = mode;
|
||||
}
|
||||
return PyInt_FromLong(requested_mode); // Return the mode
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_get_snr(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
float snr_est = 0.0;
|
||||
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
if (rx_channel[0].hFreedv)
|
||||
freedv_get_modem_stats(rx_channel[0].hFreedv, NULL, &snr_est);
|
||||
return PyFloat_FromDouble(snr_est);
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_get_version(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
if ( ! hLib)
|
||||
GetAddrs(); // Get the entry points for funtions in the codec2 library
|
||||
return PyInt_FromLong(freedv_version);
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_set_squelch_en(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
int i,state;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", &state))
|
||||
return NULL;
|
||||
quisk_freedv_squelch = state;
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
if( rx_channel[i].hFreedv )
|
||||
freedv_set_squelch_en(rx_channel[i].hFreedv, state);
|
||||
}
|
||||
return PyInt_FromLong(state);
|
||||
}
|
||||
|
||||
|
||||
// This file is no longer used.
|
||||
|
||||
@@ -1,12 +1 @@
|
||||
extern int freedv_current_mode; // the current FreeDV running mode
|
||||
|
||||
#define FREEDV_MODE_1600 0
|
||||
#define FREEDV_MODE_700 1
|
||||
#define FREEDV_MODE_700B 2
|
||||
#define FREEDV_MODE_2400A 3
|
||||
#define FREEDV_MODE_2400B 4
|
||||
#define FREEDV_MODE_800XA 5
|
||||
#define FREEDV_MODE_700C 6
|
||||
#define FREEDV_MODE_700D 7
|
||||
#define FREEDV_MODE_2020 8
|
||||
#define FREEDV_MODE_700E 13
|
||||
// This header file is no longer used.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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[:]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
After Width: | Height: | Size: 128 KiB |
@@ -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 |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -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,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.
|
||||
@@ -168,6 +169,7 @@ static void modem_status(void)
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <termios.h>
|
||||
|
||||
static int fdComm = -1; // File descriptor to read the serial port
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#
|
||||
@@ -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")
|
||||
@@ -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;
|
||||
@@ -372,7 +372,7 @@ static void process_alc(complex double * cSamples, int count, struct alc * pt, r
|
||||
|
||||
static int tx_filter(complex double * filtered, int count)
|
||||
{ // Input samples are creal(filtered), output is filtered. The input rate must be 8000 or 48000 sps.
|
||||
int i, is_ssb;
|
||||
int i, is_ssb, mic_interp;
|
||||
int sample_rate = 8000;
|
||||
double dsample, dtmp, magn;
|
||||
complex double csample;
|
||||
@@ -423,9 +423,10 @@ static int tx_filter(complex double * filtered, int count)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
mic_interp = MIC_OUT_RATE / sample_rate;
|
||||
// check size of dsamples[] and csamples[] buffer
|
||||
if (count > samples_size) {
|
||||
samples_size = count * 2;
|
||||
samples_size = count * 2 * mic_interp;
|
||||
if (dsamples)
|
||||
free(dsamples);
|
||||
if (csamples)
|
||||
@@ -541,8 +542,8 @@ static int tx_filter(complex double * filtered, int count)
|
||||
// remove clipping distortion
|
||||
count = quisk_cDecimate(csamples, count, &cfiltAudio3, 1);
|
||||
// Interpolate up to 48000
|
||||
if (MIC_OUT_RATE != sample_rate)
|
||||
count = quisk_cInterpolate(csamples, count, &cfiltInterp, MIC_OUT_RATE / sample_rate);
|
||||
if (mic_interp != 1)
|
||||
count = quisk_cInterpolate(csamples, count, &cfiltInterp, mic_interp);
|
||||
// convert back to 16 bits
|
||||
for (i = 0; i < count; i++) {
|
||||
filtered[i] = csamples[i] * CLIP16;
|
||||
@@ -573,8 +574,8 @@ static int tx_filter(complex double * filtered, int count)
|
||||
// remove clipping distortion
|
||||
count = quisk_dFilter(dsamples, count, &dfiltAudio3);
|
||||
// Interpolate up to 48000
|
||||
if (MIC_OUT_RATE != sample_rate)
|
||||
count = quisk_dInterpolate(dsamples, count, &dfiltInterp, MIC_OUT_RATE / sample_rate);
|
||||
if (mic_interp != 1)
|
||||
count = quisk_dInterpolate(dsamples, count, &dfiltInterp, mic_interp);
|
||||
// convert back to 16 bits
|
||||
for (i = 0; i < count; i++) {
|
||||
filtered[i] = dsamples[i] * CLIP16;
|
||||
@@ -614,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
|
||||
@@ -623,123 +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.
|
||||
int i;
|
||||
// The input sample rate is mic_sample_rate, either 8000 or 48000 sps.
|
||||
// The FreeDV codec requires an input n_speech_sample_rate of either 8000 or 16000 sps depending on the mode. Input is always real.
|
||||
// The FreeDV codec will output samples at n_modem_sample_rate of 8000, 16000 or 48000 sps depending on the mode, and output may be real or complex.
|
||||
double dtmp, magn, dsample;
|
||||
static int samples_size = 0;
|
||||
static int last_rx_mode;
|
||||
static int last_freedv_mode;
|
||||
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 filter2;
|
||||
static struct quisk_dFilter filtDecim;
|
||||
static struct quisk_cFilter cfiltInterp;
|
||||
|
||||
if (do_init) { // initialization
|
||||
//QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16);
|
||||
do_init = 0;
|
||||
last_rx_mode = -1;
|
||||
last_freedv_mode = -1;
|
||||
memset(&filter2, 0, sizeof(filter2));
|
||||
quisk_filt_dInit(&filtDecim, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // pass 3000, stop 4000
|
||||
quisk_filt_cInit(&cfiltInterp, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
||||
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;
|
||||
}
|
||||
if (last_freedv_mode != freedv_current_mode) { // correct filter2 depends on freedv mode
|
||||
last_freedv_mode = freedv_current_mode;
|
||||
last_rx_mode = -1; // re-tune new filter
|
||||
if (filter2.cSamples)
|
||||
free(filter2.cSamples);
|
||||
filter2.cSamples = NULL;
|
||||
if (filter2.cpxCoefs)
|
||||
free(filter2.cpxCoefs);
|
||||
filter2.cpxCoefs = NULL;
|
||||
if (filter2.cBuf)
|
||||
free(filter2.cBuf);
|
||||
filter2.cBuf = NULL;
|
||||
switch(freedv_current_mode) {
|
||||
case FREEDV_MODE_700D: // filter2 is not used
|
||||
break;
|
||||
// TODO: add modes that return real samples that must be converted to complex
|
||||
default:
|
||||
quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // pass 1500, stop 1800
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last_rx_mode != (int)rxMode) { // change to sideband
|
||||
last_rx_mode = rxMode;
|
||||
if (rxMode == FDV_U) // upper sideband
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / n_modem_sample_rate, 1);
|
||||
else if (rxMode == FDV_L) // lower sideband
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / n_modem_sample_rate, 0);
|
||||
}
|
||||
if ( ! filtered)
|
||||
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)
|
||||
count = quisk_dDecimate(dsamples, count, &filtDecim, quisk_sound_state.mic_sample_rate / n_speech_sample_rate);
|
||||
else if (quisk_sound_state.mic_sample_rate == 8000 && n_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 / n_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);
|
||||
//quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0);
|
||||
if (filter2.cSamples) // convert float samples to complex with an analytic filter
|
||||
count = quisk_cCDecimate(filtered, count, &filter2, 1);
|
||||
// Interpolate up to 48000
|
||||
if (MIC_OUT_RATE != n_modem_sample_rate )
|
||||
count = quisk_cInterpolate(filtered, count, &cfiltInterp, MIC_OUT_RATE / n_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
|
||||
@@ -876,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]);
|
||||
@@ -891,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;
|
||||
@@ -898,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) {
|
||||
@@ -910,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);
|
||||
@@ -953,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;
|
||||
@@ -1010,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;
|
||||
}
|
||||
@@ -1084,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;
|
||||
@@ -1260,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
|
||||
@@ -1301,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;
|
||||
@@ -1308,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;
|
||||
}
|
||||
@@ -1344,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) {
|
||||
@@ -1403,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;
|
||||
@@ -1429,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
|
||||
@@ -1560,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);
|
||||
}
|
||||
|
||||
@@ -1606,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#
|
||||
@@ -0,0 +1,209 @@
|
||||
# This file is part of the Multus project. See https://www.multus-sdr.com/.
|
||||
# It controls what items appear on the Hardware configuration screen and
|
||||
# provides logic for each radio.
|
||||
|
||||
|
||||
################ Receivers Multus CW, A transceiver from the Multus project.
|
||||
## hardware_file_name Hardware file path, rfile
|
||||
# This is the file that contains the control logic for each radio.
|
||||
#hardware_file_name = "multuspkg/quisk_hardware.py"
|
||||
|
||||
## widgets_file_name Widget file path, rfile
|
||||
# This optional file adds additional controls for the radio.
|
||||
#widgets_file_name = ""
|
||||
|
||||
## 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
|
||||
|
||||
## tx_level Tx Level, dict
|
||||
# This is the level of the Tx audio sent to SoftRock hardware after all processing as a percentage
|
||||
# number from 0 to 100.
|
||||
# The level should be below 100 to allow headroom for amplitude and phase adjustments.
|
||||
# Changes are immediate (no need to restart).
|
||||
#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
|
||||
|
||||
## keyer_speed Keyer speed WPM, integer
|
||||
# This sets the keyer speed in words per minute.
|
||||
#keyer_speed = 8
|
||||
#keyer_speed = 13
|
||||
#keyer_speed = 18
|
||||
#keyer_speed = 20
|
||||
#keyer_speed = 30
|
||||
#keyer_speed = 40
|
||||
|
||||
## keyer_type Type of keyer, text choice
|
||||
# Use "Straight" mode for a straight key or a bug. Connect the tip of the key to the transceiver.
|
||||
# Otherwise, select Iambic-A or Iambic-B mode of operation.
|
||||
#keyer_type = "Straight"
|
||||
#keyer_type = "Iambic-A"
|
||||
#keyer_type = "Iambic-B"
|
||||
|
||||
## keyer_space Keyer space, text choice
|
||||
# This sets the type of spacing between elements or letters.
|
||||
# "Element" attempts to provide proper spacing between the elements of a character.
|
||||
# (The keyer always provides proper spacing between elements).
|
||||
# "Letter" turns on letter spacing. This attempts to provide proper spacing between characters.
|
||||
#keyer_space = "Element"
|
||||
#keyer_space = "Letter"
|
||||
|
||||
## keyer_weight Keyer weight, integer choice
|
||||
# A weight of 50% means that the dit time equals the inter-element time, which is standard.
|
||||
# Reducing the weight increases the inter-element time, increasing the weight reduces it.
|
||||
#keyer_weight = 50
|
||||
#keyer_weight = 25
|
||||
#keyer_weight = 75
|
||||
|
||||
## keyer_paddle Keyer paddle, text choice
|
||||
# "Normal" means the left paddle is DIT. "Reverse" means the left paddle is DAH.
|
||||
#keyer_paddle = "Normal"
|
||||
#keyer_paddle = "Reverse"
|
||||
|
||||
import configure
|
||||
import softrock
|
||||
from softrock.hardware_usb import Hardware as BaseHardware
|
||||
from softrock.hardware_usb import IN, OUT, UBYTE2, UBYTE4
|
||||
import _quisk as QS
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
def __init__(self, app, conf):
|
||||
# Set constant parameters here:
|
||||
conf.usb_vendor_id = 0x16c0
|
||||
conf.usb_product_id = 0x05dc
|
||||
conf.si570_i2c_address = 0x55
|
||||
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
|
||||
if DEBUG:
|
||||
print ("The name of this radio is", configure.Settings[1])
|
||||
print ("The hardware file type is", conf.hardware_file_type)
|
||||
def TransferOut(self, address, message): # message is a bytes array or an integer 0 to 255
|
||||
if isinstance(message, int):
|
||||
message = message.to_bytes(1, 'big')
|
||||
if self.usb_dev:
|
||||
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()
|
||||
return text
|
||||
def ChangeMode(self, mode):
|
||||
ret = BaseHardware.ChangeMode(self, mode)
|
||||
if mode in ("CWL", "CWU"):
|
||||
cw_mode = b'C' # Adding "b" does not affect the value sent
|
||||
if DEBUG:
|
||||
print ("Mode is CW")
|
||||
else:
|
||||
cw_mode = b'U'
|
||||
if DEBUG:
|
||||
print ("Mode is not CW")
|
||||
self.TransferOut(0x70, cw_mode)
|
||||
return ret
|
||||
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
|
||||
if not hasattr(conf, "keyer_speed"):
|
||||
conf.keyer_speed = 18
|
||||
if not hasattr(conf, "keyer_type"):
|
||||
conf.keyer_type = "Straight"
|
||||
if not hasattr(conf, "keyer_space"):
|
||||
conf.keyer_space = "Element"
|
||||
if not hasattr(conf, "keyer_weight"):
|
||||
conf.keyer_weight = 50
|
||||
if not hasattr(conf, "keyer_paddle"):
|
||||
conf.keyer_paddle = "Normal"
|
||||
# We need to initialize in case the persistent hardware values differ from the Quisk values.
|
||||
for name in ("keyer_speed", "keyer_type", "keyer_space", "keyer_weight", "keyer_paddle", "cwTone"):
|
||||
self.ImmediateChange(name)
|
||||
def ImmediateChange(self, name):
|
||||
BaseHardware.ImmediateChange(self, name)
|
||||
value = getattr(self.conf, name)
|
||||
if DEBUG:
|
||||
print ("ImmediateChange", name, value)
|
||||
if name == "keyer_speed":
|
||||
self.TransferOut(0x7B, value)
|
||||
elif name == "keyer_type":
|
||||
if value == "Straight":
|
||||
mode = 0
|
||||
elif value == "Iambic-A":
|
||||
mode = 1
|
||||
elif value == "Iambic-B":
|
||||
mode = 2
|
||||
else:
|
||||
mode = 0
|
||||
self.TransferOut(0x71, mode)
|
||||
elif name == "keyer_space":
|
||||
if value == "Element":
|
||||
spacing = 0
|
||||
elif value == "Letter":
|
||||
spacing = 1
|
||||
else:
|
||||
spacing = 0
|
||||
self.TransferOut(0x75, spacing)
|
||||
elif name == "keyer_weight":
|
||||
weight = int(value)
|
||||
self.TransferOut(0x77, weight)
|
||||
elif name == "keyer_paddle":
|
||||
if value == "Normal":
|
||||
paddle = 0
|
||||
elif value == "Reverse":
|
||||
paddle = 1
|
||||
else:
|
||||
paddle = 0
|
||||
self.TransferOut(0x73, paddle)
|
||||
elif name == "cwTone":
|
||||
if value < 500:
|
||||
tone_index = 0 # 400 Hz
|
||||
elif 500 <= value < 700:
|
||||
tone_index = 1 # 600 Hz
|
||||
elif 700 <= value < 900:
|
||||
tone_index = 2 # 800 Hz
|
||||
else:
|
||||
tone_index = 3 # 1000 Hz
|
||||
self.TransferOut(0x7F, tone_index)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
#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>
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
#include <winsock2.h>
|
||||
@@ -107,12 +109,14 @@ static PyObject * pyApp; // Application instance
|
||||
static int fft_size; // size of fft, e.g. 1024
|
||||
int data_width; // number of points to return as graph data; fft_size * n
|
||||
static int graph_width; // width of the graph in pixels
|
||||
rx_mode_type rxMode; // 0 to 13: CWL, CWU, LSB, USB, AM, FM, EXT, DGT-U, DGT-L, DGT-IQ, IMD, FDV-U, FDV-L, DGT-FM
|
||||
rx_mode_type rxMode; // 0 to 13: CWL, CWU, LSB, USB, AM, FM, EXT, DGT_U, DGT_L, DGT_IQ, IMD, FDV_U, FDV_L, DGT_FM
|
||||
int quisk_noise_blanker; // noise blanker level, 0 for off
|
||||
int quiskTxHoldState; // hold Tx until the repeater frequency shift is complete
|
||||
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
|
||||
@@ -150,9 +154,16 @@ 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 softrock_correct_freq; // gain and phase corrections for SoftRock
|
||||
static double softrock_correct_gain;
|
||||
static double softrock_correct_phase;
|
||||
static int softrock_correct_active; // 0 No correction adjustment; 1 Manual adjustment; 2 Calculate Rx corrections
|
||||
|
||||
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
|
||||
static int multirx_fft_width; // size of FFT samples
|
||||
@@ -179,6 +190,7 @@ int quisk_sidetoneFreq; // frequency in hertz for the sidetone
|
||||
int quisk_start_cw_delay = 15; // milliseconds to delay output on CW key down
|
||||
int quisk_start_ssb_delay = 100; // milliseconds to discard output on SSB etc. key down
|
||||
static int maximum_tx_secs; // Failsafe timeout for Tx in seconds
|
||||
static int TxRxSilenceMsec = 50; // Play silence in msec on Tx to Rx
|
||||
static int key_is_down = 0; // internal key state up or down
|
||||
|
||||
static double agcReleaseGain=80; // AGC maximum gain
|
||||
@@ -211,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
|
||||
@@ -561,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
|
||||
@@ -1845,7 +1855,6 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
int i;
|
||||
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 {
|
||||
@@ -1918,6 +1927,10 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
}
|
||||
if(bank == 0)
|
||||
dAutoNotch(dsamples, nSamples, rit_freq, quisk_filter_srate);
|
||||
if (ssb_squelch_enabled) {
|
||||
ssb_squelch(dsamples, nSamples, quisk_filter_srate, MeasureSquelch + bank);
|
||||
d_delay(dsamples, nSamples, bank, SQUELCH_FFT_SIZE);
|
||||
}
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio12p2, 2);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
@@ -1937,6 +1950,10 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
}
|
||||
if(bank == 0)
|
||||
dAutoNotch(dsamples, nSamples, rit_freq, quisk_filter_srate);
|
||||
if (ssb_squelch_enabled) {
|
||||
ssb_squelch(dsamples, nSamples, quisk_filter_srate, MeasureSquelch + bank);
|
||||
d_delay(dsamples, nSamples, bank, SQUELCH_FFT_SIZE);
|
||||
}
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio12p2, 2);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
@@ -1959,7 +1976,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
ssb_squelch(dsamples, nSamples, quisk_filter_srate, MeasureSquelch + bank);
|
||||
d_delay(dsamples, nSamples, bank, SQUELCH_FFT_SIZE);
|
||||
}
|
||||
quisk_calc_audio_graph(pow(2, 31) - 1, NULL, dsamples, nSamples, 1);
|
||||
//quisk_calc_audio_graph(pow(2, 31) - 1, NULL, dsamples, nSamples, 1);
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p4, 2);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
//quisk_calc_audio_graph(pow(2, 31) - 1, NULL, dsamples, nSamples, 1);
|
||||
@@ -2073,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);
|
||||
@@ -2099,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);
|
||||
@@ -2137,42 +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.
|
||||
quisk_check_freedv_mode();
|
||||
// added conditional for 2400A/B mmodes
|
||||
// current coding assumes modem sample rate is 8000 16000 or 48000
|
||||
if( n_modem_sample_rate <= 16000 )
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3);
|
||||
if (bank == 0)
|
||||
process_agc(&Agc1, cSamples, nSamples, 1);
|
||||
else
|
||||
process_agc(&Agc2, cSamples, nSamples, 1);
|
||||
// Perhaps decimate by an additional fraction
|
||||
if (quisk_decim_srate != 48000) {
|
||||
dd = quisk_decim_srate / 48000.0;
|
||||
nSamples = cFracDecim(cSamples, nSamples, dd);
|
||||
quisk_decim_srate = 48000;
|
||||
}
|
||||
quisk_filter_srate = n_speech_sample_rate;
|
||||
// added conditional for 2400A/B mmodes
|
||||
if( n_modem_sample_rate == 8000 )
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2);
|
||||
// pass data decimated down to n_modem_sample_rate
|
||||
// return data is as n_speech_sample_rate
|
||||
if (pt_quisk_freedv_rx)
|
||||
nSamples = (* pt_quisk_freedv_rx)(cSamples, dsamples, nSamples, bank);
|
||||
if(bank == 0) {
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
measure_audio_sum += dsamples[i] * dsamples[i];
|
||||
measure_audio_count += 1;
|
||||
}
|
||||
}
|
||||
// current coding assumes speech sample rate is only 8000 or 16000 - needs tweak for others
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||
if( n_speech_sample_rate == 8000 )
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
break;
|
||||
}
|
||||
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||
measured_audio = sqrt(measure_audio_sum / measure_audio_count) / CLIP32 * 1e6;
|
||||
@@ -2395,7 +2378,7 @@ int quisk_process_samples(complex double * cSamples, int nSamples)
|
||||
quisk_sound_state.sample_rate;
|
||||
nout = (int)dOutCounter; // number of samples to output
|
||||
dOutCounter -= nout;
|
||||
playSilence = (int)(quisk_sound_state.playback_rate * 50e-3); // Play silence after sidetone ends, number of samples
|
||||
playSilence = (int)(quisk_sound_state.playback_rate * 1E-3 * TxRxSilenceMsec); // Play silence after sidetone ends, number of samples
|
||||
keyupEnvelope = 0;
|
||||
if (quisk_active_sidetone == 2 && QUISK_CWKEY_DOWN) { // Play sidetone instead of radio for CW
|
||||
if (! sidetoneIsOn) { // turn on sidetone
|
||||
@@ -2467,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.
|
||||
@@ -2669,7 +2653,7 @@ int quisk_process_samples(complex double * cSamples, int nSamples)
|
||||
multirx_cSamples[0][i] = dsamples2[i] + I * dsamples2[i];
|
||||
process_agc(&Agc3, multirx_cSamples[0], n, 0);
|
||||
}
|
||||
play_sound_interface(quiskPlaybackDevices[QUISK_INDEX_SUB_RX1], n, multirx_cSamples[0], 1, 1.0);
|
||||
play_sound_interface(quiskPlaybackDevices[QUISK_INDEX_SUB_RX1], n, multirx_cSamples[0], 1, digital_output_level);
|
||||
}
|
||||
|
||||
// Perhaps decimate by an additional fraction
|
||||
@@ -2679,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) {
|
||||
@@ -2707,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]);
|
||||
@@ -2867,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
|
||||
@@ -2875,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_speech_sample_rate;
|
||||
break;
|
||||
}
|
||||
//QuiskPrintf("Filter rate %d\n", filter_srate);
|
||||
return PyInt_FromLong(filter_srate);
|
||||
@@ -3043,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)
|
||||
{
|
||||
}
|
||||
@@ -3065,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;
|
||||
@@ -3084,6 +3151,10 @@ static PyObject * get_params(PyObject * self, PyObject * args)
|
||||
return PyInt_FromLong(quisk_serial_ptt);
|
||||
if (strcmp(name, "hl2_txbuf_errors") == 0)
|
||||
return PyInt_FromLong(hl2_txbuf_errors);
|
||||
if (strcmp(name, "softrock_correct_active") == 0)
|
||||
return PyInt_FromLong((long)softrock_correct_active);
|
||||
if (strcmp(name, "quisk_tx_inhibit") == 0)
|
||||
return PyInt_FromLong((long)quisk_tx_inhibit);
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
@@ -3113,18 +3184,23 @@ static PyObject * read_fftw_wisdom(PyObject * self, PyObject * args)
|
||||
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", NULL} ;
|
||||
"bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause",
|
||||
"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, "|iiiiiiiidi", 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))
|
||||
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause,
|
||||
&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)
|
||||
@@ -3160,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;
|
||||
}
|
||||
|
||||
@@ -3685,6 +3773,7 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
|
||||
//code_version = quisk_hermes_to_pc[3];
|
||||
if ((quisk_hermes_to_pc[0] & 0x01) != 0) // C1
|
||||
quisk_sound_state.overrange++;
|
||||
quisk_tx_inhibit = (quisk_hermes_to_pc[0] & 0x02) == 0; // should limit to Hermes Lite2 only
|
||||
hardware_ptt = buf[start] & 0x01; // C0 bit zero is PTT
|
||||
quisk_hardware_cwkey = (buf[start] & 0x04) >> 2; // C0 bit two is CW key state
|
||||
switch (hl2_txbuf_state) {
|
||||
@@ -3731,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;
|
||||
@@ -3766,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
|
||||
@@ -3796,6 +3927,10 @@ static int read_rx_udp17(complex double * cSamples0) // Read samples from UDP
|
||||
fft_data * ptFFT;
|
||||
fd_set fds;
|
||||
static int block_number=0;
|
||||
static complex double dc_average = 0; // Average DC component in samples
|
||||
static complex double dc_sum = 0;
|
||||
static int dc_count = 0;
|
||||
static time_t time0 = 0;
|
||||
// Data from the receiver is little-endian
|
||||
|
||||
if ( ! rx_udp_gain_correct) { // correct for second stage CIC decimation JIM JIM
|
||||
@@ -3901,6 +4036,16 @@ static int read_rx_udp17(complex double * cSamples0) // Read samples from UDP
|
||||
if (xr & 0x100) { // channel 1
|
||||
if (quisk_invert_spectrum) // Invert spectrum
|
||||
sample = conj(sample);
|
||||
// correct the DC level
|
||||
dc_sum += sample;
|
||||
dc_count++;
|
||||
sample -= dc_average;
|
||||
if (dc_count >= 48000 && time(NULL) != time0) {
|
||||
time0 = time(NULL);
|
||||
dc_average = dc_sum / dc_count;
|
||||
dc_sum = dc_count = 0;
|
||||
//printf ("New DC average %.0lf\n", cabs(dc_average));
|
||||
}
|
||||
// Put samples into the fft input array.
|
||||
ptFFT = fft_data_array + fft_data_index;
|
||||
if ( ! (xi & 0x100)) { // zero marker for start of first block
|
||||
@@ -3979,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;
|
||||
@@ -4028,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)
|
||||
@@ -4168,6 +4313,13 @@ static PyObject * change_scan(PyObject * self, PyObject * args) // Called from G
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject * softrock_corrections(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, "i", &softrock_correct_active))
|
||||
return NULL;
|
||||
return Py_BuildValue("idd", softrock_correct_freq, softrock_correct_gain, softrock_correct_phase);
|
||||
}
|
||||
|
||||
static PyObject * change_rates(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{ // Change to new sample rates
|
||||
|
||||
@@ -4235,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)
|
||||
@@ -4438,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);
|
||||
}
|
||||
@@ -4462,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);
|
||||
}
|
||||
@@ -4629,6 +4781,9 @@ static PyObject * ImmediateChange(PyObject * self, PyObject * args) // called fr
|
||||
else if ( ! strcmp(name, "maximum_tx_secs")) {
|
||||
maximum_tx_secs = QuiskGetConfigInt(name, 0);
|
||||
}
|
||||
else if ( ! strcmp(name, "TxRxSilenceMsec")) {
|
||||
TxRxSilenceMsec = QuiskGetConfigInt(name, 50);
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
@@ -4952,6 +5107,135 @@ static PyObject * get_bandscope(PyObject * self, PyObject * args) // Called by t
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void check_channel_delay(fft_data * ptFft)
|
||||
{
|
||||
int i, maxi, freq;
|
||||
static int count=0, freq0;
|
||||
static double gain=0, phase=0;
|
||||
double d, ampl, maxa;
|
||||
static double complex * samples1=NULL, * samples2;
|
||||
|
||||
if (samples1 == NULL) {
|
||||
samples1 = (double complex *)malloc(fft_size * sizeof(double complex));
|
||||
samples2 = (double complex *)malloc(fft_size * sizeof(double complex));
|
||||
}
|
||||
for (i = 0; i < fft_size; i++) {
|
||||
samples1[i] = creal(ptFft->samples[i]);
|
||||
samples2[i] = cimag(ptFft->samples[i]);
|
||||
}
|
||||
fftw_execute_dft(quisk_fft_plan, samples1, samples1);
|
||||
fftw_execute_dft(quisk_fft_plan, samples2, samples2);
|
||||
maxa = 0;
|
||||
maxi = 0;
|
||||
for (i = 0; i < fft_size / 2; i++) {
|
||||
ampl = cabs(samples1[i]);
|
||||
if (ampl > maxa) {
|
||||
maxa = ampl;
|
||||
maxi = i;
|
||||
}
|
||||
}
|
||||
freq = maxi * fft_sample_rate / fft_size;
|
||||
//freq = maxi * 44100 / fft_size;
|
||||
gain += cabs(samples1[maxi]) / cabs(samples2[maxi]);
|
||||
d = carg(samples1[maxi]) - carg(samples2[maxi]);
|
||||
if (d > M_PI)
|
||||
d -= M_PI * 2;
|
||||
else if(d < - M_PI)
|
||||
d += M_PI * 2;
|
||||
phase += d;
|
||||
count += 1;
|
||||
if (count == 1) {
|
||||
freq0 = freq;
|
||||
}
|
||||
else if (freq != freq0) {
|
||||
gain = phase = count = 0;
|
||||
return;
|
||||
}
|
||||
if (count > 10) {
|
||||
gain /= count;
|
||||
phase /= count;
|
||||
//printf("Channel delay freq %6d, gain %6.3lf, phase %8.5lf radians, %6.3lf degrees\n",
|
||||
// freq, gain, phase, phase * 57.29578);
|
||||
printf("%6d, %6.3lf, %6.3lf\n", freq, gain, phase * 57.29578);
|
||||
gain = phase = count = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void softrock_correct_fft(fft_data * ptFft, int fixit)
|
||||
{ // See i/Q balance in Rocky: https://www.dxatlas.com/Rocky/Advanced.asp
|
||||
double ampl, r1, r2, i1, i2, Zr, Zi, Pwr, Gain, Phase, OutR, OutI, S1R, S1I, S2R, S2I;
|
||||
static double tot_ga=0, tot_ph=0;
|
||||
static int count=0;
|
||||
double maxa = 0;
|
||||
int i, maxi = 0, freq;
|
||||
double complex Z, s1, s2, Hr, Hi, S1, S2;
|
||||
double tim;
|
||||
static double time0 = 0;
|
||||
|
||||
for (i = 0; i < fft_size; i++) {
|
||||
ampl = cabs(ptFft->samples[i]);
|
||||
if (ampl > maxa) {
|
||||
maxa = ampl;
|
||||
maxi = i;
|
||||
}
|
||||
}
|
||||
if (maxi < fft_size / 2)
|
||||
freq = maxi;
|
||||
else
|
||||
freq = maxi - fft_size;
|
||||
freq = freq * fft_sample_rate / fft_size;
|
||||
s1 = ptFft->samples[maxi];
|
||||
s2 = ptFft->samples[fft_size - maxi];
|
||||
Z = s1 * s2;
|
||||
r1 = creal(s1);
|
||||
r2 = creal(s2);
|
||||
i1 = cimag(s1);
|
||||
i2 = cimag(s2);
|
||||
Pwr = r1 * r1 + i1 * i1 + r2 * r2 + i2 * i2;
|
||||
Zr = creal(Z) / Pwr;
|
||||
Zi = cimag(Z) / Pwr;
|
||||
Gain = sqrt((1.0 + 2 * Zr) / (1.0 - 2 * Zr));
|
||||
Phase = asin(Zi * (Gain * Gain + 1.0) / Gain);
|
||||
tot_ga += Gain;
|
||||
tot_ph += Phase;
|
||||
count += 1;
|
||||
tim = QuiskTimeSec();
|
||||
if (tim - time0 >= 0.5) {
|
||||
time0 = tim;
|
||||
softrock_correct_freq = freq;
|
||||
softrock_correct_gain = tot_ga / count;
|
||||
softrock_correct_phase = tot_ph / count;
|
||||
//if (softrock_correct_active)
|
||||
// printf("Freq domain Gain %10.7lf, Phase %10.7lf, %10.7lf, freq %d\n",
|
||||
// softrock_correct_gain, softrock_correct_phase, softrock_correct_phase * 57.29578, freq);
|
||||
tot_ga = tot_ph = count = 0;
|
||||
}
|
||||
if (fixit) {
|
||||
Hr = softrock_correct_gain * cos(softrock_correct_phase);
|
||||
Hi = softrock_correct_gain * sin(softrock_correct_phase);
|
||||
for (i = 1; i < fft_size / 2; i++) {
|
||||
S1 = ptFft->samples[fft_size - i];
|
||||
S2 = ptFft->samples[i];
|
||||
S1R = creal(S1);
|
||||
S1I = cimag(S1);
|
||||
S2R = creal(S2);
|
||||
S2I = cimag(S2);
|
||||
OutR = S2R + S1R - (S2I + S1I) * Hi + (S2R - S1R) * Hr;
|
||||
OutI = S2I - S1I + (S2I + S1I) * Hr + (S2R - S1R) * Hi;
|
||||
ptFft->samples[i] = OutR + I * OutI;
|
||||
S1R = creal(S2);
|
||||
S1I = cimag(S2);
|
||||
S2R = creal(S1);
|
||||
S2I = cimag(S1);
|
||||
OutR = S2R + S1R - (S2I + S1I) * Hi + (S2R - S1R) * Hr;
|
||||
OutI = S2I - S1I + (S2I + S1I) * Hr + (S2R - S1R) * Hi;
|
||||
ptFft->samples[fft_size - i] = OutR + I * OutI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the GUI thread
|
||||
{
|
||||
int i, j, k, m, n, index, ffts, ii, mm, m0, deltam;
|
||||
@@ -5024,7 +5308,10 @@ static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the G
|
||||
// Continue with FFT calculation
|
||||
for (i = 0; i < fft_size; i++) // multiply by window
|
||||
ptFft->samples[i] *= fft_window[i];
|
||||
//check_channel_delay(ptFft);
|
||||
fftw_execute_dft(quisk_fft_plan, ptFft->samples, ptFft->samples); // Calculate FFT
|
||||
if (softrock_correct_active == 2)
|
||||
softrock_correct_fft(ptFft, 0);
|
||||
// Create RMS s-meter value at known bandwidth
|
||||
// The pass band is (rx_tune_freq + filter_start_offset) to += bandwidth
|
||||
// d1 is the tune frequency
|
||||
@@ -5075,6 +5362,9 @@ static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the G
|
||||
//QuiskPrintf(" %d %.4lf At %5d to %5d place %5d to %5d for block %d\n", fft_size, scan_valid, mm, m, ii, i, ptFft->block);
|
||||
}
|
||||
else {
|
||||
// Zero frequency is at index fft_size / 2.
|
||||
// There are fft_size/2 positive frequencies and fft_size/2-1 negative frequencies.
|
||||
// The frequency at index k and (fft_size - k) are equal except for sign.
|
||||
count_fft++;
|
||||
k = 0;
|
||||
for (i = fft_size / 2; i < fft_size; i++) // Negative frequencies
|
||||
@@ -5467,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");
|
||||
@@ -5502,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);
|
||||
@@ -5543,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;
|
||||
}
|
||||
@@ -5786,6 +6072,7 @@ static PyObject * record_app(PyObject * self, PyObject * args)
|
||||
quisk_start_cw_delay = QuiskGetConfigInt("start_cw_delay", 15);
|
||||
quisk_start_ssb_delay = QuiskGetConfigInt("start_ssb_delay", 100);
|
||||
maximum_tx_secs = QuiskGetConfigInt("maximum_tx_secs", 0);
|
||||
TxRxSilenceMsec = QuiskGetConfigInt("TxRxSilenceMsec", 50);
|
||||
quisk_sound_state.sample_rate = rate;
|
||||
fft_sample_rate = rate;
|
||||
is_little_endian = 1; // Test machine byte order
|
||||
@@ -5899,6 +6186,8 @@ 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."},
|
||||
{"get_overrange", get_overrange, METH_VARARGS, "Return the count of overrange (clip) for the ADC."},
|
||||
@@ -5945,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."},
|
||||
@@ -5985,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."},
|
||||
@@ -5999,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."},
|
||||
|
||||
@@ -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,13 +322,11 @@ 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 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
|
||||
extern int quisk_close_file_play;
|
||||
extern double digital_output_level;
|
||||
|
||||
extern PyObject * quisk_set_spot_level(PyObject * , PyObject *);
|
||||
extern PyObject * quisk_get_tx_filter(PyObject * , PyObject *);
|
||||
@@ -337,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 *);
|
||||
@@ -360,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
|
||||
@@ -394,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);
|
||||
@@ -402,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)(complex double *, double *, int, int);
|
||||
typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int);
|
||||
extern ty_dvoice_codec_rx pt_quisk_freedv_rx;
|
||||
extern ty_dvoice_codec_tx pt_quisk_freedv_tx;
|
||||
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 *);
|
||||
@@ -464,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])
|
||||
@@ -479,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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -419,6 +424,8 @@ hermes_PTT_hang_time = 4
|
||||
## hermes_antenna_tuner Antenna tuner, text choice
|
||||
# This option only applies to the Hermes Lite 2. Set this to None if you don't have a tuner.
|
||||
# Set this to "Tune" to control the Icom AH-4 compatible ATU attached to the Hermes Lite 2 end plate.
|
||||
# Note that this option only applies to the HL2 end plate, not the IO board.
|
||||
# A tuner attached to the IO board is controlled from a button on the last line of the screen.
|
||||
# Then when the Spot button is pressed with a positive power, a tune request is sent to the ATU.
|
||||
# If the Spot button is pressed with a zero power level, the tuner is set to bypass mode.
|
||||
# Changes are immediate (no need to restart).
|
||||
@@ -481,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.
|
||||
@@ -498,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
|
||||
@@ -576,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'
|
||||
@@ -592,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'
|
||||
@@ -623,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'
|
||||
@@ -734,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'
|
||||
@@ -749,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
|
||||
@@ -818,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'
|
||||
@@ -850,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.
|
||||
@@ -1075,7 +1199,20 @@ digital_rx1_name = ""
|
||||
## digital_output_level Digital output level, number
|
||||
# This is the volume control 0.0 to 1.0 for digital playback to fldigi, etc.
|
||||
# Changes are immediate (no need to restart).
|
||||
digital_output_level = 0.7
|
||||
digital_output_level = 0.700
|
||||
|
||||
## file_play_level File play level, number
|
||||
# Use this to reduce the volume when playing files. For example, if the volume of the CQ
|
||||
# message is too loud, reduce the level relative to radio sound.
|
||||
# Changes are immediate (no need to restart).
|
||||
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:
|
||||
#
|
||||
@@ -1270,55 +1407,44 @@ dc_remove_bw = 100
|
||||
#dc_remove_bw = 200
|
||||
#dc_remove_bw = 400
|
||||
|
||||
## fixed_tune_offset Fixed tune offset, integer
|
||||
# If this is zero, Quisk tunes as usual. The hardware frequency (the VFO or center frequency)
|
||||
# is fixed and Quisk tunes
|
||||
# within the spectrum returned by the hardware. Otherwise enter a fixed offset between the
|
||||
# center frequency and the tuning frequency. Then tuning is done by the hardware.
|
||||
# This is useful with Softrock hardware as
|
||||
# it simplifies the corrections for amplitude and phase imbalance.
|
||||
# Changes are immediate (no need to restart).
|
||||
fixed_tune_offset = 0
|
||||
#fixed_tune_offset = 5000
|
||||
#fixed_tune_offset = 10000
|
||||
|
||||
|
||||
|
||||
|
||||
################ 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.
|
||||
@@ -1369,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 = ""
|
||||
@@ -1646,6 +1817,15 @@ start_ssb_delay = 100
|
||||
# Changes are immediate (no need to restart).
|
||||
maximum_tx_secs = 0
|
||||
|
||||
## TxRxSilenceMsec Tx-Rx silence msec, integer
|
||||
# When changing from transmit to receive a radio may receive either noise or the end of the transmission.
|
||||
# This may be returned as received audio, and can be heard as a loud pop.
|
||||
# This parameter plays silence for the specified number of milliseconds when changing from transmit to receive.
|
||||
# Make it long enough to zero out the pop, but not too much longer.
|
||||
# It is ignored when in full duplex mode.
|
||||
# Changes are immediate (no need to restart).
|
||||
TxRxSilenceMsec = 50
|
||||
|
||||
## keyupDelay Keyup delay msecs, integer
|
||||
# This is the key hang time for semi-breakin CW. It is the time in milliseconds
|
||||
# from the last CW key release until changing to receive.
|
||||
@@ -1744,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 2020', 8), ('Mode 700E', 13))
|
||||
|
||||
# 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)
|
||||
@@ -1770,7 +1933,7 @@ FilterBwFM = (8000, 10000, 12000, 16000, 18000, 20000)
|
||||
FilterBwIMD = FilterBwSSB
|
||||
FilterBwDGT = (200, 400, 1500, 3200, 4800, 10000)
|
||||
FilterBwEXT = (8000, 10000, 12000, 15000, 17000, 20000)
|
||||
FilterBwFDV = (1500, 2000, 3000, '', '', '')
|
||||
FilterBwFDV = (1500, 2000, 3000, '6000', '', '')
|
||||
|
||||
# If your hardware file defines the method OnButtonPTT(self, event), then Quisk will
|
||||
# display a PTT button you can press. The method must switch your hardware to
|
||||
@@ -1890,6 +2053,8 @@ btn_text_switch = u"\u21C4" # Character to switch left-right
|
||||
color_scheme = 'A'
|
||||
#color_scheme = 'B'
|
||||
#color_scheme = 'C'
|
||||
#color_scheme = 'DK1MI_GR'
|
||||
#color_scheme = 'DK1MI_AM'
|
||||
|
||||
## waterfall_palette Waterfall colors, text choice
|
||||
# This controls the colors used in the waterfall. The default color scheme is A, and you can change this scheme
|
||||
@@ -1897,6 +2062,7 @@ color_scheme = 'A'
|
||||
waterfall_palette = 'A'
|
||||
#waterfall_palette = 'B'
|
||||
#waterfall_palette = 'C'
|
||||
#waterfall_palette = 'DK1MI_AM'
|
||||
|
||||
|
||||
|
||||
@@ -1931,6 +2097,8 @@ color_txline = 'red' # vertical line color for tx in graph
|
||||
color_rxline = 'green' # vertical line color for rx in graph
|
||||
color_graph_msg_fg = 'black' # text messages on the graph screen
|
||||
color_graph_msg_bg = 'lemonchiffon2' # background of text messages on the graph screen
|
||||
color_onoff = '#77DD77' # on/off power button
|
||||
color_onoff_text = 'black' # on/off power button text
|
||||
|
||||
# This color scheme B, a dark color scheme designed by Steve Murphy, KB8RWQ.
|
||||
# Additional colors added by N2ADR.
|
||||
@@ -1960,6 +2128,8 @@ color_scheme_B = {
|
||||
'color_rxline' : 'green',
|
||||
'color_graph_msg_fg' : 'white',
|
||||
'color_graph_msg_bg' : '#111111',
|
||||
'color_onoff' : '#77DD77',
|
||||
'color_onoff_text' : 'black',
|
||||
}
|
||||
|
||||
# This is color scheme C:
|
||||
@@ -1997,12 +2167,73 @@ color_scheme_C = {
|
||||
'color_rxline' : '#3CC918',
|
||||
'color_graph_msg_fg' : '#000000',
|
||||
'color_graph_msg_bg' : 'lemonchiffon2',
|
||||
'color_onoff' : '#77DD77',
|
||||
'color_onoff_text' : 'black',
|
||||
}
|
||||
#############################################################################################
|
||||
# This is a green dark color scheme designed by Michael
|
||||
color_scheme_DK1MI_GR = {
|
||||
'color_bg' : '#0F0F0F', # Lower screen background
|
||||
'color_bg_txt' : '#00CC00', # Lower screen text color
|
||||
'color_graph' : '#0F0F0F', # Graph background
|
||||
'color_config2' : '#008000', # color in tab row of config screen
|
||||
'color_gl' : '#2F4F4F', # Lines on the graph
|
||||
'color_graphticks' : '#CCCCCC', # Graph ticks
|
||||
'color_graphline' : '#00FF00', # graph data line color
|
||||
'color_graphlabels' : '#00FF00', # graph label color
|
||||
'color_btn' : '#141414', # button color
|
||||
'color_check_btn' : '#005500', # color of a check button when it is checked
|
||||
'color_cycle_btn' : '#005500', # color of a cycle button when it is checked
|
||||
'color_adjust_btn' : '#005500', # color of an adjustable button when it is checked
|
||||
'color_test' : '#005500', # color of a button used for test (turn off for tx)
|
||||
'color_freq' : '#208B57', # background color of frequency and s-meter
|
||||
'color_freq_txt' : '#00FF00', # text color of frequency display
|
||||
'color_entry' : '#2E2E2E', # frequency entry box
|
||||
'color_entry_txt' : '#00FF00', # text color of entry box
|
||||
'color_enable' : '#00CC00', # text color for an enabled button
|
||||
'color_disable' : '#FF0000', # text color for a disabled button
|
||||
'color_popchoice' : '#555555', # text color for button that pops up a row of buttons
|
||||
'color_bandwidth' : '#003300', # color for bandwidth display; thanks to WB4JFI
|
||||
'color_txline' : '#00CC00', # vertical line color for tx in graph
|
||||
'color_rxline' : '#00FF00', # vertical line color for rx in graph
|
||||
'color_graph_msg_fg' : '#008800', # text messages on the graph screen
|
||||
'color_graph_msg_bg' : '#1E1E1E', # background of text messages on the graph screen
|
||||
'color_onoff' : '#005500', # On/Off button
|
||||
'color_onoff_text' : '#FF0000', # On/Off text
|
||||
}
|
||||
#############################################################################################
|
||||
# This is an amber dark color scheme designed by Michael
|
||||
color_scheme_DK1MI_AM = {
|
||||
'color_bg' : '#0F0F0F', # Lower screen background
|
||||
'color_bg_txt' : '#FFBF00', # Lower screen text color
|
||||
'color_graph' : '#0F0F0F', # Graph background
|
||||
'color_config2' : '#FF8000', # Color in tab row of config screen
|
||||
'color_gl' : '#2F4F4F', # Lines on the graph
|
||||
'color_graphticks' : '#CCCCCC', # Graph ticks
|
||||
'color_graphline' : '#FFBF00', # Graph data line color
|
||||
'color_graphlabels' : '#FFBF00', # Graph label color
|
||||
'color_btn' : '#141414', # Button color
|
||||
'color_check_btn' : '#865E2E', # Color of a check button when it is checked
|
||||
'color_cycle_btn' : '#865E2E', # Color of a cycle button when it is checked
|
||||
'color_adjust_btn' : '#865E2E', # Color of an adjustable button when it is checked
|
||||
'color_test' : '#865E2E', # Color of a button used for test (turn off for tx)
|
||||
'color_freq' : '#FF9933', # Background color of frequency and s-meter
|
||||
'color_freq_txt' : '#000000', # Text color of frequency display
|
||||
'color_entry' : '#2E2E2E', # Frequency entry box
|
||||
'color_entry_txt' : '#FFBF00', # Text color of entry box
|
||||
'color_enable' : '#FFBF00', # Text color for an enabled button
|
||||
'color_disable' : '#FF0000', # Text color for a disabled button
|
||||
'color_popchoice' : '#555555', # Text color for button that pops up a row of buttons
|
||||
'color_bandwidth' : '#4B2E0F', # Color for bandwidth display
|
||||
'color_txline' : '#FFBF00', # Vertical line color for tx in graph
|
||||
'color_rxline' : '#FF8000', # Vertical line color for rx in graph
|
||||
'color_graph_msg_fg' : '#FF8000', # Text messages on the graph screen
|
||||
'color_graph_msg_bg' : '#1E1E1E', # Background of text messages on the graph screen
|
||||
'color_onoff' : '#865E2E', # On/Off button
|
||||
'color_onoff_text' : '#FF0000', # On/Off text
|
||||
}
|
||||
|
||||
|
||||
# These are the palettes for the waterfall. The one used is named waterfallPallette,
|
||||
# so to use a different one, overwrite this name in your configuration file.
|
||||
# These are the palettes for the waterfall.
|
||||
waterfallPalette = (
|
||||
( 0, 0, 0, 0),
|
||||
( 36, 85, 0, 255),
|
||||
@@ -2060,6 +2291,18 @@ waterfallPaletteC = ( # from David Fainitski
|
||||
(255, 245, 99, 3)
|
||||
)
|
||||
|
||||
waterfallPaletteDK1MI_AM = ( # from Michael
|
||||
(0, 0, 0, 0),
|
||||
(32, 25, 20, 0),
|
||||
(64, 58, 40, 6),
|
||||
(96, 78, 50, 16),
|
||||
(128, 120, 85, 29),
|
||||
(160, 144, 100, 51),
|
||||
(192, 195, 140, 43),
|
||||
(224, 198, 150, 35),
|
||||
(255, 255, 191, 0)
|
||||
)
|
||||
|
||||
# This is the data used to draw colored lines on the frequency X axis to
|
||||
# indicate CW and Phone sub-bands. You can make it anything you want.
|
||||
# These are the colors used for sub-bands:
|
||||
@@ -2085,6 +2328,26 @@ RxOnly = '#AAAAAA'
|
||||
# Colors start at the indicated frequency and continue until the
|
||||
# next frequency. The special color "None" turns off color.
|
||||
#
|
||||
BandPlanColors = [ # Display name and corresponding color
|
||||
['CW, General', CW],
|
||||
['CW, Extra', eCW],
|
||||
['Phone, General', Phone],
|
||||
['Phone, Extra', ePhone],
|
||||
['AM', AM],
|
||||
['Data', Data],
|
||||
['DxData', DxData],
|
||||
['RTTY', RTTY],
|
||||
['SSTV', SSTV],
|
||||
['Packet', Packet],
|
||||
['Beacons', Beacons],
|
||||
['Satellite', Satellite],
|
||||
['Repeater out', Repeater],
|
||||
['Repeater in', RepInput],
|
||||
['Simplex', Simplex],
|
||||
['Rx only', RxOnly],
|
||||
['Special', Special],
|
||||
['Other', Other]
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."""
|
||||
@@ -1549,6 +1555,8 @@ class QuiskMenu(wx.Menu):
|
||||
item.Check(is_checked)
|
||||
self.InitData(item, handler)
|
||||
return item
|
||||
def AppendSeparator(self):
|
||||
wx.Menu.AppendSeparator(self)
|
||||
def IsItemChecked(self, item_text):
|
||||
nid = self.item_text2id[item_text]
|
||||
menu_item = self.FindItemById(nid)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#
|
||||
@@ -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)
|
||||
@@ -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.28'
|
||||
|
||||
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',
|
||||
@@ -39,8 +34,10 @@ if sys.platform == "darwin": # Build for Macintosh
|
||||
libraries = ['portaudio', 'fftw3', 'm']
|
||||
if os.path.isdir('/opt/local/include'): # MacPorts
|
||||
base_dir = '/opt/local'
|
||||
elif os.path.isdir('/usr/local/include'): # HomeBrew
|
||||
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"):
|
||||
@@ -83,9 +80,9 @@ N1MM+ and software that uses Hamlib.
|
||||
url = 'http://james.ahlstrom.name/quisk/',
|
||||
packages = ['quisk', 'quisk.n2adr', 'quisk.softrock', 'quisk.freedvpkg',
|
||||
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg',
|
||||
'quisk.sdrmicronpkg', 'quisk.perseuspkg', 'quisk.ac2yd'],
|
||||
'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'],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -178,6 +178,8 @@ class Hardware(BaseHardware):
|
||||
vfo = self.vfo + int(offset * 1000) # Convert kHz to Hz
|
||||
self.ChangeFrequency(vfo, vfo, 'repeater')
|
||||
return False
|
||||
def ImmediateChange(self, name):
|
||||
return
|
||||
def OnSpot(self, level):
|
||||
pass
|
||||
def HeartBeat(self): # Called at about 10 Hz by the main
|
||||
|
||||
@@ -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>
|
||||
@@ -85,8 +86,10 @@ char * sound_format_names[4] = {"Int32", "Int16", "Float32", "Int24"} ;
|
||||
static struct wav_file file_rec_audio, file_rec_samples, file_rec_mic;
|
||||
static int close_file_rec;
|
||||
|
||||
static double digital_output_level = 0.7;
|
||||
double digital_output_level = 0.7;
|
||||
static double file_play_level = 1.0;
|
||||
static int dc_remove_bw=100; // bandwidth of DC removal filter
|
||||
static int multus_cw_samples = 0; // always send CW Tx samples to the Multus radio, even in Rx
|
||||
|
||||
static ty_sample_start pt_sample_start;
|
||||
static ty_sample_stop pt_sample_stop;
|
||||
@@ -111,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();
|
||||
@@ -175,9 +178,10 @@ static void correct_sample (struct sound_dev * dev, complex double * cSamples, i
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
re = creal(cSamples[i]);
|
||||
im = cimag(cSamples[i]);
|
||||
re = re * dev->AmPhAAAA;
|
||||
im = re * dev->AmPhCCCC + im * dev->AmPhDDDD;
|
||||
cSamples[i] = re + I * im;
|
||||
cSamples[i] = re * dev->AmPhAAAA + I * (re * dev->AmPhCCCC + im * dev->AmPhDDDD);
|
||||
//re = re * dev->AmPhAAAA;
|
||||
//im = re * dev->AmPhCCCC + im * dev->AmPhDDDD;
|
||||
//cSamples[i] = re + I * im;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,8 +734,11 @@ void * quisk_make_txIQ(struct sound_dev * dev, int rewind)
|
||||
}
|
||||
// amplitude and phase corrections
|
||||
if (dev->doAmplPhase) {
|
||||
dSamp0 = dSamp0 * dev->AmPhAAAA;
|
||||
dSamp1 = dSamp0 * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||
//dSamp0 = dSamp0 * dev->AmPhAAAA;
|
||||
//dSamp1 = dSamp0 * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||
d = dSamp0;
|
||||
dSamp0 = d * dev->AmPhAAAA;
|
||||
dSamp1 = d * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||
}
|
||||
switch (dev->sound_format) {
|
||||
case Int16:
|
||||
@@ -836,6 +843,34 @@ int quisk_play_sidetone(struct sound_dev * dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void testing(double complex * cSamples, int nSamples)
|
||||
{
|
||||
int i;
|
||||
static double AvgR=0, AvgQ=0, AvgRQ=0;
|
||||
double d, p, Gain, Phase;
|
||||
time_t tim;
|
||||
static time_t time0;
|
||||
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
d = creal(cSamples[i]);
|
||||
AvgR += d * d;
|
||||
p = cimag(cSamples[i]);
|
||||
AvgQ += p * p;
|
||||
AvgRQ += d * p;
|
||||
}
|
||||
tim = time(NULL);
|
||||
if (tim - time0 >= 2) {
|
||||
time0 = tim;
|
||||
Gain = sqrt(AvgR / AvgQ);
|
||||
Phase = AvgRQ / AvgQ / Gain;
|
||||
Phase = asin(Phase);
|
||||
printf("Time domain Gain %10.7lf, Phase %10.7lf, %10.7lf degrees\n", Gain, Phase, Phase * 57.29578);
|
||||
AvgR = AvgQ = AvgRQ = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int quisk_read_sound(void) // Called from sound thread
|
||||
{ // called in an infinite loop by the main program
|
||||
int i, nSamples, mic_count, mic_interp, retval, is_cw, mic_sample_rate;
|
||||
@@ -913,9 +948,10 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
//QuiskPrintTime("quisk_read_sound end", 0);
|
||||
if (Capture.channel_Delay >= 0) // delay the I or Q channel by one sample
|
||||
delay_sample(&Capture, (double *)cSamples, nSamples);
|
||||
DCremove(cSamples, nSamples, quisk_sound_state.sample_rate, key_state);
|
||||
//testing(cSamples, nSamples);
|
||||
if (Capture.doAmplPhase) // amplitude and phase corrections
|
||||
correct_sample(&Capture, cSamples, nSamples);
|
||||
DCremove(cSamples, nSamples, quisk_sound_state.sample_rate, key_state);
|
||||
if (nSamples <= 0)
|
||||
QuiskSleepMicrosec(2000);
|
||||
#if DEBUG_IO > 1
|
||||
@@ -951,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
|
||||
@@ -965,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, 1.0); // replace radio sound
|
||||
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)
|
||||
@@ -1026,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);
|
||||
@@ -1075,7 +1122,7 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
if (is_cw) { // Transmit CW; use capture device for timing, not microphone
|
||||
cwCount += (double)retval * quisk_MicPlayback.sample_rate / quisk_sound_state.sample_rate;
|
||||
mic_count = 0;
|
||||
if (QUISK_CWKEY_DOWN || quiskSpotLevel >= 0) {
|
||||
if (QUISK_CWKEY_DOWN || quiskSpotLevel >= 0 || multus_cw_samples) {
|
||||
while (cwCount >= 1.0) {
|
||||
if (cwEnvelope < 1.0) {
|
||||
cwEnvelope += 1. / (quisk_MicPlayback.sample_rate * 5e-3); // 5 milliseconds
|
||||
@@ -1401,6 +1448,8 @@ void quisk_open_sound(void) // Called from GUI thread
|
||||
RawSamplePlayback.channel_I = 0;
|
||||
RawSamplePlayback.channel_Q = 1;
|
||||
|
||||
file_play_level = QuiskGetConfigDouble("file_play_level", 1.0);
|
||||
|
||||
set_num_channels (&Capture);
|
||||
set_num_channels (&quisk_Playback);
|
||||
set_num_channels (&MicCapture);
|
||||
@@ -1565,10 +1614,9 @@ PyObject * quisk_micplay_channels(PyObject * self, PyObject * args) // Called fr
|
||||
PyObject * quisk_set_sparams(PyObject * self, PyObject * args, PyObject * keywds)
|
||||
{ /* Call with keyword arguments ONLY; change local parameters */
|
||||
static char * kwlist[] = {"dc_remove_bw", "digital_output_level", "remote_control_slave", "remote_control_head",
|
||||
NULL} ;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idii", kwlist, &dc_remove_bw, &digital_output_level,
|
||||
&remote_control_slave, &remote_control_head))
|
||||
"file_play_level", "multus_cw_samples", NULL} ;
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idiidi", kwlist, &dc_remove_bw, &digital_output_level,
|
||||
&remote_control_slave, &remote_control_head, &file_play_level, &multus_cw_samples))
|
||||
return NULL;
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
|
||||
@@ -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)) {
|
||||
@@ -495,8 +496,37 @@ static snd_pcm_format_t check_formats(struct sound_dev * dev, snd_pcm_hw_params_
|
||||
{
|
||||
snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
|
||||
dev->sample_bytes = 0;
|
||||
|
||||
#if 0
|
||||
char * card_name;
|
||||
int card;
|
||||
snd_pcm_info_t * pcm_info;
|
||||
printf("driver %s\n", snd_ctl_card_info_get_driver(info));
|
||||
int loopback = 0;
|
||||
if (snd_pcm_info_malloc(&pcm_info) == 0) {
|
||||
if (snd_pcm_info (dev->handle, pcm_info) == 0) {
|
||||
card = snd_pcm_info_get_card (pcm_info);
|
||||
if (card >= 0) {
|
||||
if (snd_card_get_name(card, &card_name) == 0) {
|
||||
if (strcmp(card_name, "Loopback") == 0)
|
||||
loopback = 1;
|
||||
printf("name %s\n", card_name);
|
||||
free(card_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
snd_pcm_info_free(pcm_info);
|
||||
}
|
||||
#endif
|
||||
strMcpy (dev->msg1, "Available formats: ", QUISK_SC_SIZE);
|
||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S16) == 0) {
|
||||
if (!dev->sample_bytes) {
|
||||
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
||||
dev->sample_bytes = 2;
|
||||
dev->sound_format = Int16;
|
||||
format = SND_PCM_FORMAT_S16;
|
||||
}
|
||||
strncat(dev->msg1, "S16 ", QUISK_SC_SIZE);
|
||||
}
|
||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S32) == 0) {
|
||||
if (!dev->sample_bytes) {
|
||||
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
||||
@@ -515,15 +545,6 @@ static snd_pcm_format_t check_formats(struct sound_dev * dev, snd_pcm_hw_params_
|
||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_U24) == 0) {
|
||||
strncat(dev->msg1, "U24 ", QUISK_SC_SIZE);
|
||||
}
|
||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S16) == 0) {
|
||||
if (!dev->sample_bytes) {
|
||||
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
||||
dev->sample_bytes = 2;
|
||||
dev->sound_format = Int16;
|
||||
format = SND_PCM_FORMAT_S16;
|
||||
}
|
||||
strncat(dev->msg1, "S16 ", QUISK_SC_SIZE);
|
||||
}
|
||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S24_3LE) == 0) {
|
||||
if (!dev->sample_bytes) {
|
||||
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
||||
@@ -1109,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||